DDD 核心概念与 Domain primitive

本文详细介绍了领域驱动设计(DDD)的核心概念,包括领域、子域、限界上下文、实体、值对象、聚合等。文章强调了DDD在微服务架构中的重要性,指出DDD和微服务的结合能实现业务与架构的一致性。同时,文章提出了Domain Primitive(DP)的概念,解释了DP在减少接口模糊性、数据验证、业务逻辑清晰度和可测试性等方面的优势,并通过案例展示了DP的使用和重构效果,强调了DP在特定领域中定义明确、自我验证和具备行为的特性。
摘要由CSDN通过智能技术生成

一、什么是DDD?

领域驱动设计(Domain-Driven Design 简称DDD),是一套成熟的理论方法来指导中台领域建模以及微服务拆分和设计,聚焦于“如何在复杂业务场景下设计软件”。

2003年埃里克·埃文斯(ErjcEvans)出版了《领域驱动设计》这本书后, DDD诞生。DDD的核心思想是从业务视角出发,根据限界上下文边界划分业务的领域边界,定义领域模型,确定业务边界。在微服务落地时,建立业务领域模型与微服务代码模型的映射关系,从而保证业务架构与微服务系统架构的—致性。但DDD提出后在软件开发领域一直都是“雷声大雨点小” ,直到MartjnFowler提出微服务架构后,DDD才真正 迎来了自己的时代。

DDD首先从业务领域入手,划分业务领域边界,采用事件风暴工作坊方法,分析并提取业务场景中的实体、值对象、聚合根、聚合、领域事件等领域对象,根据限界上下文边界构建领域模型,将领域模型作为微服务设计的输入,进而完成微服务洋细设计。用DDD方法设计出来的微服务,业务和应用边界非常清晰,符合“高内聚,低耦合”的设计原则,可以轻松适应业务模型变化和微服务架构演进。
——《基于DDD和微服务的中台架构与实现》

微服务与DDD相辅相成,属于共生关系。这体现在两个方面:

  • 微服务提倡将应用进行服务化拆分,通过业务领域边界实现应用服务边界的划分。
  • DDD恰好提供了一种基于业务限界上下文边界来实现微服务“高内聚,低藕合”的服务构建方法。

正因如此,将两者合理搭配使用,研发组织可以轻松实现面向服务的设计,享受持续交付与架构演进。

DDD与微服务,乃至中台设计的结合,目前仍是—个非常新的领域。

二、DDD核心知识体系概念

DDD的核心知识有:领域、子域、核心子域、通用子域、 支撑子域、限界上下文、实体、值对象、聚合和聚合根、领域事件、领域服务、应用服务和分层架构等。

2.1 领域

DDD的领域就是这个边界内要解决的业务问题域。

2.2 子域

领域会被细分为不同的子域,可以根据子域自身的重要性和功能属性将它们划分为三类子域,分别是:核心子域、通用子域和支撑子域。

2.3 核心子域

在企业内决定产品或企业核心竞争力的功能子域是核心子域。

2.4 通用子域

那些没有太多 个性化的诉求,同时又会被多个子域重复使用的通用功能子域是通用子域。

2.5 支撑子域

是企业必需的,但它既不是决定产品或企业核心竞争力的功能,也不是被其他子域复用的通用功能,这类子域是支撑子域。

2.6 限界上下文

限界上下文就是在限定的上下文环境内,用来封装通用语言领域对象。保证领域内的—些术语、领域对象等有一个确切的含义的没有语义二义性的一个业务边界。

通用语言中的名词一般可以给领域对象命名,如商品、订单等,它们对应领域模型中的 实体对象。而动词则表示—个动作或领域事件,如商品已下单、订单支付等,它们对应领域模型中的 领域事件 或者 命令。

比如:商品在不同的阶段有不同的表达形式。商品在销售阶段是商品,这是它的原始含义。在销售阶段结束后,商品就进人了运输阶段,这时商品就变成了货物。可见,同样的一件商品,由于业务领域边界的不同,这些通用语言的术语就有了不同的含义。限界上下文就是用来定义这些通用语言的上下文边界的。这个边界既是业务领域的边界,也是微服务拆分的边界。

2.7 实体

实体一般对应业务对象,它具有相对丰富的业务属性和业务行为。
在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法。通过这些方法实现实体自身的业务行为和业务逻辑。DDD更强调面向对象的设计方法。这些实体类通常采用充血模型,与实体相关的所有业务逻辑都在实体类方法中实现,跨多个实体的领域逻辑则在领域服务中实现。实体以领域对象(DO)的形式存在,而与数据库字段映射的应该以持久化对象(PO)的形式存在,PO是贫血的。

DO不一定要与表结构对应,比如用户user与角色role两个持久化对象可生成权限实体,一个DO实体会对应两个持久化对象,这是一对多的场景。
再比如,有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息customer和账户信息account两类数据保存到同—张数据库表中。客户和账户两个实体可根据需要从—个持久化对象中生成,这就是多对—的场景。

充血模型与贫血模型的关键差异:在充血模型中,业务逻辑都在领域实体对象中实现,实体本身不仅包含了属性,还包含了它的业务行为。DDD领域模型中实体是一个具有业务行为和逻辑的对象。而在贫血模型中领域对象大多只有setter和getter方法,业务逻辑统一放在业务逻辑层实现,而不是在领域对象中实现。

2.8 值对象

值对象本质是—个属性集合,主要完成对实体的状态和特征描述。是若干个基于描述目的“具有整体概念和不可修改的属性”。在应用运行时,我们主要关注这些属性集的“值”。如下图,传统数据模型中一般如右边在类中罗列地址相关属性,而在领域模型中地址相关属性被封装为地址“值对象”,人员实体持有这个地址的引用。
屏幕截图 2021-07-10 171331.png
这样做在逻辑上显然更清晰。这样实现在表结构上有两种设计方法:

屏幕截图 2021-07-10 171852.png

2.9 聚合

聚合在领域模型里是一个逻辑边界,它本身没有业务逻辑实现相关的代码。

实体和值对象都只是个体化的业务对象,它们所表现出来的是个体的行为和能力。在领域模型中我们需要一个这样的组织,将这些紧密关联的个体对象聚集在一起,按照组织内统—的业务规则共同完成特定的业务功能,因此就有了聚合的概念。
领域模型内的 实体 和 值对象 就类似这些组织中的个体,而能让实体和值对象协同工作的组织就是聚合。

比如,订单聚合就有自己内部的业务规则,在订单聚合内每次修改商品数据时,它们都必须符合订单聚合的业务规则:“订单总金额等于所有商品明细金额之和。”违反了这个业务规则,就会出现聚合数据不一致等诸多问题。

聚合是数据修改和持久化的基本单元。在传统数据模型中每一个实体都是对等的,在业务逻辑实现时,可以随意找到实体或数据库表完成数据修改, 但这类操作在DDD的聚合内是不被允许的!

2.10 聚合根

如果把聚合比作组织,那聚合根就是这个组织的负责人。
聚合根也称为根实体,但它 不仅是实体,还是聚合的管理者。

  • 聚合根是实体,作为实体,它拥有实体的业务属性和业务行为,可以在聚合根实现自身的业务逻辑。
  • 它作为聚合的管理者,在聚合内负责协调实体和值对象,按照固定的业务规 则,协同完成聚合共同的业务逻辑°
  • 它还是聚合对外的联络人和接口人,聚合之间以聚合根ID关联的方式接受聚合的外部任务和请求,在限界上下文内实现聚合之间的业务协同,聚合外部对象不能直接通过对象引用的方式访问聚合内的对象。比如,当你需要访问其他聚合的实体时,可以在应用服务中调用其他聚合的领域服务,将关联的聚合根ID作为服务参数,先访问聚合根,再通过聚合根导航到聚合内部实体。

2.11 领域事件

在领域建模时,除了命令和操作等业务行为以外,还有—类非常重要的事件,这类事件发生后通常会触发进一步的业务操作,比如:“如果发生……,则……”,当做完……时,通知……”,发生……时,则……”等。在DDD中这类事件被称为领域事件(DomainEvent)。

举例来说,领域事件可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作;
也可以是定时批处理过程中发生的事件,比如批处理生成季缴保费通知单,触发缴费邮件通知操作;
还可以是一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作。

2.12 分层架构

  • **用户接口层:**面向前端用户提供服务和数据适配。这一层聚集了接口和数据适配相关的功能。
  • **应用层:**实现服务组合和编排,主要适应业务流程快速变化的需求。这一层聚集了应用服务和事件订阅相关的功能。
  • **领域层:**实现领域模型的核心业务逻辑。这—层聚集了领域模型的聚合、聚合根、 实体、值对象、领域服务和事件等领域对象,通过各领域对象的协同利组合形成领域模型的核心业务能力。
  • **基础层:**它贯穿所有层,为各层提供基础资源服务。这—层聚集了各种底层资源相关的服务和能力。常见的功能是完成实体的数据库持久化。

领域模型的业务逻辑从领域层、应用层到用户接口层逐层组合和封装,对外提供灵活的服务。既实现了各层的分工和解耦,又实现了各层的协作。
屏幕截图 2021-07-10 193120.png

三、Domain Primitive

实体与值对象,是领域模型的基础单元。
Domain Primitive 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有行为的 Value Object 。

下面从代码示例上来分析DP的好处。

3.1 案例

我们先看一个简单的例子,这个 case 的业务逻辑如下:

一个新应用在全国通过 地推业务员 做推广,需要做一个用户注册系统,同时希望在用户注册后能够通过用户电话(先假设仅限座机)的地域(区号)对业务员发奖金。

先不要去纠结这个根据用户电话去发奖金的业务逻辑是否合理,也先不要去管用户是否应该在注册时和业务员做绑定,这里我们看的主要还是如何更加合理的去实现这个逻辑。一个简单的用户和用户注册的代码实现如下:

public class User {
   
    Long userId;
    String name;
    String phone;
    String address;
    Long repId;
}

// 注册服务
public class RegistrationServiceImpl implements RegistrationService {
   

    // 销售员仓储
    private SalesRepRepository salesRepRepo;
    // 用户仓储
    private UserRepository userRepo;

    public User register(String name, String phone, String address) 
      throws ValidationException {
   
        // 校验逻辑
        if (name == null || name.length() == 0) {
   
            throw new ValidationException("name");
        }
        if (phone == null || !isValidPhoneNumber(phone)) {
   
            throw new ValidationException("phone");
        }
        // 此处省略address的校验逻辑

        // 取电话号里的区号,然后通过区号找到区域内的SalesRep
        String areaCode = null;
        String[] areas = new String[]{
   "0571", "021", "010"};
        for (int i = 0; i < phone.length(); i++) {
   
            String prefix = phone.substring(0, i);
            if (Arrays.asList(areas).contains(prefix)) {
   
                areaCode = prefix;
                break;
            }
        }
        SalesRep rep = salesRepRepo.findRep(areaCode);

        // 最后创建用户,落盘,然后返回
        User user = new User();
        user.name = name;
        user.phone = phone;
        user.address = address;
        if (rep != null) {
   
            user.repId = rep.repId;
        }

        return userRepo.save(user);
    }

    private boolean isValidPhoneNumber(String phone) {
   
        String
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值