DDD实战分享-消息中心
DDD解决什么问题/为什么要用DDD
你可以把答案写到评论区
DDD整体流程(来源ThoughtWorks)
v1
v2
最大的区别在于第一步的事件风暴与换成了战略设计的识别核心域。个人觉得对于新业务不够熟悉的情况下不适合提前讨论核心域,因为没有掌握足够信息全靠猜测,前期过多讨论浪费时间。
个人理解的DDD流程
- 前期
- 价值、痛点、需求
- 事件风暴
- 识别事件
- 命令
- 领域名词
- 业务建模/战略设计(开发与业务的建模)
- 划分子域/核心域
- 限界上下文
- 建立业务模型
- 模型设计/战术设计(开发之间的建模)
- 模型设计
- api设计
- 分层架构
- 数据库设计
- 代码编写
- 发布
上一层的输出是下一层的输入。
DDD战术设计相关概念
概念 | 说明 |
---|---|
限界上下文 | 一个微服务存在多个限界上下文,简单可以理解成模块、包、命名空间。比如上下文A存在category聚合,上下文B也可能存在category聚合。都叫category但含义不同 |
聚合 | 也是一种边界,将实体和值对象划分为聚合并围绕着聚合定义边界 |
聚合根 | 聚合里面的主实体,可由多个实体和值对象构成,一个聚合只有一个聚合根 |
实体 | 聚合根是实体,实体不一定是聚合根,实体是多张表模型的抽象(可等价po也可由多个po组成) |
值对象 | 没有ID(唯一标志)的对象 |
领域对象 | 包括:聚合、聚合根、实体、值对象、领域事件。在领域层domain里的对象都叫领域对象 |
DO | Domain Object,同上,领域对象。所处位置:领域层 |
DTO | Data Transfer Object,数据传输对象。存在意义:上下游服务的流通对象。所处位置:接口层 |
PB | Proto Buffer,存在意义:GRPC上下游服务的流通对象,在我们这里等价DTO。所处位置:接口层 |
PO | Persistence Object,持久化对象。存在意义:等价数据表模型。所处位置:基础设施层 |
VO | View Object,视图对象。存在意义:领域对象的一种,主要用于查询。所处位置:领域层 |
Value Object | 值对象,所处位置:领域层 |
BO | Business Object,业务对象。存在意义:给两个及两个以上的领域对象进行组装的容器。所处位置:应用层 |
DDD四层结构
层次 | 职责 |
---|---|
接口层/展示层 | 负责向用户展现信息以及解释用户命令 |
应用层 | 很薄的一层,用来编排一或多个领域服务,返回领域对象或业务对象给接口层 |
领域层 | 最核心的一层,负责业务逻辑编写,对于数据库持久化之类的操作委托给基础设施层实现 |
基础设施层 | 业务对象持久化实现;调用下游服务 |
DDD 各层流通对象(消息中心现状)
DDD代码目录
- server 接口层
- application 应用层
- domain 领域层
- infra 基础设施层
一个微服务存在多个限界上下文,domain层的下层目录是指限界上下文的划分。其他infra/persistent、server、application层的下层目录是按模块分包。按模块+限界上下的设计是为了解决每个上下文四层目录设计的落地成本的折中方案。
按模块分包的好处是解决了领域名词冲突。比如上下文A存在category聚合和上下文B可能存在category聚合的同名冲突。
粒度划分:
领域>子域>限界上下文>聚合>聚合根/实体/值对象
Go GRPC DDD框架
Go GRPC DDD框架详细目录说明
|-- application //应用层
| |-- directories //模块(技术视角),这里指开发商名录模块
| | |-- assembler //应用层对象转换器
| | | |-- category.go
| | | `-- developer_dir.go
| | `-- developer_dir_app.go //开发商名录app
| |-- example //模块(技术视角),example模块
| | `-- example_app.go
| |-- module3 //模块(技术视角),示例模块3
| | `-- xxxx_app.go
| |-- module4 //模块(技术视角),示例模块4
| | `-- xxxx_app.go
| `-- module5 //模块(技术视角),示例模块5
| `-- xxxx_app.go
|-- build.yaml
|-- domain //领域层
| |-- directories //限界上下文(领域视角),这里指开发商名录限界上下文
| | |-- aggregate3 //聚合,示例aggregate3
| | |-- category //聚合,分类
| | | |-- category_repo.go //分类聚合仓储接口
| | | |-- category_service.go //分类聚合服务
| | | |-- entity //实体
| | | |-- event //事件
| | | `-- vo //表单对象
| | `-- developer_dir //聚合,开发商名录聚合
| | |-- developer_dir_repo.go
| | |-- developer_dir_service.go
| | |-- entity
| | |-- event
| | `-- vo
| |-- example //限界上下文(领域视角),示例上下文
| | |-- aggregate1
| | |-- aggregate2
| | `-- example
| | |-- entity
| | |-- event
| | |-- example_repo.go
| | |-- example_service.go
| | `-- vo
| |-- ctx1 //限界上下文(领域视角),示例上下文1
| | `-- aggregate1
| | |-- aggregate1_repo.go
| | |-- aggregate1_service.go
| | |-- entity
| | |-- event
| | `-- vo
| |-- ctx2
| | |-- aggregate1
| | `-- aggregate2
|-- go.mod
|-- go.sum
|-- golangci.yml
|-- infra //基础设施层
| |-- common
| | |-- authorize.go
| | |-- jwt.go
| | |-- page_info.go
| | |-- queue
| | | |-- consumer.go
| | | |-- etcd.go
| | | |-- message.go
| | | |-- mns_client.go
| | | |-- queue_producer.go
| | | |-- server.go
| | | |-- setup.go
| | | `-- topic_producer.go
| | |-- rpc_client
| | | `-- rpc_client.go
| | `-- tree.go
| |-- di
| | |-- developer_dir.go
| | `-- example.go
| |-- persistent //持久化层
| | |-- directories //模块
| | | |-- assembler //对象转换器
| | | `-- repsitory //仓储实现
| | |-- example
| | | `-- repsitory
| | |-- module3
| | | `-- repsitory
| | `-- module4
| | `-- repsitory
| |-- pkg
| | |-- businesscode
| | | `-- businesscode.go
| | |-- constant
| | | `-- common.go
| | |-- errcode
| | | |-- common_errcode.go
| | | `-- custom_errcode.go
| | `-- utils
| | |-- common.go
| | `-- db_insert_build.go
| |-- po //持久化对象,数据表模型
| | |-- oa_developer_dir.go
| | |-- oa_developer_dir_category.go
| | |-- oa_developer_dir_suppliers.go
| | `-- oa_developer_dir_user_down.go
| |-- remote //远程调用
| | `-- micro_basic_service
| | `-- id_generation_service.go
| `-- startup
| |-- config.go
| |-- mq_register.go
| |-- register.go
| `-- vars.go
|-- main.go
|-- micro-msgcenter-mng-service
|-- proto //pb协议
| |-- micro_msgcenter_mng_service_proto
| | `-- micro-msgcenter-mng-service
| | |-- directories //模块
| | |-- example //模块
| | `-- module3
| |-- swagger_json.go
|
|-- server //接口层
| |-- directories //模块
| | `-- developer_dir.go
| |-- example
| | `-- example.go
| |-- module3
| `-- module4
|-- xxx.yaml
`-- vendor
PHP BFF目录结构
proto目录
BFF按限界上下文划分模块、proto也按限界上下文分包
案例讲解
根据战略设计划分出来的消息中心业务模型
下文对产品上下文的实现做分析。
战术设计-产品上下文的模型设计
api设计
BFF
接口名称 | 模块(限界上下文) | 控制器(聚合) | 行为(事件) | url |
---|---|---|---|---|
获取产品应用 | product | product | get-applications | micro-msgcenter-mng-api.cc/product/... |
获取应用场景 | product | app | get-scenes | micro-msgcenter-mng-api.cc/product/... |
新增&修改应用场景 | product | app | set-scenes | micro-msgcenter-mng-api.cc/product/... |
GRPC
grpc接口名称 | 包名(微服务名下划线.限界上下文) | rpc服务名(聚合Service) | rpc方法(事件/行为) | 服务路径 |
---|---|---|---|---|
获取产品应用 | micro_msgcenter_mng_service.product | ProductService | GetApplications | micro_msgcenter_mng_service.product.ProductService/GetApplications |
获取应用场景 | micro_msgcenter_mng_service.app | AppService | GetScenes | micro_msgcenter_mng_service.app.AppService/GetScenes |
新增&修改应用场景 | micro_msgcenter_mng_service.app | AppService | SetScenes | micro_msgcenter_mng_service.app.AppService/SetScenes |
GRPC-Gateway
接口名称 | 模块(限界上下文) | 聚合 | 行为(事件) | url |
---|---|---|---|---|
获取产品应用 | product | product | get-applications | micro-msgcenter-mng-service/product/product/get-applications |
获取应用场景 | product | app | get-scenes | micro-msgcenter-mng-service/product/app/get-scenes |
新增&修改应用场景 | product | app | set-scenes | micro-msgcenter-mng-service/product/app/set-scenes |
设计之初不太建议限界上下文名称和聚合名称同名;
接口路径按照:“限界上下文/聚合/行为”定义。
BFF 获取产品应用 APIDOC定义
/**
* @api {get} /product/product/get-applications 获取产品应用
* @apiDescription 获取产品应用
* @apiSampleRequest http://micro-msgcenter-mng-api.cc/product/product/get-applications
* @apiVersion 2.0.0
* @apiName get-applications
* @apiGroup /product/product
* @apiSuccess {Boolean} success 返回状态
* @apiSuccess {String} message 返回消息内容
* @apiSuccess {Object} data 结果集
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
"success": true,
"message": "",
"error_code": "",
"data": [
{
"product_code": "product1",
"product_name": "产品1",
"app": [
{
"app_id": "1",
"app_name": "app1",
},
{
"app_id": "2",
"app_name": "app2",
}
]
}
]
}
*/
可以看到,这个接口需要产品聚合和应用聚合组合出来的数据。
由于组合出来的对象无法归属到产品实体和应用实体的任何一个,是两个领域对象的组合体,需要有一个对象来装载,这个对象就是BO(业务对象)。
类比微服务,微服务只负责本微服务内的领域,聚合只负责本聚合的实体。
DDD的核心就是边界划分清晰。
下图是应用层的服务编排
应用和场景的聚合设计
使用了golang继承的方式实现实体定义,减少结构体繁琐的定义。
与传统表模型不同的是,DDD实体是表模型的抽象,一个实体可能由多个表模型组合而成,当然这多个表模型要归属一个聚合下的。本例中,应用和场景组成了一个实体,应用是聚合根(主实体),场景是实体,聚合根是与外部对象沟通的代表,非主实体的业务操作需要通过聚合根来完成,如想要修改或查看应用场景,要经过应用聚合来操作:
获取场景:AppService.GetScenes(ctx, appId)
// app = *entity.App
设置场景:AppService.SetScenes(ctx, app)
而不是弄个场景服务来操作
获取场景:ScenesService.GetScenes(ctx, sceneId)
// scene= *entity.Scene
设置场景:ScenesService.SetScenes(ctx, scene)
聚合理念参考文章:深入理解DDD的聚合模式
DDD 各层流通对象(推荐版)
问题
事务
其他
从DDD可以学习那些思维模型
分层思维
“计算机领域的任何问题都可以通过增加一个间接的中间层来解决”
![在这里插入图片描述]
归类思维
对于众多问题,如何减少解决问题数量的数量,可以归类处理。
如:
- 如何赚一个亿
- 如何保持身体健康
- 如何提高表达能力
- 如何实现财富自由
- 如何找到女朋友
- 如何拥有幸福的家庭
- 如何高效工作
- 如何高效沟通
- 如何带小孩
- 怎么提高自己的写作技巧
- 什么样的运动最健康
- 跑步怎么跑
问题域归类
也可以更加抽象,抽象层次越高,视野越宽广,需要考虑的问题越多。
边界思维
举例:公司组织架构分部门/小组
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: