仓储模式到底是不是反模式?
【導讀】倉儲模式我們已耳熟能詳,但當我們將其進行應用時,真的是那么得心應手嗎?確定是解放了生產(chǎn)力嗎?這到底是怎樣的一個存在,確定不是反模式?
一篇詳文我們探討倉儲模式,這里僅我個人的思考,若有更深刻的理解,請在留言中給出
倉儲反模式
5年前我在Web APi中使用EntityFramework中寫了一個倉儲模式,并將其放在我個人github上,此種模式也完全是參考所流行的網(wǎng)傳模式,現(xiàn)如今在我看來那是極其錯誤的倉儲模式形式,當時在EntityFramework中有IDbSet接口,然后我們又定義一個IDbContext接口等等,大同小異,接下來我們看看在.NET Core中大多是如何使用的呢?
?????定義通用IRepository接口
public?interface?IRepository<TEntity>?where?TEntity?:?class {///?<summary>///?通過id獲得實體///?</summary>///?<param?name="id"></param>///?<returns></returns>TEntity?GetById(object?id);//其他諸如修改、刪除、查詢接口 }當然還有泛型類可能需要基礎(chǔ)子基礎(chǔ)類等等,這里我們一并忽略
?????定義EntityRepository實現(xiàn)IRepository接口
public?abstract?class?EntityRepository<TEntity>?:?IRepository<TEntity>?where?TEntity?:?class {private?readonly?DbContext?_context;public?EntityRepository(DbContext?context){_context?=?context;}///?<summary>///?通過id獲取實體///?</summary>///?<param?name="id"></param>///?<returns></returns>public?TEntity?GetById(object?id){return?_context.Set<TEntity>().Find(id);} }?????定義業(yè)務倉儲接口IUserRepository接口
public?interface?IUserRepository?:?IRepository<User> {///?<summary>///?其他非通用接口///?</summary>///?<returns></returns>List<User>?Other(); }?????定義業(yè)務倉儲接口具體實現(xiàn)UserRepository
public?class?UserRepository?:?EntityRepository<User>,?IUserRepository {public?List<User>?Other(){throw?new?NotImplementedException();} }我們定義基礎(chǔ)通用接口和實現(xiàn),然后每一個業(yè)務都定義一個倉儲接口和實現(xiàn),最后將其進行注入,如下:
??services.AddDbContext<EFCoreDbContext>(options?=>{options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");});services.AddScoped(typeof(IRepository<>),?typeof(EntityRepository<>));services.AddScoped<IUserRepository,?UserRepository>();services.AddScoped<IUserService,?UserService>());有一部分童鞋在項目中可能就是使用如上方式,每一個具體倉儲實現(xiàn)我們將其看成傳統(tǒng)的數(shù)據(jù)訪問層,緊接著我們還定義一套業(yè)務層即服務層,如此第一眼看來和傳統(tǒng)三層架構(gòu)無任何區(qū)別,只是分層名稱有所不同而已
每一個具體倉儲接口都繼承基礎(chǔ)倉儲接口,然后每個具體倉儲實現(xiàn)繼承基礎(chǔ)倉儲實現(xiàn),對于服務層同理,反觀上述一系列操作本質(zhì),其實我們回到了原點,那還不如直接通過上下文操作一步到位來的爽快
上述倉儲模式并沒有帶來任何益處,分層明確性從而加大了復雜性和重復性,根本沒有解放生產(chǎn)率,我們將專注力全部放在了定義多層接口和實現(xiàn)上而不是業(yè)務邏輯,如此使用,這就是倉儲模式的反模式實現(xiàn)
倉儲模式思考
所有脫離實際項目和業(yè)務的思考都是耍流氓,若只是小型項目,直接通過上下文操作未嘗不可,既然用到了倉儲模式說明是想從一定程度上解決項目中所遇到的痛點所在,要不然只是隨波逐流,終將是自我打臉
根據(jù)如下官方在微服務所使用倉儲鏈接,官方推崇倉儲模式,但在其鏈接中是直接在具體倉儲實現(xiàn)中所使用上下文進行操作,毫無以為這沒半點毛病
EntityFramework?Core基礎(chǔ)設施持久化層
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implementation-entity-framework-core
但我們想在上下文的基礎(chǔ)上進一步將基本增、刪、改、查詢進行封裝,那么我們?nèi)绾畏庋b基礎(chǔ)倉儲而避免出現(xiàn)反模式呢?
我思倉儲模式
在進行改造之前,我們思考兩個潛在需要解決的重點問題
其一,每一個具體業(yè)務倉儲實現(xiàn),定義倉儲接口是一定必要的嗎?我認為完全沒必要,有的童鞋就疑惑了,若我們有非封裝基礎(chǔ)通用接口,需額外定義,那怎么搞,我們可以基于基礎(chǔ)倉儲接口定義擴展方法
其二,若與其他倉儲進行互操作,此時基礎(chǔ)倉儲不滿足需求,那怎么搞,我們可以在基礎(chǔ)倉儲接口中定義暴露獲取上下文Set屬性
其三,若非常復雜的查詢,可通過底層連接實現(xiàn)或引入Dapper
首先,我們保持上述封裝基礎(chǔ)倉儲接口前提下添加暴露上下文Set屬性,如下:
??///?<summary>///?基礎(chǔ)通用接口///?</summary>///?<typeparam?name="TEntity"></typeparam>public?interface?IRepository<T>?where?T?:?class{IQueryable<T>?Queryable?{?get;?}T?GetById(object?id);}上述我們將基礎(chǔ)倉儲接口具體實現(xiàn)類,將其定義為抽象,既然我們封裝了針對基礎(chǔ)倉儲接口的實現(xiàn),外部只需調(diào)用即可,那么該類理論上就不應該被繼承,所以接下來我們將其修飾為密封類,如下:
public?sealed?class?EntityRepository<T>?:?IRepository<T>?where?T?:?class {private?readonly?DbContext?_context;public?EntityRepository(DbContext?context){_context?=?context;}public?T?GetById(object?id){return?_context.Set<T>().Find(id);} }我們從容器中獲取上下文并進一步暴露上下文Set屬性
public?sealed?class?EntityRepository<T>?:?IRepository<T>?where?T?:?class {private?readonly?IServiceProvider?_serviceProvider;private?EFCoreDbContext?_context?=>?(EFCoreDbContext)_serviceProvider.GetService(typeof(EFCoreDbContext));private?DbSet<T>?Set?=>?_context.Set<T>();public?IQueryable<T>?Queryable?=>?Set;public?EntityRepository(IServiceProvider?serviceProvider){_serviceProvider?=?serviceProvider;}public?T?GetById(object?id){return?Set.Find(id);} }若為基礎(chǔ)倉儲接口不滿足實現(xiàn),則使用具體倉儲的擴展方法
最后到了服務層,則是我們的業(yè)務層,我們只需要使用上述基礎(chǔ)倉儲接口或擴展方法即可
最后在注入時,我們將省去注冊每一個具體倉儲實現(xiàn),如下:
以上只是針對第一種反模式的基本改造,對于UnitOfWork同理,其本質(zhì)不過是管理操作事務,并需我們手動管理上下文釋放時機就好,這里就不再多講
我們還可以根據(jù)項目情況可進一步實現(xiàn)其對應規(guī)則,比如在是否需要在進行指定操作之前實現(xiàn)自定義擴展,比如再抽取一個上下文接口等等,ABP vNext中則是如此,ABP vNext對EF Core擴展是我看過最完美的實現(xiàn)方案,接下來我們來看看
ABP?vNext倉儲模式
其核心在Volo.Abp.EntityFrameworkCore包中,將其單獨剝離出來除了抽象通用封裝外,還有一個則是調(diào)用了EF Core底層APi,一旦EF Core版本變動,此包也需同步更新
ABP vNext針對EF Core做了擴展,通過查看整體實現(xiàn),主要通過擴展中特性實現(xiàn)指定屬性更新,EF Core中當模型被跟蹤時,直接提交則更新變化屬性,若未跟蹤,我們直接Update但想要更新指定屬性,這種方式不可行,在ABP vNext則得到了良好的解決
在其EF Core包中的AbpDbContext上下文中,針對屬性跟蹤更改做了良好的實現(xiàn),如下:
除此之外的第二大亮點則是對UnitOfWork(工作單元)的完美方案,將其封裝在Volo.Abp.Uow包中,通過UnitOfWorkManager管理UnitOfWork,其事務提交不簡單是像如下形式
額外的還實現(xiàn)了基于環(huán)境流動的事務(AmbientUnitOfWork),反正ABP vNext在EF Core這塊擴展實現(xiàn)令人嘆服,我也在持續(xù)學習中,其他就不多講了,博客園中講解原理的文章比比皆是
好了,本文到此結(jié)束,倒沒什么可總結(jié)的,在文中已有概括,我們下次再會
總結(jié)
以上是生活随笔為你收集整理的仓储模式到底是不是反模式?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ABP vNext IOC替换原有Ser
- 下一篇: dnSpy反编译、部署调试神器