NoSQL深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)

当世界让设计(DDD)的案例被,仓储及其上下文都是开发人员学习与讨论的基本点。对及时半独内容之议论,大致包含两个方面:第一单方面是关于仓储及其上下文在整个应用程序架构中之职位;第二个点,则是储存及其上下文的规划与现实技术实现。我拿当本文中,结合Byteart
Retail案例,对这点儿单内容进行讨论。

积存及其上下文在周应用程序架构中的职

积存是DDD中管理对象生命周期的一个第一组件。在面向对象的社会风气里,不仅仅是DDD,甚至是任何软件设计和支付进程,都去不起头对象生命周期的管制:对象的开创、持久化(Persistence)、反持久化(Materialize)以及销毁,每种管理职责都对准目标的状态造成影响。在人情的应用程序开发被,我们会以类DAO(Data
Access
Object)的门类来兑现目标的持久化、反持久化操作,或者会采用Finder/Mediator来就接近之任务。在DDD中,同样是正在些许种植和对象生命周期管理任务相关的零件,它们就是是储存以及工厂。与DAO、Finder/Mediator所不同的是,仓储的落实更加限定在针对整个聚合的操作及(事实上工厂也是如此),通过对聚合根的援来成功所有聚合的持久化、反持久化操作;而DAO、Finder/Mediator则随意性更胜似:它们的统筹可以是面向DTO(Data
Transfer
Object)的,也可以是直接面向数据库的。

恰提到,仓储的贯彻需要限制在针对整聚合的操作及(工厂也是这样),因此,不管是自持久化机制读取对象,还是用对象保存至持久化机制,都亟待经聚合根,以聚众为单位。根据DDD不难理解,聚合是天地模型的主要内容,而于所有应用程序的架中,领域模型是属世界层的,于是,仓储也是天地层的一个有些。

近些年,有网友向本人询问这样的题目:如果说仓储是小圈子层的一个部分,但是仓储的实现多次要涉及到许多技层面达到的东西,比如使以关系项目数据库作为目标持久化机制,那么仓储的兑现就需要封装类似ORM的效应,这样做岂不是令世界层需要负这些技能的切实落实,从而使得两者之间紧密耦合?

于这个题目之答应,我思念当从个别单方面考虑。首先,领域层与领域模型是有限独概念,前者是应用程序架构中之一模一样栽分层,而后人则是应用程序的政工中心组件。领域模型定义在圈子层中,领域层中尚能够包含各级如仓储、工厂、服务等零件,一方面辅助领域模型就整体的事情处理要求,另一方面也世界模型提供生命周期管理。因此,即使世界层耦合了任何基础结构组件,它为能经过合理之模式使,将这些实现细节从世界模型中剥离开来,以确保领域模型的纯净度;其次,即使好去掉领域模型和基础结构组件的耦合关联,我们也未应有要是世界层为一直依赖这些零部件,否则,我们得到的名堂是,当基础结构组件发生变动时,整个世界层组件将转移得不再可用,我们不得不对世界层也进行重构,以适应新的接口需求。

综上所述,一方面,仓储的操作对象是天地模型中的联谊,无论是从DDD的执行思路及,还是打仓储和天地模型中的关联及考虑,仓储都该属于世界层,然而和世界模型不同,仓储需要经过基础结构组件的支持来供劳务,因此储存又将负让这些零件。这就是使开发人员在筹划应用程序架构的时刻,对于仓储的有些具体该什么统筹有了疑惑。

合理之做法是,将积存的接口定义和切实落实分开处理,仓储接口定义在世界层,而仓储的切切实实贯彻则分到世界层之外(注意,这里可以掌握为拿积存的实际贯彻划分到基础结构层,也得以了解呢搭的如出一辙种植外部插件的兑现)。具体到.NET应用程序架构,仓储接口定义在圈子层的次第集中,仓储的有血有肉落实则同时援引领域层程序集和基础结构组件程序集,以贯彻仓储接口。这里恐怕又见面引来一个初的题材:既然仓储的现实贯彻引用了世界层的程序集,那世界层如何调用仓储也?再失去引用仓储的具体贯彻,岂不是招了循环引用?我之答案是:领域层不欲,也无应有引用仓储的切实可行落实,仓储的实际实现该因靠注入(Dependency
Injection)的方,在应用层中赢得,并由应用层通过囤来形成领域对象管理以及职责协调(比如:通过启用分布式事务来保证仓储以及劳务总线之间的事务性)。

产图自于微软的DDD分层架构案例:Microsoft
NLayerApp,从图中的异彩高亮部分足看来,仓储接口和储存实现各自放在世界层与根基结构层:

NoSQL 1

基于以上总结,我大体描绘了一下.NET解决方案被各层的程序集(Assembly)之间的援关系,以供参考。

NoSQL 2

每当Byteart
Retail案例被,仓储接口定义在ByteartRetail.Domain程序集中,而仓储的兑现有则形容以了ByteartRetail.Domain.Repositories程序集中,以下是Visual
Studio
2012遭遇化解方案资源管理器下的种类组织,我为此数字对四只根本部分做了标注:1、领域层的所有情节都定义在ByteartRetail.Domain程序集中;2、在该程序集的Repositories目录(命名空间)下,定义了储存的接口(事实上还隐含了蕴藏及下文的接口定义);3、仓储的实际实现部分写于了ByteartRetail.Domain.Repositories程序集中,该程序集引用了ByteartRetail.Domain程序集;4、在ByteartRetail.Domain.Repositories程序集中提供了针对Entity
Framework的蕴藏实现。

NoSQL 3

接下,再让我们共同了解一下,Byteart Retail案例中,基于Entity
Framework的囤实现。

储存及其上下文的统筹与实现

仓储的实现其实网上发出那么些息息相关的材料,有依据NHibernate的贮存实现,也出基于Entity
Framework Code
First的,在自家要好开支之面向世界让的应用程序开发框架Apworks遭到,就提供了冲三种植技术的储存实现:NHibernate、Entity
Framework Code
First以及MongoDB。相对而言,网文中所提供的有化解方案则简易有效,但和实际项目以中或者时有发生一定之异样。比如,对于EF
Code
First的贯彻,在多篇被,都是直以仓储的泛型基类中查封装了DbContext对象,这样做得好日常的事务处理需求,但要小心的是,由于DbContext对象被查封装在泛型类吃,因此,这种事务性只能以在对有特定聚合的存储操作上,例如:Repository<Customer>可以保证所有对Customer聚合的储存操作都于同一个事务处理范围外,而Repository<SalesOrder>则好包所有对SalesOrder聚合的囤积操作都在另一个事务处理范围外。从DDD的应用层角度看,由于应用层服务承担任务协调,而多只任务很有或需要在相同业务下完了,如果有任务急需同时更新Customer及其相关的SalesOrder,那么,将DbContext限定于仓储之泛型类吃,显然无法就这么的规划要求。

为化解之题目,Byteart
Retail案例与Apworks框架都引入了蕴藏及下文(Repository
Context)的概念,Repository
Context负责事务处理,每一个Repository的实现还见面吃波及到一个Repository
Context上,以便来不同仓储的操作会吃拘于跟一个作业中。具体地说,在这种设计下,应用层服务就待通过劳定位器来赢得一个Repository
Context的实例,就能够保证持续的仓储操作都是于拖欠Repository
Context所管理的与一个业务中:由于劳务定位器的使用,应用层服务在获Repository实例的还要,会由此劳定位器来分析获得Repository
Context,因此,只要以IoC容器中注册Repository
Context类型时,使用了成立之生命周期管理器(Lifetime
Manager),就可知管有Repository<T>类型受到所运用的Repository
Context是与一个实例,于是,当应用层服务到位任务处理下,直接用Repository
Context的Commit方法,即可将工作一破提交。

从今落实达标看,仓储及下文应用了Unit Of
Work模式[PoEAA],鉴于主流ORM框架都抱有对象状态托管职能,因此,仓储及下文的兑现多也都是指向ORM会话组件(比如NHibernate
Session或者EF
DbContext)的卷入。当然如此的封装会有自然之风险性,以NHibernate为条例,由于Session对象并无是线程安全之,因此尽量不要跨线程使用Session;更进一步,由于仓储及下文是指向这些会话组件的包,所以,在利用仓储及下文时也应当遵照一些顶尖级操作条款,比如尽量不要使用单件(Singleton)模式来创造及动用Repository
Context,除非你针对你的规划具有十足的握住。下面的UML类图体现了当Byteart
Retail中,Repository和Repository
Context相关的类型定义以及这些项目中的关系,到目前为止,我们的议论还处于抽象层面,并没引入和NHibernate或者Entity
Framework相关的类型定义。(注意:图备受单展示了所波及的品类及其关联,为了简化图形,类型中智及属性的定义并不一定与Byteart
Retail案例之源代码完全一致,如发出入,以自代码为按)

NoSQL 4

紧接下去,我以讨论在Byteart Retail中基于Entity Framework Code
First的仓储设计及贯彻细节。在就片座谈着,我莫会见了多地提到EF Code
First的用法,需要了解怎么当应用程序开发中行使EF Code
First的读者,请直接参考Byteart
Retail的来源于程序代码。更多地,我会把重大在架构设计部分,让读者充分了解及选如此平等栽架构的裨益。

基于Entity Framework Code First的存储设计以及兑现

积存的兑现是多样化的,总体达标提,还是因项目我的实际上情况只要肯定。比如根据NoSQL的储存实现所用的技能,就同因关系项目数据库的积存实现所下的技巧差;即使是关乎项目数据库,使用不同的ORM,也会见造成仓储兑现达标之异样,不难理解,基于NHibernate的存储实现同基于EF
Code
First的存储实现中便时有发生正值定之界别。不过不管怎样,如果采用上文给出之囤及其上下文的计划能够满足项目需要的话语,我们总是好于是框架的功底及进行扩张,以促成面向特定技术的储存及其上下文组件。

当Byteart Retail中,我选用了EF Code
First作为ORM,实现了储存(Repository)和储存及下文(Repository
Context),先来看看Repository Context。从技术实现角度分析,基于EF Code
First的Repository
Context封装了DbContext,这跟达到文中的分析是同样的,从设计与框架下之角度解析,基于EF
Code First的Repository
Context需要实现IRepositoryContext的接口,以便当服务定位器在解析并提供IRepositoryContext类型实例的时刻,能够回来我们的EF
Repository Context。为了供一定之扩展性,我当Byteart
Retail的ByteartRetail.Domain.Repositories程序集中引入了一个初的接口:IEntityFrameworkRepositoryContext,在这个接口中,向外面公开了拜访DbContext的性:

/// <summary>
/// 表示继承于该接口的类型,是由Microsoft Entity Framework支持的一种仓储上下文的实现。
/// </summary>
public interface IEntityFrameworkRepositoryContext : IRepositoryContext
{
    #region Properties
    /// <summary>
    /// 获取当前仓储上下文所使用的Entity Framework的<see cref="DbContext"/>实例。
    /// </summary>
    DbContext Context { get; }
    #endregion
}

由于Repository类本身引用了Repository Context,因此,对于EF
Repository而言,它能挺方便地经之DbContext属性来贯彻基于EF的积存操作(CRUD相关的操作)。至于IEntityFrameworkRepositoryContext接口的求实实现,我就非多探索了,读者朋友请求直接参考ByteartRetail.Domain.Repositories.EntityFramework命名空间下之EntityFrameworkRepositoryContext类的源代码。

连下去是基于EF Code
First的积存设计。仓储设计相对简便易行,不需要引入新的接口,只需要连续上文所设计之Repository抽象类即可,当然,为了能够当蕴藏中行使EF的DbContext,在EF
Repository的构造函数中,需要以注入的IRepositoryContext实例转换为IEntityFrameworkRepositoryContext实例,例如:

public class EntityFrameworkRepository<TAggregateRoot> : Repository<TAggregateRoot>
    where TAggregateRoot : class, IAggregateRoot
{
    private readonly IEntityFrameworkRepositoryContext efContext;

    public EntityFrameworkRepository(IRepositoryContext context)
        : base(context)
    {
        if (context is IEntityFrameworkRepositoryContext)
            this.efContext = context as IEntityFrameworkRepositoryContext;
    }
    // 暂时忽略其它方法和属性
}

每当引入了基于Entity Framework Code
First的囤实现后,与仓储相关的花色及其关系得以据此生图表示(同样,省略了成百上千道以及特性之定义):

NoSQL 5

现今复叫咱对仓储部分的实践以及行使中之几个问题进行再进一步的思想。

设计尤为瞩目的存储接口

其一标题听起像未极端好明。在地方的筹划受到,仓储类型且是以泛型的艺术定义的,于是,无论在通向IoC容器注册之时段,还是以运用的时候,都用为泛型的方法开展定义跟调用,这样虽没什么不好,但老会让代码看起别扭。或许,在咱们的规划受到重复增长同样种更加瞩目的仓储接口会显示又好有的。例如,对于User的蕴藏,我们得定义这样的接口:

public interface IUserRepository : IRepository<User>
{
    #region Methods
    /// <summary>
    /// 根据指定的用户名,获取用户实体。
    /// </summary>
    /// <param name="userName">需要获取的用户的用户名。</param>
    /// <returns>用户实体。</returns>
    User GetUserByName(string userName);
    /// <summary>
    /// 根据指定的电子邮件地址,获取用户实体。
    /// </summary>
    /// <param name="email">需要获取的用户的电子邮件地址。</param>
    /// <returns>用户实体。</returns>
    User GetUserByEmail(string email);
    #endregion
}

当此接口中,我们好观看零星独可读性更好的方式:GetUserByName和GetUserByEmail,从点子名就能够很快意识到其意义,当然,这些办法本身也是动一些规则(Specification)来调用已有些仓储方法来博结果,不过增加了代码的可读性,而且于IoC注册仓储实例的时刻,也堪直接以这些接口,这对仓储部分的纵向扩展是发生益处的。这本身用于底下介绍就有的情。

IUserRepository接口的落实比较简单,如下:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(IRepositoryContext context)
        : base(context)
    { }

    public User GetUserByName(string userName)
    {
        return Find(new UserNameEqualsSpecification(userName));
    }

    public User GetUserByEmail(string email)
    {
        return Find(new UserEmailEqualsSpecification(email));
    }

}

仓储的横向扩张

在Byteart Retail中,我使用的是基于Entity Framework Code
First的蕴藏实现,假如我们希望Byteart
Retail能够运用基于NHibernate或者MongoDB等另外技术之贮存,应该怎么处置为?

骨子里深粗略,只要定义两个分级继承于RepositoryContext以及Repository抽象类的类,并在及时半只类别中应用这些技巧来就仓储及其上下文的操作,最后当ByteartRetail.Services项目之web.config中配备使用新的囤积实现即可。这并无是一个颇麻烦的题目,关键是设力所能及管理好仓储所下的技艺资源。

存储的纵向扩展

如上之储存及其上下文的宏图,作为同样种植框架而言,无法涵盖所有的目标持久化/反持久化操作需求。比如原先发许多对象咨询过自己,假如自己希望仓储能够基于多只字段展开排序,然后以分页的点子受来某页中之目标集合,应该怎么处置?不错,目前底之规划无法满足这样的需要,因为仓储的接口中尚无一个计会经受多单排序字段的参数,但是,我们可借上面“更为瞩目的接口”对这企划开展扩张。

先是,另外定义一个接口,比如:ICustomUserRepository,使得这个接口继承给IUserRepository接口,然后在这接口中定义支持多配段排序、分页获取对象的法;然后,另外定义一个类,比如:CustomUserRepository类,使该继续给UserRepository类,并落实ICustomUserRepository接口,如此一来,就随便需修改外现有的蕴藏代码,即可完成新职能的丰富。最后,我们会意识:我们新引入了一个接口和一个类似(你得将它们定义在另外一个独立的Assembly中),同时我们尚修改了ByteartRetail.Services项目的web.config,将IUserRepository接口的报替换为ICustomUserRepository(或者也可增加了ICustomUserRepository的登记),于是,整个仓储框架无需修改,更无需二涂鸦编译。恭喜您,你早就好将是蕴藏框架当作一个通用组件发布了!

你也许还闹疑问,这样一来,岂不是自家还用改仓储的调用部分?这即将扣押你的上上下下应用程序的规划是否能满足如此的改了。对于接近Byteart
Retail这样的面向DDD分层架构的应用程序来说,仓储的调用部分都位居下(Application)层,而Byteart
Retail的应用层也是面向接口定义的,因此,使用面向对象的手腕来替换应用层的实现并非难事。

总结

正文详细介绍了仓储及其上下文在全体应用程序架构中之岗位,并组成Byteart
Retail案例教学了基于EF Code
First的囤积设计和促成方式。在本文的最终部分,对这样的存储设计进行了重复怪层次之分析和座谈,尤其是在蕴藏扩展的连带问题上。希望本文能够解答读者朋友心里大多数跟世界让设计“仓储”相关的疑难。也接大家积极参与讨论,提出宝贵意见。

网站地图xml地图