领域驱动设计 DDD
为什么需要DDD
很多项目的实际情况
用户或者产品经理的需求零零散散。不断变更。
工程师在各处代码中寻找可以实现这些需求变更的代码,修修补补。
软件只有需求分析,并么有真正的设计,系统没有一个统一的领域模型维持其内在的逻辑一致性。
功能特性并不是按照领域模型内在的逻辑设计,而是按照各色人等自己的主观想象设计。
项目时间一长各种困难重重,需求不断延期,线上bug不断,管理者考虑是不是要推倒重来,而程序员则考虑是不是要跑路。
贫血模型VS充血模型
由于事务脚本模式中,Service、Dao这些对象只有方法,没有数值成员变量,而方法调用时传递的数值对象,比如Contract,没有方法(或者只有一些gettter,setter方法),因此事务脚本又被称作贫血模型。
领域模型的对象则包含了对象的数据和计算逻辑,比如合同对象,既包含合同数据,也包含合同相关的计算。因此从面向对象的角度看,领域模型才是真正的面向对象。收入确认是合同强相关的,是合同对象的一个职责,那么合同对象就应该提供一个calculateRecognition方法计算收入。
领域模型是合并了行为和数据的领域的对象模型。通过领域模型对象的交互完成业务逻辑的实现,也就是说,设计好了领域模型对象,也就设计好了业务逻辑实现。和事务脚本被称作贫血模型相对应的,领域模型也被称为充血模型。
我们现在工作中,实际是贫血模型的方式来编程的,充血模型比贫血模型更好。
DDD战略设计
包含
战略设计
领域、子域、界限上下文、上下文映射图
战术设计
实体、值对象、聚合、CQRS、事件溯源
通过战略设计,划分模块和服务的边界及依赖关系,对微服务架构的设计至关重要。
领域是一个组织所做的事情以及其包含的一切,通俗地说,就是组织的业务范围和做事方式,也是软件开发的目标范围。
领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。
子域
领域四一个组织所做的事情以及其包含的一切。这个范围就太大了,不知道该如何下手。所以通常的做法是把整个领域拆分成多个子域,比如、用户、商品、订单、库存、物流、发票等。
如何划分子域
限界上下文
在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该
边界内部的确切含义。这样边界便称为限界上下文。限界上下文和子域具有一对一的关系,用来控制子域的边界,保证子域内的概念统一性。
通常限界上下文对应一个组件或者一个模块,或者一个微服务,一个子系统。
上下文映射图
不同的限界上下文,也就是不同的子系统或者模块之间会有各种的交互合作。DDD使用上下文映射图来设计这种关联和交互。
实体
领域模型对象也被称为实体,每个实体都是唯一的,具有一个唯一标识,一个订单对象是一个实体,一个产品对象也是一个实体,订单ID或者产品ID是它们的唯一标识。实体可能会发生变化,比如订单的状态会变化,但是它们的唯一标识不会变化。
实体设计是DDD的核心所在,首先通过业务分析,识别出实体对象,然后通过相关的业务逻辑设计实体的属性和方法。这里最重要的,是要把握住实体的特征是什么,实体业务承担什么职责,不应该承担什么职责,分析的时候要放在业务场景和界限上下文中,而不是想当然地认为这样的实体就应该承担这样的角色。
值对象
并不是领域内的对象都应该被设计为实体,DDD推荐尽可能将对象设计为值对象。比如像住址这样的对象就是典型的值对象,也许建在住址上的房子可以被当做一个实体,但是住址仅仅是对房子的一个描述,像这样仅仅用来做度量或描述的对象应该被设计为值对象。
值对象的特点是不变性,一个值对象创建以后就不能再改变了。如果地址改变了,那就是一个新地址,而一个订单实体则可能会经历创建、待支付、已支付、代发货、已发货、代签收、待评价等各种变化。
聚合
聚合是一个关联对象的集合,我们将其作为一个单元来处理数据更改。每个集合都有一个跟和一个边界。边界定义聚合内部的内容。根是聚合中包含的单个特定实体。
聚合根:将多个实体和值对象聚合在一起的实体。
一个微服务可以包含一个聚合或者多个聚合,但是一个聚合不能被拆到多个服务里面去了。
演变,早起一个微服务可以有多个聚合,慢慢再拆分成很多单个或者很少聚合的服务。
DDD分层架构
领域实体的组合调用和事务控制在应用层。
DDD六边形架构
领域模型通过应用程序封装成一个相对比较独立的模块,而不同的外部系统则通过不同的适配器和领域模型交互,比如可以通过HTTP接口访问领域模型,也可以通过WebService 或者消息队列访问领域模型,只需要为这些不同的访问接口提供不同的适配器就可以了。
评论