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来持久化领域对象的情景。

图片 1

即便您的天地模型与你的数据模型类似,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地图