建材秒知道
登录
建材号 > 设计 > 正文

领域驱动设计(DDD)实践之路(第二篇)

害怕的鱼
无心的牛排
2022-12-21 05:37:50

领域驱动设计(DDD)实践之路(第二篇)

最佳答案
和谐的发带
跳跃的钢笔
2025-12-02 17:33:14

在领域驱动里面,infrastructure作为基础设施,是提供技术细节的模块。需要强调的是,很多人会误以为infrastructure就是传统的DAO层,其实infrastructure包括但不限于DAO层,比如文件处理,三方调用,使用缓存,发送异步消息等具体的技术细节实现都存在于infrastructure层。那么技术细节是什么呢。在我们看来,技术细节包含以下特征

案例1:我们的实体需要持久化(存储),所以我们需要提供存储的实现。领域层的repository.save等方法提供了持久化接口约定,对于infrastructure来说,如何实现这个方法的代码,就是技术细节。那么我们如何实现这个过程呢?自然是选择缓存,OSS存或者数据库存。如果选择数据库,则进而需要选择orm框架,配置...,实现repository.save的接口,这些都属于持久化所需的技术细节代码。

案例2:我们的应用需要导出资产包相关的excel形式数据,那么当导出资产包数据时,文件领域模块提供了导出的统一接口,资产领域模块提供了资产包的适配接口,而导出excel的代码需要使用easyExcel或者POI等第三方框架,属于技术细节代码。

案例3: 接案例2,为了实现导出时所需的excel排版格式,排版本身的格式与业务有关,比如在我们的业务场景下,我们导出调解明细(我们项目特定的一个领域模型)的时候,只需要按照常见的导出方式即可,而导出资产明细(我们项目特定的一个领域模型)则需要解析拼接所有的动态数据列,合并显示每条数据不同的动态列,而这一切是由业务决定的。根据业务不同有不同的排版要求这一点体现了资产域需要提供文件域的导出策略,调解域也需要实现文件域的导出策略。这些都属于描述业务信息的约定,而这些约定的具体实现比如怎么把实体的那一个属性映射到excel的哪一行哪一列,则属于技术细节。这种区分方式显性化了业务的概念,同时又将实现放在了基础设施层,提供了一定的解耦性。

说完了infrastructure的技术细节的定义,我们接下来聊几个在采用DDD研发模式下,infrastructure层开发过程中经常会遇到的一些问题及我们的解决方案。

为了让业务逻辑和代码实现解耦,在repository的约定中,我们通常用“save保存”代替我们通常说的“insert(插入)“,”update(更新)”这样的技术术语,以屏蔽技术细节。这样带来的一个副作用是,在save时就需要根据策略判断调用insert还是update,我们使用的策略是根据id是否是空决定,即我们所有的实体对象都有一个属性,类型为Id类的子类,id对象的属性(数据库里面实际存放的id值)可能为null,但是id对象,本身不会为null,根据这个对象可以判断当前实体id是否为空。

对于聚合场景,子实体是需要知道聚合根的id的,因为在存储到数据库时可能需要以外键的方式存储对象间的映射关系。

然而,在具体实现中,我们认为,实体之间的对象关系才是标识两个实体之间关系的方式,而不是id,所以生成实体时,先通过对象引用关联对象,表明聚合和实体之间的关系,在保存到数据库的时候,通过实体生成数据库映射类的时候就可以知道当前数据的id是否为空,同时又能知道当前数据之间的关系。

对象之间的关系在1:1聚合保存的时候可能体现不明显,但是当1:N或者N:N批量保存聚合的时候,作用就比较明显了。在我们的系统中发起调解业务就需要批量保存调解批次。代码如下(欢迎吐槽,拥抱进步)

通过这种方式就解决了批量插入不能返回id,同时又能继续复用id.isNew()判断是否为新数据的方式(这里我们没有创建entity基类,所以判断放在了Id上)。

以上方法提供了批量保存时如何区分是新增还是更新。下面我们来谈谈我们项目内提供的插入和更新模板代码。

对于领域来说,save是基本的保存代码。方法传入的参数往往是一个存在于内存中的聚合根对象,有时包含全量的子实体,VO和全量的字段,而在插入场景,对批量请求我们希望支持批量插入,减少对数据库的IO频率,在更新场景下,我们希望减少update时的更新字段的数量(只更新需要更新的字段),这有助于减少数据库IO次数、binlog大小和mysql数据库索引变更带来的开销,所以是非常有必要的。因此对于infrastructure来说,可以提供统一的定制化模板方便repository定制化更新字段的方法快速实现。

由于我们的系统使用的是mybatisplus的ORM方案,所以我们根据api和mysql的批量语句开关提供了一个批量插入和批量更新的Mapper基类,其中insertBatchSomColumn是mybatisplus自带的,updateBatchById则是我们实现的,文档链接如下https://mp.toutiao.com/profile_v4/graphic/preview?pgc_id=7062223527654916621通过这种方式可以轻松地提供定制化更新某几列的sql,减轻sql编写负担。

这一次要讲的其实就是上面提到过的excel导入导出的案例。对于我们的系统来说,具有资产域,文件域,调解域等。其中资产域、调节域等三个域需要导入导出excel。但是我们在设计的时候认为文件的操作属于文件域的概念,所以应当由文件的domain提供功能。但是很明显,具体的导入导出的策略根据数据的不同是可以变化的。所以针对这种情况,我们回归到领域驱动的实现的本质------面向对象技术来思考这个问题的优雅解法。以导入为例

代码如下

上面4份代码是domain的,最下面的是infrastructure的,这里我们只讲infrastructure的(但是我个人认为领域分层后还是需要整体考虑的,所以才会贴上domain的代码)。

这是我们对于跨域业务逻辑的处理办法。

为了保证各领域模型间的解耦,我们经常通过最轻量级的领域事件的方式实现,而不是类似metaq,msgbroker这样的异步分布式消息中间件。领域事件的发送有很多的实现方案,我们倾向于直接使用spring的功能,因为我们需要同步保证事务。但是spring的event发送需要继承ApplicationEvent而领域事件我们又希望独立于spring的event体系,所以我们通过对spring的了解发现了spring已经提供了 PayloadApplicationEvent 可以实现这种功能实现上和其他的spring的event一致,获取我们自己定义的event的方法如下

这里的getPayload()可以获取到我们放进去的领域事件TimeoutEvent

在任何系统中都会有批处理的业务。可能是批处理聚合,可能是批处理聚合内的实体类。这里说一下我之前遇到的一个帖子(jdon)上的讨论。帖子上说的是有一个排班业务,一条班表数据作为聚合存在着每日排班子实体,每日排班下又存在着排班明细子实体,当日期逐渐增加时一条排班需要加载好几年的数据用于生成聚合,而实际上则仅仅只需要计算最近几周的数据。这里存在两点问题

第一点自然不用多说,技术实现以提供业务功能为核心是我一直以来的主张。所以当数据量可能会不断增大的情况下不用加载完整自然是必须的(哪怕内存存储的下也应当尽可能少的消耗)。第二点来说帖子的一位回复者倾向于DomainService提供专门的适配方法,用于加载几周的数据。

我们的系统中存在一个有一些类似的业务。我们的系统需要每隔几分钟就运行一次批处理任务,获取所有已经过期的调解明细,并且设置为过期。调解明细属于调解批次的聚合,所以我们有同样的需求。

我们在此提供一种我们的实现,供参考。

repository的实现根据面向对象原则,仅仅提供如何查询过滤数据库数据

迭代器的实现提供了迭代职责实现

至此实现了批处理加载聚合的逻辑,同时可以提供聚合的部分加载(需要注意业务的正确性不会因为聚合的不完全加载而产生问题)。

最后总结一下

最新回答
怕黑的芒果
开放的飞鸟
2025-12-02 17:33:14

本周推荐书籍为《领域驱动设计》,这是一本主要面对软件开发人员的书籍。那大家肯定会说我们的大多数的人都不是软件开发人员,很好奇我推荐给大家的理由是什么呢?

我推荐给大家的是这本书的绪言写的太棒了。绪言中明确清晰的写明了本书编写的框架,编写的手法,更重要的是前言中对面向不同读者的阅读建议。

IT中常见的不要求完美的迭代开发方式相信大家无论在任何行业都是了解一二的,但是在现在这个追求快速发展的时代,如何能让迭代设计出的反应迅速的小的模块整合而成的大的软件产品跟的上大家的需求呢?在快速和敏捷设计的过程中有两个概念是要兼顾的,第一个是极简主义,这个所谓的简单并不是说单纯的简单,因为它在迭代过程中会持续重构,第二个概念就是可扩展性,软件设计的底层逻辑是可以支撑持续重构的,所以就要求软件设计人员有一个前瞻性和全局的概念,考虑后面的开发要求。

领域驱动设计的实质就是消化吸收大量知识,最后产生一个反映深层次领域知识,并聚焦于关键概念的模型。这是领域专家与开发人员的协作过程领域专家精通领域知识,而开发人员知道如何构建软件。由于开发过程是迭代式的,因此这种协作必须贯穿整个项目的生命周期。

本书的框架非常清晰地展示了起源于IT行业的敏捷项目管理的逻辑。

本书的第1部分写出了,欲驱动开发的基本目标,然后定义了一些术语,并给出了领域模型来驱动和沟通设计的总体意义,朗读这本书的人非常清晰,目标和为什么要做这个事情?

第2部分的话就是给出一些领域建模中的一些最佳实践提炼的构造块。让团队有共同的工作语言,使团队成员之间更容易理解彼此的工作,在讨论模型和设计决策过程中更容易达成一致。

在这个过程中的话每一个模型可以进行一些小规模的仔细设计,追求在某些个别元素上的极致设计,形成自己的特色。

第3部分讲的就是通过重构来加深理解,讨论如何将构造块装配为使用的模型,从而实现其价值。

第4部分战略设计,讨论在复杂系统和大型组织,以及外部系统和遗留系统的交互中出现的一些复杂情况。这一部分讨论了作为一个整体应用于系统的三条原则:上下文、提炼和大型结构。

综上所述,先设立目标,理清概念,而后统一工作语言,工作需求,然后具体实施,分模块或团队去设计,最后再战略整合,这是一个完整的企业管理的决策流程,不仅仅是一个软件设计的一个流程,适用于非常多的项目管理场景。

尤其是第4部分的战略设计中提出的三条原则,和我们在企业中做企业战略的逻辑是完全一致的。

这一部分明确的写出了针对不同读者,大家所需要的学习方法和阅读方法。最基础的软件开发人员,到中层的软件开发团队的管理人员,再到高级的管理人员,每种人读者在前沿过程中都给出了一个非常清晰的阅读指导。

有的人需要通读,有的人需要略读,有的人只需要读其中重点的几章。

从我作为一个销售背景的人出身来看这部分的内容,万分欣喜。写这本书的团队也好,个人也好完全遵循了他在第4部分介绍的战略设计的这个要求,他看了他的上下文(读者调查)、提炼(将读者进行清晰分类)和大型结构(在写之前就兼顾了各种不同读者的阅读需求与阅读习惯)。然后通过记叙体的方式呈现出来,读者可以从头开始读也可以从任意一张的开头开始阅读。所以也无怪乎本书成为后来很多书的参照书籍了。

风趣的鸭子
俊逸的柠檬
2025-12-02 17:33:14
# DDD概览

## 启迪

领域可以理解为业务,领域专家就是对业务很了解的人。

限界上下文也就是微服务的边界,也可以理解为微服务,一个限界上下文=一个微服务。

个人理解领域驱动设计就是微服务驱动设计,从战略上先进行微服务的划分,从战术上针对某个微服务进行领域模型的设计也就是业务模型的设计。

领域模型包括:

- 实体

- 值对象

- 聚合

- 领域服务

- 领域事件

- 资源库

- 应用服务

## 什么是领域驱动设计?

理解领域驱动设计是什么之前,我们先来理解下什么是领域?

领域可以理解为业务,领域专家就是对业务很了解的人。

领域驱动设计的核心就是和最了解业务的人也就是领域专家一起通过领域建模的方式去设计我们的软件程序。

- 那么领域如何驱动设计?或者说业务如何驱动设计?

传统开发过程我们都是基于面向数据开发,拿到产品原型脑海里想着都是应该创建哪些表和哪些字段才能满足需求。

而领域驱动设计开发过程是让我们基于面向业务开发、面向领域模型开发。

领域模型的核心是通过承载和保存领域知识,并通过模型与代码的映射将这些领域知识保存在程序代码中,

在传统开发中,当业务被转换为一张张数据表时,丢失最多的就是领域知识(领域知识也就是我们在模型中定义的一些业务逻辑行为)。

面向领域模型开发的优点:

- 存储方便,统一使用JSON进行存储。

>例:

>订单领域包含基础信息、商品信息、金额信息、支付信息等包含订单全生命周期的子域,

>对于传统面向数据的开发模式我们需要创建N张表进行存储订单的信息,但是面向领域开发时我们

>可以通过利用nosql数据库(mongo、es等)进行保存整个订单域的信息,提高查询、更新效率,简化代码

- 复用性高,引用某个领域模型,就可以拥有该领域模型的所有行为。

>例:

>基于微服务架构下,某个电商应用需要一个判断某个订单是否是在线支付订单的逻辑时,

>对于传统的开发模式我们需要调用订单中心的服务查询订单信息,然后写一个判断是否在线支付订单的方法。

>如果有多个应用都需要这个逻辑时,每个应该都需要重复写相同的方法。

>但面向领域开发时,只需要引用订单中心的jar包,然后统一调用订单领域内的方法即可。

>这样就实现了业务的高内聚

## DDD可以做什么

DDD主要分为两个部分,战略设计与战术设计

- 战略设计

- 围绕微服务拆分

- 战术设计

- 微服务内部设计

## DDD怎么做

- 战略设计

- 和领域专家一起通过(过往经验、事物联系、事件风暴等)划分【限界上下文】

>限界上下文也就是微服务的边界,也可以理解为微服务。

>一个限界上下文=一个微服务

- 战术设计

- 开发人员通过(领域模型)保存【领域知识】

>领域知识也就是事物(角色)、行为(规则)和关系

>

## DDD领域模型

领域模型包含什么?

- 实体

>具有唯一标识,包含着业务知识的【充血模型】对象,用于对唯一性事物进行建模。

>例:

>```

>public class Order {

>private long orderId

>private OrderAmount amount

>private List item

>}

>```

- 值对象

>生成后即不可变对象,通常作为实体的属性,用于描述领域中的事物的某种特征。

>例:

>```

>public class OrderItem {

>private long orderId

>private String productCode

>private String productName

>}

>```

- 聚合

>将实体和值对象在一致性边界之内组成聚合,使用聚合划分微服务(限界上下文)内部的边界

- 领域服务

>分担实体的功能,承接部分业务逻辑,做一些实体不变处理的业务流程。不是必须的

>主要承接内部领域服务调用和外部微服务调用,及一些聚合业务逻辑处理。

>例:

>```

>@Service

>public class ShoppingcartDomainService {

>private final ShoppingcartRepository shoppingcartRepository

>private final ProductFacade productFacade

>private final UserFacade userFacade

>private final PromotionFacade promotionFacade

>

>

>// 1.查询购物车信息

>ShoppingcartDO entity = shoppingcartRepository.loadShoppingcart(userId)

>

>// 2.调用【用户中心】服务查询用户信息

>User user = userFacade.getUser(userId)

>

>// 3.调用【商品中心】服务查询商品信息

>Product product = productFacade.getProduct(productCode)

>

>// 4.调用【活动中心】服务查询活动信息

>Promotion promotion = promotionFacade.getPromotionByProductCode(productCode)

>

>// 5.创建购物车实体

>Shoppingcart shoppingcart = new Shoppingcart(entity.getId, user, product, promotion)

>

>// 6.购物车按活动分组

>shoppingcart.groupby4Promotion()

>}

>```

>

>

- 领域事件

>表示领域中发生的事情,通过领域事件可以实现本地微服务(限界上下文)内的信息同步,同时也可以实现对外部系统的解耦

- 资源库

>保存聚合的地方,将聚合实例存放在资源库(Repository)中,之后再通过该资源库来获取相同的实例。

>

- 应用服务

>应用服务负责流程编排,它将要实现的功能委托给一个或多个领域服务来实现,

>本身只负责处理业务用例的执行顺序以及结果的拼装同时也可以在应用服务做些权限验证等工作。

>![](images/application-service.png)

冷静的钥匙
自觉的抽屉
2025-12-02 17:33:14

DDD的意思是领域驱动设计,是domain driven design的缩写。

英 [dəˈmeɪn ˈdrɪvn dɪˈzaɪn]   美 [doʊˈmeɪn ˈdrɪvn dɪˈzaɪn]  

领域驱动设计。

There was no focus on domain driven design.

这说明系统并没有采用领域驱动设计。

Rather, use several federated CIM models based on the Domain Driven Design approach.

相反,应该基于领域驱动设计的方法使用几个联邦的CIM模型。

Also, this book was the main inspiration for my "Domain Driven Design and Development In Practice" article published last year.

此外,该书还促使我在去年发表了“领域驱动设计与开发实践”的文章。

谨慎的西装
温柔的爆米花
2025-12-02 17:33:14

DDD的概念众所周知,各个文章中的基本概念也都大同小异。这里就不再累述了。DDD最大的好处是使用 领域通用语言(UBIQUITOUS LANGUAGE) 将原先晦涩难懂的业务通过领域概念清晰的显性化表达出来。写这篇文章的目的在于学习怎么使用DDD来降低应用的复杂度,增加框架的可扩展性。本文主要阐述了我在项目中的思考过程和架构实现。

我们从以下一些方面进行些讨论以帮助我们完成DDD的具体实践。

  分层最大的意义是在架构的层面上来进行职责的拆分,分离关注点,每一层都确定独立的职责,分层内部保持高度内聚性,让每一层只解决该层关注的问题,从而将复杂的问题简化,它们 只对下层进行依赖 ,实现高内聚低耦合、职责分离的思想。

  应用层代表。

那么应用层代表什么,就是代表系统;所以, 我们要明白人与系统的关系;人使用系统,系统提供外部功能,系统内部有领域模型;

这个问题,DDD通过DCI架构(Data、Context和Interactive三层架构),显式的用role对行为进行建模,同时让role在context中对应的领域对象进行绑定(cast)来解决。

待扩展.......

由于DDD分层架构这种向下依赖的模式使得整个框架对于Infrastructure层过度依赖,这不是我们所期望的,我们希望能在Application和Domain层更多的决定领域的功能和流程。所以根据DIP(依赖倒置)原则, 高层模块和低层模块都应该依赖于抽象,为了让模型的逻辑定义更为内聚,我们可以把抽象放在上层(Application、Domain),Infrastructure提供实现。

例如在现在的项目中,我们在Domain层中定义IRepository、ISender、IListener等行为接口,在Frameworks and Drivers层(The Clean Architecture)实现接口,通过注入的方式提供给Domain层。这样能够使依赖的核心层更加内聚,同时对外层的扩展预留出了足够的空间。

(同样的操作我们还尝试的用在对领域层中,将领域对象抽象化,使领域服务依赖于领域对象的抽象,将真正的的领域对象交给更外层去实现,来满足我们架构对于相似业务的扩张能力。但这种做法是极具风险的,不同业务的差异化和领域对象抽象的能力都是极难把握的。总的来说,我们所期望的方向是:1.抽象能够涵盖所有业务。2.业务的差异化只在抽象的实现层被应用。只有满足以上2点,我们的想法才有可能被实现。)

聊完了高低层模块的依赖,我们再来聊下同层之间的依赖。

分层架构的一个重要原则是 每层只能与位于其下方的层发生耦合 ,有些书中还有严格分层架构和松散分层架构两种区分。两者的区别在于能否对非直接下层发生耦合。但有一点是相同的,同分层之间的耦合都是不被允许的。但问题是有些时候,实际情况使它并不是容易被实现的。

例如在我们的项目中,为了满足高吞吐的业务需求,我们选用了Actor模型和AKKA多线程框架来构建我们的架构。这种响应式的异步模式上层无法及时获得下层的反馈,这使上层对下层的协调、分配变得异常艰难。所以我们需要在不同的分层中都创建Actor进行交互,通过对sender的回件来进行解耦。(TODO:图)

Actor模型对于DDD的使用还是有很多帮助的,他们都有相同的对象理念,同时,这种响应式架构使领域事件到其他的边界上下文或微服务变得更容易。

经过一些分层、抽象,The Clean Architecture是我们项目期望的目标。

关于整洁架构...等有时间再写了...

The Clean Code Blog by Robert C. Martin (Uncle Bob)

领域建模是整个DDD中比较核心的步骤,大部分DDD的工程都是基于领域建模展开的。这里同样不再对实体,值对象等做过多的名词解释,只来记录一次领域建模的过程。

这是一个关于交易平台报价行情系统的部分需求:

1.交易员提交报价 2.交易员选择[群组]提交报价

3.交易核心处理报价 4.交易核心下发[报价报告] 5.交易核心下发[报价行情消息]

6.行情系统订阅交易核心相关消息

7.行情系统收到[全市场][报价报告],开始计算报价行情:

  a.根据[报价报告][计算]出[公有报价行情]。b.根据[公有报价行情][聚合]出[公有聚合报价行情]。c.根据[公有报价行情]为[机构]生成[私有报价行情]。d.根据[私有报价行情][聚合]出[私有聚合报价行情]。

8.行情系统收到[群组][报价报告],开始计算报价行情:

  a.根据[报价报告]为[群组]包含的[机构][计算]出[私有报价行情]。b.根据[私有报价行情][聚合]出[私有聚合报价行情]。

9.行情系统收到[公有报价行情消息],开始计算报价行情:

  a.将[公有报价行情消息][转化]成[公有报价行情]。b.根据[公有报价行情][聚合]出[公有聚合报价行情]。

10.行情系统收到[私有报价行情消息],开始计算报价行情:

  a.将[私有报价行情消息][转化]成[私有报价行情]。b.根据[私有报价行情][聚合]出[私有聚合报价行情]。

第一步:分析需求,定义关键类,描述业务流程

1.从需求中抽象出我们所关心的类

Book:报价簿,报价行情形象话的表现

Page:报价簿中记录内容的抽象类

Institution:.........

2.建立类的大致内容、行为和关系

上图只是对类的简单定义,项目中最后落地一定不可能如此简单,需要进行反复的细化工作。

3.描述大致的业务活动图

第二步:划分主要构成

我们必须在这步中识别出模型中的实体、值对象,定义出核心域、子域并理清关系,划分应用层和服务层,确定限界上下文边界。

本需求寻找核心有两种方向,一种以 行情计算和聚合 为核心,机构、群组等作为独立子域;一种以 行情拥有对象(全市场、机构) 为核心,行情计算、聚合作为支撑子域;第一种偏向横向分层,第二种偏向纵向分层。按实际情况,我们选择了第二种方式,围绕Owner(全市场和机构)展开它们的行情计算、价格预警、价格变化、单机构等等。同时,上面的类图、流程图都需要调整,使依赖尽量向核心(Owner)偏移。

核心域:Owner。生成报价行情的对象,报价行情的所有者。

子域:报价簿子域、资产子域、群组子域、预警子域、监控子域、报价明细子域。

实体:机构、群组、全市场、报价单、报价簿、报价明细、资产信息。

值对象:报价簿规则、报价簿ID。

领域服务:机构服务、群组服务、行情计算服务、行情聚合服务、报价预处理服务、仓库服务。

第三步:聚合

根据前面的识别出来的各类对象初步构造出模型。

待续....

Actor是Actor.class模型的核心,怎样在DDD的架构中使用Actor?

Actor的能力和Service相似,服务是无状态的,但状态却是Actor模型的特点。一个无状态的领域服务一般使用单例模式,而当一个类有了状态,就好像一个物体有了生命,“我是谁?我在哪?”这些也都成了它所需要关心的问题。我们需要维护服务列表、制定路由规则、实现服务发现功能...写到这,我们可以发现这些和微服务的服务治理非常相似。所以,我们可以建立我们的“注册中心”封装select,create,actorof等Akka的操作来实现服务发现,还能在里面提供限流、熔断、合并等等扩展。

面向对象

DDD是一种方法论,它的本质还是面向对象的思想。DDD在OOAD的基础上提炼演进出了一套架构设计理论。帮助我们,使我们能更容易的以面对对象的思想来设计工程。DDD的设计也需要遵循OOAD的SOLID原则。

Effective Aggregate Design by Vaughn Vernon

The Clean Code Blog by Robert C. Martin (Uncle Bob)

Reactive-Systems-Akka-Actors-DomainDrivenDesign

复杂度应对之道 - COLA应用架构

活力的发夹
甜美的海燕
2025-12-02 17:33:14
看你如何理解了。

领域驱动设计强调要建立“领域模型”,描述了这个模型能解决什么问题,有哪些特点,但是并没有给出系统化的建模方法,这给了大家很多的发挥空间。

但是不管社区为领域驱动设计引入的建模方式再五花八门,面向对象分析设计建模依然是当前最系统和成熟的方法。只是区别在于:领域驱动设计不再将模型割裂为分析模型、设计模型和实现模型,而是用一个领域模型贯穿设计和实现,并强调代码与模型要保持一致。

另外领域驱动设计建议采用敏捷的“演进式设计”方法逐步设计、演进和精炼领域模型,这需要相应的团队协作方式(业务专家和软件团队长期协作)和对应的演进式工程能力(流水线、自动化测试、重构、简单设计...)做基础。

朴素的故事
留胡子的板凳
2025-12-02 17:33:14
“工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象”

其实这个作用跟设计模式的工厂模式是一样的,都是封装对象的复杂创建过程,并且后一句“不需要客户去引用那个实际被创建的对象”即实际创建的对象(实现)由工厂封装,客户只需要引用定义的接口。

那DDD中的工厂有什么不同呢?

除了创建对象之外,工厂并不需要承担领域模型中的其他职责。即DDD中的工厂,也是也仅是创建对象的作用。即从这里意义上看,其实也没什么不同。但DDD中的工厂,既是领域模型,那也应该是通用语言的表达,即工厂方法出现的地方,工厂方法的创建行为都是体现通用语言的。

那工厂用于创建哪种领域对象呢?聚合

在DDD中工厂主要用在什么场景呢?

1.在聚合根中使用工厂方法

2.在领域服务中使用工厂方法

在聚合根中使用工厂方法,是最常见的。如:

public interface Product {

BacklogItem planBacklogItem()

Release scheduleRelease()

Sprint scheduleSprint()

}

在产品product中去计划一个待定项BaklogItem,或去排期一个发布release,或去排期一个冲刺。可以看出,这里的工厂并不单纯体现一个工厂模式,更多是体现通用语言:一个产品是由多个发布和冲刺排期迭代而来的。而很多时候,DDD中工厂也并不会完全体现工厂模式中技术要素,比如抽象工厂,更多是业务的需要。而这里也看出,一般是在一个聚合根中使用工厂方法创建另一个聚合根。

而领域服务中使用工厂呢?领域服务作为工厂,一般是和集成限界上下文相关,简单理解就是领域服务中创建出另一个限界上下文的实体。书中以一个例子作为说明:

public interface CollaboratorService {

public Author authorFrom(Tenant tenant, String identity)

public Creator creatorFrom(Tenant tenant, String identity)

public Moderator moderatorFrom(Tenant tenant, String identity)

public Owner ownerFrom(Tenant tenant, String identity)

}

即该领域服务类将身份与访问上下文的对象翻译成协作上下文中的对象,即在身份和访问上下文中的用户(由tenant和identity标识)转换为协作上下文的作者,创建者,支持者和拥有者的概念。

害羞的镜子
现实的紫菜
2025-12-02 17:33:14

领域驱动设计的概念是 2004 年 Evic Evans 提出的 Domain-Driven Design,简称 DDD。随着软件技术发展,微服务技术架构的兴起,大家逐渐意识到领域驱动设计的重要性。我也在网上搜索查看过很多文章以及视频,看完后都是一知半解。更别说用DDD去设计一个新项目或者新模块需求了。因此我想自己去尝试一下如何去使用DDD去落地一个具体的项目设计,在这个过程中, 只要对DDD有更深次的理解,那我的目的就达到了。那么首先,我们需要先去了解一下DDD的核心思想以及解决了哪些痛点问题。

我不会像网上大部分文章中那样直接流水式的描述定义,那样即使对每个核心概念都了解了,但是组合在一起,我相信不仅仅是我,大家也会定义模糊。

首先我们抛开DDD不谈,咱们先来理解一下领域。

看上去比较晦涩难懂,举个例子吧,抛开 DDD以及软件开发思想,在我们装修的时候,请到的设计公司来出设计图纸,其实就是一种模型设计。而自住商品房和公建的装修设计,装修公司给出的设计方案肯定有所不同,这个针对不同领域提供不同的模型,就叫做建模。

可以看到在不同的领域(超市和电商)中,对应的建模是不同的。

在超市进销存系统建模中,我们可以看到 当顾客选购完商品只需要 收银员扫描商品条形码 结算支付后,方可离开,而整个超市的 商品库存维护,都是依赖商品条形码来进行的。而订单 相当于超市结算小票,只提供支付单据的作用。

在电商中,用户需要查看商品信息以及图片挑选商品,因此 商品的模型中 图片,属性等内容就比较重要,同时用户购买商品是以订单为枢纽的,包括 支付,配送,售后等,因此订单在电商中起着至关重要的作用。

因此我们看一看出,同样是购买商品,在电商领域和 超市进销存领域中,他们的建模是不一样的,因此 建模一定要针对领域问题去建模。

《领域驱动设计》中关于领域的定义:

通俗点讲就是针对某一特定领域结合领域知识以及业务需求进行建模。

以上面所举的电商模型为例,具体DDD模型分解如下:

通过分层架构隔离领域层、仔细选择模型和设计方案等措施保持实现与模型一致。

这只是一种理念,我觉得与其看网上各种的解释文章不如尝试用自己的方式去理解。比如上面的举例 商品域和交易域之间,如果需要新增一个搜索功能,即面向C端用户,又面向B端用户。根据模型驱动的设计理念,应新增一个搜索域来通过一定的数据转换去实现该功能。

本文主要围绕 DDD 领域驱动设计涉及到的一些基本概念和思想以及相应的问题进行了简单的说明,接下来我自己深入 DDD 具体落地实践。希望能有所收获。