深入浅出DDD编程

news2024/12/23 6:41:57

在这里插入图片描述

作者 | 刘嘿嘿、离夏、立羽

导读

最近几年,微服务拆分大行其道,在业务越来越复杂的情况下,许多业务纷纷抛弃了传统单体架构,拥抱微服务。但随着微服务的拆分结束,大家又发现了新的问题,比如服务间逻辑复杂,运维复杂性变高,微服务架构变得越来越难以管理,最终演化成大泥球架构。

而本文主要介绍如何通过DDD对微服务进行拆分,首先介绍了什么是DDD,通过从分析DDD的优势,到如何通过DDD进行业务拆分,并且在最后通过代码样例的方式,深入浅出的为读者介绍了DDD代码的核心实现。帮助大家进一步的了解DDD应该如何落地。

全文6271字,预计阅读时间16分钟。

01 什么是DDD

DDD(领域驱动设计),起源于2004年Eric Evans出版《领域驱动设计》,近些年由于微服务的兴起,大家逐渐对单体服务进行拆分。

但是随着微服务拆分,由于业务逻辑拆分不合理导致调用环路问题、重试风暴问题等等,都给系统造成了更多的风险,并且随着业务更加复杂微服务职责划分出现问题,则业务迭代效率变得越来越差,最终变成一个大泥球系统。

而DDD的优势便是指导业务进行微服务拆分,下面我们以会员中心为例来具体讲解一下如何进行业务拆分以及相关的代码实现。

02 使用DDD的优势是什么

2.1 语言统一,消除误解

很多时候未必产品经理才是最懂业务的那个人,例如某些B端服务很多时候是运营人员在向产品同学提需求,在经过产品经理的翻译后,才转化成一个需求文档,这样就会导致有时候产品经理并不能完全表达出实际的需求,这就会导致开发人员交付的软件无法达到预期。从而导致返工,浪费人力。

而DDD需要设计一种通用的语言,拉齐各个需求方的理解,一旦产品同学和技术同学对业务具备了相同的理解,统一的语言,那在后续的需求迭代种就会变得非常顺畅。

在改造初期我们耗费了非常大的精力向产品同学讲清楚哪些抽象应该定义为实体,实体与实体的关系是什么,在不断的沟通、磨合中,最好我们成功建立起了一些通用的语言,拉齐了产品经理、运营同学、开发人员的理解,最大幅度的消除了由于理解不一致导致的返工、重构等工作。

2.2 更专注于业务的战略设计

战略设计侧重于业务梳理,结合业务流程划分对应的核心域、通用域、支撑域。战略设计的核心价值是围绕产品规划重点投入资源,确保重点子业务可以确保得到足够的人力支持。

2.3 设计即代码,代码即设计

在过去的项目详细设计中,我们的重心在数据怎么存储?数据流通是什么样的。这样可能导致在设计文档和代码中就具备较大的Gap,实现上就可能有问题。

而DDD倡导的是思考,而不是写代码。在代码设计之前定义好领域语言,和领域专家沟通无碍,定义好领域规则,这样在写代码的时候留下较少的思考。代码只是把设计文档翻译成代码,写代码更像是在照着设计文档在做填空题,只需要将代码填到指定的文件中即可。

03 如何使用DDD

3.1 DDD战略设计

3.1.1 划分核心域,通用域、支撑域

在实际的工作中,很多产品经理会陷入到各种繁杂的业务指标中,无法从繁杂的业务中抽身,定义好哪些是重要的模块,或者无法表达出业务各个模块中最重要的是什么。这种情况就会导致每个,产品没有这就会导致在人员分工、资源申请上出现一些问题。

做战略设计,最核心的事情就是划分清楚核心域,通用域、支撑域,我们把更多的精力投入到核心的问题中,而不被大量次要的问题淹没。

  • 核心域:业务最核心的部分,这部分需要产品同学确定,例如,从长线来看我们主要核心做的投入,是做流量引入,还是做变现

  • 支撑域:业务中非核心的部分,若产品确定现有核心域是流量引入,那在流量变现部分业务,就是支撑域

  • 通用域:例如登录验证、验证码、支付能力等则更多的使用公司内部的中台能力,若公司没有通用的中台能力,我们也会以建设中台的思路自建一个内部的中台服务

本处仅仅描述我们对于战略设计理解,不对战略设计展开说明。

3.1.2 划分边界

微服务职责的划分是执行环节的第一步,也是最重要的一步,尤其从大单体拆分为多个微服务时,需要考虑以下几点:

  1. 要通过领域驱动划分边界,若暂时考虑不清楚边界,那就先不要拆分;

  2. 明确微服务分层,上游服务只能对下游服务产生依赖,防止微服务环路调用问题,同时下游服务需要考虑重试风暴问题;

  3. 核心域的微服务需要具备故障降级,容灾能力;

  4. 要基于组织架构进行边界的划分,微服务的梳理其实也是团队的梳理,过度的拆分可能导致更多的沟通成本。

3.2 DDD战术设计

3.2.1 名词解释

聚合与聚合根:是一组相关对象的组合,可以作为拆分微服务的最小单位,具有高内聚、低耦合的特点,聚合在DDD中是一个很重要的概念,核心领域往往都需要用聚合来表达;聚合根为其根节点,聚合根有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。

领域服务:一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。

领域事件:领域事件是对领域内发生的活动进行的建模。

实体:多个属性、行为及操作的载体,实体有全局唯一性标识(ID),有独立的生命周期。例如会员用户中,每个会员都可以被认为一个实体,都有userid唯一性标识。

值对象:通过对象属性来识别的对象,没有标识符概念,无生命周期,只描述业务属性。如在一个会员系统中,会员权益信息集合即可看为一个值对象,只用于对权益属性的描述,只有数据初始化操作和有限的不涉及修改数据的行为。

3.2.2 如何进行战术设计

接下来我们以会员中心为例为大家详细介绍。

在战略模型中我们已经划分清楚边界,梳理不同领域及相关关系。接下来我们需要从战术层面上剖析领域模型内部之间的关系,对会员上下文进行建模(下文为简化版)。

图片

在会员上下文中,我们以会员实体为中心,通过会员(vipinfo)这个聚合根来控制会员权限,一个会员包括用户ID(uid)、会员权益(Privilege)、所属机构(tp)以及会员码(vip_code),而会员码针对订单维度分别对应不同的权益内容(privilege)。

这些值对象不具有业务行为特征,只关心本身属性值。会员实体具有业务行为及业务逻辑,例如会员入驻、会员变更、会员绑码等,外部访问会员权益值对象等都需要通过会员实体来进行。

在会员域中,我们同时支持会员码及会员维度的领域服务,包括购买、获取会员信息、绑码等服务。

3.3 DDD代码实现

3.3.1 项目介绍

  • 会员微服务主要实现获得会员信息、会员码信息、绑会员码、会员码退款等操作。

  • 服务使用ddd四层架构,分为接口层、应用层、领域层和基础层。

  • 本服务因为业务复杂性较低,为减少冗余代码,使用松散分层。(架构根据耦合的紧密程度又可以分为两种:严格分层架构和松散分层架构。严格分层:任何层只能依赖与他相邻的下层。松散分层:任何层可以依赖任意他的下层。)

3.3.2 项目结构

项目结构如图所示分为四层、对应到到代码目录上(附录1),代码一级目录有interface(接口层)、application(应用层)、domain(领域层)、infrastructure(基础层)四个目录。

图片

接口层:

接口层处理接口定义、批处理相关逻辑。目录如下:

|-- interface
|   |-- command // 批处理接口层
|   |   |-- controller 
|   |   |   `-- vip
|   |   |       |-- add.go 
|   |   |       `-- update.go
|   |   |-- router.go // 代码入口定义
|   |   `-- script.go
|   `-- http // api接口层
|       |-- controller // 接口入参校验、定义,调用下层代码
|       |   |-- lawyer
|       |   |   |-- add.go
|       |   |   `-- update.go
|       |   `-- vipcode
|       |       |-- add.go
|       |       `-- update.go
|       |-- router.go // api路由

interface目录下有command、http两个目录,其中,

command:包含批处理入口,批处理路由,编排批处理相关领域层服务、事件、实体和基础层相关函数。批处理代码无需应用层直接依赖领域层、基础层,降低代码冗余度。

http:包含接口路由、定义,接口入参校验、定义。

应用层:

主要负责组织、编排领域层服务、事件、实体和基础层相关函数。

application下有service、viewmodel。

|-- application
|   |-- service //应用层服务
|   |   |-- lawyer
|   |   |   |-- add.go
|   |   |   `-- update.go
|   |   `-- vip
|   |       |-- add.go
|   |       `-- update.go
|   `-- viewmodel // 视图
|       |-- lawyer
|       |   |-- transform.go // 转化函数
|       |   `-- vm.go //视图数据结构
|       `-- vip
|           |-- transform.go
|           `-- vm.go

service:对多个领域服务、基础层ral调用、数据持久化服务进行封装、编排,为上层提供更粗粒度的服务,调用领域层服务,仓储和事件,因为松散分层结构,也可以调用基础层服务。

viewmodel:为上层多变的数据结构要求,提供相应视图定义和实体到视图的转化方法。

领域层:

领域层存放业务核心逻辑包括聚合根、实体、值对象、仓储接口、领域服务、领域事件接口等。

领域层下分有五个目录:

|-- domain
|   |-- aggregate // 聚合
|   |   |-- lawyer
|   |   |   |-- entity.go // 实体定义
|   |   |   `-- vo.go // 值对象
|   |   `-- vipcode
|   |       |-- entity.go
|   |       |-- vo.go
|   |-- event // 领域事件
|   |   `-- vipcode
|   |       `-- order.go
|   |-- repository // 仓储接口
|   |   |-- lawyer.go
|   |   `-- vipcode.go
|   |-- adaptor // 防腐层
|   |   `-- sms.go
|   `-- service // 领域服务
|       |-- lawyer
|       |   `-- vipcode.go
|       `-- vipcode
|           `-- vipcode.go

aggregate:放置聚合根,实体、值对象数据结构定义,以及相关初始化代码。

领域内数据流转处理依赖,相关聚合根,下游服务发生改变——如数据表结构变换,只需将相关数据转化为业务定义聚合根,代码更改只需在基础层,不涉及上层。

下面是会员码实体示例,里面又包含有订单值对象,会员码机构值对象和会员码权益值对象。

// EntityVipCode 会员码实体(简化版本)
type EntityVipCode struct {
  ValidityStart *time.Time       // 绑码开始时间
  ValidityEnd   *time.Time       // 绑定会员码结束时间
  OrderInfo     *VOOrderInfo     // 订单信息值对象
  BuyerInfo     *VOCodeBuyerInfo // 买会员码机构信息
  PrivilegeInfo *VOPrivilege     // 会员码包含的权益
}

event:放置基础层事件抽象的接口——为了实现依赖倒置。

repository:放置基础层数据持久化服务抽象的接口。

service:存放一下领域服务代码,向应用层服务提供方法调用,依赖倒置在ddd中使用频繁 。

adaptor:存放防腐层数据结构定义、转化函数。

防腐层在下游服务和上游服务之间,将下游服务翻译为上游服务语言,抛去无需关注的,防止上层服务掺杂过多无需关注杂质。

ddd中广泛应用了依赖倒置原则(即调用要依赖于抽象接口,不要依赖于具体实现),减少ddd各层之间的耦合性,提高系统的稳定性,减少并行开发风险,提高代码的可读性和可维护性,非常适合ddd这样为应对频繁迭代的设计思想。

如下创建订单体现依赖倒置思想,无需关注具体实现,假若使用订单方发生了变更(如更换服务提供方、服务提供方更换实现逻辑或者服务实现逻辑更改了),我们在上层代码只需要更改传入的参数,无需关注其他变更。

// ReqCreateOrder 创建订单
func ReqCreateOrder(ctx context.Context, vipRepo repository.IVipCodeRepo, vipcodeentity vipcode.EntityVipCode) (*order.PreorderRetData, error)

type IVipCodeRepo interface {
  CreateOrder(ctx context.Context, ev vipcode.EntityVipCode) (*liborder.PreorderRetData, error)
  UpdateVipCode(ctx context.Context, patch map[string]interface{}, conditions map[string]interface{}) (int64, error)
}
基础设施层:

基础层存放领域事件、数据持久化、ral调用相关代码。

其下有三个目录:

|-- infrastructure
|   |-- event // 领域事件实现
|   |   |-- init.go
|   |   `-- vipcode
|   |       `-- consume_order.go
|   |-- persistence // 持久化存储实现
|   |   |-- init.go
|   |   |-- lawyer
|   |   |   |-- po.go
|   |   |   |-- repo.go
|   |   |   `-- transform.go
|   |   `-- vipcode
|   |       |-- po.go
|   |       |-- repo.go
|   |       `-- transform.go
|   `-- rpc // rpc调用实现
|       |-- db
|       |-- init.go
|       |-- redis

event:领域事件具体实现,依赖rpc服务。

persistence:放置储存持久化代码,数据库存储对应PO,和PO到实体的转化方法,所有具体实现方法都出参都需要转化成实体供给上层使用。

rpc:rpc远程调用其他微服务、消息中间件等服务代码。

04 总结

用好DDD的关键,就是理解DDD和核心思想,其本质也是面向对象的设计方法,即是把业务模型转换为对象模型从而来控制业务持续变化而导致系统的复杂性,使得系统更加具有可扩展性、可维护性。

在相对比较小、逻辑简单的微服务,在代码实现层面,我们并没有按照DDD进行开发,传统的MVC足以应对,若强行使用DDD则会徒增大家的工作量。

DDD的核心是通过战略设计来匹配产品层面的业务规划,在战术设计层面通过对每个模块进行抽象、建模来完成业务梳理划分边界,在代码实现层面来完成设计文档到代码的映射,做到设计即代码、代码即设计。

而DDD只适用于大型的、复杂的业务场景。切勿为了DDD而DDD。

————END————

参考资料

[1] 《领域驱动设计》

[2]《实现领域驱动设计》

[3]https://mp.weixin.qq.com/s/y57l-PhzibAjjL3EzPqSow

[4]https://mp.weixin.qq.com/s/_ggIPOvB-ptBanbqqKULxQ

[5]https://mp.weixin.qq.com/s/jU0awhez7QzN_nKrm4BNwg

推荐阅读:

百度APP iOS端内存优化实践-内存管控方案

Ernie-SimCSE对比学习在内容反作弊上应用

质量评估模型助力风险决策水平提升

合约广告平台架构演进实践

AI技术在基于风险测试模式转型中的应用

Go语言躲坑经验总结

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕业设计之java+ssm峰值预警停车场管理系统

项目介绍 随着城市建设与经济的不断发展,城市车辆的数量也不断增涨,为解决停车问题修建停车场。基于经营、安全、管理等多角度的考虑,希望在目前传统的大型车库管理系统中有机地结合车牌识别技术,以求得日后在停车库运营时更安全、管理上更细致、经营中…

SuperMap 云原生常见问题解决办法-keycloak启动异常

有些客户在使用iManager for K8S 云套件的产品的时候,偶尔会遇到机器异常重启的情况,比如说服务器断电,重启后可能会出现云套件启动异常的情况,比如说keycloak启动不了,一直抛出异常导致服务无法正常使用。本篇文章就结…

MongoDB故障转移案例详细操作

MOngoDB故障转移 文章目录MOngoDB故障转移1.数据库提权操作1.1.使用命令查询主库信息1.2.给db02升级权重1.3.修改完成后进行加载配置1.4.主库执行降级操作2.恢复主库权限2.1.先给库权重降低2.2.执行降级命令2.3.恢复成功MongoDB的主从目前是我们在配置副本集的时候设置但是如果…

强化深度学习中使用Dyna-Q算法确定机器人问题中不同规划的学习和策略实战(超详细 附源码)

需要源码请点赞关注收藏后评论区留下QQ并且私信~~~ 一、模型、学习、规划简介 1:模型 Agent可以通过模型来预测环境并做出反应,这里所说的模型通常指模拟模型,即在给定一个状态和动作时,通过模型可以对下一状态和奖赏做出预测 …

学生个人网页设计作品 学生个人网页模板 简单个人主页成品 个人网页制作 HTML学生个人网站作业设计 汉语言文学设计题材网页

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

在Postman中使用 FineBI提供的接口获取数据

FineBI 通过各种样式如表格、图表等来呈现数据,进行统计分析。 FineBI 是 B/S 架构的纯 Java 软件。 这些数据表格或图表,用户在开发系统的时候也可以自己编程来实现,FineBI也提供了相应的接口。 在Postman中使用 FineBI提供的接口获取数据…

数据可视化软件使用

一 前言 数据可视化平台是通过三维表示技术来表达复杂的信息,实现海量数据的立体体现。可视化技术借鉴人脑的视觉显示能力,通过挖掘重要数据之间的关系,揭示数据中隐藏的关联和发展趋势,从而提高数据的使用效率。可视化平台使人们…

在这个艰难的环境下,我裸辞了

2022年,疫情期间,工作了22年的我,从软件研发管理的相关工作中,辞职创业,开启我的独立咨询顾问生涯。很多人不解和迷惑,也有朋友关切的询问我的近况,就差用手来摸我的额头以判断我是否发烧了。因…

[附源码]SSM计算机毕业设计江苏人才信息管理系统JAVA

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

.net----委托和事件

委托和事件委托声明实例化调用将类型安全的函数指针(方法)作为其他方法的参数进行传递,从而实现函数回调方法委托:匿名方法委托多播委托委托:委托的异步调用委托:委托的兼容性事件事件实际上是委托的一种特殊形式,C#使…

软考-系统架构师-计算机与网络基础知识-数据库系统基础知识

文章目录1.关系数据库基础1.1关系型数据库基础1.1.1数据库的结构与模式1.1.2实体联系E-R模型1.1.3数据的规范化1.1.4事务管理1.1.5并发控制1.1.6数据库的备份和恢复2.关系数据库设计2.1数据库设计的特点2.2数据库的设计方法2.3数据库设计的基本步骤3.分布式数据库系统3.1分布式…

simulink中比scope模块还好用的平替出图工具?

今天在捣拾scope模块比较几个数据大小,拉坐标线非常的不方便,而且对于调参时几组数据的比较非常繁琐,这里介绍以下simulink中自带的数据检查器(Data Inspector),个人认为比scope模块方便查看出图结果,有帮助的童鞋们赶…

算法设计与分析 SCAU11090 最大m段乘积和最小m段和(优先做)

11090 最大m段乘积和最小m段和(优先做) 时间限制:1000MS 代码长度限制:10KB 提交次数:0 通过次数:0 题型: 编程题 语言: G;GCC;VC;JAVA Description 一个n位十进制整数S,若将S划分为m个段,则可以得到m个整数。 (1)这m个整数的…

有限元在游乐设施中的应用-焊缝计算

作者 | 九峰知己千杯少 一、前言 游乐设施金属结构所采用的连接方式有焊接连接、铆钉连接、普通螺栓连接和高强螺栓连接4种,将两块分离的金属其接头部分局部加热到熔化或半熔化状态,采取施加压力或不加压,或填充其他金属,利用原…

C#上位机系列(4)—示波器一新窗口的建立

本文是讲解C#.net平台的Winform框架下的第四个内容,手把手介绍上位机项目的创建方式以及一些写软件时常用的功能,讲解从零开始的每一个步骤。 本次介绍上位机中新窗口的建立方式和软件示波器的代码原理。 从此节开始,所有代码附后 1.新窗口…

element-plus中menu的基本知识点

在vue后台管理系统中,menu是经常会用到的必不可少的导航组件,这个组件如果是单纯的去使用,很简单。但是在实际开发过程中,与其有关的路由相结合使用,还是容易搞混一些东邪,所以想在这里记录一下。 从产品的…

SuperMap 云原生常见问题解决办法-consul启动异常

在iManager for K8S产品中,如果创建了云套件站点,会有三个consul的服务,consul在云套件中充当的角色是服务发现,服务注册,以及配置共享。如果consul服务失效,云套件的整体服务将不能正常运行。客户在使用云…

用DIV+CSS技术设计的环保主题网站(web前端网页制作课作业)

🎀 精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

教你如何在优麒麟上搭建 RISC-V 交叉编译环境

由于 RISC-V 设备价格昂贵、不易采购等诸多原因,许多小伙伴虽然很感兴趣,但仍无法参与 RISC-V 开发工作,今天就教大家如何在优麒麟上搭建 RISC-V 交叉编译环境,快学起来吧! 交叉编译(Cross Compile&#x…

收藏 | 机器学习公共数据集集锦(附下载链接)

>>>深度学习Tricks&#xff0c;第一时间送达<<< &#x1f680;&#x1f680;&#x1f680;近期&#xff0c;小海带在空闲之余&#xff0c;收集整理了一批机器学习公共数据集供大家参考。 整理不易&#xff0c;小伙伴们记得一键三连喔&#xff01;&#xff0…