DDD理论学习连串(1二)– 仓库储存

DDD理论学习连串——案例及目录


1. 引言

DDD中的Repository,主要有三种翻译:资源库仓储,本文取仓储之译。

谈到仓库储存,大家必然就想开了库房,仓库壹般用来存放在货物,而储藏室一般由仓管员来保管。当工厂生产了一群货物时,只需付出仓管员即可,他负担货物的堆积;当供给发货的时候,仓管员负责从仓库中捡货举办商品出库处理。当供给仓库储存盘点时,仓管员负责把关货物状态和仓库储存。换句话说,仓管员负责了商品的出入库管理。通过储藏室管理员这么些剧中人物,保障了储藏室和工厂的独立性,工厂只须要承受生产即可,而有关商品怎么着存放工厂无需关切。

而大家要讲的存款和储蓄就就像于仓管员,只然则它担负的不再是商品的管理,而是聚合的管制,仓库储存介于领域模型和数据模型之间,首要用来聚合的持久化和搜索。它隔开了世界模型和数据模型,以便大家关怀于天地模型而不需求考虑什么举行持久化。

2. DDD中的仓库储存

二.一. 存款和储蓄的会面个性

储存代表1个集聚的聚众,其行为与.Net集合1样,仓库储存用来存款和储蓄和删除聚合,但同时提供针对性聚合的显式查询以及集中。

二.二. 仓库储存与数量访问层的界别

  1. 存款和储蓄限定了不得不通过聚合根来持久化和寻找领域对象,以有限支持全部改变和不变性由聚合处理。
  2. 储存通过逃匿聚合持久化和搜索的最底层技术达成世界层的的持久化毫无干系性(即世界层不供给精通什么持久化领域对象)。
  3. 仓库储存在数据模型和天地模型定义了二个边际。

二.叁. 存款和储蓄举例

上面大家先是来看一个大约仓库储存的定义:

namespace DomainModel
{
 public interface ICustomerRepository
 {
 Customer FindBy(Guid id);
 void Add(Customer customer);
 void Remove(Customer customer);
 }
}

常常来说,仓库储存由运用服务层调用。仓库储存定义应用服务执行工作用例时索要的全部的数量访问方法。而仓库储存的落到实处普通位于基础架构层,由持久化框架来支撑。以下的仓储达成是凭借OHavalM框架Nhibernate的ISession接口,它扮演1个的网关剧中人物,负责领域模型和数据模型的映照。

namespace Infrastructure.Persistence {
    public class CustomerRepository : ICustomerRepository {
        private ISession _session;
        public CustomerRepository (ISession session) {
            _session = session;
        }
        public IEnumerable<Customer> FindBy (Guid id)
            return _session.Load<Order> (id);
        }

        public void Add (Customer customer) {
            _session.Save (customer);
        }

        public void Remove (Customer customer) {
            _session.Delete (customer);
        }
    }
}

从地点大家得以看看,将世界模型的持久化转移到基础设备层,隐藏了世界模型的技艺复杂,从而使世界对象能够专注于业务概念和逻辑。

2.肆. 储存的误会

储存也设有不少误解,许多人以为其是不要求的虚幻。当使用于不难的小圈子模型时,能够平素动用持久化框架来开始展览数量访问。不过当对复杂的天地模型实行建立模型时,仓库储存是模型的壮大,它标志聚合检索的意向,能够对世界模型进行有意义的读写,而不是一个技术框架。

也有广大人觉着仓储是壹种反情势,因为其隐藏了基础持久化框架的意义。而碰巧那便是仓库储存的要义。基础持久化框架提供了开放的接口用于对数据模型的寻找和改动,而仓库储存通过使用定义的命名查询格局来界定对聚集的走访。通过使查询显式化,就更易于调整查询,且更要紧的是仓库储存明显了询问的来意,便于领域专家了然。举个例子:大家在存款和储蓄中定义了三个主意GetAllActiveUsers()与sql语句select * from users where isactive = 1var users =db.Users.Where(u=>u.IsActive ==1)比较之下,很显眼仓库储存的主意命名就能让大家清楚了询问的企图:查询全部处于Active状态的用户。除此而外查询,仓库储存仅暴露供给的持久化方法而不是提供全数的CU途锐D方法。

2.5. 仓库储存的要点

积存的中央思想并不是使代码更易于测试,也不是为了有利于切换底层的持久化存款和储蓄格局。当然,在某种程度上,这也着实是储存所带来的利好。积存的要义是维持您的天地模型和技巧持久化框架的独立性,那样你的圈子模型能够凝集来自底层持久化技术的熏陶。假若未有仓库储存这一层,你的持久化基础设备可能会漏风到世界模型中,并影响世界模型完整性和终极1致性。

三. 天地模型 VS 数据模型

一经选用关系型数据库作为持久化存款和储蓄,我们能够重视O索罗德M框架来贯彻世界模型和数据模型之间的照射和持久化操作。

而OLX570M又是什么样呢?

根据文章初叶中的例子,倘若存款和储蓄对应仓管员的剧中人物,那OQashqaiM就一定于仓库机器人,而储藏室就一定于数据库。为了便于区别商品的分类存放,对库房进行分区,分区就一定于数据表。当集团吸收接纳一笔订单做发货处理时,销售员将发货公告单告知仓管员,仓库管理员再分配OKugaM机器人进行捡货。很分明,ORubiconM机器人必须能够分辨发货文告单,将发货通知单中的商品对应到仓库中贮存的商品。那中间发货公告单就也正是天地模型,而储藏室中蕴藏的货品就属于数据模型。

信任基于上面包车型客车比方,大家对O宝马X3M有了主导的认识。O揽胜M,全称是Object
Relational
Mapping,对象关联映射。OHummerH贰M的前提是,将指标的习性映射到数据库字段,将指标时期的引用映射到数码库表的涉及。换句话说,ORubiconM负责将代码中定义的靶子和事关映射到数据库的表结构中去,并在拓展多少访问时再将表数据映射到代码中定义的靶子,借助OOdysseyM我们不须要去手动写SQL语句就足以成功数据的增加和删除改查。O逍客M仅仅抽象了关周密据模型,它只是以面向对象的不二等秘书籍来表示数据模型,以利于我们在代码中轻轻松松地处理数量。

下边大家来商量一下数据模型与世界模型的异议。关周到据库中的数据模型,它由表和列组成,它只是简短的存款和储蓄结构,用于保存领域模型有些时间点的状态。数据模型能够分流在多少个表甚至多少个数据库中。其余,能够使用多样情势的持久化存款和储蓄,例如文件、web服务器、关周到据库或NoSQL。领域模型是对难题域的肤浅,具有足够的语言和表现,由实体和值对象组成。对于壹些领域模型,或许与数据模型相似,甚至同一,但在概念上它们是充裕例外的。O昂CoraM与天地模型非亲非故。仓储的功力正是将世界模型与数据模型分开,而不是让它们模糊成二个模型。O猎豹CS陆M不是储存,可是仓库储存能够应用O福特ExplorerM来持久化领域对象的事态。

假诺你的领域模型与您的数据模型类似,OOdysseyM能够一贯照射领域模型到多少存款和储蓄,不然,则供给对O奥德赛M实行额外的映照配置。

四. 仓库储存的定义和落到实处

位置也关系过,大家一般在圈子层定义仓库储存接口,在基础设备层落成仓库储存,以切断领域模型和数据模型。

四.一. 仓库储存方法需分明

积存是标准化上是圈子模型与持久化存款和储蓄之间明显的契约,仓库储存定义的接口方法不不过CU帕杰罗D方法。它是小圈子模型的恢弘,并以领域专家所理解的术语编写。仓储接口的定义应该依据应用程序的用例要求来创立,而不是从类似CUKugaD的数目访问角度来营造。

大家来看1段代码:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatMatch (Query query);
        IEnumerable<Customer> FindAllThatMatch (String hql);
        void Add (Customer customer);
    }
}

如上仓库储存定义了二个FindAllThatMatch办法以支撑客户端以其余形式查询领域对象。这一个方式的陈设思想无可置否,灵活且能够扩大,然则它并不曾驾驭的申明查询的来意,我们就失去了对查询的控制。为了真正理解哪些选拔这一个措施,开发职员须求跟踪相关调用堆栈,才能知悉方法的意向,更别说出现品质难题时怎么入手优化了。因为仓库储存定义的接口方法过于宽泛且不具体,它模糊了世界的的定义,所以定义那样的七个接口方法是空虚的。

大家得以如下改造:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatAreDeactivated ();
        IEnumerable<Customer> FindAllThatAreOverAllowedCredit ();
        void Add (Customer customer);
    }
}

通过以上改造,大家经过艺术的命名来威名赫赫询问的用意,符合通用语言的正规。

四.二. 泛型仓库储存

在实践中大家兴许会发觉,为每二个集合定义一个储存会招致重复代码,因为超越一半的多寡操作都以看似的。为了代码重用,泛型仓库储存就应时而生。

泛型仓库储存举例:

namespace DomainModel {
    public interface IRepository<T> where T : EntityBase {
        T GetById (int id);
        IEnumerable<T> List ();
        IEnumerable<T> List (Expression<Func<T, bool>> predicate);
        void Add (T entity);
        void Delete (T entity);
        void Edit (T entity);
    }

    public abstract class EntityBase {
        public int Id { get; protected set; }
    }
}

泛型仓储达成:

namespace Infrastructure.Persistence {
    public class Repository<T> : IRepository<T> where T : EntityBase {
        private readonly ApplicationDbContext _dbContext;
        public Repository (ApplicationDbContext dbContext) {
            _dbContext = dbContext;
        }
        public virtual T GetById (int id) {
            return _dbContext.Set<T> ().Find (id);
        }

        public virtual IEnumerable<T> List () {
            return _dbContext.Set<T> ().AsEnumerable ();
        }

        public virtual IEnumerable<T> List (Expression<Func<T, bool>> predicate) {
            return _dbContext.Set<T> ()
                .Where (predicate)
                .AsEnumerable ();
        }

        public void Insert (T entity) {
            _dbContext.Set<T> ().Add (entity);
            _dbContext.SaveChanges ();
        }

        public void Update (T entity) {
            _dbContext.Entry (entity).State = EntityState.Modified;
            _dbContext.SaveChanges ();
        }

        public void Delete (T entity) {
            _dbContext.Set<T> ().Remove (entity);
            _dbContext.SaveChanges ();
        }
    }
}

因此定义泛型仓库储存和暗中同意的实现,极大程度上开展了代码重用。可是,尝试将泛型仓库储存应用拥有存款和储蓄并不是3个好的呼声。对于简易的集纳大家得以一贯采纳泛型仓库储存来简化代码。但对于复杂的聚合,泛型仓储恐怕就会不太适合,假如根据泛型仓库储存的办法开展数据访问,就会搅乱对聚集的访问意图。

对于复杂的聚众,大家得以重新定义:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatAreDeactivated ();
        void Add (Customer customer);
    }
}

在促成时,我们能够引用泛型仓库储存来制止代码重复。

namespace Infrastructure.Persistence {
    public class CustomerRepository : ICustomerRepository {
        private IRepository<Customer> _customersRepository;
        public Customers (IRepository<Customer> customersRepository) {
            _customersRepository = customersRepository;
        }
        // ....
        public IEnumerable<Customer> FindAllThatAreDeactivated () {
            _customersRepository.List(c => c.IsActive == false);
        }
        public void Add (Customer customer) {
            _customersRepository.Add (customer);
        }
    }
}

由此那种办法,我们即显著了查询了意图,又简化了代码。

4.3. IQueryable Vs IEnumerable

在概念仓储方法的再次来到值时,大家或者会相比较思疑,是相应一向回到数据(IEnumerable)照旧回到查询(IQueryable)以便实行更为的细化查询?重临IEnumerable会相比安全,但IQueryable提供了越来越好的油滑。事实上,要是应用IQueryable用作重回值,大家仅提供1种读取数据的不二等秘书籍即可举办各个查询。
而是那种办法就会引进三个难点,便是事情逻辑会渗透到应用层中去,并冒出多量重复。比如,在实体中咱们一般采纳IsActiveIsDeleted个性来代表软删除,而如若实体中的某条数据被删除,那么UI中挑钱塘不会再展现那条数据,那对于实体的查询都急需包括类似Where(c=> c.IsActive)的linq表明式。对于那种题材,大家最棒在存款和储蓄中的方法中,比如List()或者ListActive()做暗许处理,而不是在动用服务层每一趟去钦定询问条件。
但具体是回来
IQueryable还是IEnumerable每一个人的观点分裂,具体可参看Repository 返回
IQueryable?还是
IEnumerable?

伍. 事务管理和做事单元

事务管理重如若使用服务层的关切点。但是,因为仓库储存和事务管理紧凑有关的。仓库储存仅关切单一聚合的管制,而2个业务用例大概会涉嫌到三种的聚合。

事务管理由UOW(Unit of
Work)处理。UOW方式的功能是在事情用例的操作中跟踪聚合的具有变更。一旦爆发了转移,UOW就动用工作来协调持久化存款和储蓄。为了保险数量的完整性,假若提交数据战败,则会回滚全数变更,以确认保证数量保持有效情形。

而有关UOW又是三个扑朔迷离的话题,大家继承再讲。

陆. 囤积的反格局(注意事项)

  1. 并非匡助权且查询(ad hoc query)
    积存不该开放扩展,不要为了协理四种格局的查询,定义相比较常见的询问办法,它不只不能够显著宣布仓库储存查询的来意,更也许会促成查询品质。

  2. 推迟加载是1种设计臭味
    集合应围绕不变性营造,并涵盖全体供给的习性去辅助不变性。
    由此,当加载聚合时,要么加载全体,要么贰个也不加载。
    假诺您有多个关周密据库并且正在选拔OEnclaveM作为数据模型,那么你只怕能够延缓加载一些领域对象属性,那样就能够延缓加载不须要的联谊部分。可是,那样做的题材是,假诺你不得不部分加载聚合,恐怕会招致您的集纳边界错误。

  3. 不用使用聚合来完成报表须要
    报表只怕会波及到几个项目标集合,而仓库储存是拍卖单1聚合的。此外仓库储存是依据事务的,大概会导致报表的性质难题。

  4. 总结

  5. 积存看作世界模型和数据模型的中介,它担负映射领域模型到持久化存款和储蓄。

  6. 存款和储蓄达成了晶莹剔透持久化,即世界层不供给关切世界对象怎么着持久化。
  7. 储存是3个契约,而不是数码访问层。它明显标明聚合所不能缺少的数额操作。
  8. O科雷傲M框架不是储存。仓库储存是1种架构方式。O帕杰罗M用来以面向对象的主意来表示数据模型。仓库储存使用ORM来协调领域模型和数据模型。
  9. 积存适用于全体丰硕领域模型的界限上下文。对于尚未复杂工作逻辑的简要限界上下文,直接利用持久化框架即可。
  10. 行使UOW实行事务管理。UOW负责跟踪对象的情状,仓库储存在UOW协调的事务中开始展览实际的持久化学工业作。
  11. 仓库储存用于管理单个聚合,它不应有控制作业。

参考资料:
领域驱动设计(DDD)的实践经验分享之持久化透明
Repository Pattern–A data persistence
abstraction

领域驱动设计(DDD)的实践经验分享之O翼虎M的合计

网站地图xml地图