领域驱动设计案例:Tiny Library:仓储
在领域驱动设计的案例中,仓储的设计是很具有争议性的话题,因为仓储这个角色本身就与领域模型和基础结构层对象相关,它需要序列化领域对象(应该说是聚合),然后将其保存到基础结构层的持久化机制。于是,在领域驱动设计的社区中,存在两种观点:
1、领域模型不能访问仓储,理由是:仓储需要跟技术架构层打交道,在领域模型中访问仓储就会破坏领域模型的纯净度。需要使用仓储的,需要在领域模型上加上一层,比如Application层,在该层中获取仓储实例并处理持久化逻辑
2、领域模型可以访问仓储,但仅仅是通过仓储接口和IoC容器访问仓储;仓储的具体实现通过IoC注入到领域模型中
其实,这只不过是个人习惯问题,我认为两种方法都可以接受,具体采用哪种方法,就要看具体项目的需求和实现情况而定。在Tiny Library中,由于业务简单,所以采取的是第一种方式,但上述的理由并不充分,换句话说,出于业务需求,我采用了第一种方式,但并不是因为仓储需要跟技术架构层打交道所以才把对仓储的访问放在Application层中。仓储也是领域模型的一部分,领域模型依赖于仓储的抽象。
在TinyLibrary中,仓储的实现仍然依赖Apworks Application Development Framework,因为Apworks已经为仓储、规约的实现搭好了架子,我们无需重新编写一套仓储的实现代码。
一、EdmRepository<TEntity>基类
此类继承于Apworks.Domain.Repositories.Repository<TEntity>类,它的主要任务是定义一个Entity Framework中的Object Context(在本案例中为TinyLibraryContainer),然后通过只写属性的方式,向Repository Transaction Context提供一个设置接口;另外,EdmRepository还实现了几个抽象方法,这样做是为了便于子类的实现(开发人员可以少写点重复代码)。
二、ReaderRepository和BookRepository子类
这两个类提供了基于Reader聚合和Book聚合的仓储实现。泛型约束分别绑定到Reader类和Book类(详情请参考源代码)。
三、EdmTransactionContext类
此类实现Apworks.Domain.Repositories.IRepositoryTransactionContext接口,目的是基于Entity Framework实现一个事务处理上下文。仓储将由此上下文创建获得。从此类的GetRepository泛型方法可以看到,仓储的实体是通过IoC容器获得的,IoC容器的配置位于TinyLibrary.Services项目的web.config文件中。
整个Repository的实现类图如下:
四、TinyLibrary.Services项目app.config配置文件
打开TinyLibrary.Services项目的app.config配置文件,我们可以看到,Apworks采用Unity作为IoC容器:
-
<apworksConfiguration>
-
<objectContainer provider="Apworks.IoC.Unity.UnityContainer, Apworks.IoC.Unity"/>
-
<modelAssemblies>
-
<modelAssembly name="TinyLibrary.DomainModel"/>
-
</modelAssemblies>
-
<caching>
-
<cached key="Infrastructure.ObjectContainer" active="true" cacheManager="cacheManager"/>
-
<cached key="Domain.Repository" active="true" cacheManager="cacheManager"/>
-
</caching>
-
</apworksConfiguration>
而Unity的配置部分,我们分别将ReaderRepository和BookRepository注册到了IRepository<Reader>和IRepository<Book>接口上:
-
<unity>
-
<containers>
-
<container>
-
<types>
-
<type type="Apworks.Domain.Repositories.IRepositoryTransactionContext, Apworks.Domain"
-
mapTo="TinyLibrary.Repositories.EdmTransactionContext, TinyLibrary.Repositories">
-
</type>
-
-
<type type="Apworks.Domain.Repositories.IRepository`1[[TinyLibrary.Domain.Book, TinyLibrary.Domain]], Apworks.Domain"
-
mapTo="TinyLibrary.Repositories.BookRepository, TinyLibrary.Repositories"/>
-
-
<type type="Apworks.Domain.Repositories.IRepository`1[[TinyLibrary.Domain.Reader, TinyLibrary.Domain]], Apworks.Domain"
-
mapTo="TinyLibrary.Repositories.ReaderRepository, TinyLibrary.Repositories"/>
-
-
<type type="Apworks.Core.IIdentityGenerator, Apworks.Core"
-
mapTo="Apworks.Core.GeneralGuidGenerator, Apworks.Core"/>
-
</types>
-
</container>
-
</containers>
-
</unity>
五、关于项目依赖(Project Dependencies)
细心的读者会发现,TinyLibrary.Repositories引用了TinyLibrary.Domain项目,这是为了实现仓储的本职工作:持久化领域模型中的聚合。假设领域模型需要访问仓储,则无法直接引用TinyLibrary.Repositories,只能通过IoC/DI。事实上,TinyLibrary.Services项目根本没有去引用TinyLibrary.Repositories项目,因为TinyLibrary.Services并不需要去依赖仓储的具体实现方式,于是从逻辑上讲,不应该将TinyLibrary.Repositories项目添加到TinyLibrary.Services的引用中去。然而,TinyLibrary.Services的正常运行,是需要TinyLibrary.Repositories的支持的,这个在配置文件中已经体现出来了。为了开发方便,我对这些项目作了如下改动:
- 右键单击TinyLibrary.Repositories项目,选择Properties,在Build选项卡下的Output部分,将项目的编译输出指定到TinyLibrary.Services的bin目录下
- 右键单击TinyLibrary.Services项目,选择Project Dependencies,在弹出的对话框中,勾选TinyLibrary.Repositories项目
早在《EntityFramework之领域驱动设计实践(八)》一文中,我就介绍了仓储的实现方式,在那篇文章的最后给出了一幅图,描述了各个组件之间的关系,这种关系在Tiny Library的项目中也是适用的,在此再转贴一次,以示总结。