文章 62
浏览 15135
领域驱动开发相关规范经验

领域驱动开发相关规范经验

image.png

领域驱动开发相关规范


1. 包命名规范

  • controller 包:用户界面层
  • service 包:承接来自用户界面层的请求,属于应用服务层;服务类以 AppService 为后缀
  • task 包:定时任务相关的包,属于应用服务层,即定时任务的业务逻辑依旧交割各领域完成。
  • security 包:安全相关的包,属于应用服务层。
  • aop 包:完成某个应用逻辑的 AOP,属于应用服务层。
  • dto 包:按领域放,如果是涉及多个领域,则可直接放在根目录
    • <领域名>
  • domain 包:领域层
    • <领域名>:
      • service:领域服务类,大部分以 Service 为后缀,少部分可以更贴切其业务逻辑/职责来命名,如 managerschedulerbalancervalidator
        • impl:如果 sevice 只有一两个就不要分 impl 包了
      • entity:实体包
      • vo 包:valueObject 类
      • common 包:领域的常量类、工具类;如果常量类、工具类仅在领域内使用,或者只受这个领域管理,则可以放在领域在。
      • dto 包:用于 domain 层可接收的数据传输类,也可直接使用 controller 传输进来的 dto 包,这两种 dto 包均不能有冗余字段;
      • repository 包:数据库接口类;

下面是基础设施层的各种包:

  • dao 包:
    • <领域名>
      • mapper 类
      • 其他的资源类
  • rpc 包:
  • util 包:工具类

领域层不能直接使用 mapper 类的原因

  • mapper 类本质上是 ORM 框架 MyBatis 的技术实现,这些类需要依赖于 MyBatis 相关的类或注解;简而言之,直接使用无法与 MyBatis 解耦;而且有部分需求是使用
  • 封装实体的更新动作与实际持久化操作的不一致:如实体删除动作实际上是逻辑更新、聚合根的持久化操作实际上可能是多个实体的持久化,而 Mapper 是半自动化 ORM,无法做到。

2. 接口方法规范:

  • 除基础设施层外,其他层的包名、类名、方法名的命名都应具备领域知识含义(业务逻辑的概念);
    • Service 层的接口命名规范:
      • 如果 Service 层的方法不包含特别逻辑,仅仅是使用 Repository 进行操作,则应与 Repository 接口保持一致;否则,Service 层的方法应包含领域知识。
      • OrThrows 为后缀来指明方法会以抛出 Exception 的方式来处理异常,如 findByNameOrThrows(name) 就是为空时会抛出异常
      • 检查方法以 check 开头,意味着会布尔类型的检查结果(true、false);
      • 校验方法以 validate 开头,意味着;
    • Repository 的接口规范:
      • 查找接口:findBy...findBy...OR..OR..findBy...AND..AND..
      • 插入、更新类接口:savesaveBatch;
      • 删除类接口:deletedeleteBatch;
  • 基础设施层的命名应根据其技术来命名,以暴露技术实现细节:
    • Mapper 层的接口命名规范:
      • 查询类接口:select...By...query...By...find...By... selectPage;需要标明查询字段
      • 更新类接口:updateupdatexxupdate...By..updateBatch;默认是根据 ID 更新的,如果不是应标明字段名
      • 插入类接口:insertinsertBatchsavesaveBatch;
      • 删除类接口:deletedeleteBatch

总的来说,本规范是希望通过统一的命名规范,让接口方法命名就能轻松表达出方法含义,避免维护大量的方法注释、代码注释,而且越是下层的方法命名、类命名更应该仔细认真,因为这块越下层的类、方法的粒度就越细,复用率很高,命名不好,很影响协作。

注意,上面只是一般性的接口命名规范,如果有些方法原本逻辑复杂,如查询接口的查询字段和查询条件复杂,无法通过方法名去完全表示,则应准备好充分的注释,包括输入字段

3. 应用服务层、领域服务层、实体/值对象的职责划分

3.1 应用服务层 VS 领域服务层的职责划分

  • 每一层的 Service 类严禁不能相互调用,只能垂直向下调用(具体看文档)。
  • 应用服务层应尽量简单,尽量薄,它最大的作用就是跨领域、跨 Service 调用,除此之外,就是与业务无关的横切逻辑。所以在绝大部分情况下,应用服务类会很简单。

>对外,应用服务为外部调用者提供了一个简单统一的接口,该接口为一个完整的用例场景提供了自给自足的功能,使得调用者无需求助于别的接口就能满足业务需求。对内,应用服务自身并不包含任何领域逻辑,仅负责协调领域模型对象,通过它们的领域能力来组合完成一个完整的应用目标。应用服务作为应用外观,仅仅是领域层的一个入口点,通过它可以降低客户程序与领域层实现之间的依赖。作为领域模型对象的包装,它自身不应该包含任何领域逻辑。由此可得到应用服务设计的第一条准则:不包含领域逻辑的业务服务应被定义为应用服务。

>“应用服务是协调者,它们只是负责提问,而不负责回答,回答是领域层的工作。”**注意,对所谓“提问”和“回答”的理解,要站在一个完整用例场景的高度来阐释。当客户端发来请求要执行一个完整的用例场景时,作为协调者的应用服务只负责安排任务,至于任务该怎么做,就是领域模型对象要完成的工作。这实际上是业务价值(Why)与业务功能(What)之间的关系。对于一个用例场景,需要为参与者提供业务价值,该价值由应用服务提供;要实现这一业务价值,需要若干业务功能按照某种顺序进行组合,组合的顺序就是编制,编制的业务功能就是回答问题的领域模型对象。

  • 领域服务的作用同样也是如此,它的作用是协调各个领域对象;不过,实际上不少动作是不属于对象的,所以一般来说,领域服务无法做得很薄。
  • 不要在领域层放置与业务逻辑无关的横切关注点:如 监控指标收集、错误处理、审计日志埋点收集、事务、认证与授权、定时任务等等,这些与业务逻辑无关的横切点应在应用层完成

3.2. 领域服务与实体/值对象的职责划分

实体是领域内的最小操作单元,而领域服务则是协调各个实体来完成应用服务层的请求。领域服务其实是面向过程编程的类,那么哪些操作应该是放在实体,哪些操作应该放在领域服务内,才不会让领域服务过度膨胀。

  • 领域服务只允许放不适合由实体承担的一些动作,或者跨实体调用的逻辑(注意,实体不能调用实体,但聚合根实体可调用其内部领域对象的行为操作)。简单来说,可以优先将操作放在实体内考虑,在确认实体不适合承担该职责时才考虑放在领域服务内。
  • 从粒度来看,实体是一个不可分的细粒度操作行为,复用度应该是很高的(供领域服务类复用);而领域服务则是提供一个中粒度的操作,相对定制化,可复用度相低(只在应用服务层复用);而应用服务则是基本不可复用,是直接针对应用功能定制化。
  • 比如,返回结果封装成定制化的 dto 对象,很明显就是领域服务或应用服务的操作。

3.3 聚合根、实体、值对象、基本类型

  • 实体有两个特征:1)唯一标识(可能是全局的、也可能是局部的)、2)有生命周期管理;
  • 值对象与实体的区别仅在于是否有唯一标识:值对象是没有唯一标识;值对象有可能是有生命周期,也可能是没有生命周期(如仅作为类型);值对象也是可能是
  • 聚合根只能是有全局唯一标识的实体
  • 聚合内部有两种领域对象,一种是普通实体对象,一种是值对象;
  • 实体对象和值对象也可能会有值对象

[图片]
image

3.4 小结

不管是应用服务层,还是领域服务层,都是面向过程编程的,都不应该过于复杂。下面一些经验结论可以帮助大家判断 Service 类的设计是否有问题:

  • 根据代码行数判断:如果一个 service 类超过 100 行代码,可能是有问题的,如果是超过 200 行,那么很大概率是有问题的。
  • Service 类不应包含属性,如果包含属性,那很可能需要使用面向对象建模去抽象一个对象出来。
  • 实体/值对象不允许直接调用 Repository 接口:对象不应该包含持久化相关逻辑,这块应该交给领域服务完成。

应用服务层 和 领域服务层的优化的手段:

  • 尝试把逻辑下放到下一层或使用;
  • 以面向对象去抽象出对象,封装好对象的属性和行为。

4. 实体类规范 & 单元测试

  • 实体需要生成 equalshashCode 方法:以实体的唯一标识去生成;
  • 实体类需要进行单元测试:要覆盖测试实体的行为;
  • 领域服务类的单元测试应模拟实体类:实体是领域服务的下一层业务逻辑,不要耦合在一起测试,不然很难维护;
  • 单元测试要善用一些工具类去快速生成输入参数:如 com.google.common.collect 下的集合工具类

5. 其他规范

  • 不要污染实体类,让实体类有临时传输用的字段
  • 使用 lombok 框架避免维护 get/set 方法:目前是面向对象编程,对象是有行为的,get/set 方法的维护会消耗我们的精力

标题:领域驱动开发相关规范经验
作者:xiaohugg
地址:https://xiaohugg.top/articles/2023/05/26/1685074847868.html

人民有信仰 民族有希望 国家有力量