领域驱动设计
domain-driven design
领域驱动设计(英语:domain-driven design,缩写 DDD)是软件代码的结构及语言(类别名称、类方法、类变量)需符合业务领域中的习惯用法。
领域驱动设计可以将实现对应到持续进化的模型[1]。
领域驱动设计的前提是:
- 把项目的主要重点放在核心领域(core domain)和领域逻辑
- 以领域中的模型为基础,进行复杂的设计
- 让技术人员以及领域专家合作,以迭代方式来完善特定领域问题的概念模型
概念
模型中有以下概念:
上下文(Context) 情境、脉络、上下文。比如:电子商务系统。
领域(Domain) 知识,影响,或活动。客户使用软件要处理旳问题种类即为软件的领域。
模型(Model) 一类描述域的不同方面并可用于解决相关问题的系统化的抽象。
通用语言(Ubiquitous Language) 一种领域专家使用,为了描述域模型而构造的语言,以减少沟通成本。
战略
理想情况下,只有一个统一的模型。 但是通常情况下都无法实现,因此在实践中通常分成多个模型。 认识这个事实并并且准守它对实践是非常有益的。 策略设计的目的是设计一套原则用于是维护模型完整性,提升领域模型和使用多个模型。
界限上下文(bounded context)
任何大型项目都有多个模型。 然而,当基于不同模型的代码相结合,软件变得越来越多,不可靠,并且难以理解。 团队成员之间的交流变得越来越难。 模型的使用情境变得越来越不清晰。
因此:需要明确定义模型适用的上下文,并且根据团队组织,应用程序特定部分的使用情况以及代码库和数据库模式等物理表现明确设置边界。 保持模型在这些范围内严格一致,并且不被外部的问题影响。
持续集成(continuous integration)
当愈多人在相同的有限背景下工作时,模型就愈应该分裂。 团队越大,问题就越大,即使只有三四个人也会遇到严重的问题。 然而,将系统分解为更小的环境最终会失去一个有价值的集成和一致性。
因此:创建一个经常合并所有代码和其他实现工件的过程,用自动化测试快速标记碎片。通过持续地运用统一术语去夯实随着概念在不同人的头脑中的演变而逐渐形成对模型的共同观点。
上下文映射(context map)
在缺乏全局认识的情况下,个别有界上下文会留下一些问题。 其他模型的背景可能仍然是模糊不清的。 其他团队的人不会意识到上下文的界限,并且会不知不觉地做出模糊边缘或使连接复杂化的变化。 当连接必须在不同的上下文之间进行时,它们往往会相互渗透。
因此:确定项目中正在使用的每个模型并定义其有界的上下文。 这包括非面向对象子系统的隐式模型。 命名每个有界的上下文,并将其命名为通用语言的一部分。 描述模型之间的关联点,确保任何用于共享交流的词语都有清晰明确的含义。 映射现有的情形。
基础
在领域驱动设计一书中[2]阐述了一些高层次的概念和实践,比如通用语言,这意味着领域模型应该形成领域专家为描述系统需求而提供的共同语言,同样的,这些语言也需要能够被商业用户或赞助商和软件开发商使用。本书专注于将领域层描述为具有多层体系结构的面向对象系统中的常见层次之一。在 DDD 中,有表示,创建和检索域模型的工件:
Entity:
一个不由自身属性定义而是由标识线和它的身份定义的对象 例如:大多数航空公司在每次航班上都独特地区分每个座位。每个席位都是在这种情况下的一个实体。不过,西南航空,EasyJet 和瑞安航空并没有区分每个座位;所有的座位都是一样的。在这种情况下,一个席位实际上是一个Value Object
Value Object:
只包含元素属性的不可变对象 例如:当人们交换名片时,他们一般不会区分每张独特的名片;他们只关心印在卡片上的信息。在这种情况下,名片是 Value Object
Service:
强调与其他对象的关系,只定义了可以为客户做什么,不应该替代 Entity 和 Value Object 的所有行为
Module:
一种表达机制,划分代码和概念
Factory:
对于那些需要创建特定域对象的方法应该委派给工厂对象,因为这样可以更容易的替换实现
Repository:
对于检索特定域对象的方法应该委派给 Repository 对象,因为这样可以很容易地互换替代存储的实现
Aggregate:
由 ROOT ENTITY 绑定在一起的对象的集合,也称为聚合根。聚合根通过禁止外部对象保持对其成员的引用来保证在聚合内进行的更改的一致性 例如:驾驶汽车时,不必思考如何让车轮前进,如何点燃引擎等。你只需要正常的使用。在这种情况下,汽车是其他几个对象的集合,并作为所有其他系统的聚合根
Domain Event:
一个域对象定义了一个事件。域事件是域专家所关心的事件
局限
为了帮助保证模型能作为一个单纯并有用的语言结构,团队通常必须在领域模型中实现大量的隔离和封装。因此,基于领域驱动设计的系统可能会花费相对较高的成本。虽然域驱动设计提供了许多技术优势,如可维护性,但 Microsoft 建议仅将它应用于复杂领域中,在这些复杂领域中,通过模型和语言处理能够在复杂信息中提供交流便利性的,并且能够该领域达成共识。
限界上下文
限界上下文概念
BC与业务的关系:
通过对业务的划分,比如订单系统,订单是一个子域;库存是一个子域; 其中商品再不同的子域中所表示的意义也不同,比如在订单上下文中的商品表示商品的单价、折扣等等;而在库存的上下文中商品表示商品的库存量、成本、存放位置等。
BC与技术的关系:
多个子域之间必须需要在应用层进行聚合,而聚合的过程中就引出了技术方案,比如订单到库存到支付,他们应该采用同步方式;这几个子域调用通知都应该是异步,那么可能就需要消息中间件或其它技术方案
限界上下文划分规则
一般来说,先考虑团队规模,来决定最终需要划分到多细粒度的BC,如果团队规模过小而BC过细,则对后期的运维、部署、上线都会造成很大的负担; 在确定好粒度后,可以对语义相关性、功能相关性-业务方向、功能相关性-非业务方向进行划分 按照以上的规则划分之后就得到了多个BC啦
一个BC代表一个微服务吗?:
概念:微服务一般是指将高度相关功能的一个开发部署单元,有自己的技术自治性、技术选型、弹性扩缩容、发布上下频率等,说白了就是各自维护一个业务,然后多个业务组成一个系统,多个业务之间各自管理 关系:这里的BC其实就是一个领域或一个模块或一个业务,如果两个领域相关性很高,就可以包含多个BC,或者如果一个领域访问量非常大,则需要部署在一个微服务中以提高性能
领域驱动设计的四重边界
根据上图所示,我们通过四重来进行架构设计: 分而治之:DDD通过规划四重边界,把领域知识做了合理的固化和分层。业务有核心领域和支持域、业务域中又拆分成多个限界上下文(BC),一个BC中又根据领域知识核心与否进行分层,领域层中按照多个业务(子域)的强相关性进行聚合成一个子域
- 【第一重边界】确定项目的愿景与目标,确定问题空间,确定核心子领域、通用子领域(多个子领域可以复用)、支撑子领域(额外功能,如数据统计、导出报表)
- 【第二重边界】解决方案空间里的限界上下文就是一道进程隔离层面的物理边界
- 【第三重边界】每个限界上下文内,使用分层架构划分为:接口层、领域层、应用层、基础设施层之间的最小隔离
- 【第四重边界】领域层里为了保证各个领域的完整性和一致性,引入聚合的设计作为隔离领域模型的最小单元