领域驱动设计(DDD)的第一印象

什么是领域驱动设计

领域驱动设计(Domain-Driven Design,简称DDD)是一种以业务领域为核心的软件设计方法,由埃里克·埃文斯(Eric Evans)在2004年提出。其核心思想是通过构建领域模型,将业务逻辑与技术实现紧密结合,从而解决复杂业务系统中的设计和演进问题。

领域模型驱动的代码范式,是围绕着领域知识设计的,需要先理解业务模型,再将业务模型映射到软件的对象模型中来。理想情况下,领域层代码作为系统的最核心资产模块,可以被打包迁移到任何应用上,而不关心具体的三方服务提供方和具体的持久化方案,即外部服务的变化对领域层代码是没有任何侵入。

从上图来看,领域对象(ENTITY、AGGREGATION、VALUEOBJECT、MODEL)用于描述业务模型,是业务关系最重要的体现;为了屏蔽持久化方案的细节,我们需要仓库(REPOSITORY)来查询和持久化领域对象;当我们期望直接获取到一个领域对象,而不关心这个对象从哪里来,如何构造,那我们就需要工厂(FACTORY)来帮我们生产这个对象;当领域内依赖外部服务能力时,需要门面(FACADE/Gateway)帮助我们屏蔽具体的服务提供方。

有了以上这些模型对象和基础能力模块,我们需要领域服务(DomainServie)层作为“上帝之手”帮我们编排具体的业务逻辑。具体又可以分为用例/场景领域服务、业务流程服务、实体操作服务。

数据模型

失血模型:模型只是数据接口,没有任何的方法/能力。

贫血模型:模型包含了一些原子能力。

充血模型:模型包含了除了持久化之外的所有能力。

胀血模型:模型无所不包。

理想模型是中间的贫血和充血模型,尽量闭环领域模型的内部逻辑,不要两边的极端模型。

Cola架构

目录层级大家已经很熟悉了,重点说下各层的分工和要点:

领域层属于核心业务逻辑,属于经常被修改的地方。这部分的需求经常随着产品的迭代进行变更。领域层不依赖其他层,领域模型里的实现逻辑只依赖倒置接口。入参: 实体、聚合根、基础的数据结构,出参:实体、聚合根、基础的数据结构。

应用层属于业务用例或者场景。业务用例一般都是描述比较大方向的需求,比较薄,接口相对稳定。编排是应用层最关心的事情,他负责将业务编排到各个领域中,本层不应该有业务逻辑。

接口层主要负责解决外部通信、协议等问题,将外部的定时任务、请求、rpc、事件消费都进行透明处理。

基础设施层贯穿所有层的,作用就是为其它各层提供通用的技术和基础服务。包括第三方接口、驱动、消息中间件、网关、文件、缓存以及数据库等。采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

领域模型

统一语言(Ubiquitous Language)

又称作通用语言,用于定义某个领域内上下文的含义,包括领域术语和领域行为。本质是一种概念,要求清晰明确,而且属于团队共识。统一语言不是一次性的培训过程,是需要产运研持续构建的文化建设过程。

概念是关于一切抽象事物的思考和定义,是反映事物本质属性或特有属性的思维方式

DDD的核心就是强调概念和语义,概念的重要性体现在通用语言上。因此强烈建议各领域维护核心词汇表!

实体对象(Entity)

在领域模型中,需要通过一个有意义的唯一标识而不是其属性来区分,且在其生命周期中具有连续性的对象。简单来说,就是一个标识没变的对象,在其他自身属性发生变化后,它依然是它,那么它就是实体。

任何实体的行为只能直接影响到本实体(和其子实体),不能影响其他实体。

值对象(Value Object)

《领域驱动设计》值对象的定义:当一个实体内的部分属性,我们发现它们具有较强的相关性,这些属性单独抽象成一个对象可以更好的描述事物,且这个对象并不具备唯一性,我们就将它归类为值对象。具备以下特征:不需要唯一标识来代表其唯一性;一些有关系的属性的聚合;有自己的特征;对模型有重要的意义;是用来描述事物的对象;属性改了就不是原来的对象了。

例子如下:


领域原语(Domain Primitive)

简称DP,DP是领域设计中的核心概念,可以理解为业务领域中最基本、不可分割的数据元素,是业务与技术融合的最小单元。它类似于编程语言中的基础数据类型(如Integer、String),但附加了业务语义和规则,具有精准定义、自我验证等核心特性。

DP与值对象的区别如下所示:

总结:Domain Primitive 是值对象的升级版,通过业务规则内聚和行为封装,将隐式知识显性化,确保数据合法性与业务一致性。值对象更侧重数据不可变和属性聚合,适合描述无需复杂规则的领域片段。

聚合对象(Aggregate)

当需要描述多个实体之间的关系和保持更改的一致性时,除了使用对象关联外,还可以建立一个对象组,将有着紧密关系的实体和值对象封装在一起,这个对象组就是领域模型中的聚合。

聚合拥有两个重要特征:边界:定义聚合内有什么,与其他聚合区分。聚合根:聚合中的一个特定实体,要求:

1)选择聚合中的一个Entity作为聚合根;

2)通过根来控制对边界内其他对象的访问;

3)只允许外部对象保持对根的访问;

4)对边界内的其他对象通过根来关联发现。

一般是通过工厂类或者工厂方法来创建聚合对象,保证固定规则和原子性。

领域服务(Domain Service)

当领域模型中某个动作或者操作不能看作某个领域对象自身的职责时,可以将其托管到一个单独的服务类中,这种服务类,我们把它叫做领域服务,领域服务作为“上帝之手”帮我们编排具体的业务逻辑。

对于领域服务,一定要把守住一条底线,领域服务一定不要有状态。因为操作是无状态的,也就是我们所说的“纯函数”,仅关注于领域对象之间的关系和其状态的变化,而不会引入领域逻辑以外的复杂性。

与应用服务Application Service的区别:

应用服务是协调领域模型与外部系统交互的中间层,负责处理非业务逻辑的横切关注点。如日志记录、安全认证、参数校验、异常捕获、权限验证、数据转换等,保持领域层与非业务逻辑解耦。此外,应用服务还需要对外暴露REST API或RPC接口等。

领域事件(Domain Event)

将领域中所发生的活动建模成一系列离散事件,希望限界上下文/跨领域/微服务中其他对象能够感知到的通知机制。领域事件是需要和业务专家共同识别出来的。

每个事件都用领域对象(通常是值对象)表示,采用EventBus来实现进程内的通知机制。通过显性化的事件,将事件触发和事件处理解耦。主要用于保证聚合间的数据一致性、实时流程驱动等场景。

具体到业务场景,缓存刷新、数据同步比较切合领域事件。但不推荐使用。

限界上下文(Bounded Context

前面说过,“DDD的核心就是强调概念和语义,概念的重要性体现在通用语言上,那么语义的重要性则体现在限界上下文”。

定义了每个模型/软件系统/团队等的应用范围和边界,保证模型内部的一致性,包括统一语言,不受外界干扰。具体包括模型对象、数据库等方方面面构成了边界上下文,并不是一个真实的对象!出了限界上下文这个边界这层含义就不复存在。

比如电商领域的商品,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界。

限界上下文常是用于微服务划分的依据之一,核心域、通用域、支撑域等子域划分也基于此。

上下文映射(Context Map

在大多数场景下,限界上下文无法单独存在,需要多个限界上下文通力协作,才能完成一个完整的用例场景。

此时就会用到上下文映射。上下文映射(Context Map)是战略设计的核心工具之一,主要用于管理和可视化不同限界上下文之间的关系。其核心作用包括:定义限界上下文的边界和展示限界上下文间的交互方式。当系统规模逐渐扩大时,Context Map甚至能清晰展示领域模型的分布与依赖,便于后续拆分、合并或重构。

代码中定义的各个子领域的Context上下文,本质就是上下文映射一种实现。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐