ddd领域驱动设计(DDD的层次)

本文目录
DDD的层次
根据Eric的理论,业务层将细分为两个层次:应用层和领域层。应用层:定义软件可以完成的工作,并且指挥具有丰富含义的领域对象来解决问题,保持精练;不包括业务规则或知识,无业务情况的状态;领域层:负责表示业务概念、业务状态的信息和业务规则,是业务软件核心。层次之间必须清晰分离,每个层都是内聚的,并且只依赖它的下层.
Eric特别指出:那种将业务逻辑交由业务界面处理的快速UI方式是旁门左道。希望象C/S结构那样可视化拖拖图形就完成的软件开发是一种错误的方向,开发时快速,难于维护和扩展,虽然使用J2EE技术,其实是一种伪多层技术。建议购买领域驱动设计这本译书学习下.
在领域对象的生命周期中,有三个模式来维护对象的完整性:聚合(Aggregate)定义清晰的所有权和边界使模型更加紧凑,避免出现盘根错节的对象关系网;工厂(Factory)和仓储(Respository)。当一个对象生命周期之始,使用工厂和组合提供访问和控制模型对象的方法。建立聚合的模型,并把工厂和组合加入到设计中来,可以使我们系统地对模型对象进行管理。聚合圈出一个范围,在这个范围中,对象无论在哪个生命周期,保持不变性。
MF(MartinFowler)曾经提出有名的贫血模型或充血模型,他认为实体模型对象中只有弱行为setter和getter方法,没有真正行为,好像缺少血液的人,不和谐了,而Eric认为,在DDD中,领域中的一些概念是不能作为模型中的对象来处理的,如果将这些功能概念强行加给实体对象和值对象,会破坏模型中对象的定义.我们的DDD项目中都是以充血模型存在着,所以,Eric呼唤:建模专家必须懂得实现,懂得软件技术。
领域驱动设计(DDD)实践之路(第二篇)
在领域驱动里面,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定制化更新字段的方法快速实现。
***隐藏网址***
这一次要讲的其实就是上面提到过的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的实现根据面向对象原则,仅仅提供如何查询过滤数据库数据
迭代器的实现提供了迭代职责实现
至此实现了批处理加载聚合的逻辑,同时可以提供聚合的部分加载(需要注意业务的正确性不会因为聚合的不完全加载而产生问题)。
最后总结一下
DDD领域驱动设计的项目实践
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.交易核心下发
6.行情系统订阅交易核心相关消息
7.行情系统收到,开始计算报价行情:
a.根据。
8.行情系统收到,开始计算报价行情:
a.根据。
9.行情系统收到,开始计算报价行情:
a.将。
10.行情系统收到,开始计算报价行情:
a.将。
第一步:分析需求,定义关键类,描述业务流程
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应用架构
DDD领域驱动设计-DDD概览
# 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)

更多文章:
最全ascii码对照表汉字范围(汉字 ascii值得范围 字符编码为utf-8 求所有汉字的ascii值得范围)
2026年8月24日 12:00
个人述职报告ppt(简约彩色个人述职报告PPT模板最新怎么样)
2025年9月19日 22:30
pycharm安装labelme(Pycharm及python安装详细教程 python详细安装教程)
2025年9月28日 13:15
competition的中文意思(game,tournament,match和competition这四个词使用上的注意以及区分)
2025年12月19日 12:15
linuxvim全部删除(vim如何删除光标所在行后面的所有行)
2025年10月30日 16:30
rhino的trim怎么用(rhino中如何能切割不在同一平面的线)
2026年4月10日 11:30
firstfloor是几楼(为什么英国说的一楼,其实是中国和美国人说的二楼.)
2026年1月15日 13:00
dirty pair(李阳疯狂英语100个发音秘诀 字母歌歌词的翻译,不要 网页链接什么的,求直接写下来)
2026年8月13日 19:00
nginx实战反向代理(Nginx 最全操作——nginx反向代理(5))
2025年9月6日 23:45
sqlserver数据库名称是哪个(重命名SQLServer数据库的方法)
2025年9月30日 05:15
杰克琼斯旗下高端品牌叫什么(杰克琼斯和ONLY是不是一家公司的)
2026年7月11日 21:00
gpg error(cydia出现GPG error错误怎么办)
2025年10月26日 19:30











