DDD的战略设计

DDD的战略设计主要包括领域/子域, 限界上下文, 通用语言 , 架构风格等.

DDD的战略设计更偏向于软件架构, 从宏观的角度俯视整个系统, 通过一定的原则对系统进行子域和限界上下文划分.

DDD的战略设计可以指导微服务的划分.

# 领域模型

既然是领域驱动设计, 那么主要的关注点理所当然的应该放在如何设计领域模型上, 以及对领域模型的划分.

模型是用来反映事物的某部分特征的物件, 无论是实物还是虚拟的

比如地图用线条和颜色作为地理信息的模型, 古人用八卦作为世界运行规律的模型, 程序员用UML类图作为对象关系的模型.

我们要做一个可持续维护的系统, 实际上需要对业务进行充分的抽象, 找出隐藏的模型, 搬到系统中来. 如果一个业务场景中的每个事物都能在系统中找到对应的对象, 那么这个系统的逻辑就非常完备.

通过对实际业务出发,而非马上关注数据库、程序设计. 通过识别出固定的模式, 并将这些业务逻辑的承载者抽象到一个模型上. 这个模型负责处理业务逻辑, 并表达当前的系统状态. 因为 是从具体业务(领域)中提取出来的, 因此又叫做领域模型. 这个抽象的过程也就是领域驱动设计.

领域指的是特定行业或者场景下的业务逻辑

领域不是多么高深的概念, 比如一个餐厅的领域包含了菜单, 菜品等概念; 一个电商网站包含了产品名录, 订单, 发票, 物流等概念. 我们主要需要理解的是领域的划分, 即将一个大的领域划分成若干个子域.

日常开发中, 通常会将一个大型软件拆分成若干个子系统, 这种划分有可能是基于架构方面的考虑, 也有可能是基于基础设施的. 但是在DDD中, 系统的划分是基于领域的, 也即是基于业务的.

于是问题来了:

  • 首先, 哪些概念应该建模在哪些子系统中? 我们可能会发现一个领域概念建模在子系统A中是可以的, 建模在子系统B中似乎也合情合理.
  • 各个子系统之间应该如何集成?

不同的子系统之间的集成涉及到基础设施和不同领域概念在两个系统之间的翻译, 稍不注意就会对领域模型造成污染, 这两个问题的解决方式就是限界上下文

# 限界上下文

在一个领域/子域中, 会创建一个概念上的领域边界, 在这个边界中, 任何领域对象都只表示特定于该边界内部的确切含义. 这样的边界便成为限界上下文. 限界上下文与领域是一对一的关系. 限界上下文的识别没有明确的准则, 划分没有对错, 只有是否合适.

二义性

比如有一天我们开发了一个电商系统, 有一天市场告诉我们, 这个系统中有一个逻辑问题, 商品删除后, 订单也不能查看, 在我们之前的认知订单与商品是一对多关系, 当商品不存在后, 订单也就失去了 存在的意义.

这时, 商品在这里存在了致命的二义性, 这里的商品实际上有两个含义:

  • 在订单中, 表达这个订单的订单项, 例如小花哥购买的<<领域驱动设计>>一本, 使用了多少元的折扣.
  • 在商品管理中, 商品包括文字描述, 封面图片等信息.

统一语言

当商品被删除后, 不应该产生新订单, 同时也不应该对订单中商品造成任何影响.

这些问题是因为研发人员与业务人员的语言没有统一, DDD认识到了这个问题, 统一语言是实现良好模型的前提, 因此应该"大声的建模". 这个过程就是统一语言

上下文

在统一语言建模之后, 所有人都明白商品在不同地方具有不同的含义, 这个不同的地方被叫做上下文, 上下文不止由二义性决定, 还有可能是完全不相干的概念产生, 例如订单和折扣券, 我们在谈折扣券的 时候, 其实完全是在谈别的东西, 所以折扣券也应该是单独的上下文

在物理上讲, 一个限界上下文最终可能是一个jar, 也可能是一个package中的所有对象.

# 不同领域之间的集成

在集成领域时, 我们主要关心的是领域模型和集成手段之间的关系, 比如一个Rest资源(如Spring Cloud微服务)集成, 需要提供基础设施如Spring的RestTemplate或Okhttp, 但是这些设施不是核心领域模型的一部分, 这时就会用到防腐层.

防腐层

防腐层负责与外部服务提供方打交道, 同时将外部概念翻译成自己的核心领域能够理解的概念

防腐层的实现和很多种, 比如:

  • RPC调用
  • 消息的发布订阅
  • RESTFul

# 架构风格

DDD并不要求采用特定的架构风格,因为它是对架构中立的。你可以采用传统的三层式架构,也可以采用REST架构和事件驱动架构等。但是在《实现领域驱动设计》中,作者比较推崇事件驱动架构和六边形架构.

当下, 面向接口编程和依赖注入原则已经在颠覆者传统的分层架构, 在进一步, 便得到了六边形架构, 也成为端口和适配器. 在六边形架构中, 已经不存在分层的概念, 所有组件平等, 这得益于软件抽象的好处, 各个组件之间 的交互完全通过接口完成, 而不是具体的实现细节(设计模式的依赖倒转).

抽象不应该依赖于细节, 细节应该依赖于抽象

采用六边形架构的系统中存在着很多的端口和适配器的组合. 端口表示一个软件系统的输入和输出, 而适配器则是对每一个端口的访问方式. 比如, 一个web程序中, http协议可以作为一个端口, 向用户提供html页面 并接收表单提交;, 而servlet或者Spring中的controller则是相对应于http协议的适配器. 再比如数据持久化, 此时数据库系统可以看作是一个端口, 而访问数据库的Driver则是对应的数据库的适配器, 如果要增加新的访问方式, 只需要再对应的增加一个端口和适配器即可.

领域模型与端口和适配器之间的交互

软件系统的真正价值在于提供业务功能, 我们会将所有的业务功能分解为若干个业务用例, 每一个业务用例都是对软件系统的一次原子操作. 所以首先, 软件中应该存在一个这样的组件, 他们的作用即以业务用例的方式向 外接暴露该系统的业务功能. 在DDD中, 这样的组件叫做应用层(ApplicationService)

application-service

图中的领域模型位于应用程序的核心部分, 外界与领域模型的交互都通过应用层完成, 应用层是领域模型的直接客户. 然而, 应用层中不应该包含有业务逻辑, 他应该是很薄的一层, 起到协调的作用(门面模式), 他所做的 只是将业务操作代理给领域模型, 同时如果业务操作有事务需求, 事务的管理也应该放在应用层上, 因为事务也是以业务用例为单位.

上次更新: 2022/3/11 15:12:48