簡體   English   中英

存儲庫模式最佳實踐

[英]Repository Pattern Best Practice

所以我在應用程序中實現了存儲庫模式,並且在我對模式的理解中遇到了兩個“問題”:

  1. 查詢 - 我已經讀過使用存儲庫時不應該使用IQueryable的響應。 但是,顯而易見的是,您希望每次調用方法時都不返回完整的對象列表。 應該實施嗎? 如果我有一個名為List的IEnumerable方法,那么IQueryable的一般“最佳實踐”是什么? 應該/不應該有哪些參數?

  2. 標量值 - 最好的方法(使用存儲庫模式)返回單個標量值而不必返回整個記錄是什么? 從性能的角度來看,在整行上只返回一個標量值會不會更有效率?

嚴格地說,Repository提供了用於獲取/放置域對象的集合語義。 它提供了圍繞實現實現(ORM,手動,模擬)的抽象,以便域對象的使用者與這些細節分離。 在實踐中,存儲庫通常抽象對實體的訪問,即具有標識的域對象,並且通常是持久的生命周期(在DDD風格中,存儲庫提供對聚合根的訪問)。

存儲庫的最小接口如下:

void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);

雖然你會看到命名差異和添加Save / SaveOrUpdate語義,但上面是“純粹”的想法。 您將獲得ICollection添加/刪除成員以及一些查找程序。 如果您不使用IQueryable,您還會在存儲庫中看到finder方法,如:

FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();

在此上下文中使用IQueryable存在兩個相關問題。 第一種可能是以域對象的關系形式向客戶泄露實現細節,即違反Demeter法則。 第二個是存儲庫獲取可能不屬於域對象存儲庫的查找職責,例如,查找與所請求的域對象相比較少於相關數據的投影。

此外,使用IQueryable“中斷”模式:具有IQueryable的存儲庫可能提供也可能不提供對“域對象”的訪問。 IQueryable為客戶提供了許多關於在最終執行查詢時將實現的內容的選項。 這是關於使用IQueryable的辯論的主旨。

關於標量值,您不應使用存儲庫來返回標量值。 如果你需要一個標量,你通常會從實體本身得到它。 如果這聽起來效率低,那就是,但您可能不會注意到,這取決於您的負載特性/要求。 如果您需要域對象的備用視圖,由於性能原因或者您需要合並來自許多域對象的數據,您有兩種選擇。

1)使用實體的存儲庫查找指定的實體並將項目/地圖映射到展平視圖。

2)創建一個finder接口,專門用於返回一個新的域類型,該類型封裝了您需要的展平視圖。 這不是一個存儲庫,因為沒有Collection語義,但它可能會使用現有的存儲庫。

如果使用“純”存儲庫訪問持久化實體,則需要考慮的一件事是,您會損害ORM的一些好處。 在“純”實現中,客戶端無法提供域對象如何使用的上下文,因此您無法告訴存儲庫:'嘿,我只是要更改customer.Name屬性,所以不要得到那些急切加載的參考資料。 另一方面,問題是客戶是否應該知道這些東西。 這是一把雙刃劍。

至於使用IQueryable,大多數人似乎都習慣於“打破”模式以獲得動態查詢組合的好處,特別是對於諸如分頁/排序之類的客戶端職責。 在這種情況下,您可能有:

Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();

然后,您可以取消所有這些自定義Finder方法,這些方法會隨着查詢要求的增長而使存儲庫變得混亂。

為了回應@lordinateur,我不太喜歡指定存儲庫接口的事實方法。

因為解決方案中的接口要求每個存儲庫實現至少需要一個Add,Remove,GetById等。現在考慮一個通過存儲庫的特定實例保存沒有意義的場景,您仍然需要實現其余的方法使用NotImplementedException或類似的東西。

我更喜歡拆分我的存儲庫接口聲明,如下所示:

interface ICanAdd<T>
{
    T Add(T entity);
}

interface ICanRemove<T>
{
    bool Remove(T entity);
}

interface ICanGetById<T>
{
    T Get(int id);
}

因此,SomeClass實體的特定存儲庫實現可能如下所示:

interface ISomeRepository
    : ICanAdd<SomeClass>, 
      ICanRemove<SomeClass>
{
    SomeClass Add(SomeClass entity);
    bool Remove(SomeClass entity);
}

讓我們退后一步,看看為什么我認為這比在一個通用接口中實現所有CRUD方法更好。

有些對象的要求與其他對象不同。 可能不會刪除客戶對象,無法更新PurchaseOrder,只能創建ShoppingCart對象。 當使用通用IRepository接口時,這顯然會導致實現中出現問題。

那些實現反模式的人通常會實現他們的完整接口,然后會為他們不支持的方法拋出異常。 除了不同意眾多OO原則之外,這打破了他們能夠有效使用他們的IRepository抽象的希望,除非他們也開始在其上設置方法,以確定是否支持給定對象並進一步實現它們。

此問題的一個常見解決方法是轉移到更精細的接口,如ICanDelete,ICanUpdate,ICanCreate等等。這解決了在OO原則方面出現的許多問題,同時也大大減少了代碼重用的數量。在大多數情況下,人們將無法再使用Repository具體實例。

我們都不喜歡一遍又一遍地編寫相同的代碼。 然而,作為建築接縫的存儲庫合同是擴大合同以使其更通用的錯誤位置。

這篇文章中可以看出這些摘錄,你可以在評論中閱讀更多的討論。

關於1:據我所知,IQuerable本身並不是從存儲庫返回的問題。 存儲庫的重點是它應該看起來像一個包含所有數據的對象。 因此,您可以向存儲庫詢問數據。 如果您有多個對象需要相同的數據,則存儲庫的工作是緩存數據,因此存儲庫的兩個客戶端將獲得相同的實例 - 因此,如果一個客戶端更改屬性,另一個將看到,因為他們指向同一個實例。

如果存儲庫實際上是Linq提供者本身,那么它就適合。但是大多數人只是讓Linq-to-sql提供者的IQuerable直接通過,這實際上繞過了存儲庫的責任。 所以存儲庫根本不是存儲庫,至少根據我對模式的理解和使用。

關於2:當然,從數據庫中返回單個值比整個記錄更具性能效果。 但是使用存儲庫模式,根本不會返回記錄,您將返回業務對象。 因此,應用程序邏輯不應該關注字段,而應關注域對象。

但是,與完整的域對象相比,返回單個值的效率如何? 如果您的數據庫模式定義合理,您可能無法衡量差異。

擁有干凈,易於理解的代碼更為重要 - 而不是預先進行微觀性能優化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM