MASA Framework - DDD设计(2)
目录
MASA Framework - 整体设计思路
MASA Framework - EventBus设计
MASA Framework - MASA Framework - DDD设计(1)
MASA Framework - MASA Framework - DDD设计(2)
Clean Architecture
国内对于Clean Architecture的翻译很多,干净/整洁/清晰。但无论哪一种都说明了它简洁、清晰的特性。
早期它长这样

看到这张图的同学可能会对另外一张图有印象
洋葱架构(Onion)

现在长这样

看起来好像是亲戚,它们的确也有着千丝万缕的关系
分析Clean Architecture
这部分主要是根据explicit architecture文章的理解整理的,有翻译也有自己理解消化的。如有错漏欢迎指正,谢谢
三大构建块
- 用户界面
- 基础设施
- 应用核心

控制流
- 用户界面
- 应用核心
- 基础设施
- 应用核心
- 用户界面

工具
左右两侧形成鲜明对比,动机不同
- HTTP/CLI:告诉应用要做什么
- SMS/Mailing Server/Search Engine...:应用告诉它们要做什么

链接工具和交付机制到应用核心
将工具连接到应用程序核心的代码单元称为适配器(端口和适配器架构)。
告诉我们的应用程序做某事的适配器称为主适配器或主动适配器,而我们的应用程序告诉我们做某事的适配器称为从适配器或被动适配器。
端口
这些适配器为了适应应用核心的一个非常特定的入口点,即端口。端口只不过是工具如何使用应用程序核心或应用核心如何使用它的规范。
你可以看作是接口和DTO
主适配器或主动适配器
主适配器或主动适配器围绕一个端口并使用它来告诉应用核心该做什么。
我们的主动适配器是Controller或Console Commands,它们在其构造函数中注入了一些对象,该对象的类实现了Controller或Console Commands所需的接口(端口)。
端口可以是控制器需要的服务接口或存储库接口,然后将 Service、Repository 或 Query 的具体实现注入并在 Controller 中使用。
或者,端口可以是Command Bus或Query Bus的接口。在这种情况下,将Command Bus或Query Bus的具体实现注入到Controller中,然后Controller构造Command或Query并将其传递给相关Bus。
注:这里其实提到了CQRS

从适配器或被动适配器
与围绕在端口周围的主动适配器不同,从适配器实现一个端口、一个接口,然后在需要端口的任何地方注入应用核心。
可以理解为是右侧是符合应用核心的需要的接口或者对象,而左侧则是包装用例的传达机制,如HTTP/CLI等

假设我们有一个需要持久化数据的需求:
- 我们创建一个持久化接口(在左侧端口周围),有一个保存数据的方法和一个通过ID删除数据的方法
- 基础设施中提供一个实现类,通过IoC注入这个接口和对应的实现类
- 在需要持久化数据的类的构造函数中注入一个持久化接口
- 如果有一天我们需要从SQL Server换到MongoDb,只需要替换步骤2中的实现类和注入新的实现类即可
IoC
与上图相比,仅仅是多了一个蓝色的箭头从外部直插入应用核心内部。在上面例子中有提到通过IoC的作用这里就不再重复了。
至此,我们一致在讲解应用核心的外围,而应用核心是我们架构设计的重点。

应用核心组织
洋葱架构采用 DDD 分层并将它们合并到端口和适配器架构中。这些层旨在为业务逻辑带来一些组织,即端口和适配器“六边形”的内部,就像在端口和适配器中一样,依赖方向是朝向中心的。
应用层
用例(Use Case)是我们的应用中的一个或多个用户界面触发的流程(业务逻辑)。用户界面可以是终端用户界面也可以是管理界面,或者控制台界面和API。
用例在应用层定义,由DDD和洋葱架构提供,它可以包含端口,ORM接口,搜索引擎接口,消息接口等,也可以是CQRS处理Handlers的地方,发送邮件,调用第三方API等。
应用服务/Command Handler包含用例的业务逻辑,作用是:
- 使用Repository查找一个或多个实体
- 告诉这些实体做一些领域逻辑
- 使用Repository持久化这些实体,保存数据更改
Command Handler可以有两种不同的使用方式:
- 包含执行用例的真实业务逻辑
- 作为架构中的中间件,接收Command并触发应用服务中的逻辑

领域层
领域内的对象除了有对象本身的属性外,还可以操作该对象内部的属性,这是特定于域本身的,并且独立于触发该逻辑的业务流程,它们是独立的,完全不知道应用层。

领域服务
有时我们会遇到一些涉及不同实体的领域逻辑,无论是否相同,该领域逻辑不属于实体本身,它没有直接责任。
那我们可以使用领域服务来承载这部分逻辑,可能有人会觉得那可以放应用层,但领域逻辑在其他用例中就不能重用了。领域逻辑应该在领域内部,不要上升到应用层。
领域服务可以使用其他领域服务,或者其他领域对象
领域模型
在最中心,依赖于它之外的任何东西,是领域模型,它包含代表领域中某些事物的业务对象。至于如何定义领域模型可以参考第一篇。
组件
组件与应用核心内所有的层交叉,从外贯穿到内部。例如身份验证、授权、计费、用户、评论或账户,但它们依然与领域有关。
像授权和身份验证这样的限界上下文应该被视为隐藏在某种端口后面的外部工具。

解耦组件
具有完全解耦的组件意味着一个组件不直接了解任何另一个组件。换句话说,它可能没有接口,所以我们需要一些新的架构结构。
比如事件、最终一致性、服务发现等。当你往这条路上走的时候,你就开始脱离单体了。
这里Dapr或许是个不错的选择,它包含了这些功能,对Dapr感兴趣的可以看之前的手把手教你学Dapr系列
MASA Framework解决方案
结合DDD和Clean Architecture以及MASA Framework的特性,我们将在MASA.BuildingBlocks中以接口的形式定义规范,在MASA.Contrib中对接口进行实现。
这意味着你可以只关心BuildingBlocks中的接口定义来编写你的代码,也可以基于接口重新实现在DDD落地中你自己的业务特性来调整或扩展我们提供的默认行为。比如,你有自己的UoW、仓储层等都可以随意换掉。
应用层
应用服务:
实现应用程序的用例,衔接表示层(接口层)与领域层
除此之外,基于MASA EShop的示例中的MASA.EShop.Services.Catalog的CQRS架构演示,应用层也可以承载CQRS的Command Hanlder。除了可以继续使用领域层来解决Command业务外,你也可以选择在此中止,在Command Handler里简化架构直接对Command进行处理。
工作单元:
默认事件是在应用服务中首次开启,所以UoW也会在应用层被激活(实际上底层会根据仓储的操作,只有首次增删改才会自动激活,这个功能可以关闭,改为手动控制)
中间件:
对于使用Event Bus开发来说,应用层还可以作为统一的AOP出入口。
例如统一的事件参数验证:
然后为对应的Event/Command编写验证逻辑 https://github.com/masalabs/MASA.EShop/blob/develop/src/Services/MASA.EShop.Services.Catalog/Application/Catalogs/Commands/DeleteProductCommandValidator.cs
领域层
对于实体、聚合、值对象等概念就不再介绍了,可以参考上一章的内容。
贫血模型VS充血模型
领域中需要限定领域内的业务逻辑,加上EF Core对充血模型的支持,充血模型更适合用作领域模型的开发。
将数据与行为封装,表现出现实业务对象完整行为,每个领域具备明确的职责划分,将逻辑分散到领域对象中。这也是应用层与领域层的一个比较明显的区别。
对于实体相关对象,我们提供了对应的类,当然也包括审计和值对象可能需要用到的枚举类。
枚举类:我们提供了Enumeration,参考自:https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types

领域服务:
领域服务中可以调用其他的领域服务(包括进程内或跨进程),所以我们提供了IDomainService,它的功能包括:
- 自动协调进程内和跨进程的事件传递
- 支持被调用方是CQRS
- 默认支持事件压栈,在UoW Commit后统一触发。也支持实时发送事件(如后续业务可被降级,但跨进程的事件为主业务逻辑不可被降级)
- 跨进程事件支持最终一致性和Saga

仓储:
领域中定义的仓储为接口,代表在当前领域内关心的业务。比如用户,在用户管理和名片两种业务中,对于IRepository的定义是不同的。但在基础设施中BaseRepository可以是同一个,因为BaseRepository可以是最完整的实现,但领域内仓储服务只认其中一部分

基础设施层
给接口提供实现,如仓储接口的实现、Event Bus中MQ或中介者的实现(MASA Framework已实现,所以我们的示例中目前只有仓储接口的实现)等。
MASA Framework模板架构
在MASA Framework模板中提供了自由组合的方式,你可以根据你的需求随意调整如是否包含Blazor、Dapr、DDD、CQRS等。
我们的MASA.EShop推荐采用了4种架构方式,从简到繁,本篇介绍最复杂的一个
Minimal APIS + CQRS + Dapr actor

MASA.EShop中的Ordering服务就是采用这种架构分层,其实分层解释上面也有,只是之前解释的是站在MASA BuildingBlocks的角度,而接下来将是站在开发者角度。
User Interface Layer:它负责提供用户接口,完整前端逻辑。用户也可以是计算机系统,不特指是人。所以这里既可以是API,也可以是Blazor、MVC等。
Application Layer:它可以很薄也可以很厚(在当前分层下推荐薄)。负责协调User Interface和Domain,包括服务的编排和转发,AOP,发送事件等。
如果你有Domain Layer可以把Command做的很薄调用Domaiin。如果你要精简CQRS,也可以不用Domain,在这一层直接做应用服务。当然Query也一样,但Query即便使用Domain也推荐把查询放在应用服务里,这样可以把Query和Command分离来获得CQRS的优势。
Domain Layer:业务核心,包括了领域对象和领域服务以及适配器的接口。建议采用充血模型将行为留在领域内,跨领域且需要被复用的可以使用领域服务。仓储接口则限定领域内的仓储行为,与物理仓储不同的是更聚焦业务本身,而不是实体的完整仓储能力。
Infrastructure Layer:给接口提供实现,如仓储接口的实现、Event Bus中MQ或中介者的实现(MASA Framework已实现,所以我们的示例中目前只有仓储接口的实现)等。
总结
至此,我们不仅实现了对单体架构的支持,还通过Event Bus对微服务架构提供了支持。
如果你对DDD或者MASA Framework感兴趣,不妨把MASA.EShop跑起来看一下,它提供了4种架构方式参考,可以满足大部分业务场景对架构的要求。
学以致用,学无止境。
参考:
DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together:https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
开源地址
MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib:https://github.com/masastack/MASA.Contrib
MASA.Utils:https://github.com/masastack/MASA.Utils
MASA.EShop:https://github.com/masalabs/MASA.EShop
MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor
如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

MASA Framework - DDD设计(2)的更多相关文章
- MASA Framework - DDD设计(1)
目录 MASA Framework - 整体设计思路 MASA Framework - EventBus设计 MASA Framework - MASA Framework - DDD设计(1) DD ...
- MASA Framework - EventBus设计
目录 MASA Framework - 整体设计思路 MASA Framework - EventBus设计 概述 利用发布订阅模式来解耦不同架构层级,亦可用于解决隔离业务之间的交互 优点: 松耦合 ...
- MASA Framework - 整体设计思路
源起 年初我们在找一款框架,希望它有如下几个特点: 学习成本低 只需要学.Net每年主推的技术栈和业务特性必须支持的中间件,给开发同学减负,只需要专注业务就好 个人见解:一款好用的框架应该是补充,而不 ...
- MASA Framework -- EventBus入门与设计
概述 事件总线是一种事件发布/订阅结构,通过发布订阅模式可以解耦不同架构层级,同样它也可以来解决业务之间的耦合,它有以下优点 松耦合 横切关注点 可测试性 事件驱动 发布订阅模式 通过下图我们可以快速 ...
- 领域驱动(DDD)设计和开发实战
领域驱动设计(DDD)的中心内容是如何将业务领域概念映射到软件工件中.大部分关于此主题的著作和文章都以 Eric Evans 的书<领域驱动设计>为基础,主要从概念和设计的角度探讨领域建模 ...
- MASA Auth - 权限设计
权限术语 Subject:用户,用户组 Action:对Object的操作,如增删改查等 Object:权限作用的对象,也可以理解为资源 Effect:规则的作用,如允许,拒绝 Condition:生 ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...
随机推荐
- Amazon EKS 中 EFS 持久性存储
作者:SRE运维博客 博客地址:https://www.cnsre.cn/ 文章地址:https://www.cnsre.cn/posts/220110850573/ 相关话题:https://www ...
- 使用.NET 6开发TodoList应用(28)——实现应用程序健康检查
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 应用健康检查在容器部署的微服务场景下非常常见,相比而言单体非容器部署的应用就不太关心这个特性,为了后续的内容我们在本文中简单介 ...
- Linux 安装 MySQL 8.0.26 超详细图文步骤
1.MySQL 8.0.26 下载 官方网站下载 MySQL 8.0.26 安装包,下载地址: https://downloads.mysql.com/archives/community/ 需要注意 ...
- STC8H开发(六): SPI驱动ADXL345三轴加速度检测模块
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- 【解决了一个小问题】golang build中因为缓存文件损坏导致的编译错误
编译的过程中出现了一个吓人的错误: GOROOT=C:\Go #gosetup GOPATH=C:\Users\ahfuzhang\go #gosetup C:\Go\bin\go.exe mod t ...
- 【转载】select case break引发的血案
原文请看:select case break引发的血案 我也遇到了,浪费了一个多小时. 牢记: for { switch var1{ case "not match": go En ...
- jmeter - 阶梯式性能指标监听
概述 我们在进行阶梯式压力测试的时候,聚合报告生成的结果是一个汇总数据.并不会阶梯式的统计压测性能数据.这样我们就不能去对比不同阶梯压力下的性能数据变化趋势. 期望 假设现在一共会加载100个线程,我 ...
- 带你学习BFS最小步数模型
最小步数模型 一.简介 最小步数模型和最短路模型的区别? 最短路模型:某一个点到另一个点的最短距离(坐标与坐标之间) 最小步数模型:不再是点(坐标),而是状态到另一个状态的转变 BFS难点所在(最短路 ...
- SourceGenerator的应用: .Net多进程开发库 - Juxtapose
背景 进程间通讯属于老生常谈的话题,可能已经有很多的通信示例代码,但在实际使用中需要做的东西还比较多.例如协议定制.消息收发.进程管理等都需要实现,进阶需求可能还需要实现回调函数.取消等. 个人在工作 ...
- python21day
内容回顾 re模块的常用方法 findall(正则,待匹配字符串,flag):返回所有匹配项的列表 search:返回一个变量,通过group取到第一个匹配项 match:从头开始找第一个,其他同se ...