DDD理论学习系列(12)– 仓储

DDD理论学习系列——案例与目录


1. 引言

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

说及囤,我们肯定就是想到了储藏室,仓库一般用来存放货物,而仓库一般由仓库管理员来管理。当工厂生产了同样批判货物时,只待交仓库管理员即可,他承受货物的堆积;当得发货的时节,仓库管理员负责从仓库被捡货进行商品出库处理。当用库存盘点时,仓库管理员负责把关货物状态和库存。换句话说,仓库管理员负责了商品的出入库管理。通过储藏室管理员是角色,保证了储藏室和工厂的独立性,工厂只待承担生产即可,而有关商品如何存放工厂无需关注。

一旦我们如果摆的积存就仿佛于仓库管理员,只不过它承受的不再是货物之管制,而是聚合的管制,仓储介于领域模型和数据模型之间,主要用以汇的持久化和寻找。它隔离了世界模型与数据模型,以便我们关注为世界模型如果无欲考虑什么开展持久化。

2. DDD中之囤

2.1. 存储的集结特性

积存代表一个集聚的联谊,其作为与.Net集合一样,仓储用来储存和去聚合,但还要提供针对性聚合的显式查询及集中。

2.2. 储存以及数据访问层的界别

  1. 囤限定了不得不通过聚合根来持久化和找领域对象,以保有改变和未变性由汇处理。
  2. 囤通过逃匿聚合持久化和搜索的脚技术实现世界层的之持久化无关性(即世界层不需要懂得如何持久化领域对象)。
  3. 积存在数据模型和天地模型定义了一个边际。

2.3. 仓储举例

下我们首先来拘禁一个简约仓储的概念:

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

普普通通来说,仓储由应用服务层调用。仓储定义应用服务执行工作用例时需要的有的数据看方法。而仓储的落实普通位于基础架构层,由持久化框架来支撑。以下的贮存实现是乘ORM框架Nhibernate的ISession接口,它去一个的网关角色,负责领域模型与数据模型的炫耀。

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.4. 储存的误解

存储也在许多误解,许多人觉得该是免必要之泛。当使用被简单的世界模型时,可以直接利用持久化框架来展开数据看。然而当对复杂的领域模型进行建模时,仓储是范的恢弘,它标志聚合检索的作用,可以针对世界模型进行有义的读写,而未是一个技能框架。

为发生众多口看仓储是一致种反模式,因为那隐藏了根基持久化框架的作用。而恰好这多亏仓储的要义。基础持久化框架提供了放的接口用于对数据模型的索和修改,而仓储通过以定义的命名查询方式来界定对聚集的拜访。通过使查询显式化,就再度爱调整查询,且再度要之是积存明确了查询的意,便于领域专家理解。举个例子:我们当存储中定义了一个道GetAllActiveUsers()与sql语句select * from users where isactive = 1var users =db.Users.Where(u=>u.IsActive ==1)对待,很扎眼仓储的章程命名就会为咱懂得了询问的用意:查询所有处于Active状态的用户。除此之外查询,仓储仅暴露必要的持久化方法而无是供有的CURD方法。

2.5. 仓储的中心思想

积存的要义并无是要代码更易于测试,也不是为着便于切换底层的持久化存储方。当然,在某种程度上,这也确确实实是储存所带动的利好。囤的要是维系君的圈子模型与技艺持久化框架的独立性,这样你的世界模型可以凝集来自底层持久化技术之震慑。如果没有仓储这同样交汇,你的持久化基础设备或者会见漏风及世界模型中,并影响世界模型完整性和终极一致性。

3. 世界模型 VS 数据模型

假定选事关项目数据库作为持久化存储,我们可以凭ORM框架来兑现世界模型与数据模型之间的照射和持久化操作。

倘ORM又是呀也?

遵循文章开始中之例证,如果存储对许仓库管理员的角色,那ORM就一定给仓库机器人,而储藏室就相当给数据库。为了好不同商品之归类存放,对库房进行分区,分区就相当给数据表。当企业吸收一画订单做发货处理时,销售员将发货通知单告知仓库管理员,仓库管理员再分配ORM机器人进行捡货。很明显,ORM机器人必须能辨识发货通知单,将发货通知不过中之商品对承诺交库房着储存的货。这中发货通知只是就一定给世界模型,而仓库着存储的货物虽属数据模型。

深信基于上面的比方,我们针对ORM有矣骨干的认。ORM,全称是Object
Relational
Mapping,对象关系映射。ORM的前提是,将对象的习性映射到数据库字段,将对象之间的援映射到数量库表的干。换句话说,ORM负责将代码中定义的目标同关系映射到数据库的发明结构面临失去,并在开展数量看时又以表明数据映射到代码中定义的靶子,借助ORM我们无待去手动写SQL语句就足以完成多少的增删改查。ORM就抽象了关系数据模型,它就是因面向对象的计来代表数据模型,以造福我们当代码中轻松地处理多少。

下我们来探讨一下数据模型与天地模型的异同。关系数据库中之数据模型,它由表和排成,它只是简单的蕴藏结构,用于保存领域模型有时间点的状态。数据模型可以分散于几只说明还几只数据库被。此外,可以下多种形式的持久化存储,例如文件、web服务器、关系数据库或NoSQL。领域模型是针对问题域的肤浅,具有丰富的言语与作为,由实体和价值对象成。对于片领域模型,可能和数据模型相似,甚至同,但在概念上她是老大不同的。ORM同世界模型无关。仓储的企图就是是以世界模型与数据模型分开,而不是吃其模糊化一个型。ORM不是储存,但是仓储可以使用ORM来持久化领域对象的状态。

如您的圈子模型与您的数据模型类似,ORM可以直接照射领域模型到数存储,否则,则要针对ORM进行额外的投配置。

4. 储存的定义和兑现

方也波及了,我们一般在领域层定义仓储接口,在基础设备层实现仓储,以切断领域模型和数据模型。

4.1. 囤积方法要明确

仓储是标准化及是圈子模型与持久化存储之间明显的契约,仓储定义之接口方法不但是CURD方法。它是世界模型的扩展,并盖领域专家所了解的术语编写。仓储接口的概念应该依据应用程序的用例需求来创造,而休是从接近CURD的数码访问角度来构建。

咱来拘禁无异段代码:

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);
    }
}

经上述改造,我们由此艺术的命名来明确询问的意图,符合通用语言的业内。

4.2. 泛型仓储

在实践中我们也许会见发觉,为各国一个成团定义一个存储会造成更代码,因为大部分的数据操作都是接近之。为了代码用,泛型仓储就应时而生。

泛型仓储举例:

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 ();
        }
    }
}

通过定义泛型仓储及默认的贯彻,很可怜程度上开展了代码用。但是,尝试用泛型仓储应用拥有存储并无是一个好之呼声。对于简易的集纳我们可以直接行使泛型仓储来简化代码。但对此复杂的聚合,泛型仓储可能就见面无绝符合,如果因泛型仓储的主意进行多少看,就会搅乱对聚集的走访意图。

对复杂的集结,我们好重定义:

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作返回值,我们只有提供平等种读取数据的措施即可进行各种查询。
然而这种艺术尽管会见引入一个问题,就是事情逻辑会渗透到应用层中失去,并冒出大量再次。比如,在实体中我们一般以IsActiveIsDeleted特性来代表软删除,而如实体中之某条数据为剔除,那么UI中挑大梁无会见重显这条数,那对于实体的询问都亟需包含类似Where(c=> c.IsActive)的linq表达式。对于这种问题,我们尽好于仓储中的艺术吃,比如List()或者ListActive()召开默认处理,而休是当应用服务层每次去指定询问条件。
但是具体是返回
IQueryable还是IEnumerable每个人的观点不同,具体而参照Repository 返回
IQueryable?还是
IEnumerable?。

5. 事务管理和工作单元

事务管理主要是应用服务层的关注点。然而,因为仓储和事务管理紧密相关的。仓储就关注单一聚合的管理,而一个业务用例可能会见波及到又的汇聚。

事务管理由UOW(Unit of
Work)处理。UOW模式的意图是以事情用例的操作着跟踪聚合的备改变。一旦闹了改动,UOW就动工作来协调持久化存储。为了确保数量的完整性,如果提交数据失败,则会回滚所有改变,以保数量保持中状态。

假若至于UOW又是一个犬牙交错的话题,我们后续又出口。

6. 储存的反倒模式(注意事项)

  1. 无须支持即查询(ad hoc query)
    存储不应有开放扩展,不要以支持多种形式的询问,定义比较大的查询艺术,它不光未可知一目了然表述仓储查询的意图,更或者会见造成查询性能。

  2. 推加载是如出一辙栽设计臭味
    集结应围绕不变性构建,并蕴藏有必要的特性去支持不变性。
    因此,当加载聚合时,要么加载所有,要么一个也无加载。
    如果您来一个关系数据库并且正在利用ORM作为数据模型,那么你可能能够延缓加载一些天地对象属性,这样就算得缓加载不待之聚合部分。但是,这样做的题目是,如果您不得不有加载聚合,可能会见招致你的聚集边界错误。

  3. 永不用聚合来落实报表需求
    表可能会见波及到几近个类型的聚众,而仓储是拍卖单一聚合的。另外仓储是冲事务的,可能会见造成报表的属性问题。

  4. 总结

  5. 积存当作世界模型与数据模型的中介,它当映射领域模型到持久化存储。

  6. 存储实现了晶莹剔透持久化,即世界层不需关怀世界对象如何持久化。
  7. 仓储是一个契约,而休是数量访问层。它不言而喻标明聚合所必要的数操作。
  8. ORM框架不是储存。仓储是平等种架构模式。ORM用来为面向对象的方法来表示数据模型。仓储使用ORM来协调领域模型与数据模型。
  9. 存储适用于具有丰富领域模型的境界上下文。对于无复杂工作逻辑的简约限界上下文,直接动用持久化框架即可。
  10. 动用UOW进行事务管理。UOW负责跟踪对象的状态,仓储以UOW协调的工作中进行实际的持久化工作。
  11. 仓储用于管理单个聚合,它不应有控制作业。

参考资料:
天地让设计(DDD)的实践经验分享的持久化透明
Repository Pattern–A data persistence
abstraction
领域让设计(DDD)的实践经验分享的ORM的考虑

网站地图xml地图