DDD 业务改造总结:
业务编写复杂了,定义业务术语 不依赖 基础设施提供的数据,意味着 我们需要转换,将更多的语义转换成 业务概念, 也是为了提供数据的变化,不会导致业务的修改,特别适合 微服务中台业务的抽象; 我认为,如何判断自己的DDD架构设计是否合理,就是DDD四层是否可以拆分模块而不影响, 层次说明: 基础设施(infrastructure) -> 领域(domain)-> 应用(application) 依赖关系。 四大模块 即未来的微服务 DDD架构设计 互相映射。 业务逻辑:业务逻辑可以放到工具类上去写,这样相当于 业务上的处理都属于 业务工具,我认为这个工具**概念很重要。
还可以结合 设计模式去写业务逻辑,业务工具类,这样,DDD上基于 隔离的**就能得到很好得体现,聚合根与不同聚合根之间得交互变得立体。
一、说明
本代码源于极客时间《DDD实战课》,DDD知识体系和代码详解可参考专栏。
在《DDD实战课》专栏第18节中我们用事件风暴完成了“在线请假考勤”项目的领域建模和微服务设计。 我们一起从程序员的视角去看看用DDD方法设计和开发出来的微服务代码到底是什么样的?
二、项目回顾
“在线请假考勤”项目中,请假的核心业务流程是:“请假人填写请假单提交审批。根据请假人身份、请假类型和请假天数进行校验并确定审批规则。根据审批规则确定审批人,逐级提交上级审批,逐级核批通过则完成审批,否则审批不通过则退回申请人。”
在第18节的DDD领域建模和微服务设计中,我们已经拆分出了两个微服务:请假和考勤微服务。 本部分是请假微服务的示例代码,采用的开发语言和数据库分别是:Java、Spring boot和PostgreSQL。
三、请假微服务采用的DDD设计**
请假微服务中用到了很多DDD设计**和方法,主要包括以下几点。
1.聚合的管理:聚合根、实体和值对象的关系。
2.聚合数据的初始化和持久化:工厂和仓储模式。
3.聚合的解耦:聚合代码的解耦、跨聚合的服务调用和对象解耦。
4.领域事件管理:领域事件实体结构、持久化和事件发布。
5.DDD分层架构:基础层、领域层、应用层和用户接口层的协作。
6.服务的分层与协作:实体方法、领域服务、应用服务、接口服务,服务的组合和编排,跨多个聚合的服务管理和协同。
7.对象的分层和转换:DTO、DO和PO等对象在不同层的转换和实现过程。
8.微服务之间的访问:登录和认证服务。
得物技术: DDD分层模型总结
https://blog.csdn.net/SmartCodeTech/article/details/115252056
流量请求入口
用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户(H5/APP)、程序(API)、消息队列(MQ)、超时中心回调,自动化测试和批处理脚本等等。
在最初的设计中,只有用户的调用是放在这一层的,其他调用是放在了application,这样导致了两个结果,分层不明确和入口分散。
所以本次将所有的流量入口全部放在了interfaces做收口。
应用层连接用户接口层和领域服务层,它是很薄的一层,主要职能是协调领域层多个聚合完成服务的组合和编排。
应用服务层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。
应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。
在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。
举个精简的例子,假设出价分四步,
1、调用商品接口校验商品是否上架,
2、出价规则校验,
3、数据落地,
4、调用库存接口生成库存;
那么应用层的主要功能就是编排这四步,不做业务处理。其中第1步和第4步放到基础层处理(基础层封装外部RPC),第2步在规则域执行,第3步在出价域执行。
在初版的设计讨论中,有过一个定义是查询业务可以穿领域层,直达基础层,理由是纯查询的业务没有复杂的逻辑,仅仅是拿数据而已。但这也只是一个想法而已,经过实际的业务实践可知查询还是有很多的业务逻辑组装,而基础层是不处理业务逻辑的,拿到数据后只能在应用层做封装,导致了应用层过于厚重。同时也考虑到一个合理的规范性,约定同样的流程以后,会更好的推进DDD。
me
描述中,定义这一层只对 业务流程进行编排,不做业务处理;规则域和出价域怎么定义,为什么有这么一个定义。
领域服务层由多个 业务职责单一 的聚合构成,实现核心的领域逻辑。
领域层的作用是实现领域核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。
抽象概念:
领域模型:
其业务能力:
领域服务:
与之对应的是贫血模型,我们经常使用的MVC模型中,实体类只定义属性,并没有定义实体行为。这种将数据与业务逻辑分离,其实是违反了 OOP 的封装特性,实际上是一种面向过程的编程风格。任意代码都可以修改实体的属性,那么实体的属性值就不受限制了。这其实是自下而上的设计**,是SQL 驱动(SQL-Driven)的开发模式。
而充血模型则是将该实体所有行为也进行了定义(比如save,modify,remove等操作)。任何想要修改实体属性的操作,必须要通过实体本身来实现。这是一种自上而下的设计**,由具体的业务驱动开发,不需要关心底层的SQL实现。
实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。
聚合根是一种更大范围的封装,把一组在业务上不可分隔的实体和值对象聚合在一起,通过根实体的唯一标识对外提供能力。
实体是领域中需要唯一标识的领域概念。相同的两个实体,如果唯一标识不一样, 那么即便实体的其他所有属性都一样,我们也认为它们两个是不同的实体,比如同一个用户对同一个sku同价格的两个库存实体,除了主键ID外,其余均相同,但这仍然是两个实体。
同时,不应该给实体定义太多的属性或行为,而应该寻找关联,将一些属性或行为转移到其他关联的实体或值对象上。 比如Inventory实体,会存储一些商品信息(sku、spu等),由于商品信息是一个完整的有业务含义的概念,所以,我们可以定义一个Commodity对象,然后把Inventory实体中商品相关的信息转移到Commodity对象上。如果没有Commodity对象,而把这些商品信息直接放在Inventory对象上,并且如果对于一些其他的比如费用信息、仓库唯一码等信息也放到进去,会导致Inventory对象很混乱,结构不清晰,最终导致它难以维护和理解。
值对象就是上面所说的Commodity对象,并不是每一个值对象都必须有一个唯一标识,也就是说我们不关心对象是哪个,而只关心对象是什么。以Commodity对象为例,如果有两个Commodity的spuId是一样的,我们就会认为这两个Commodity是同一个。也就是说只要spuId一样,我们就认为是同一个商品。
实体和值对象的根本区别:**实体不仅需要知道它是什么?而且还需要知道它是哪个?而值对象只需要知道它是什么?**值对象脱离于 实体后将无意义。
DDD 领域驱动设计-三个问题思考实体和值对象 - 田园里的蟋蟀 - 博客园 (cnblogs.com)
基础设施层是数据封装层,在这里获取各类数据,比如数据库、缓存、外部领域、外部接口等。
基础层是贯穿除领域层外所有层的,比较常见的功能还是提供数据库持久化和外部领域服务调用的。
基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
比如说,在传统架构设计中,由于上层应用对数据库的强耦合,很多的架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,我们只需要更换数据库基础服务就可以了,这样就将资源变更对应用的影响降到了最低。
依赖倒置:
Domain 层不再直接依赖 Infrastructure 层,而是引入了一个适配器模式(Port/Adapter),使用DIP(Dependency Inversion Principle,依赖倒置)反转了 Domain 层和 Infrastructure 层的依赖关系,其关系如上图所示Domain 层以接口的方式开放端口,让Infrastructure层去实现,这样设计的有点是 Domain 层的演变和进化完全是独立的,向上不受 Application 层影响,向下不受 Infrastructure 层影响
举个例子,领域层是通过仓储接口(repository)获取基础资源的数据对象,仓储接口会调用仓储实现,具体的基础资源的数据处理过程是在仓储实现中完成的。这样做的好处是,避免将仓储实现的代码混入上层业务逻辑中。如果以后替换数据库,由于做了基础资源的个性的代码隔离,所以实现了应用逻辑与基础资源的解耦。在更换数据库时只需要更换仓储相关的代码就可以了,应用的逻辑不会受太大的影响。
分层是为了各层独立演进的,上层使用下层定义的服务,而下层对上层一无所知,另外每一层对上层隐藏细节实现,依赖契约交互,独立层在技术方案调整时只要遵守契约则可以做到上层无感知迁移,这样也方便各层的维护和标准化工作。DDD 有两种架构,严格分层架构和松散分层架构。优化后的 DDD 分层架构模型就属于严格分层架构,任何层只能对位于其直接下方的层产生依赖。而传统的 DDD 分层架构则属于松散分层架构,它允许某层与其任意下方的层发生依赖,建议使用严格分层架构。
interface 层: request、response
application 层:DTO (data transfer object)
domain 层:entity,VO(value Object)
infrastructure 层: PO (persist object)
首先用户接口层通过 request/response 对象来进行跨进程间的交互数据;应用服务层使用(DTO)来进行数据交互;在领域内部,我们通过领域对象(entity/VO)作为领域内部的数据和行为载体;在基础设施层,我们使用持久化对象(PO)进行数据库资源的交互。
适配层(Adapter)
在一个上下文中,有时需要对外部上下文进行访问, 通常会引入防腐层的概念来对外部上下文的访问进行一次转义。
有以下几种情况会考虑引入防腐层:
1.需要将上层的模型翻译成当前层可以理解的模型。比如上层定义了两个值id和type,不同的type所对应的id含义不同,那么翻译的时候,可以直接根据type进行转换。
-
不同上下文之间的团队协作关系,如果有依赖关系,建议引入防腐层,避免下层变动造成上层的变动,即避免参数的引用传递。
-
避免将上层过多的参数传递到下层。
领域事件是领域模型中非常重要的一环,领域事件将会导致进一步的业务操作,有助于实现业务的解耦,并完成业务闭环。
举例来说,领域事件是业务流程的一个中间步骤,比如出价领域出价成功后将通知库存域添加库存动作;也可能是批处理过程发生的事件,比如求购域批处理程序扫描求购业务表判断是否要的群体触发基础服务域的push服务告知求购用户最新的求购进展。
领域事件可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。通过领域事件+补偿机制来达到最终一致性,提高系统的稳定性和性能;
在一致性要求不高时,可以通过领域事件订阅器直接向消息队列发送事件。
对一致性要求高时,需要先将事件存储,然后通过后台线程加载并分发到消息队列。
案例:
下单? 库存占用!