领域驱动设计的基础知识总结

1. 什么是领域(Domain)

咱们所作的软件系统的目的都是来解决一系列问题,例如作一个电商系统来在线销售本身企业的产品;作一个灰度发布平台来提高服务的质量和稳定性。任何一个系统都会属于某个特定的领域,例如:html

  • 论坛是一个领域:要作一个论坛,那这个论坛的核心业务是肯定的:好比用户发帖、回帖等核心基本功能;
  • 电商系统是一个领域:只要是电商领域的系统,那核心业务就是:商品浏览、购物车、下单、减库存、付款交易等核心环节;

同一个领域的系统都具备相同的核心业务,由于他们要解决的问题的本质是相似的。所以能够推断:一个领域本质上能够理解为一个 问题域 。只要肯定了系统所属的领域,那么这个系统的核心业务,即要解决的关键问题就基本肯定了。一般咱们说,要成为一个领域的专家,必需要在这个领域深刻研究不少年才行,只有这样才会遇到很是多的该领域的问题,积累了丰富的经验。算法

2.界限上下文(Bounded Context)

一般来讲,一个领域有且只有一个核心问题,咱们称之为该领域的『核心子域』。在核心子域、通用子域、支撑子域梳理的同时,会定义出子域中的『限界上下文』及其关系,用它来 阐述子域之间的关系 。界限上下文能够简单理解成一个子系统或组件模块。数据库

例如:下图是对酒店管理的子域和界限上下文的梳理:设计模式

3. 领域模型(Domain Model)

领域驱动设计(Domain-Driven Design)分为两个阶段:安全

  1. 以一种领域专家、设计人员、开发人员都能理解的通用语言做为相互交流的工具,在交流的过程当中发现领域概念,而后将这些概念设计成一个领域模型;
  2. 由领域模型驱动软件设计,用代码来实现该领域模型;

因而可知,领域驱动设计的核心是创建正确的领域模型。领域模型具备如下特色:架构

  1. 对具备某个边界的领域的一个抽象,反映了领域内用户 业务需求的本质 。它属于『解决问题空间』。领域模型是有边界的,只反应了咱们在领域内所关注的部分,包括 实体概念(如:货物,书本,应聘记录,地址等),以及 过程概念(如:资金转帐等);
  2. 提升软件的 可维护性,业务可理解性以及可重用性。领域模型确保了咱们的软件的业务逻辑都在一个模型中,帮助开发人员相对平滑地将领域知识转化为软件构造;
  3. 贯穿软件 分析、设计、开发 的整个过程。领域专家、设计人员、开发人员面向同一个模型进行交流,彼此共享知识与信息,因此能够防止需求走样,让软件开发人员作出来的软件真正知足需求;要创建正确的领域模型并不简单,须要领域专家、设计、开发人员积极沟通共同努力,而后才能使你们对领域的认识不断深刻,从而不断细化和完善领域模型;
  4. 为了让领域模型看的见,使用的经常使用表达领域模型的方式:图、代码或文字;
  5. 重要性:领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型可以更快速的响应需求变化;

4. 领域通用语言

由软件专家和领域专家合做开发一个领域的模型是有必要的。开发过程当中, 开发人员以类、算法、设计模式、架构等进行思考与交流。但领域专家对此一无所知,他们对技术上的术语没有太多概念,只了解特有的领域专业技能,例如:在空中交通监控样例中,领域专家知道飞机、路线、海拔、经度、纬度,他们有本身的术语来讨论这些事情。软件专家和领域专家交流过程当中,须要作翻译才能让对方理解这些概念。函数

领域驱动设计的一个核心原则是使用一种基于模型的语言。使用模型做为语言的核心骨架,要求团队在进行全部的交流是都使用一致的语言,在代码中也是这样,这种语言被称为『通用语言』。工具

5.建模思考的问题:用户需求

『用户需求』不能等同于『用户』,捕捉『用户心中的模型』也不能等同于『以用户为核心设计领域模型』。设计领域模型时不能以用户为出发点去思考问题,不能老想着用户会对系统作什么;而应该从一个客观的角度,根据用户需求挖掘出领域内的相关事物,思考这些事物的本质关联及其变化规律做为出发点去思考问题。性能

领域模型是 排除了人以外的客观世界模型 ,包含了人所扮演的参与者角色。可是通常状况下不要让参与者角色在领域模型中占据主要位置,不然各个系统的领域模型将变得没有差异,由于软件系统就是一我的机交互的系统,都是以人为主的活动记录或跟踪。例如:学习

  • 论坛中若是以人为主导,那么领域模型就是:人发帖,人回帖,人结贴,等等;
  • 货物托运系统中若是以人为主导,就变成了:托运人托运货物,收货人收货物,付款人付款,等等;

以一个货物运输系统为例子简单说明一下。在用户需求相对明朗以后,这样描述领域模型:

  • 一个Cargo(货物)涉及多个Customer(客户,如托运人、收货人、付款人),每一个Customer承担不一样的角色;
  • Cargo的运送目标已指定,即Cargo有一个运送目标;
  • 由一系列知足Specification(规格)的Carrier Movement(运输动做)来完成运输目标;

以上描述没有从用户的角度去描述领域模型,而是以领域内的相关事物为出发点,考虑这些事物的本质关联及其变化规律的:

  • 以货物为中心,把客户当作是货物在某个场景中可能会涉及到的关联角色,如货物会涉及到托运人、收货人、付款人;
  • 货物有一个肯定的目标,货物会通过一系列的运输动做到达目的地。

以用户为中心来思考领域模型的思惟只是停留在需求的表面,而没有挖掘出真正的需求的本质。领域建模时须要努力挖掘用户需求的本质,这样才能真正实现用户需求。

6. 经典分层架构

 

用户界面/展现层:1)请求应用层获取用户所需的展现数据;2)发送命令给应用层执行用户的命令

应用层:薄薄的一层,定义软件要完成的任务。对外为展现层提供各类应用功能,对内调用领域层(领域对象或领域服务)完成各类业务逻辑。应用层不包含业务逻辑

领域层:表达业务概念、业务状态信息及业务规则,是业务软件的核心

基础设施层:为其余层提供通用的技术能力,提供了层间通讯;为领域层提供持久化机制。

7. 使用的模式

7.1. 总览图

7.2. 关联的设计

关联在领域建模的过程当中很是重要,关联的设计能够遵循以下的一些原则:

  • 关联 尽可能少。对象之间复杂的关联容易造成对象的关系网,对于理解和维护单个对象很不利,同时也很难划分对象与对象之间的边界;另外,减小关联有助于简化对象之间的遍历;
  • 关联尽可能保持 单向 的关联;
  • 在创建关联时,须要挖掘是否存在关联的 限制条件 。若是存在,那么最好把限制条件加到关联上,每每这样的限制条件能将关联化繁为简,即将多对多简化为1对多,或将1对多简化为1对1;

7.3. 实体(Entity)

实体就是领域中须要 惟一标识 的领域概念。由于咱们有时须要区分是哪一个实体:有两个实体,若是惟一标识不同,那么即使实体的其余全部属性都同样,也认为他们是两个不一样的实体。

不该该给实体定义太多的属性或行为,而应该寻找关联,将属性或行为转移到其余关联的实体或值对象上。好比:Customer 实体,有一些地址信息,因为地址信息是一个完整的有业务含义的概念,因此咱们能够定义一个 Address 对象,而后把 Customer 的地址相关的信息转移到 Address 对象上。若是没有 Address 对象,而把这些地址信息直接放在 Customer 对象上,而后对于一些其余的相似Address的信息也都直接放在Customer 上,会致使 Customer 对象很混乱,结构不清晰,最终致使它难以维护和理解。

7.4. 值对象(Value Object)

并非每个事物都必须有一个惟一标识。就以上面的地址对象 Address 为例,若是两个 Customer 的地址信息是同样的,咱们就会认为这两个 Customer 的地址是同一个。用程序的方式来表达就是:若是两个对象全部属性的值都相同,咱们会认为它们是同一个对象,那么就能够把这种对象设计为值对象。

值对象的特征:

  • 值对象 没有惟一标识 ,这是它和实体的最大不一样。值对象在判断是不是同一个对象时是经过它们的全部属性是否相同,若是相同则认为是同一个值对象。在区分是不是同一个实体时,只看实体的惟一标识是否相同,而无论实体的属性是否相同。
  • 值对象是 不可变 的,即全部属性都是只读的,因此能够被安全的共享。

应该给值对象设计的尽可能简单,不要让它引用不少其余的对象。值对象只是一个值,相似(int a = 3)中的『3』,只不过是用对象来表示。值对象虽然是只读的,是一个完整的不可分割的总体,可是能够被整个替换掉:相似(a = 4)把a的值由『3』替换为为『4』,当修改 Customer 的 Address 对象引用时,不是经过 Customer.Address.Street 这样的方式来修改属性,能够这样作:Customer.Address = new Address(…)

7.5. 领域服务(Domain Service)

领域中的一些概念不太适合建模为对象(实体对象或值对象),由于它们本质上就是一些操做、动做,而不是事物。这些操做每每须要 协调多个领域对象。若是强行将这些操做职责分配给任何一个对象,则被分配的对象就是承担一些不应承担的职责,从而会致使对象的职责不明确很混乱。DDD认为领域服务模式是一个很天然的范式用来对应这种跨多个对象的操做。通常的领域对象都是有状态和行为的,而领域服务没有状态只有行为。

领域服务还有一个很重要的功能就是能够避免领域逻辑泄露到应用层。由于若是没有领域服务,那么应用层会直接调用领域对象完成本该是属于领域服务该作的操做,须要了解每一个领域对象的业务功能,以及它可能会与哪些其余领域对象交互等一系列领域知识。这样一来,领域层可能会把一部分领域知识泄露到应用层。对于应用层来讲,经过调用领域服务提供的简单易懂且意义明确的接口确定也要比直接操纵领域对象容易的多。

说到领域服务,还须要提一下软件中通常有三种服务:应用层服务、领域服务、基础服务。从如下的例子中能够清晰的看出每种服务的职责:

应用层服务

  1. 获取输入(如一个XML请求)
  2. 发送消息给领域层服务,要求其实现转账的业务逻辑
  3. 领域层服务处理成功,则调用基础层服务发送Email通知

领域层服务

  1. 获取源账号和目标账号,分别通知源账号和目标账号进行扣除金额和增长金额的操做
  2. 提供返回结果给应用层

基础层服务

  1. 按照应用层的请求,发送Email通知

7.6. 聚合及聚合根(Aggregate,Aggregate Root)

聚合定义了一组具备 内聚关系 的相关对象的集合,以及对象之间清晰的所属关系和边界,避免了错综复杂的难以维护的对象关系网的造成。咱们把聚合看做是一个修改数据的单元。

聚合有如下特色:

  1. 每一个聚合有一个根和一个边界:根是聚合内的某个实体;边界定义了一个聚合内部有哪些实体或值对象;
  2. 聚合根是外部能够保持对聚合引用的惟一元素,负责与外部其余对象打交道并维护本身内部的业务规则。聚合内部的对象之间能够相互引用,可是聚合外部若是要访问聚合内部的对象时,必须经过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象;
  3. 聚合内除根之外的其余实体的惟一标识都是本地标识,也就是只要在聚合内部保持惟一便可,由于它们老是从属于这个聚合的;
  4. 聚合内部的对象能够保持对其余聚合根的引用;
  5. 删除一个聚合根时必须同时删除该聚合内的全部相关对象,由于他们都同属于一个聚合,是一个完整的概念;
  6. 基于聚合的以上概念,咱们能够推论出从数据库查询时的单元也是以聚合为一个单元,不能直接查询聚合内部的某个非根的对象;

如何识别聚合:

能够从业务的角度分析哪些对象它们的关系是内聚的,可当作一个总体来考虑的,而后这些对象能够放在一个聚合内。关系内聚是指这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。当修改一个聚合时,必须在 事务级别 确保整个聚合内的全部对象知足这个固定规则。聚合尽可能不要太大,不然可能带来必定的性能问题。一般在大部分领域模型中,有70%的聚合一般只有一个实体,即聚合根,该实体内部没有包含其余实体,只包含一些值对象;另外30%的聚合中,基本上也只包含两到三个实体。

如何识别聚合根:

若是一个聚合只有一个实体,那么这个实体就是聚合根;若是有多个实体,那么咱们能够思考聚合内哪一个对象有独立存在的意义而且能够和外部直接进行交互。

7.7. 工厂(Factory)

DDD中的工厂也是一种体现 封装思想 的模式。DDD中引入工厂模式的缘由是:有时建立一个领域对象是一件比较复杂的事情,不只仅是简单的new操做。工厂是用来封装建立一个复杂对象尤为是聚合时所需的知识,将建立对象的细节(如何实例化对象,而后作哪些初始化操做)隐藏起来。

客户传递给工厂一些简单的参数,若是参数符合业务规则,则工厂能够在内部建立出一个相应的领域对象返回给客户;可是若是参数无效,应该抛出异常,以确保不会建立出一个错误的对象。固然也并不老是须要经过工厂来建立对象,事实上大部分状况下领域对象的建立都不会太复杂,只须要简单的使用构造函数就能够了。隐藏建立对象的好处:能够不让领域层的业务逻辑泄露到应用层,同时也减轻了应用层的负担,它只须要简单的调用领域工厂建立出指望的对象便可。

7.8. 仓储(Repository)

仓储被设计出来的缘由:领域模型中的对象自从建立后不会一直留在内存活动,当它不活动时会被持久化到DB中,当须要的时候会重建该对象。因此,重建对象是一个和DB打交道的过程,须要提供一种机制,提供相似集合的接口来帮助咱们 管理对象

仓储里存放的对象必定是聚合,由于以前提到的领域模型是以聚合的概念来划分边界的。咱们 只对聚合设计仓储 ,把整个聚合当作一个总体,要么一块儿取出来,要么一块儿被删除,不会单独对某个聚合内的子对象进行单独查询和更新。仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,在领域模型中定义仓储的接口,而在基础设施层实现具体的仓储。

8.设计领域模型时通常步骤

  1. 根据需求创建初步的领域模型,识别明显的领域概念和之间的关联(1:1, 1:n的关系),用文字精确没有歧义的描述出每一个领域概念的含义;
  2. 分析主要的软件功能,识别主要的应用层的类,这样有助于及早发现哪些是应用层的职责,哪些是领域层的职责;
  3. 进一步分析领域模型,识别出实体、值对象、领域服务;
  4. 分析关联,经过对业务的深刻分析和软件设计原则及性能方面的权衡,明确关联的方向,去掉一些不须要的关联;
  5. 找出聚合边界及聚合根,在分析过程当中会出现难以清洗判断的选择问题,这就依赖平时分析经验的积累了;
  6. 为聚合根配置仓储,通常状况下为一个聚合分配一个仓储,此时设计好仓储的接口便可;
  7. 遍历全部场景,肯定设计的领域模型能有效解决业务需求;
  8. 考虑如何建立实体和值对象,是经过工厂仍是构造函数;
  9. 重构模型,寻找模型中有疑问或蹩脚的地方,好比思考:聚合的设计是否正确,模型的性能等等;

领域建模是一个不断重构,持续完善的过程,你们会在讨论中将变化的部分反映到模型中,从而模型不断细化并朝正确的方向走。

9. 参考

本文是阅读学习 汤雪华的博客 后所作的一些整理,但愿能对你们有所帮助~

相关文章
相关标签/搜索