将领域对象映射到微服务代码模型中。DDD强调
以保证领域模型和微服务的一体性。但在构建领域模型时,我们往往是在业务视角,并且有些领域对象还带业务语言。我们还需要将领域模型作为微服务设计的输入,对领域对象进行设计和转换,让领域对象与代码对象建立映射关系。
完成微服务拆分后,领域模型的边界和领域对象就基本确定了。
第一个工作就是,整理事件风暴过程中产生的各个领域对象,比如:聚合、实体、命令和领域事件,将这些领域对象和业务行为记录到下面表格。
这张表格包含:领域模型、聚合、领域对象和领域类型四维。 一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。
从领域模型到微服务落地,还需进一步设计和分析。事件风暴中提取的领域对象,还需经过用户故事或领域故事分析,以及微服务设计,才能用于微服务系统开发。 主要关注内容如下:
这个设计过程建议参与的角色有:DDD专家、架构师、设计人员和开发经理。
事件风暴结束时,领域模型聚合内一般会有:聚合、实体、命令和领域事件等领域对象。 完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。
这些领域对象如何得来?
大多数情况下,领域模型的业务实体与微服务的数据库实体一一对应。但某些领域模型的实体在微服务设计时:
比如分析个人用户时,还要有地址、电话和邮箱等实体,它们被聚合根引用,不易在领域建模时发现,需在微服务设计过程中识别和设计出。
在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为。 比如地址实体有新增和修改地址方法,邮箱实体有新增和修改银行账号的方法。
实体类放在领域层的Entity目录。
聚合根源于领域模型,在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地址、电话及邮箱的生命周期。 个人客户聚合根通过工厂和仓储模式,实现聚合内地址、邮箱等实体和值对象数据的初始化和持久化。
聚合根是一种特殊的实体,它有自己的属性和方法。聚合根可实现聚合间的对象引用,还可引用聚合内的所有实体。聚合根类放在代码模型的Entity目录结构下。聚合根有自己的实现方法,比如生成用户编码,新增和修改用户信息。
根据需要将某些实体的某些属性设计为值对象。值对象类放在代码模型的Entity目录。在个人用户聚合中,用户拥有用户证件类型,它以枚举值形式存在,所以将其设计为值对象。
如果这个领域对象在其它聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,即可设计为值对象。
如果这个对象是多条且需基于其做查询统计,推荐设计为实体。
如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。首先确定领域事件发生在微服务内还是微服务之间。然后设计事件实体对象,事件的发布和订阅机制,以及事件的处理机制。判断是否需要引入事件总线或MQ。
在个人用户聚合中有用户已创建的领域事件,因此它有用户创建事件这个实体。
领域事件实体和处理类放在领域层的Event目录。领域事件的发布和订阅类推荐放在应用层的Event目录。
如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。 领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
按严格分层架构,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。所以如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。然后再被封装为创建个人客户信息应用服务,向前端应用暴露。
领域服务类放在领域层的Service目录。
每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。仓储包括仓储的接口和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。 仓储代码放在领域层的Repository目录结构下。
应用层的主要领域对象是应用服务和事件的发布以及订阅。
在事件风暴或领域故事分析时,我们往往根据用户或系统发起的命令,设计服务或实体方法。为了响应这个命令,我们需要分析和记录:
严格分层架构不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。 如需实现服务的跨层调用,推荐采用服务逐层封装。
服务的封装和调用主要有以下几种方式。
封装时服务前面的名字可以保持一致,可用*DomainService
或*AppService
后缀来区分领域服务或应用服务。
完成上面的分析和设计后,即可建立像下图一样的,领域对象与微服务代码对象的映射关系了。
个人客户领域模型中的个人客户聚合,就是典型的领域模型,从聚合内可以提取出多个实体和值对象以及它的聚合根。该图个人客户聚合做了进一步的分析。提取了个人客户表单这个聚合根,形成了客户类型值对象,以及电话、地址、银行账号等实体,为实体方法和服务做了封装和分层,建立了领域对象的关联和依赖关系,还有仓储等设计。 这个过程建立了领域对象与微服务代码对象的映射。
在建立这种映射关系后,我们就可以得到如下图的微服务代码结构了。
有些业务场景可能并不能如你所愿,你可能无法设计出典型的领域模型。这类业务中有多个实体,实体之间相互独立,是松耦合的关系,这些实体主要参与分析或者计算,你找不出聚合根,但就业务本身来说它们是高内聚的。而它们所组合的业务与其它聚合是在一个限界上下文内,你也不大可能将它单独设计为一个微服务。
这种业务场景其实很常见。比如,在个人客户领域模型内有客户归并的聚合,它扫描所有客户,按照身份证号码、电话号码等是否重复的业务规则,判断是否是重复的客户,然后对重复的客户进行归并。这种业务场景你就找不到聚合根。
那对于这类非典型模型,我们怎么办?
我们还是可以借鉴聚合的思想,仍然用聚合来定义这部分功能,并采用与典型领域模型同样的分析方法,建立实体的属性和方法,对方法和服务进行封装和分层设计,设计仓储,建立领域对象之间的依赖关系。唯一可惜的就是我们依然找不到聚合根,不过也没关系,除了聚合根管理功能外,我们还可以用DDD的其它设计方法。