DDD系列:四、领域层设计规范

   在一个DDD架构设计中,领域层的设计合理性,会直接影响整个架构的代码结构以及应用层、基础设施层的设计。但不同业务的领域层设计是需要不断思考和演进的,既要避免未来的扩展性差,又要确保不会过度设计导致复杂性。

传统OOP的缺陷:

  1. extends导致的强依赖父类对象行为,子类若重写父类方法时,违反OCP、里氏替换原则
  2. open for extension可以通过extend语法实现,但close for modified比较难实现,但可以考虑通过组合来实现扩展性(即将新增一个类,将两者的类组合起来)。
  3. 各个对象行为划分问题,某个行为可能影响多个对象,那么其业务逻辑实现应该在哪里?
  4. 多个对象有相同行为时,会有重复的代码,在两个对象中存在,如何维护

设计规范

Entity 实体类设计规范

   Entity中,包含了一个领域的状态和对这个领域的操作。Entity的设计,应当保证实体的不变性,这里所说的不变性指的是Entity在初始化或接受任何操作后,内部的属性没有一致性或冲突问题。

1. 创建即一致

   保证业务流程中,拿到的Entity属性是完整且有效的。为了达到这个效果,建议Entity的创建使用下面两种方式:

使用constructor创建Entity,对所有属性进行初始化

   该方案中,可能导致业务使用时,对Entity construct中,传入过多的参数,如:

Weapon weapon=new Weapon(name,null,weaponTypeEnum,weaponHealth,buffer)

使用Factory模式来创建Entity,降低创建的复杂度

   将初始化动作用Factory来完成,业务只需要传入特有的变化数据。如:

Weapon weapon=WeaponFactory.createWeaponFromEmptyId(name,WeaponPrototype weaponPrototype);

2. 使用Entity的行为对方法进行命名,替代public setter

   传统的public setter,只对Entity中某个属性值进行变更,非常容易打破Entity中的一致性。如订单Entity中,如果只是用setter修改订单状态,那么内部关联的子订单就有可能出现不一致的情况。

public class Weapon{
	int  damageType;
  
  /**
  在DDD模式下,setter更名为changeDamageTo更合理,且方法中可以对相关联的属性进行设值
  **/
  public void setDamageType(int type){
    this.damageType=damageType;
  }
  
}

3. 不强依赖外部的领域服务或聚合根

   原因:Entity依赖(指的是Entity属性中的依赖)外部的 DomainService 或聚合根时,当前Entity 无法保证依赖对象变更后,自己内部的数据是否还具有正确性和一致性。

正确的对外部依赖的方法有如下两个:

  • Entity中依赖外部DomainServie的id,这个id建议是强类型的id,非Long型的id。因为强类型的id可以包含校验动作,可以在entity构建时,和其他基础类型区分开。
  • 通过入参的方式,传入外部DI的Service服务,如equip(Weapon, EquipmentService)

Domain Service 领域服务设计规范

需要使用domain service的原则:

  1. 操作多个entity,确保多个实体的变更具有一致性
  2. 要使用托管的第三方服务来操作Entity
  3. 通用组件,多个Entity都具有的某个行为

思考

如何判断 Entity 设计是否合理?

  1. 是否符合业务概念:Entity应该反映出业务中的实体概念、重要信息,并且能够正确地表达实体之间的关系和属性
  2. 是否具有可测试性:Entity 应该具有可测试性,能够通过自动化测试来验证其正确性。
  3. 内部一致性:创建的 Entity 应当保证内部数据的合理、有效,Entity 对外提供的操作,也应当保证该原则。

随笔

ECS(Entity-Component-system)架构的优点:

  • 组件化:将数据的变化和行为,拆分为不同的组件,实现组件化,降低系统复杂度

  • 行为抽离:即将通用的行为,做到公共的模块中

  • 数据驱动:对象的行为由数据驱动,通过数据驱动+组件化,让对象的行为可根据数据实时变化

DDD下各个层级应当承载的内容

Interface层

   Interface层通常返回统一的Result模型或其子模型,包装所有从Application、domian、infrastructure层抛出的异常。

接口层组成的功能:

   完成鉴权、session校验、接口统一日志、限流、统一接口返回内容、前置缓存、异常统一管理等内容。

接口定义的数量和业务隔离问题:

   在接口层面,当把多个类似的业务,做到同一个接口中时,容易出现参数膨胀,所以在设计接口时,考虑Single Responsibility Principle,若不同的接口有相同的一些操作,需要下沉到Application Service层中去。

   接口层直接对接业务,可以快速的变化,但我们希望ApplicationService层的调整比较小,Domain层几乎没有变化。

Application层

核心内容:

  1. ApplicationService:业务服务编排层,理论上讲,不承接业务,但对领域模型的构建要求较高,所以,可以运行ApplicationService承接部分非核心的、非通用的业务逻辑
    • 服务编排和业务逻辑的区分,需要看具体的代码逻辑来界定,没有一个非常明确的定义。
  2. DTO Assemble:将内部领域模型向DTO转换
  3. 入参由Command、Query、Event组成
  4. 出参为DTO
  5. 不负责处理异常,由上层统一完成处理

Command、Query、Event的应用

CommandQueryEvent单一的入参,如Id
语义待执行的一个命令查询动作已经发生的一个事件对这个id做某个动作
返回内容DTO、BooleanDTOvoidvoid

为什么需要CQE对象

  1. 由于业务的发展,方法的入参会越来越多,导致阅读困难、在使用时容易把同类型的入参位置填错
  2. CQE中,可以对对象中的字段进行校验,避免简单的校验逻辑,出现在application Service中。
    • 如果在使用已存在的CQE时,发现CQE对象中的校验在新的业务场景不合适时,那么说明这个是两个业务,需要再建一个不同的CQE对象,其命名与老的区分开。

Infrastructure层

   利用Anti-Corruption Layer,将外部的透出的模型,转为应用内部的数据模型,屏蔽外部接口变化对应用内业务代码的影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值