写点什么

DDD 实战分享 - 消息中心

用户头像
麦麦
关注
发布于: 4 小时前
DDD实战分享-消息中心

DDD 解决什么问题/为什么要用 DDD


可以把答案写到评论区
复制代码

DDD 整体流程(来源 ThoughtWorks)

v1



v2




最大的区别在于第一步的事件风暴与换成了战略设计的识别核心域。个人觉得对于新业务不够熟悉的情况下不适合提前讨论核心域,因为没有掌握足够信息全靠猜测,前期过多讨论浪费时间。

个人理解的 DDD 流程

  1. 前期价值、痛点、需求

  2. 事件风暴识别事件命令领域名词

  3. 业务建模/战略设计(开发与业务的建模)划分子域/核心域限界上下文建立业务模型

  4. 模型设计/战术设计(开发之间的建模)模型设计 api 设计分层架构数据库设计代码编写发布

上一层的输出是下一层的输入。

DDD 战术设计相关概念



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


GRPC


GRPC-Gateway


设计之初不太建议限界上下文名称和聚合名称同名;接口路径按照:“限界上下文/聚合/行为”定义。

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 可以学习那些思维模型

分层思维

“计算机领域的任何问题都可以通过增加一个间接的中间层来解决”


归类思维

对于众多问题,如何减少解决问题数量的数量,可以归类处理。如:

  • 如何赚一个亿

  • 如何保持身体健康

  • 如何提高表达能力

  • 如何实现财富自由

  • 如何找到女朋友

  • 如何拥有幸福的家庭

  • 如何高效工作

  • 如何高效沟通

  • 如何带小孩

  • 怎么提高自己的写作技巧

  • 什么样的运动最健康

  • 跑步怎么跑

问题域归类


也可以更加抽象,抽象层次越高,视野越宽广,需要考虑的问题越多。



边界思维

举例:公司组织架构分部门/小组


发布于: 4 小时前阅读数: 8
用户头像

麦麦

关注

还未添加个人签名 2021.03.06 加入

还未添加个人简介

评论

发布
暂无评论
DDD实战分享-消息中心