各大back-end framework幾乎都採用了「MVC」架構。
它們至少會有「views」、「 controllers」、「 models」三個資料夾。
「views」跟「controllers」沒太大問題,但是「models」資料夾根本不該存在。
我要對這些框架提出嚴厲指控:
「models」資料夾的存在是一種錯誤的架構設計。它不但阻礙新手學習,還會傷害scalability。
任何back-end framework都不應該有「models」資料夾。
我會在這篇文章解釋理由,並且提出改善架構設計的幾個方向。
哪些框架有「models」資料夾?
光是我接觸過的Ruby、PHP框架,就至少有:
- Rails(Ruby)
- CodeIgniter(PHP)
- Yii(PHP)
- …
除此之外,像是Java, Python還是什麼語言,
一定也有框架做這種事:放一個「models」資料夾在那邊。
這真是大錯特錯。
你到底要放什麼東西到「models」資料夾裡面?你覺得Model是什麼?
Model是什麼?
撇開我之前提到的MVC正名爭議不談,光是MVC的M該如何解釋就已經是個大哉問。
看看這則出色的Stack Overflow問答:
How should a model be structured in MVC?
Model是layer、它包羅萬象、它涵蓋你全部的business logic。
眾說紛紜中,這是我們唯一能有的共識。
Model難以定義、沒有絕對正確的架構設計。聽起來真令人洩氣,對吧?
不!這樣很好!這樣才對!軟體架構本來就是大哉問,有無限種可能的方法,
這也是我們所有人應該要一起討論和嘗試的地方。
而「models」資料夾卻嚴重妨礙我們討論、阻止我們思考,
它不但阻礙新手學習,還會傷害scalability。
「models」資料夾如何阻礙新手學習?
說明這段之前,我先定義一個名詞:「entity」。
我將entity定義成「代表現實生活中的一種事物」。
以常見的Active Record pattern來說,
// user.rb // Rails class User < ActiveRecord::Base
// user.php // Laravel class User extends Eloquent
// post.php // Yii class Post extends CActiveRecord
類似這樣的東西,你一定看過。
user.rb、user.php、post.php,這些就是我所謂的「entity」。
也就是這些「entity」,讓新手容易誤以為「entity」就是MVC裡面的M。
錯!M是layer,entity只是M裡面的組成元素之一而已。
「models」資料夾的存在本身,會讓新手以為「弄幾個entity類別丟進去就搞定架構了」。
然後entity的行為、對entity做出的行為、關乎兩個以上entity的行為,管他什麼logic,
管他什麼行為,全部想辦法塞進entity類別。
下場通常就是:那些entity類別最後變得超肥胖、難以理解、動輒達到上千行程式碼。
我稱之為「胖胖entity」。
「models」資料夾帶給新手「model == entity」錯覺!
「models」資料夾誘惑新手去做出一堆胖胖entity!
「models」資料夾如何傷害scalability?
看看Rails社群這篇出名的文章:
7 Patterns to Refactor Fat ActiveRecord Models
幹得好!它點出「胖胖entity」的問題,並給出7個patterns去協助你設計軟體架構。
抽出行為邏輯變成Service Objects、抽出表單驗證邏輯變成Form Objects、抽出資料庫查詢邏輯變成Query Objects、抽出呈現邏輯變成View Objects...聽起來真棒,也確實很有幫助,不是嗎?
問題來了:抽出來的這些類別,到底要放哪裡?
我們看看文章下面comments提到的範例:GItLab
它的檔案結構如下:
/lib /gitlab /tasks /... /app /assets /controllers /finders /helpers /mailers /models /services /uploaders /views /workers
新的問題來了:那個「models」資料夾到底代表什麼?它是我們包羅萬象的偉大Model layer嗎?那finders、services、uploaders為什麼跟models在同一層,而不是在models裡面?lib/底下的gitlab/又是怎麼回事?難道GitLab的商業邏輯也出現在lib?
這就是我想說的:「models」資料夾的存在從一開始就污染了架構設計。
它引誘人們把entity全丟進去。結果除了entity以外的東西,像是前面的Service Objects、 Form Objects、Query Objects、View Objects,還有後面的finders、services、uploaders、gitlab全都不知道放哪了。只好隨便亂放。
GItLab原始碼中的models有代表MVC的M嗎?怎麼不改名叫entities?
就算改了又如何?Model layer到底在哪裡?四分五裂、結構鬆散。
一團混亂的設計、難以理解的命名、與MVC的M不相容的檔案結構。
所以我說,「models」資料夾傷害scalability!
那該怎麼辦?
要解決這個問題,首先得要了解MVC是三個極度不對稱的存在。
V: 負責呈現UI
C: 負責接受request、請M處理、回傳response
M: 負責全部的business logic
M幾乎是你的整個application。
你可以在框架底下,找地方建一個空資料夾,用公司名稱或是專案名稱替它命名,
然後開始煩惱軟體架構這件事。
好好煩惱entity要放在哪裡、Service Objects、 Form Objects、Query Objects、View Objects、finders、services、uploaders這些要放哪裡,彼此又要怎麼分門別類。
MVC不是萬靈丹,只是軟體架構的入門磚。
架構設計本來就是這麼難,OOP本來就是這麼難。
恭喜你,至少你跨出第一步了:
你不再把一堆胖胖entity丟進「models」資料夾,然後覺得設計完軟體架構了。
Q&A
Q1: 「models」資料夾毫無優點嗎?
「models」資料夾還是有少數優點。
它是一種quick and dirty作法,鼓勵你眼中只看見entity,然後把所有business logic全塞進裡面。
換句話說,它在小型的專案可以幫你節省時間。但它的優點也僅此而已。
Q2: 你的結論好空泛,什麼建一個公司名稱空資料夾啊。拜託給點方向?
沒問題,我給你兩個架構設計的參考方向。
第一個來自這篇文章:
引用作者的話,核心精神如下:
Rails不是你的application。它可以是你的views還有資料來源,但不是你的application。把你的application放在Gem裡面或是lib/資料夾底下。
我不覺得這樣有很優雅,但至少點出一個可能方向,並且至少不再有models資料夾。
我第二個要給你的,是Laravel官方論壇的原始碼。
這個Laravel.io專案簡稱為Lio,結構如下:
/app /controllers /views /Lio /Core /... /Accounts /User.php /UserPresenter.php /UserRepository.php /UserCreator.php /.... /Articles /Article.php /ArticlePresenter.php /ArticleRepository.php /ArticleCreator.php /.... /Comments /...
光看檔案結構就很優雅。也正是我前面所說的:建一個空資料夾,用公司名稱或是專案名稱替它命名,然後開始煩惱架構設計這件事。
想想看Laravel官方論壇的原始碼為什麼長這樣吧。
Q3: 講得好像多有道理!我覺得你只是在鬼扯!框架的製作團隊都是業界大神,既然他們決定要有「models」資料夾,必定有它的正當性!
不,你錯了。那些業界大神只是背負了行銷框架的壓力。
他們為了滿足用戶的錯誤期待而委屈地放了個「models」資料夾在那。
但還是有高尚的人存在。PHP最被推崇的框架Laravel就沒有「models」資料夾。
向Laravel的Taylor Otwell致敬吧!
他不願成為殘害新手的幫凶,硬是把「models」拿掉了,
強迫你去思考:「軟體架構到底該長怎樣」。
你要自己在Laravel裡面做一個「models」資料夾,然後把那些entity class全丟進去嗎?
那你是自願把entity當成整個Model,真遺憾,
但別說是Laravel鼓勵你這麼做。Laravel盡力了。
Q4: 少自以為了解Taylor Otwell了!你憑什麼代替他發言!
Reddit上有一則 Why would anyone choose Laravel over Symfony or Silex?
Taylor Otwell本人親自做出回答。下文擷取自第四段:
我個人在開發Laravel 4的早期階段就想把「models」資料夾整個移除了。因為我不覺得它有用,也不覺得它能協助你設計軟體架構。而且它還會引誘人們掉入「model == database」的陷阱裡。所以,我希望你不要覺得我對架構設計很無知。我花了點時間才想清楚我到底想在PHP世界打造什麼。
Laravel實作Active Record Pattern,資料表映對到entity class。他指的「model == database」陷阱就是我說的「model == entity」錯覺。我並沒有代替他發言。
Q5: 我還是覺得,你沒有資格批評那麼多框架。「models」資料夾就是有某種正當性。除了Taylor Otwell,我看也沒有其他權威支持你的說法!
前面提到的出色Stack Overflow問答:
How should a model be structured in MVC?
作者是tereško。
Stack Overflow上關於MVC的幾個最高分討論,全都是由tereško解答。
下文擷取自那篇出色問答的段落「What a model is NOT」:
model不是一個class,也不是任何一個單一物件。這是一個超級常見錯誤,因為大部分的框架都在助長這種誤解。
他選擇這樣帶過。我選擇正面指控。
然後我建議你討論事情的時候,不要太在乎權威還是前輩怎麼講。不如專注於討論事情本身。
Q6: 好啊!那來啊!照你的說法,「models」資料夾底下多放個「entities」資料夾不就搞定一切問題了?你果然是不切實際的理想主義者!最好是有框架幹這麼囉唆的事情!
有!它就是Cake(PHP)框架!
看看Cake在Model底下放了什麼:
/View /Controller /Model /Behavior /Entity /Table
看到了嗎?
Cake怕你把entity當成整個model,直接擺好幾個資料夾,逼你去思考entity跟model是什麼。
替這些用心良苦的框架歡呼吧!
Q7: 專注於討論事情本身是不是!那Cake的「models」資料夾就沒問題啊!你還說任何框架都不能有!
你看錯了,Cake沒有「models」資料夾,也沒有「Models」資料夾。它只有「Model」資料夾。
資料夾、package、資料庫table命名,都有一個關於單數/複數的原則可以參考:異質性與同質性。
你的「models」資料夾底下不再是同屬entity的class了,而是分為behavior、entity、以及其他你設計的分類,也就是異質,所以應該用單數命名。
參考這個連結:
Should package names be singular or plural?
簡單地說,既然model代表的是layer而非多個entity,資料夾命名上就應該用單數而非複數。
好吧,我這樣說有點太嚴苛了。
如果你知道自己在幹嘛的話,就繼續用你的「models」資料夾吧,我勉強可以接受。
Q8: 等等,不對勁...你整篇文章流露一股氣息...我覺得你不但反對「models」,你幾乎在否定MVC的價值?你怎麼可以覺得偉大的MVC沒有價值?
我前面提過,Taylor Otwell在Laravel 4移除了「models」資料夾,逼迫大家去思考「軟體架構」到底應該是什麼。
我告訴你第二件事。
你去逛Laravel官網,翻遍官網你都找不到「MVC」三個字。
MVC名氣多麼響亮!哪個framework不想打著MVC當作賣點?但Laravel拒絕這麼做。
我再告訴你第三件事。
2015年最新出爐的Laravel 5,它的views在resources/底下,controllers在app/Http/底下。一樣沒有models。
所以你神聖的MVC在Laravel 5底下長這樣:
/resources /views /lang /assets /app /Commands /Console /Events /Exceptions /Handlers /Http /Providers /Services /Https /Controllers /Middleware /Requests
你推崇的V跟C不再佔據檔案結構的核心位置了。你最愛的MVC現在看起來是如此渺小,
小到沒有討論價值,小到毫無意義可言。
「MVC是三個極度不對稱的存在」,這是個太過客氣的說法。
MVC這個觀念已經無法協助我們討論和思考了。放下它,往前走吧。
我來自Laravel社群。我們不聲稱自己擁戴MVC。
來把時間花在真正值得討論的概念上吧:你正在用的框架,架構合理嗎?框架有沒有擋住你的路?你在框架之下設計出的專案架構漂亮嗎?大中小型專案通用的架構存在嗎?如何分辨使用時機?怎麼做會最彈性?該怎麼描述某個框架才不會對新手揠苗助長?
放下你凡事都要套進MVC的執著,請直接思考「軟體架構」的本質。
啊,我看到MVC粉絲對Laravel 5的分析了:
「
MVC依然發揮重要的討論價值!我看到Controllers資料夾了!我看到views資料夾了!
剩下的十幾個資料夾全部統稱為Model!果然是豐富又厚重的layer!
我們來討論Model是什麼吧!MVC萬歲!
」
朋友,祝福你能得出有意義的結論。
小朱針對本文的延伸討論(2015-4-19補充)
(Photo via Alyssa L. Miller, CC licensed.)