當我們已經在應用程式中使用 ORM(例如 Entity Framework),還需要 Repository 嗎?這個問題就像「要不要使用 ORM」一樣,沒有標準答案。這類「該不該」的問題,有時真的挺傷腦筋。
更新紀錄
何謂 Repository?
按照 Martin Fowler 的定義,「Repository 是領域層與資料對應層之間的媒介,如同存在記憶體中的物件集合。用戶端會建構查詢規格(query specification),並傳遞給 Repository 以執行特定的資料操作.....概念上,Repository 將資料與其相關操作封裝成物件集合,以便用貼近物件導向的方式來存取資料」。
在設計 Repository 時,常見的做法是為每一個「主要的 entity」設計一個 Repository 類別,稱為 aggregate root。例如客戶資料會有對應的 CustomerRepository,產品資料則是 ProductRepository,訂單則是 OrderRepository;這些都是 aggregate roots。至於訂單細項,應該由 OrderRepository 一併負責處理,就不另外設計 OrderItemRepository。
此外,為了減少重複的程式碼(DRY 原則),有些人會使用泛型 Repository。
如果你想看看 Repository 或者分層設計的範例,可以參考:
有了 ORM,還需要 Repository 嗎?
在 ORM--或者說 Entity Framework--還沒那麼成熟的年代,使用 Repository 模式的理由很充分,好處也明顯。可是,當 Entity Framework 和 NHibernate 已經提供了同樣、甚至更多的功能,我們還需要再多加一層 Repository 嗎?
網路上可以找到很多文章,介紹如何搭配 Entity Framework 來設計我們的 Repository。所以「怎麼做」並不是問題,問題在於我們是否該這麼做。
使用 Repository 的理由通常有這幾個:
拿掉 Repository 之後,單元測試還是可以做,只是測試對象改成了商業邏輯層、服務層、或其他更上層的服務;單元測試的粒度可能會粗一點,視情況而定。如果連商業邏輯層都沒有,例如直接在 MVC 應用程式的 Controller 中使用 Entity Framework,單元測試也許會更麻煩一些。
其次,可抽換底層的資料存取元件當然很理想。然而,一旦使用了 Entity Framework,就算我們用 Repository 把它包起來,難保不夠周密,讓某些與 EF 相依的東西悄悄從縫隙中溜出去。除了擔心成本效益,我也懷疑真實世界中有多少案子真的需要而且實際達成了「任意切換 persistence layer」的理想。
至於 Unit of Work,其實 EF 的 ObjectContext 和 DbContext 已經提供了類似的功能。
如此說來,ORM 之上再疊一層 Repository,可能會讓我們費好一番工夫寫一堆不那麼有用、而且長得很像的程式碼,可能增加了程式的複雜性,卻得不到相對比例的好處。因此,在不需要切換 persistence layer 的情況下,比較小而單純的應用程式,我可能不會使用 Repository。如果是比較大型或複雜的系統....嗯,目前我也說不準。
誠如開頭說的,這裡沒有標準答案,有的只是個人主觀的想法(而且以後可能會變)。
文後附上一些參考文章,如果你也碰到類似問題,可以自行研究看看,寫點程式感覺一下,再做決定。
2014-05-08 補充
www.asp.net 網站上的教學文章 Getting Started with EF 5 using MVC 4 裡面有一個步驟是 Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application,可是到了 EF 6 和 MVC 5,新版本的文章 Getting Started with EF 6 using MVC 5 裡面卻把實作 Repository 的步驟拿掉了,如下圖。不知道是不是有什麼原因。
延伸閱讀
更新紀錄
- 2012-11-30:些微潤飾,增加參考資料。
- 2014-05-08:補充一點新發現,增加延伸閱讀文章。
何謂 Repository?
按照 Martin Fowler 的定義,「Repository 是領域層與資料對應層之間的媒介,如同存在記憶體中的物件集合。用戶端會建構查詢規格(query specification),並傳遞給 Repository 以執行特定的資料操作.....概念上,Repository 將資料與其相關操作封裝成物件集合,以便用貼近物件導向的方式來存取資料」。
在設計 Repository 時,常見的做法是為每一個「主要的 entity」設計一個 Repository 類別,稱為 aggregate root。例如客戶資料會有對應的 CustomerRepository,產品資料則是 ProductRepository,訂單則是 OrderRepository;這些都是 aggregate roots。至於訂單細項,應該由 OrderRepository 一併負責處理,就不另外設計 OrderItemRepository。
此外,為了減少重複的程式碼(DRY 原則),有些人會使用泛型 Repository。
如果你想看看 Repository 或者分層設計的範例,可以參考:
- Tom Dykstra 的文章:Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application(對岸有一篇長得很像的文章,乍看以為是翻譯,仔細看又不完全是,供參考:疑似簡體中文版)
- 範例程式:ProDinner - ASP.NET MVC Sample (EF4.4, N-Tier, jQuery)
- 範例程式:EFMVC - ASP.NET MVC 4, Entity Framework 4.3 Code First and Windows Azure
有了 ORM,還需要 Repository 嗎?
在 ORM--或者說 Entity Framework--還沒那麼成熟的年代,使用 Repository 模式的理由很充分,好處也明顯。可是,當 Entity Framework 和 NHibernate 已經提供了同樣、甚至更多的功能,我們還需要再多加一層 Repository 嗎?
網路上可以找到很多文章,介紹如何搭配 Entity Framework 來設計我們的 Repository。所以「怎麼做」並不是問題,問題在於我們是否該這麼做。
使用 Repository 的理由通常有這幾個:
- 方便單元測試。
- 避免上層服務直接依賴 Entity Framework,以便將來可以將 EF 換掉,換成 NHibernate 或其他 ORM。
- 可搭配 Unit of Work 模式來管理交易。
拿掉 Repository 之後,單元測試還是可以做,只是測試對象改成了商業邏輯層、服務層、或其他更上層的服務;單元測試的粒度可能會粗一點,視情況而定。如果連商業邏輯層都沒有,例如直接在 MVC 應用程式的 Controller 中使用 Entity Framework,單元測試也許會更麻煩一些。
其次,可抽換底層的資料存取元件當然很理想。然而,一旦使用了 Entity Framework,就算我們用 Repository 把它包起來,難保不夠周密,讓某些與 EF 相依的東西悄悄從縫隙中溜出去。除了擔心成本效益,我也懷疑真實世界中有多少案子真的需要而且實際達成了「任意切換 persistence layer」的理想。
至於 Unit of Work,其實 EF 的 ObjectContext 和 DbContext 已經提供了類似的功能。
如此說來,ORM 之上再疊一層 Repository,可能會讓我們費好一番工夫寫一堆不那麼有用、而且長得很像的程式碼,可能增加了程式的複雜性,卻得不到相對比例的好處。因此,在不需要切換 persistence layer 的情況下,比較小而單純的應用程式,我可能不會使用 Repository。如果是比較大型或複雜的系統....嗯,目前我也說不準。
誠如開頭說的,這裡沒有標準答案,有的只是個人主觀的想法(而且以後可能會變)。
文後附上一些參考文章,如果你也碰到類似問題,可以自行研究看看,寫點程式感覺一下,再做決定。
2014-05-08 補充
www.asp.net 網站上的教學文章 Getting Started with EF 5 using MVC 4 裡面有一個步驟是 Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application,可是到了 EF 6 和 MVC 5,新版本的文章 Getting Started with EF 6 using MVC 5 裡面卻把實作 Repository 的步驟拿掉了,如下圖。不知道是不是有什麼原因。
延伸閱讀
- [Architecture Pattern] Repository by 克拉克
- 博客園的大牛們,被你們害慘了,Entity Framework從來都不需要去寫Repository設計模式
- Do we need the repository pattern?
- Reviewing my data access layer using the Entity Framework Profiler
- Repository is the new Singleton by Oren
- The wages of sin: Over architecture in the real world by Oren
- Do we need to use the Repository pattern when working in ASP.NET MVC with ORM solutions?
- Is There a Real Advantage to Generic Repository?
- Generic Repository With EF 4.1 what is the point
- Is Unit of Work pattern required in Entity Framework 4.0
- Unit Testing DbContext
- Repository and Unit of Work for Entity Framework (EF4) for Unit Testing
Repository的設計概念,主要是做為領域層與資料對應層之間的媒介。
回覆刪除這個「媒介」的意思如果單純看做資料查詢的物件,在設計的時候會顯得綁手綁腳,因為很多功能都跟EF等等框架重疊了。
但是換個角度把Repository看做一個IoC的實做,相依的方向是資料對應層相依領域層。
也就是說Repository是作為系統邊界的存在,而這個系統邊界是存在領域層與資料對應層之間。
套用Repository最主要的工作,讓整個領域層更加的凝聚。
以這樣的角度去思考「泛型 Repository」、「aggregate root」就會發現,這些做法是以資料對應層的角度去實做,而不是以領域層的角度去設計。
基本上從本質就是有問題的設計,因為資料對應層的工作可以由EF、NHibernate來提供不需要Repository。
Repository應該只需要設計出領域層需要的介面就夠了。
簡單說Repository的用意是隔離相依,讓領域層不相依任何其他的層,提高整個領域層的重用性、可測試性。 ^^
感謝 Clark 的分享!
回覆刪除我發現你有一篇文章詳細說明了你在留言中提到的設計方式,還提供了範例程式,對於評估是否採用 Repository 也很有幫助,所以我將你的那篇文章加入本文的延伸閱讀清單了。
我的觀察是,BLL 裡面將會有一層薄薄的 Repository(例如 UserRepository、ProductRepoitory....等等),在 DAL 裡面對應的 Repository 類別才提供了對應的實作,例如你的範例中的 SqlUserRepositoryProvider。然後再透過相依性注入的方式將這兩層黏起來。
如果實際專案的需求會要切換後端資料來源,這樣的設計我想是個不錯的選擇。如果沒有這樣的需求,而且已經決定採用 ORM 的情況下,就得斟酌是否值得引進這個抽象層,畢竟它會增加初期開發的 coding 成本,以及為應用程式加入不少類別(意味著複雜性也可能升高)。
Thanks :)
採用ORM不牴觸的說,例如EFUserRepositoryProvider。:D
回覆刪除嗯,我想是的。只是有個小小疑問:EFUserRepositoryProvider 如果叫做 EFUserProvider 會不會比較恰當?因為這些 XyzRepositoryProvider 類別都是提供 Xyz 物件,而不是提供特定實作的 Repository。
回覆刪除關於 Provider 的設計模式,也許改天我會再整理一篇筆記,不過目前腦袋還很混沌,想法還很模糊...隨緣吧 ^^
IUserRepository
回覆刪除-EFUserRepository
-SqlUserRepository
目前的專案都是採用上列這樣的命名規則,因為採用EFUserProvider少了代表邊界物件的語意。
至於先前文章中會撰寫為UserRepositoryProvider這樣落落長的名稱,
是因為考量UserRepository中有時會封裝一些計算的功能,
為了跟DI進來的邊界物件作區隔,
所以額外加上了Provider尾字再隔離出去一層。
但是現在回頭看,覺得自己有點畫蛇添足阿。XD
嗯,了解。Thanks ^^
回覆刪除順便一提,為了平衡報導,我正在整理另一篇支持 Repository 的筆記。
其實大多數所謂的要切換 persistence layer 的終極目標,大多只是為了 "要能夠用讓系統配合不同的 RDBMS"。
回覆刪除專案不太需要,產品才有可能。
只要搞清楚,persistence layer 的更換真正意義是什麼,要真的是換 RDBMS 的話。
只要設計得宜,EF / NHibernate 這些東西甚至不需要出現。(有當然更好,節省開發時間)。
作者已經移除這則留言。
回覆刪除IT Player, 我昨天才從一位老朋友那裏聽到一件正在發生的事情:原本用得好好的 Oracle,客戶嫌她「太慢」,而開始慎重考慮把資料庫換成 Sybase。他們做的是專案,客戶說了算。或許真的世事難料,原本以為不可能替換的東西,還是有那麼一絲機率會發生 XD
回覆刪除至於把 Oracle 換成 Sybase 的原因是嫌 Oracle 太慢,這又是另一則故事(笑話)了 Orz
Repository一開始的用意,的確只是隔離BLL與DAL,讓領域邏輯不相依DB。系統演進的過程中就會發現,Repository除了隔離DB、讓BLL層更加的凝聚之外,也封裝的資料來源這個概念。
回覆刪除也就是說系統之外的資料來源,都可以用Repository去隔離。例如說:別人的系統、公司內部的子系統、硬體設備,這些都可以當作DAL層的來源。
嗯,Repository 作為隔離之用,合理。
回覆刪除對於系統之外的各種資料來源....老實說這個部分我還有點疑問:是只定義一個 IRepository 介面,然後有多種 Repository 實作,還是每碰到一種一種資料來源,就會有不同的 Repository 介面?
回覆刪除也就是說,是將 DRY 原則發揮到極致(也許是採用泛型 Repository?),還是只是要讓程式撰寫模型盡量趨於一致,這部分我欠缺實務經驗,只憑空想像,覺得似乎是後者的成分居多。
還看過一種說法:如果是關聯式資料庫,就使用 ORM;如果是非關聯式資料庫,則針對那類資料來源實作一個 DAO。其基本理念是:尊重底層儲存體的結構。
寫得有點多了....這些原本打算整理到下一篇筆記的。不過還是很高興聽到各種實戰經驗分享,很難得的。Thanks!!
在設計上會是一個 IRepository 介面,然後有多種 Repository 實作。
回覆刪除這裡面了設計思路,是IRepository是為BLL服務,只提供BLL服務需要的功能。
以這樣的角度去思考,就能讓IRepository的設計範圍不發散。
至於泛型Repository,我是比較少用到的設計,畢竟這樣的設計是以DAL層的角度來看待Repository。
通常會用的場合是在使用EF的場合下,EF已經幫忙建好提供CRUD的物件,
繼承這個物件並且讓這個物件實做IRepository,就能注入到BLL層,這樣是比較有可能會用到泛型Repository的情景。
了解,Thanks :)
回覆刪除我猜測MVC5+EF6抽掉Repository的原因,是因為先前EF5的版本需要Repository做切換測試,但是EF6有了DbSet,,所以不需要利用Reposity也可以直接在記憶體測試??
回覆刪除EF5 有 DbSet 耶。如果我沒理解錯,DbSet 應是從 EF 4.1 開始出現(DbContext 和 DbQuery 也是)。
回覆刪除MVC5+EF5 教學範例之所以略過 Repository,背後的原因或許只是單純因為想要簡化練習步驟,或者是因為 Repository 的相關文章到處都找得到,不需要再重複了。不過猜想畢竟只是猜想。實務上,還是依個別專案的實際需要來決定是否採用。我的看法是若沒有需要、沒有為專案帶來明顯的好處,就不要加上 Repository 層。
抱歉打錯字,上面的 "MVC5+EF5" 應是 "MVC5+EF6",
刪除