DDD 对于团队的上手成本太高了,需要每个人都理解 DDD 的思想,还要有个大佬开路,做决策,否则玩不起来,至少目前我司是弄不起来了,但是学习下其中的一些思想,对平常开发编写代码是大有裨益。
—— 2022-02-21

DDD 核心

  1. 辩证的看待每一个术语,灵活变动
  2. 事件驱动
  3. 既要耦合,又要解耦
  4. DDD 只是一套方法论,目的是写出好维护,易扩展的代码,借鉴它的思想和一些方法,并不是一昧的提倡用DDD,还是要根据业务和团队水平进行实施

    DDD 的难点

    建立领域模型

    DDD 后续的操作都基于领域模型,所以一开始拿到需求后,应该是展开事件风暴,分析建立领域模型。

例如用户基本业务属性有:用户名,真实姓名,手机号,密码,所属省,所属市,所属区,所属单位,关联角色。传统的 CRUD 做法我们会创建一个用户表来维护这些属性,但是在 DDD 中,我们会对这里分析出事件,分别是:用户被新增事件、用户名被修改事件、用户被删除事件。从事件可以推出,用户是我们的领域模型,那这个用户领域模型,应该包含哪些字段呢?

用户存在这些属性:id,用户名,真实姓名,手机号,密码,所属省,所属市,所属区,所属单位,关联角色。

对于用户领域模型来说,省、市、区地址相关属性是值对象,对用户领域模型来说不存在独立的生命周期。而角色是有独立的生命周期,能独立处理业务逻辑,是一个独立的限界上下文

通过业务逻辑,来定义领域模型,将领域能力定义在领域模型内部。

定义用户聚合根:

  1. AggregateRoot 聚合根标识
  2. 聚合根中的属性不是和数据库字段一一对应的,而是更偏向于业务语义
  3. 聚合根中没有 set 方法,因为每一个属性的变更是一个业务逻辑,传统的做法是放在业务上去处理,DDD 中属性的变更应该是在模型内的,模型是具有感知能力的

    1. /**
    2. * 用户聚合根
    3. *
    4. * @author baiyan
    5. */
    6. @Getter
    7. @Builder
    8. @NoArgsConstructor
    9. @AllArgsConstructor
    10. @Slf4j
    11. public class User implements AggregateRoot {
    12. /**
    13. * 用户id
    14. */
    15. private Long id;
    16. /**
    17. * 用户名
    18. */
    19. private String userName;
    20. /**
    21. * 用户真实名称
    22. */
    23. private String realName;
    24. /**
    25. * 用户手机号
    26. */
    27. private String phone;
    28. /**
    29. * 用户密码
    30. */
    31. private String password;
    32. /**
    33. * 用户地址
    34. */
    35. private Address address;
    36. /**
    37. * 用户单位实体
    38. */
    39. private Unit unit;
    40. /**
    41. * 角色实体
    42. */
    43. private List<Role> roles;
    44. /**
    45. * 创建时间
    46. */
    47. private LocalDateTime gmtCreate;
    48. /**
    49. * 修改时间
    50. */
    51. private LocalDateTime gmtModified;
    52. //省略部分逻辑
    53. /**
    54. * 修改用户名
    55. *
    56. * @param userName 修改后的用户名
    57. * @param existUser 根据修改后的用户名查询出来用户
    58. */
    59. public void bindUserName(String userName,User existUser){
    60. ValidationUtil.isTrue(Objects.isNull(existUser) || Objects.equals(existUser.getId(),this.id),"user.user.name.is.exist");
    61. this.userName = userName;
    62. }
    63. }


    定义地址值对象

  4. ValueObject 是值对象标识 ```java /**

    • 地址值对象 *
    • @author baiyan */ @Getter @AllArgsConstructor @NoArgsConstructor public class Address implements ValueObject

      {

      /**

      • 省 */ private String province;

      /**

      • 市 */ private String city;

      /**

      • 区 */ private String county;

      /**

      • 比较地址相等 *
      • @param address 地址
      • @return */ @Override public boolean sameValueAs(Address address){ return Objects.equals(this,address); }

}

  1. <a name="Fcp1N"></a>
  2. ## 仓储层
  3. 仓储层是将领域模型映射成数据模型,只是简单的模型映射转换,但是要注意的是,DDD 中对同一数据不允许有多个修改入口,这样会导致数据一致性难以维护或者说业务逻辑重复。所以针对这里角色和用户是多对多的关系,传统的做法会设计一个表来维护角色和用户的关系,但是这样会有两个数据模型,和前面提到的有冲突了,因此不能单独设计一个表来维护,而是把这些关系放在用户表中来维护。
  4. 用户表:
  5. ```java
  6. @Data
  7. @EqualsAndHashCode(callSuper = true)
  8. @TableName("t_user")
  9. public class UserPO extends BaseUuidEntity {
  10. /**
  11. * 用户名
  12. * */
  13. private String userName;
  14. /**
  15. * 真实姓名
  16. * */
  17. private String realName;
  18. /**
  19. * 手机号
  20. * */
  21. private String phone;
  22. /**
  23. * 密码
  24. * */
  25. private String password;
  26. /**
  27. * 关联的单位id
  28. *
  29. * */
  30. private Long unitId;
  31. /**
  32. * 关联的角色id列表
  33. * */
  34. private String roleIds;
  35. /**
  36. * 省
  37. */
  38. private String province;
  39. /**
  40. * 市
  41. */
  42. private String city;
  43. /**
  44. * 区
  45. */
  46. private String county;
  47. }

DDD 术语

领域

领域是一个业务方向,例如电商中,订单、物流、商品这些都是划分为一个领域。

子域

子域是对领域的二次划分。例如领域订单的子域可以划分为子订单、母订单。

核心域

是整个业务的核心价值提现。

支撑域

不是那么重要,但是又不能没有。

通用域

它的核心体现在高兼容性,在订单中可以用,挪到物流中也可以用。

限界上下文

用来定义领域业务的边界

防腐层

上下文A和上下文B不能直接调用而是要通过防腐层来处理,非常重要