您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

深入浅出DDD编程

时间:2022-11-24 10:25:53  来源:今日头条  作者:闪念基因

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

导读

introduction

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

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

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

GEEK TALK

01

什么是DDD

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

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

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

GEEK TALK

02

使用DDD的优势是什么

2.1 语言统一,消除误解

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

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

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

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

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

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

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

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

GEEK TALK

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远程调用其他微服务、消息中间件等服务代码。

GEEK TALK

04

总结

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

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

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

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

参考资料:

[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

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

出处:https://mp.weixin.qq.com/s/oXf84xykPVqtjToWrldM9Q



Tags:DDD编程   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
深入浅出DDD编程
最近几年,微服务拆分大行其道,在业务越来越复杂的情况下,许多业务纷纷抛弃了传统单体架构,拥抱微服务。但随着微服务的拆分结束,大家又发现了新的问题,比如服务间逻辑复杂,运维复杂性变高,微服务架构变得越来越难以管理,最终演...【详细内容】
2022-11-24  Search: DDD编程  点击:(287)  评论:(0)  加入收藏
▌简易百科推荐
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(2)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(7)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条