DDD领域驱动设计详解

DDD领域驱动设计详解DDD 领域驱动设计 1 领域驱动设计 1 1 什么是领域驱动设计 1 2 为什么用领域驱动设计 2 DDD 核心知识体系 2 1DDD 核心概念 2 2DDD 战略战术设计 2 2 1DDD 战略设计 2 2 1DDD 战术设计 3 DDD 微服务架构模型 3 1 基本架构 3 1 1DDD 分层架构 3 1 1 六边形理论 3 1 1CQRS 架构设计 3 2 代码结构 3 3 服务调用 1 领域驱动设计 1 1 什么是领域驱动设计领域驱动设计 DomainDriven 是一种从系统分析到软件建模的一套方法论

1. 领域驱动概述

1.1 领域驱动简介

领域驱动设计是Eric Evans在2004年发表的Domain Driven Design(领域驱动设计,DDD)著作中提出的一种从系统分析到软件建模的一套方法论。以领域为核心驱动力的设计体系。

从领域驱动定义来看,领域驱动设计-软件核心复杂性应对之道,从Eric 定义中可以看出,领域驱动设计是为了解决复杂的软件设计,而且只是解决软件复杂性的一种方式,并不是唯一选择。另外不是所有的业务服务都合适做DDD架构,DDD适合产品化,可持续迭代,业务逻辑足够复杂的业务系统,对于系统初期业务逻辑相对比较简单的应用,传统MVC架构更具有优势,可以减少一部分认知成本与开发成本。而且领域驱动设计并不是万金油,只是解决复杂软件的一种方案,领域驱动设计本身只提供了理论思想,具体的落地方案一定是结合具体的业务场景实现的。目前市面上也有很多依据领域驱动思想落地的开源框架可以参考。

从领域驱动对应关系来看,一方面目前很多建设中台的时候大多采用DDD思想落地,DDD很多思想比如领域划分,领域事件,领域服务,边界上下文划分,充血模型,代码防腐,统一语义等等可以很好的帮助实现中台的落地,但是中台落地DDD并不是唯一选择。另一方面对于DDD的这些思想,与DDD的关系更多是聚合关系,而不是组合关系,也就是在具体应用开发中,即使采用传统的MVC架构,这些思想依然可以很好的发挥其作用。

1.2 领域驱动优点

DDD最大的好处是:接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离。DDD让你首先考虑的是业务语言,而不是数据。DDD强调业务抽象和面向对象编程,而不是过程式业务逻辑实现。重点不同导致编程世界观不同。

领域驱动设计,又称”软件核心复杂性应对之道”。是一套基于对象思维的业务建模设计思想,相对于 CRUD 系统有更高的灵活性,是业务人员处理复杂问题的有效手段。

在这里插入图片描述

1.3 领域驱动解决复杂度方式

首先, 典型的DDD实现了业务复杂度和技术复杂度的隔离,通过分层架构隔离了关注点,举个例子,在传统的DDD四层架构中,DDD划分出了领域层、仓储层、基础设施层、接口层;

在领域层中,存放业务逻辑的关注点,即所谓的领域行为;在应用层中,DDD暴露出了 业务用例级别 (Use Case)的服务接口,粘合业务逻辑与技术实现;在基础设施层中,DDD集中放置了支撑业务逻辑的技术实现,如:MQ消息发送、对缓存的操作等;在仓储层中,DDD放置了和领域状态相关的逻辑,打通了领域状态持久化与存储设施之间的联系。

1.4 领域驱动疑问

对于领域驱动设计与传统架构设计,不同的人有不同的见解,也可以理解,这里梳理领域驱动设计也不是认为传统架构有什么问题,具体采用什么样的架构设计一定是根据现有架构并结合当前实际的业务场景所决定的,任何撇开业务场景谈架构都是耍流氓,就像撇开剂量谈药性一样荒诞。

其次,领域驱动设计全称叫领域驱动设计软件核心复杂性应对之道,是为了更好的解决复杂软件的架构设计,但是这并不意味着领域驱动设计就是万金油,可以解决所有的问题。面对复杂的业务也会存在复杂的聚合,也会存在性能问题,也需要做幂等,也需要高可用,高性能,高并发等等,会与传统架构一样存在这些问题,不同的架构设计工具解决不同的问题,与领域驱动也并不冲突。

不管是领域驱动设计还是传统三层架构都是面向对象设计,很多地方认为传统是面向数据,面向过程,数据也是对象,过程也是对象,只不过领域驱动更加贴合业务,以业务为核心展开的设计开发,对于聚合问题,业务的复杂必然带来聚合复杂,也是正常现象,不是说使用了DDD就变得不复杂了,对于性能问题,与DDD完全两个概念,领域驱动解决的是软件核心复杂性应对之道,是为了更好的应对复杂业务,使技术实现与业务更好的分离,使外部调用与内部系统相隔离,采用充血,聚合,代码防腐,领域事件,领域服务。对于性能的问题采用相应性能对应的技术手段去解决,而不应该把问题归咎于领域驱动设计。

2. 领域驱动核心知识

2.1 领域知识概念

2.2 领域战略战术设计

DDD有战略设计和战术设计之分。战略设计主要从高层”俯视”我们的软件系统,帮助我们精准地划分领域以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。

在这里插入图片描述在这里插入图片描述

3. 领域驱动战略设计

3.1 战略设计概述

3.2 领域与子域

在这里插入图片描述

3.3 限界上下文

在这里插入图片描述

3.4 领域场景分析

在这里插入图片描述

3.5 四色建模法

在这里插入图片描述

3.6 事件风暴结果图

在这里插入图片描述

3.7 限界上下文依赖结果图

在这里插入图片描述

4. 领域驱动战术设计

4.1 战术设计概述

领域驱动设计,整体包括战略和战术两部分,其中战略部分的落地需要团队合作、开发过程、流程制度等一系列支持,实施阻力相对较大。相反,战术部分,是一组面向业务的设计模式,是基于技术的一种思维方式,相对开发人员来说比较接地气,是提升个人格局比较好的切入点。

战略设计为我们提供一种高层视野来审视我们的软件系统,而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对我们程序员来得最实在的地方。

战术模式和通用语言一样,都工作在特定限界上下文内,其应用边界受限界上下文的保护。

4.2 战术模式

战术模式的作用是管理复杂性并确保领域模型中行为的清晰明确。可以使用这些模式来捕获和传递领域中的概念、关系、规则。

在这里插入图片描述

4.3 领域建模模式

他们表述实现与模型间的关系,将分析模型绑定到代码实现模型。主要用于在代码中表述模型元素的模式。

3.领域服务

4.模块

4.4 对象生命周期模式

相对来说,之前提到的模式重点在于表达领域概念。而对象生命周期模式,有点侧重于技术,用于表示领域对象的创建和持久化。

聚合根之间的关系应该通过保持对另一个聚合根 ID 的引用,而非对对象本身的引用来实现。这一原则有助于保持聚合之间的边界并避免加载不必要的对象。

4.4 其他模式

2.事件溯源

4.5 总结

实体 由唯一标识符定义 标识符在整个生命周期保存不变 基于标识符进行相等性检查 通过方法对属性进行更新 值对象 描述问题域中的概念和特征 不具备身份 不变对象 领域服务 处理无法放置在实体或值对象中的领域逻辑 无唯一标识 无状态服务 模块 分解、组织和提高领域模型的可读性 命名空间,降低耦合,提供模型高内聚性 定义领域对象组间的边界 封装比较独立的概念,是比聚合、实体等更高层次的抽象 聚合 将大对象图分解成小的领域对象群,降低技术实现的复杂性 表示领域概念,不仅仅是领域对象集合 确定领域一致性边界,确保领域的可靠性 控制并发边界 工厂 将对象的使用和构造分离 封装复杂实体和值对象的创建逻辑 保障复杂实体和值对象的业务完整性 仓库 是聚合根在内存中的集合接口 提供聚合根的检索和持久化需求 将领域层与基础实施层解耦 通常不用于报告需求 领域事件 业务人员所关心的事件,是通用语言的一部分 记录聚合根的所有变更 处理跨聚合的通信需求 事件溯源 使用历史事件记录替换快照存储 提供对历史状态的查询 

5. 领域驱动架构模型

5.1 领域驱动基本架构

5.1.1 分层架构

在这里插入图片描述

5.1.2 六边形理论

在这里插入图片描述

5.1.3 CQRS架构设计

在这里插入图片描述

5.2 领域驱动分层架构

在这里插入图片描述

1.用户接口层:面向前端用户提供服务和数据适配。这一层聚集了接口和数据适配相关的功能。 2.应用层:实现服务组合与编排,主要适应业务流程快速变化的需求。这一层聚集了应用服务和时间订阅相关的功能。 3.领域层:实现领域模型的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和领域事件,通过个领域对象的协同和组合形成领域模型的核心业务能力。 4.基础设施层:它贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。 

5.3 服务调用

在这里插入图片描述

5.4 服务封装与组合

在这里插入图片描述

5.5 领域架构对应关系

在这里插入图片描述

6. 领域驱动落地框架

6.1 leave-sample

6.2 dddbook

6.3 Xtoon

6.4 DDD Lite

6.5 ruoyi_cloud

6.6 Cola框架

cola框架是阿里大佬张建飞(Frank) 基于DDD构建的平台应用框架。“让COLA真正成为应用架构的最佳实践,帮助广大的业务技术同学,脱离酱缸代码的泥潭!”

csdn地址:https://blog.csdn.net/significantfrank/article/details/

6.7 Axon Framework

Axon Framework 是用来帮助开发人员构建基于命令查询责任分类(Command Query Responsibility Segregation: CQRS)设计模式的可伸缩、可扩展和可维护应用程序的框架。你只需要把工作重心放在业务逻辑的设计上。通过一些 Annotation ,Axon 使得你的代码和测试分离。

7. 领域驱动实践

7.1 贫血模型和充血模型

@Data public class Person { 
    / * 姓名 */ private String name; / * 年龄 */ private Integer age; / * 生日 */ private Date birthday; / * 当前状态 */ private Stauts stauts; } public class PersonServiceImpl implements PersonService { 
    public void sleep(Person person) { 
    person.setStauts(SleepStatus.get()); } public void setAgeByBirth(Person person) { 
    Date birthday = person.getBirthday(); if (currentDate.before(birthday)) { 
    throw new IllegalArgumentException("The birthday is before Now,It's unbelievable"); } int yearNow = cal.get(Calendar.YEAR); int dayBirth = bir.get(Calendar.DAY_OF_MONTH); /*大概计算, 忽略月份等,年龄是当前年减去出生年*/ int age = yearNow - yearBirth; person.setAge(age); } } public class WorkServiceImpl implements WorkService{ 
    public void code(Person person) { 
    person.setStauts(CodeStatus.get()); } } 

员工充血模型

@Data public class Person extends Entity { 
    / * 姓名 */ private String name; / * 年龄 */ private Integer age; / * 生日 */ private Date birthday; / * 当前状态 */ private Stauts stauts; public void code() { 
    this.setStauts(CodeStatus.get()); } public void sleep() { 
    this.setStauts(SleepStatus.get()); } public void setAgeByBirth() { 
    Date birthday = this.getBirthday(); Calendar currentDate = Calendar.getInstance(); if (currentDate.before(birthday)) { 
    throw new IllegalArgumentException("The birthday is before Now,It's unbelievable"); } int yearNow = currentDate.get(Calendar.YEAR); int yearBirth = birthday.getYear(); /*粗略计算, 忽略月份等,年龄是当前年减去出生年*/ int age = yearNow - yearBirth; this.setAge(age); } } 

贫血模型和充血模型的区别

/ * 贫血模型 */ public class Client { 
    @Resource private PersonService personService; @Resource private WorkService workService; public void test() { 
    Person person = new Person(); personService.setAgeByBirth(person); workService.code(person); personService.sleep(person); } } / * 充血模型 */ public class Client { 
    public void test() { 
    Person person = new Person(); person.setAgeByBirth(); person.code(); person.sleep(); } } 

上面两段代码很明显第二段的认知成本更低, 这在满是Service,Manage 的系统下更为明显,Person的行为交由自己去管理, 而不是交给各种Service去管理。

7.2 DDD实现银行转账案例

银行转账事务脚本实现在事务脚本的实现中,关于在两个账号之间转账的领域业务逻辑都被写在了MoneyTransferService的实现里面了,而Account仅仅是getters和setters的数据结构,也就是我们说的贫血模型:

public class MoneyTransferServiceTransactionScriptImpl implements MoneyTransferService { 
    private AccountDao accountDao; private BankingTransactionRepository bankingTransactionRepository; //... @Override public BankingTransaction transfer( String fromAccountId, String toAccountId, double amount) { 
    Account fromAccount = accountDao.findById(fromAccountId); Account toAccount = accountDao.findById(toAccountId); //. . . double newBalance = fromAccount.getBalance() - amount; switch (fromAccount.getOverdraftPolicy()) { 
    case NEVER: if (newBalance < 0) { 
    throw new DebitException("Insufficient funds"); } break; case ALLOWED: if (newBalance < -limit) { 
    throw new DebitException( "Overdraft limit (of " + limit + ") exceeded: " + newBalance); } break; } fromAccount.setBalance(newBalance); toAccount.setBalance(toAccount.getBalance() + amount); BankingTransaction moneyTransferTransaction = new MoneyTranferTransaction(fromAccountId, toAccountId, amount); bankingTransactionRepository.addTransaction(moneyTransferTransaction); return moneyTransferTransaction; } } 

上面的代码大家看起来应该比较眼熟,因为目前大部分系统都是这么写的。其实我们是有办法做的更优雅的,这种优雅的方式就是领域建模,唯有掌握了这种优雅你才能实现从工程师向应用架构的转型。同样的业务逻辑,接下来就让我们看一下用DDD是怎么做的。银行转账领域模型实现如果用DDD的方式实现,Account实体除了账号属性之外,还包含了行为和业务逻辑,比如debit( )和credit( )方法。

// @Entity public class Account { 
    // @Id private String id; private double balance; private OverdraftPolicy overdraftPolicy; //. . . public double balance() { 
    return balance; } public void debit(double amount) { 
    this.overdraftPolicy.preDebit(this, amount); this.balance = this.balance - amount; this.overdraftPolicy.postDebit(this, amount); } public void credit(double amount) { 
    this.balance = this.balance + amount; } } 

而且透支策略OverdraftPolicy也不仅仅是一个Enum了,而是被抽象成包含了业务规则并采用了策略模式的对象.

public interface OverdraftPolicy { 
    void preDebit(Account account, double amount); void postDebit(Account account, double amount); } public class NoOverdraftAllowed implements OverdraftPolicy { 
    public void preDebit(Account account, double amount) { 
    double newBalance = account.balance() - amount; if (newBalance < 0) { 
    throw new DebitException("Insufficient funds"); } } public void postDebit(Account account, double amount) { 
    } } public class LimitedOverdraft implements OverdraftPolicy { 
    private double limit; //... public void preDebit(Account account, double amount) { 
    double newBalance = account.balance() - amount; if (newBalance < -limit) { 
    throw new DebitException( "Overdraft limit (of " + limit + ") exceeded: " + newBalance); } } public void postDebit(Account account, double amount) { 
    } } //而Domain Service只需要调用Domain Entity对象完成业务逻辑即可。 public class MoneyTransferServiceDomainModelImpl implements MoneyTransferService { 
    private AccountRepository accountRepository; private BankingTransactionRepository bankingTransactionRepository; //... @Override public BankingTransaction transfer( String fromAccountId, String toAccountId, double amount) { 
    Account fromAccount = accountRepository.findById(fromAccountId); Account toAccount = accountRepository.findById(toAccountId); //. . . fromAccount.debit(amount); toAccount.credit(amount); BankingTransaction moneyTransferTransaction = new MoneyTranferTransaction(fromAccountId, toAccountId, amount); bankingTransactionRepository.addTransaction(moneyTransferTransaction); return moneyTransferTransaction; } } 

通过上面的DDD重构后,原来在事务脚本中的逻辑,被分散到Domain Service,Domain Entity和OverdraftPolicy三个满足SOLID的对象中。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/204257.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午8:39
下一篇 2026年3月19日 下午8:39


相关推荐

  • C++ 吃透回溯算法

    C++ 吃透回溯算法感谢博主代码随想录的视频分享下文总结皆根据上述视频总结记录 1 回溯算法的核心 1 1 介绍回溯算法都可以抽象成一个 N 叉树 每个节点都是处理集合的大小 树的深度是递归 nbsp nbsp 回溯法是优先搜索的一种特殊情况 常用需要记录节点状态的深度优先搜索策略 通常比如排列 组合 选择类问题使用回溯法 nbsp nbsp 在搜搜某一个节点时候 如果发现目前节点并不是目标节点 我们回退到原来节点继续搜索 并且把目前节点修改的状态还原 nbsp nbsp

    2026年3月16日
    2
  • java 激活码-激活码分享2022.03.09[通俗易懂]

    (java 激活码)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

    2022年4月2日
    156
  • goland 2021.12激活【最新永久激活】

    (goland 2021.12激活)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。https://javaforall.net/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~3…

    2022年3月30日
    245
  • struts2使用AbstractInterceptor拦截器 获取参数以及修改参数的值

    struts2使用AbstractInterceptor拦截器 获取参数以及修改参数的值publicclassAppLoginInterceptorextendsAbstractInterceptor{ @Override publicStringintercept(ActionInvocationinvocation)throwsException{ Mapmap=invocation.getInvocationContext().getSes…

    2022年5月15日
    42
  • 部署、收回和删除解决方式—-STSADM和PowerShell「建议收藏」

    部署、收回和删除解决方式—-STSADM和PowerShell

    2022年1月21日
    39
  • 回溯法解01背包问题_01背包问题回溯法伪代码

    回溯法解01背包问题_01背包问题回溯法伪代码一、问题n皇后问题的解空间树是一颗排列树,而01背包问题的解空间树应该是一颗子集树。再简述下该问题:有n件物品和一个容量为c的背包。第i件物品的价值是v[i],重量是w[i]。求解将哪些物品装入背包可使价值总和最大。所谓01背包,表示每一个物品看成一个整体,要么全部装入,要么都不装入。这里n=5,c=10,w={2,2,6,5,4},v={6,3,5,4,6}。01背…

    2022年8月30日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号