DDD领域驱动设计实战-DDD微服务代码结构

更多内容关注微信公众号:fullstack888

DDD并没有给出标准的代码模型,不同的人可能会有不同理解。按DDD分层架构的分层职责定义,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级目录。

ff220d7960a87e7e5d6a8ee592969708.png

1 Interfaces(用户接口层)

存放用户接口层与前端交互、展现数据相关的代码。

前端应用通过这层接口,向应用服务获取展现所需的数据。这层主要用来处理用户发送的Restful请求,解析用户输入的配置文件,并将数据传递给Application层。数据的组装、数据传输格式以及Facade接口等代码都会放在这一层目录里。封装应用服务和对外暴露接口。

1.1 细分结构

assembler、dto 和 façade

b2dd0b444f03eb17cc14b73370574cd7.png

facade

提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。比如调用应用层创建用户的方法。

dto

数据传输的载体,内部不存在任何业务逻辑,可以通过DTO把内部的领域对象与外界隔离。

比如接收请求传入的数据CustomerDTO。不同的对象在不同的层转换。用户接口层DTO和DO转换,应用层主要是DO,调外部微服务的服务的时候应用层有dto和do的转换。领域层与基础层之间,在基础层有DO和PO的转换。在接口层定义DTO对象。数据可能来源于多个DO对象。

assembler

实现DTO与DO间的相互转换和数据交换。

一般assembler与dto一同出现。比如创建用户时,将CustomerDTO转换为CustomerEntity。你可以在用户接口层创建DTO类和assembler类。在assembler类里完成映射。

2 Application(应用层)

存放应用层服务组合和编排相关的代码。

  • 应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合

  • 向上为用户接口层提供各种应用数据展现支持服务

应用服务和事件等代码会放在这层目录。

9a69a4b7907cc5cbb7e6b41cd6ca7274.png

Event(事件)

主要存放事件相关代码。包括两子目录:

publish

主要存放事件发布相关代码。比如发布用户创建事件给其它微服务。

subscribe

主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。

虽然应用层和领域层都可进行事件的发布和处理,但为实现事件的统一管理,推荐将微服务内所有事件的发布和订阅处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。

Service(应用服务)

应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。比如内部服务->创建用户;外部服务->创建日志。

3 Domain(领域层)

存放领域层核心业务逻辑相关的代码。

可包含多个聚合代码包,共同实现领域模型的核心业务逻辑。聚合以聚合内的实体、方法、领域服务和事件等代码会放在该层目录。

领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码。一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调。

Domain 由一或多个聚合包构成,共同实现领域模型的核心业务逻辑。

聚合内的代码模型是标准和统一的,包括:entity、event、repository、service 子目录

f0a32dfbe023b9bc015fc2b47b319db4.png

Aggregate(聚合)

聚合软件包的根目录,可根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。以聚合为单位的代码放在一个包里的主要是为业务内聚,更是为以后微服务之间聚合的重组。聚合之间清晰的代码边界,可让你轻松地实现以聚合为单位的微服务重组。

示例

比如进入用户聚合目录下(如CustomerAggregate)。

假设这样一个场景,主播账户作为一个聚合,优惠券模块作为一个聚合。那主播选券的命令属于主播账户聚合。然后主播账户里的优惠券就是这个聚合里的值对象。

如果有多个聚合, 比如聚合根A和聚合根B, 从业务的角度讲,可以接受AB间数据的最终一致性,但从数据展示的角度考虑, A和B是有强关联性的,也就是说在页面上,他们总是一起在页面的某部分出现, 那可以分别调两个聚合的领域服务,然后将两个聚合根的DO对象转换为一个DTO,就可以给前端提供包含两个聚合数据的数据服务了。

细分结构

Entity(实体)

存放聚合根、实体、值对象以及工厂模式(Factory,工厂模式主要是实现复杂聚合的实体的数据初始化。如果实体太多,聚合根处理起来会很复杂,通过工厂一次初始化)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。比如用户聚合根。

Event(事件)

存放事件实体以及与事件活动相关的业务逻辑代码。比如创建用户的事件。

Service(领域服务)

存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。比如具体的创建用户逻辑,比如用户是否重复校验,分配初始密码等。

Repository(仓储)

存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,设定原则:一个聚合对应一个仓储。比如将用户信息保存到数据库。

按DDD分层架构,仓储实现本应属基础层代码,但为在微服务架构演进时,保证代码拆分和重组的便利性,把聚合仓储实现的代码放到聚合包内。这样,如果需求或设计发生变化导致聚合需要拆分或重组,就可将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。

4 Infrastructure(基础层)

主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

Infrastructure 的代码目录结构有:config 和 util 两个子目录。

4b3f509720ef23d16b958761a5cd7cca.png

  • Config 主要存放配置相关代码。

  • Util 存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。

总结

聚合之间的代码边界一定要清晰。聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。

要有代码分层思想。写代码时一定要搞清楚代码的职责,将它放在职责对应的代码目录内。

  • 应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码

  • 领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层,你的基于DDD分层架构模型的微服务慢慢就会演变成传统MVC架构。

在整个微服务架构里面一般微服务上层还有BFF层、聚合服务层,一般BFF层或聚合服务层用来协调多个微服务或者做数据转换。微服务内的应用层主要处理自己的逻辑编排,bff主要处理微服务之间的逻辑。

同一个微服务内,跨领域的方法调用,我们可以在应用层进行组合和编排,那微服务间的领域方法调用是怎样的呢? 从应用层发起。方法是逐层封装,一直到应用服务。微服务内应尽量避免领域服务在不同聚合之间的调用,这样聚合之间耦合度会比较高。

Controller, Service, Repository。Controller相当于用户接口层里的Facade。由于采用了充血模型,之前三层模型中的Service的业务逻辑被封装在了domain的各个聚合下的实体之中。如果需要使用到多个实体来完成某个操作,就要使用聚合中的service。

FAQ

  1. 应用服务只能调用领域服务和实体的方法,能调用仓储接口的方法么?按理应该隔离,即应用服务应该调用领域服务的方法,再让领域服务调用仓储接口的方法吧?如果是应用服务直接调用文件或者缓存,应用服务可以之间调用仓储。但如果中间有领域实体和数据库,则需通过领域服务,然后通过聚合根来调用仓储。

  2. 实体的转换只有从用户接口层到应用服务层一次是么?即到应用服务层后,以及之后的仓储接口都是可以直接对领域实体进行操作的?用户接口层大多是DTO,应用层和领域层大多是DO,基础层则是PO,在不同层之间是需要进行数据转换的。

  3. 需要在实体中配置一些和底层存储相关的注解,这样会不会不能把领域层可仓储实现进行隔离?如果这样,那Spring Data Jdbc是不是没有严格遵守DDD?而且它提供的领域事件的发布机制实现,是在对应的实体中产生,例如在某一实体中定义产生领域事件的源头,当对应的实体保存或更新时,就会发出这样一个领域事件。按照咱们文章中讲解的事件的发布是在应用层,那么如果要这样做的话,是不是就需要在应用层重新转发领域层实体内产生的领域事件呢?如果是这样,确实领域层与数据库层会有耦合。领域事件其实放领域层也可,放应用层主要是为统一管理。如果领域事件放在实体内部,查找和运维起来就不是太方便,而且这个实体还需要对领域事件的实体进行操作。目录结构的设计主要是从边界、分层和便利性考虑的。

- END -

往期回顾

◆产线环境故障排查常用套路

◆面试题:设计高并发系统的时候,数据库层面该如何设计

◆真正的缓存之王,Google Guava 只是弟弟

◆好分期热线客服系统的架构演进

◆Prometheus这些基础概念你应该了解

◆万字追溯ChatGPT各项能力的起源

◆60 个神级 VS Code 插件,总有一个是你需要的

◆聊聊微服务架构中的用户认证方案!

fb8d0aca987f524096b71cdcae21b574.png

技术交流,请加微信: jiagou6688 ,备注:Java,拉你进架构群

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/28216.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows10环境下安装docker、Ubuntu、gitlab、wsl2

一、概述 By星年 文章参考 常用命令参考:(为了方便复制命令都做了换行处理,可直接双击复制。) 进docker terminal: docker exec -it gitlab /bin/bash 查看容器列表: docker ps -a 查看镜像列表: docker images git…

AIGC的发展史:从模仿到创造,AI的创造性探索

在 AI时代,人工智能不再是简单的机器,而是一个具有无限创造力的创造者。AIGC的诞生是人工智能从模仿到创造的一种进步,也是对人类创造力的一种新探索。 而这种由AI生成的内容究竟是如何发展而来的呢?在本文中,我们将探…

如何解决微信支付回调:支付成功及支付失败都不进行任何操作(支付坑)

这几天都在用微信支付这块功能,不得不吐槽一下微信支付的小坑 关于微信提供JSAPI文档,本来想着他们写的开发文档,肯定是没有任何问题。 结果在开发测试中,支付完成后或者支付失败都没任何效果。 我已经在JS里面做了跳转&#xff0…

android微信支付返回-1,支付失败总结!

解决办法1:看看二次生成sign的参数顺序是否跟我发的一致!很坑爹,必须一样才行! 解决办法2:请求得到prepayid参数的url必须是图中的Url

微信支付下载对账单400Bad Request问题解决方式

今天在做项目时, 需要用到微信支付的对账接口, 看了好多人的反馈, 加上自己的测试, 在用API V3生成了Sign后,对download_url进行Get请求访问时, 依然会出现问题, 就是Nginx报错400 Bad Request 看了好多人的文章, 基本上都是用的调用sdk生成的httpClient再次进行调用, 如下图 …

微信支付异常(“应答的微信支付签名验证失败“)记录

原因是: 配置错了“微信支付平台证书”; 如何解决: 1.下载 微信支付平台证书下载工具(Certificate Downloader)https://github.com/wechatpay-apiv3/CertificateDownloader 得到 CertificateDownloader-1.1.jar 2.执行命令 java -jar Cer…

postman-模拟上传图片

一、Chrome打开layui : 图片上传 右键打开检查,选择network,上传图片查看到: 二、postman测试 打开postman先设置post,并将url填好 : https://httpbin.org/post 选择form-data:添加key/value : key为与后台约定字段(一…

【苹果群发iMessage推送位置推】软件安装将会按照 Developer Program License Agreement

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

风辞远的科技茶屋:可怖的AI

大家好,我是脑极体的风辞远。一直以来我们都在写大块文章,很少有机会跟大家聊天。时间长了,总觉得这种方式有一点冷漠感,不够轻松,加上往往每篇文章只聚焦一个话题,而我们产能有限,就会有很多值…

千万别再乱点黄色APP了!

上一篇:文心一言员工跳槽工资翻倍,猎头:百万年薪很正常 网络诈骗千千万,涉黄APP占一半。 小伙来自山东菏泽,失手在手机上下载了非法的涉黄APP,当他准备观看视频时发现,需要充值成为会员或完成任…

如何分析系统平均负载过高?

文章目录 前言uptime命令平均负载平均负载到底是多少才合理平均负载和CPU的关系CPU与进程1比1,CPU使用率高导致负载变高I/O高,导致负载高进程数超过CPU数,导致负载高 前言 我相信你应该用过uptime命令查询系统负载的情况,或者在各…

线上负载过高排查(top/vmstat/ifstat/free/df)

目录 一、五大命令 二、故障排查步骤 1、top命令找出CPU占比最高的 2、ps -ef 或者 jps -l进一步定位 3、ps -mp位到具体线程或者代码 4、jstack精准定位到错误的地方 本文通过学习:周阳老师-尚硅谷Java大厂面试题第二季 总结的LinuxJDK命令操作相关的笔记 一…

解决Linux 负载过高问题过程记录

解决问题的思路 1.top命令查看该机器的负载状况 2.cd /proc/pid 查看对应高占用程序的位置 3.进入对应程序中查看日志,根据CPU和内存这两个因素分析 4.ps -ajxf 查看进程及其之下的线程,通过stat查看是否存在D僵尸进程 1.什么是负载过高 1.1load A…

假如ChatGPT 去面试前端工程师,结果会怎么样?

近日,有个叫 ChatGPT 的同学来我司面试前端,考考他面试八股文。先来问问闭包。 第一问,说说 JS 闭包 追问,详细讲讲闭包 由于篇幅太长,转成文字,以下皆为 ChatGPT 回答 闭包是一个非常重要的 JavaScript 概…

青椒肉丝饭

今天第一次来华德吃饭,为了吃这顿饭跑遍了半个上海。 先是去长乐路上的大铁门排挡吃自助,结果人家收摊了,估计都回家过年了吧。 然后又去新华路上的粮仓饭湘,结果人满为患,连个站的地方都没有。 最后回到北新泾这里…

五花青椒包菜

[猪头]每次做完菜,第一口一定要找块好肉尝尝。肉好吃😋,这道菜就差不了,下饭。 ​[调皮]肉不好吃的菜,没有灵魂。 ​[愉快]今天的菜,五花肉青椒土豆包菜。

青椒肉丝

青椒肉丝 材料 15元的肉丝 一根葱 两个辣椒 生粉,盐,油 步骤 1.开煤气,将锅烧热,小火炖下肥肉得到猪油,将残渣捞出 2.切好的肉丝撒上生粉,摔摔肉,弄匀 3.倒入油(满锅底&#xff…

java设计模式之一(工厂模式)

何为工厂模式? 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑&#xff…

Java 设计模式之迭代器模式

一、了解迭代器模式 1.1 什么是迭代器模式 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。 迭代器模式把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也可以让责任各得其所。 …

【菜谱】青椒肉丝

【菜谱】青椒肉丝 食材配料步骤准备工作开始制作 成果展示 下周一上班,今天回家收拾东西。虽然是个程序员,但是也想炒一手好菜,就先在家里练习了,意外的发现还不错! 食材 猪里脊肉 175g (老妈就给我拿了这…