领域驱动设计(DDD)概述

领域驱动设计(Domain Driven Design, DDD)是一种架构思想,它强调通过建立领域模型来解决业务与技术实现的复杂性问题。DDD不是简单的架构模式,而是一种深入业务、理解业务的方法论。

核心思想与目标

  • 核心思想:构建领域模型,使模型成为架构的核心。- 核心目标:分离业务逻辑的复杂度和技术实现的复杂度。 DDD分为战略设计和战术设计两个层面:

战略设计战略设计关注于高层次的业务划分和限界上下文的集成。

战术设计战术设计则更侧重于使用建模工具细化具体的业务上下文。

战略DDD详解

2.1 简介- 限界上下文(Bounded Context, BC):确定领域边界。- 上下文映射图:描述系统各部分之间的关系,包括: - 共享内核(Shared Kernel) - 客户/供应商(Customer/Supplier) - 追随者(Conformist) - 防腐层(Anticorruption Layer) - 公开主机服务(Open Host Service) - 发布语言(Published Language)

2.2 限界上下文(Bounded Context)#### 2.2.1 概念引入限界上下文用于界定领域边界,确保领域内的概念和操作具有明确的含义。

2.2.1 例子说明以电商平台为例,不同领域(商品、交易、支付、物流)中的相同概念(如商品)具有不同的含义和关注点。

2.2.3 定义与重要性限界上下文确保了领域模型的适用范围和团队成员对模型实现内容的共识。

2.2.4 领域拆分示例电商平台的领域拆分,如图:

// 此处应有图,但文本中无法展示

2.3 通用语言(Ubiquitous Language)团队统一使用的语言,用于清晰、准确地描述业务规则和含义。

实践建议- 明确领域边界,避免模型混淆。- 使用通用语言促进团队沟通。- 通过上下文映射图理解系统各部分的关系。

结论DDD是一种深入业务、指导技术实现的架构方法论,通过战略和战术设计的结合,帮助团队构建清晰、高效的软件系统。


随着业务的不断扩展,原有的领域可以进一步细化为多个子领域,以提高管理的精确性和效率。以下是对这些领域及其子领域的梳理:

  • 商品领域 - 类目管理:负责商品分类的设置与维护。 - 库存管理:确保库存数据的准确性和及时更新。 - 商品信息:管理商品的基本信息和属性。
  • 交易领域 - 交易流程:处理从下单到支付的整个交易过程。 - 促销活动:设计和实施各种促销策略。 - 优惠券系统:管理优惠券的发放、使用和核销。 - 售后服务:提供售后支持和解决用户问题。
  • 支付领域 - 支付核心:处理支付请求和交易结算。 - 支付路由:根据策略选择最优支付路径。 - 支付渠道:管理与各个支付渠道的接口和合作。 这种细分有助于企业更灵活地应对市场变化,同时提高业务流程的透明度和效率。
    在这里插入图片描述

微服务与领域驱动设计

1. 领域拆分与微服务

随着业务发展,领域和子域可能需要进一步拆分。在微服务架构中,每个领域可以独立成为一个微服务。理论上,一个限界上下文(Bounded Context)可以设计为一个微服务,这体现了微服务与领域拆分的紧密联系。

1.1 领域类型

  • 核心领域(Core Domain):公司的业务核心,如电商中的商品、购物车、交易等。- 通用领域(Generic Domain):功能通用,可在市场上购买,如用户管理、权限控制等。- 支撑领域(Supporting Domain):虽非核心,但对核心业务有支撑作用的模块。

2. 通用语言(Ubiquitous Language)

通用语言是团队统一的语言,能够清晰描述业务规则和含义。

2.1 通用语言的价值

  • 解决沟通障碍:确保业务需求的正确表达,避免因术语不同导致的误解。- 业务需求转化为代码:通用语言中的名词和动词可以转化为领域对象和事件。

2.2 通用语言的应用

  • 名词通常对应领域实体,如订单、商品。- 动词通常对应领域事件,如订单支付、订单发货。

3. 上下文映射图(Context Map)

上下文映射图提供了系统间关系的总体视图,包括以下几种关系:

3.1 共享内核(Shared Kernel)

在电商中,不同品类的商品购买流程可能差异很大。例如:

  • 实物商品:买家付款、卖家发货、买家确认收货。- 酒店房间:买家付款、卖家预留客房、买家入驻、买家退房结单。- 旅游线路:买家付款、卖家确认、服务履约。 这种差异性体现了共享内核在不同业务场景下的应用。

在这里插入图片描述

这些业务场景、交易流程虽然有较大的的差异,但是他们可以共同复用核心的交易流程,如下图所示:

在这里插入图片描述
在软件架构设计中,共享内核是一种重要的概念,它指的是多个团队或业务共享同一套核心逻辑或服务。以下是共享内核的一些优势:

  1. 复用性:通过使用统一的抽象交易流程,可以支持不同场景的业务需求,有效降低开发成本。2. 扩展性:当需要引入新的业务特性,如促销、优惠券或套餐时,无需在各个业务线重复开发,实现快速迭代。3. 维护性:在迭代优化时,只需调整共享内核,所有依赖的业务线均能受益,减少了维护成本。 在分布式开发中,常见的模式还包括:
  • 客户/供应商模式:接口提供者作为供应商,接口消费者作为客户,这种模式在微服务架构中十分常见。- 追随者模式:一个系统的状态变化会直接影响另一个系统,例如支付系统的状态变化会驱动交易系统订单状态的更新。- 防腐层模式:当依赖的系统设计不友好时,使用防腐层降低系统间依赖和耦合。- 公开主机服务:将系统服务暴露给其他系统使用,如微服务中的接口服务。

战术DDD(战术设计)

战术DDD关注于如何将战略设计落地到具体的系统实现中。以下是一些关键概念:

聚合与聚合根

  • 定义:在领域模型中,将业务规则相同的个体聚合在一起,形成一个操作单元,其中操作的入口称为聚合根。- 注意:聚合内对象的修改需遵循统一的业务规则,通过唯一标识引用其他聚合。

实体

  • 定义:具有唯一标识的对象,其唯一标识在状态变化后保持不变,对应业务对象。

值对象

  • 定义:没有唯一标识的简单对象,其属性描述了领域中的信息。

领域服务

  • 一些不属于实体或值对象的领域行为或操作,可以归类为领域服务。

领域事件

  • 对领域内发生的活动进行建模。

工厂模式

  • 封装复杂对象的创建过程,避免在聚合根中加入无关内容。

仓储模式

  • 引入Repository层,避免领域层和基础层的紧密耦合。

模块

  • 根据负责的内容,将系统划分为多个模块,每个模块对应一个子领域。 通过上述概念,可以指导从领域划分到系统落地的过程,例如在电商平台的微服务架构设计中,可以利用DDD的原则和模式来实现系统的高效开发和维护。
    在这里插入图片描述
    电商领域的细分与业务系统构建 ======================= 电商领域是一个庞大且复杂的行业,它可以根据业务的相似性和耦合度进行细分。本文档将探讨如何将电商领域细分,并构建相应的业务系统。

1. 电商领域细分

电商领域可以细分为多个子领域,例如:

  • 营销- 客户服务- 供应链管理- 支付处理- 物流配送

2. 业务系统构建原则在构建业务系统时,应将业务相近、耦合紧密的领域聚合在一起。这有助于提高系统的效率和可维护性。

3. 商品领域细分示例以商品领域为例,我们可以进一步细分为以下子领域,并探讨它们之间的关系:

  • 类目:商品的分类,如服装、电子产品等。- 属性:描述商品特征的参数,如颜色、尺寸等。- 属性值:属性的具体值,如红色、M号等。- SPU(Standard Product Unit):标准化产品单元,是商品的基本单位。- Item:商品的单个实例,包含SPU和特定的属性值。- SKU(Stock Keeping Unit):库存单位,是库存管理的基本单位。

4. 聚合构建将关系紧密的内容聚合在一起,形成业务聚合。例如:

  • 商品聚合:包括SPU、Item和SKU。- 属性聚合:包括属性和属性值。 通过这种方式,我们可以构建出高效、灵活且易于维护的业务系统。
    在这里插入图片描述
    在软件设计领域,存在一种称为贫血模型(Anemic Domain Model)的模式。这种模式下,领域对象仅包含数据属性和基本的访问方法,而业务逻辑则被放置在业务逻辑层中。这种设计方式违背了面向对象设计的原则,因为领域对象失去了其应有的行为,变成了简单的数据容器。下面将详细阐述贫血模型的特点和问题。

特点1. 领域对象仅包含数据:贫血模型中的领域对象仅包含属性和基本的数据访问方法,如set和get。2. 业务逻辑分离:所有的业务逻辑都集中在业务逻辑层,如Service或Manager类中。3. 缺乏行为:领域对象没有实现其应有的行为,无法表达领域概念的完整性。

问题1. 违背面向对象原则:贫血模型使得领域对象失去了封装和行为,违背了面向对象设计的基本思想。2. 增加复杂性:业务逻辑层变得复杂和难以管理,因为所有的业务逻辑都集中在一个地方。3. 难以维:随着业务逻辑的增长,维护和理解业务逻辑层的代码变得更加困难。

解决方案1. 增强领域模型:为领域对象添加行为,使其能够表达领域概念。2. 使用领域服务:对于跨越多个领域对象的复杂逻辑,可以使用领域服务来处理。3. 应用门面模式:通过门面模式来简化外部对复杂业务逻辑的访问。

典型结构在Spring框架的项目中,贫血模型尤为常见。领域对象通常只作为数据的载体,而业务逻辑层则承担了所有的逻辑处理。以下是一个典型的使用贫血模型的系统结构图:

贫血模型结构图 请注意,上述结构图仅为示意,具体实现可能会有所不同。
在这里插入图片描述

这种系统结构层次简单清晰,即:Consumer/Api -> Service -> Manager/Biz -> Dao -> Mybatis -> DB。
贫血的领域对象起的作用是:只传递数据,不包含任何业务逻辑。在[DDD-Domain Primitive](http://zshipu.com/t/index.html?url=DDD-Domain Primitive.md)中有一些简单的小例子,介绍了领域对象如果不包含逻辑,将会在持续的迭代升级中,给开发、维护工作带来大量成本。

5.2 充血模型

面向对象设计的本质是:“一个对象是拥有状态和行为的”,充血模型就是那种即拥有属性、又拥有操作的类。
修改一个用户信息,然后保存,在贫血模型的场景中示例代码如下:

1
2
3
user.setXXX();

userManager.save(user);

在充血模型的场景中,代码如下所示:

1
2
3
user.setXXX()

user.save();  保存自己

典型的系统结构图:

在这里插入图片描述

CQRS模式概述

CQRS(Command Query Responsibility Segregation)是一种将命令和查询职责分离的设计模式。其核心理念是将数据修改操作(命令)和数据检索操作(查询)区分开来,以提高系统的性能和可扩展性。

命令与查询的区分

  • 命令(Command):不返回结果,但会改变系统状态的操作,如新增、更、删除等。命令通过CommandBus分发到对应的CommandHandler执行,并通过Repository持久化数据。在事件驱动的CQRS中,命令还会触发事件,由EventHandler处理。
  • 查询(Query):返回数据查询结果,不改变系统状态。查询根据不同的业务场景定制,通过Facade提供查询接口。

CQRS的三种实现模式

单数据库CQRS

单数据库CQRS模式指的是命令和查询操作都使用同一个数据库。这种模式下,虽然命令和查询逻辑分离,但数据存储仍然共用一个数据库实例。

多数据库CQRS

多数据库CQRS模式将命令和查询操作分别使用不同的数据库。这样可以针对命令和查询的特点优化各自的数据库,提高性能。

事件驱动CQRS

事件驱动CQRS模式在命令执行后,会生成事件并发布到事件总线,由事件处理器异步处理这些事件,从而更新数据的读取模型。

CQRS的优势与挑战

  • 优点:面向对象的设计,Service层符合单一职责原则,有助于降低系统的复杂性。
  • 缺点:逻辑分配在Domain Object和服务层之间的界限不明确,导致编码成本较高。同时,事务控制也更为复杂。

结论

CQRS模式通过分离命令和查询职责,提供了一种灵活且可扩展的系统设计方法。然而,合理地分配逻辑到Domain Object或Service中,以及有效控制事务,是实现CQRS模式时需要考虑的关键点。
在这里插入图片描述

6.2.2 读写分离的CQRS

在这里插入图片描述
CQRS(命令查询职责分离)架构模式的核心目的在于实现数据的多重表示,以满足不同用户对数据的不同需求。这种模式通过分离数据的写入(Command)和读取(Query)操作,为数据的不同使用场景提供了定制化的解决方案。 首先,CQRS模式允许系统根据查询需求采用多种数据存储技术,如关系型数据库、NoSQL数据库、Redis缓存系统,以及Elasticsearch搜索引擎等。每种技术都有其独特的优势,适用于不同的查询模式。例如,对于需要进行复杂查询的场景,可以通过Command端将数据写入数据库,然后同步更新到Elasticsearch中,以便于Query端能够高效地检索所需数据。 在CQRS架构中,6.2.3节特别提到了事件源(Event Sourcing)的结合使用。事件源是一种记录系统中所有状态变化的技术,它通过事件日志来维护系统状态。将事件源与CQRS结合,可以进一步提高系统的灵活性和可扩展性。具体来说,当Command端接收到命令并更新数据后,相关的事件会被记录在事件日志中。这些事件随后可以被用来更新Query端的数据表示,确保数据的一致性和实时性。 通过这种方式,CQRS不仅提升了系统的查询性能,还增强了系统的可维护性和可扩展性。
在这里插入图片描述

当Command系统完成数据更新的操作后,会通过「领域事件」的方式通知Query系统。Query系统在接受到事件之后更新自己的数据源。所有的查询操作都通过Query系统暴露的接口完成。

6.4 CQRS架构的优点

  • Command、Query两端架构分离、相互不受束缚,各自独立设计、扩展
  • Command端通常结合DDD,解决复杂的业务逻辑;
  • Query端轻量级查询,多种不同的查询视图通过订阅事件来更新
  • Command端通过分布式消息队列水平扩展,天然支持削峰
  • EDA架构(Event-Driven Architecture, 事件驱动架构),整个系统各个部分松耦合,可扩展性好
  • 架构层面做到无并发,实现Command的高吞吐
  • 技术架构和业务代码完全分离,程序员不用关心技术问题,更方便的分工合作

6.5 CQRS架构的缺点

  • 需要处理事务问题,开发成本提高。例如一个Command可能需要修改多个DB,数据一致性处理成本较高。CQRS不是强一致性,而是面向最终一致性
  • 实效性问题。Command端修改后同步给Query端可能存在时间差,那么Command修改数据后、Query可能查询到旧数据。
  • Event传递需要稳定且性能强大的分布式消息队列
  • 必须有强大可靠的CQRS框架,从头做起成本高、风险大
  • 最好结合Event Sourcing模式,否则Command、Query分离意义不大
  • 提高了开发人员的门槛

7 参考文档

《中台架构与实践-基于DDD和微服务》

如何掌握DDD业务领域建模、数据库及聚合?

DDD 领域驱动设计:贫血模型、充血模型的深入解读

领域模型、贫血模型、充血模型概念总结

3种CQRS架构模式