news 2026/7/13 7:52:49

DDD 系列:通用语言和上下文映射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DDD 系列:通用语言和上下文映射

上一篇文章中,我们学习了领域、子域和限界上下文,知道了 DDD 的战略设计首先要解决一个问题:哪些业务应该放在一起,哪些业务应该分开。

不过,边界划分出来只是第一步。

真实项目中,即使大家都在讨论“订单”,产品、开发、测试和财务理解的也可能不是同一个东西。如果这些词没有统一,模型、接口和代码就容易各说各话;边界之间没有明确关系,服务调用也会慢慢变成互相依赖。

所以这一篇我们继续学习战略设计里面的两个概念:

  • 通用语言(Ubiquitous Language):解决同一个上下文内部如何统一表达;
  • 上下文映射(Context Map):解决不同限界上下文之间如何协作。

一、为什么业务和代码总是对不上

产品说:“用户提交订单后,可以取消订单。”

这句话听起来没有问题,但开发真正开始写代码时,一堆问题就出来了:

  • “提交订单”是保存草稿、创建成功,还是已经支付;
  • 用户取消和客服关闭是不是同一个动作;
  • 取消后需不需要释放库存、退回优惠券;
  • 已经支付的订单到底叫取消还是退款。

如果这些问题没有说清楚,代码里面很可能出现下面这种方法:

updateOrderStatus(orderId,4);

4是什么意思,为什么能改成4,修改之后还要做什么,只看这行代码完全不知道。

另一个开发可能又写了一个:

closeOrder(orderId);

这时候“取消订单”和“关闭订单”到底是不是一回事,也没人能说清楚。

问题的根源不是方法名没起好,而是团队没有先统一业务语言。

二、什么是通用语言

通用语言英文叫 Ubiquitous Language,也有人翻译成统一语言。

通用语言是一套由业务人员和开发人员共同建立,并且在同一个限界上下文中持续使用的业务语言。

也就是说,团队在讨论业务、写需求、画模型、定义接口和编写代码时,应使用同一套含义明确的词。

1. 不是开发自己定义一套术语

通用语言不是开发人员关起门来给类和字段起名字。

它应该来自业务讨论,由产品、业务、开发和测试共同确认。

比如业务上明确区分:

  • 用户主动放弃未支付订单,叫作取消订单
  • 超时未支付由系统终止,叫作关闭订单
  • 已支付订单退回资金,叫作退款

那么代码里面也应该保留这种区别:

order.cancel();order.close();order.refund();

而不是全部写成:

order.updateStatus(status);

前一种写法能直接表达业务动作,后一种写法只是修改数据。

2. 通用语言只在特定上下文中成立

上一篇文章中我们提到,限界上下文会给模型划定生效范围。

通用语言同样受这个边界约束。同一个词在不同限界上下文中,可以有不同含义。

还是以电商系统为例:

限界上下文“商品”的含义重点关注的信息
商品上下文可维护和展示的商品资料名称、类目、品牌、详情
交易上下文用户下单时购买的交易项成交价、购买数量、优惠快照
库存上下文可以入库、出库和锁定的库存单元SKU、可用量、锁定量、仓库

它们都可能说“商品”,但是关注的不是同一个模型。

如果为了追求全系统统一,强行让三个上下文共用一个Product类,这个类最后就会同时包含展示、交易、库存等字段,改任何一处都可能影响其他模块。

所以,通用语言追求的是上下文内部一致,不是整个系统只有一套万能模型。

这张图强调的不是三个对象应该怎么设计,而是语言和模型都有明确的生效边界

三、通用语言怎么落到代码里

通用语言不能只停留在会议纪要或者术语表里,它最终要进入代码。

如果开会时说“取消订单”,代码中却叫changeStatus,团队很快还会回到各说各话的状态。

1. 类名要体现业务角色

不要只根据数据库表名机械生成对象。

比如交易上下文中的订单,可以使用Order;支付上下文里记录支付过程的对象,可以使用PaymentOrder。即使它们在数据库里都带有order字样,也不代表它们是同一个业务对象。

2. 方法名要体现业务动作

业务代码里面,优先使用有业务含义的方法:

order.submit();order.cancel();order.confirmPayment();

少用看不出意图的名字:

order.update();order.handle();order.process();

一个好的业务方法名,应该让我们不看方法实现也能大概知道它在做什么。

3. 状态和规则也要使用业务词汇

下面这种状态虽然能运行,但是阅读成本很高:

privateIntegerstatus;

业务允许的情况下,可以把状态表达得更明确:

publicenumOrderStatus{WAITING_FOR_PAYMENT,PAID,CANCELED,CLOSED}

需要注意,这里不是说所有字段都必须改成枚举,也不是说用了枚举就是 DDD。

重点是:代码中的概念和业务讨论中的概念要对应得上。

4. 发现歧义时及时更新语言

通用语言不是项目启动时整理一次,以后就不变了。

随着业务深入,我们可能会发现原来的词不够准确。比如最开始只有“退款”,后来业务增加了仅退款、退货退款、部分退款,这时候模型和代码也要跟着调整。

如果一个词在会议中需要反复解释,或者不同人总是给出不同理解,通常就说明这个词还没有定义清楚。

四、什么是上下文映射

限界上下文划分完成后,各个上下文并不会完全隔离。

下单需要查询商品,下单成功需要锁定库存,支付成功需要通知订单,订单完成以后可能还要增加会员积分。

这些协作关系放在一起,就是上下文映射。

上下文映射是对多个限界上下文之间关系、依赖方向和协作方式的明确描述。

它关注的不是某个类怎么写,而是下面这些问题:

  • 哪个上下文依赖哪个上下文;
  • 谁是上游,谁是下游;
  • 双方是否共享模型;
  • 外部模型进入当前上下文时要不要转换;
  • 上游发生变化时,下游会受到多大影响。

通俗一点讲,限界上下文告诉我们“边界画在哪里”,上下文映射告诉我们“边界之间怎么打交道”。

五、常见的上下文映射关系

上下文映射有不少模式。这里先介绍项目中比较容易遇到的几种,重点理解它们解决什么问题,不需要一上来背所有英文缩写。

1. 客户方—供应方

客户方—供应方也叫 Customer/Supplier。

上游上下文提供能力,下游上下文使用能力。下游对接口有明确诉求,并且可以参与上游接口或发布计划的协商。

比如商品上下文向交易上下文提供商品查询能力。交易上下文是使用方,它可能要求接口稳定返回商品是否可售、当前价格等信息。

这种关系不是简单的“我调你接口”,重点在于:下游需求能够进入上游的协作计划。

2. 防腐层

防腐层也叫 Anticorruption Layer,简称 ACL。

当外部系统的模型和当前上下文差异较大时,我们不直接把外部对象带进领域模型,而是在边界处做一次转换。

例如外部库存系统返回:

{"sku_id":"SKU-1001","available_qty":20,"locked_qty":3}

当前交易上下文真正关心的可能只是“本次购买数量是否可用”。这时候可以通过防腐层转换成当前上下文自己的表达:

publicinterfaceInventoryGateway{booleanisAvailable(StringskuId,intquantity);}

外部接口字段、协议和异常处理都留在适配实现中,领域代码只依赖自己能理解的能力。

**防腐层的核心价值,是保护当前上下文的模型不被外部模型污染。**至于防腐层具体怎么写,后面的进阶章节会单独展开,这里先理解它在上下文关系中的位置。

3. 开放主机服务和发布语言

开放主机服务叫 Open Host Service,发布语言叫 Published Language。

当一个上下文需要向多个调用方稳定提供能力时,可以设计一套公开、稳定的服务协议,并使用双方都能理解的数据格式进行交互。

常见形式包括 REST API、RPC 接口、消息事件、OpenAPI 文档和明确版本的 DTO。

这里的关键不是用了 HTTP 还是 MQ,而是对外契约要稳定,并且不能直接暴露上下文内部模型。

4. 其他常见关系

关系适用场景注意事项
合作关系两个上下文共同开发一项业务一方修改契约时,要同步评估另一方
共享内核双方共享少量含义完全一致的模型或规则共享范围必须小,并由双方共同维护
遵奉者下游无法要求上游调整,只能接受其模型和接口接入简单,但下游容易受上游模型影响
各行其道集成成本大于收益,各自实现更简单不是所有重复都值得抽成共享能力

六、用一个电商场景串起来

假设现在有商品、交易、库存、支付和会员五个限界上下文。

它们之间可能存在下面这些关系:

上下文关系协作方式
商品 → 交易商品提供公开查询接口,交易使用稳定 DTO 获取商品信息
交易 → 库存交易通过防腐层调用库存能力,只保留“锁定库存”等本地业务语义
支付 → 交易支付成功后发布消息,交易根据契约更新订单支付结果
交易 → 会员订单完成后通知会员增加成长值,双方通过公开事件格式协作

把这些关系画成上下文映射图,大概就是下面这样:

图中的实线表示同步调用,虚线表示异步事件。交易上下文调用库存能力时增加了一层模型转换,避免库存上下文的字段和协议直接进入交易模型。

这里需要注意,上下文映射表达的是业务边界之间的关系,不等于微服务调用拓扑图。

一个限界上下文可以暂时和另一个限界上下文部署在同一个应用里,它们仍然是两个模型边界。反过来,一个微服务如果塞进了多个互相混乱的业务模型,也不能因为已经拆成服务,就认为边界一定合理。

我们可以用下面几个问题检查映射是否清楚:

  1. 调用关系的上下游是否明确;
  2. 对外契约由谁维护;
  3. 上游字段变化会不会直接冲击下游领域模型;
  4. 外部异常在哪里转换;
  5. 双方使用的同名概念是否真的含义一致;
  6. 这段共享或依赖是否真的有必要。

如果这些问题回答不出来,说明边界虽然画了,但上下文之间的关系还没有梳理清楚。

这些模式是描述真实关系的工具,不是必须全部套用的设计检查表。

七、总结

这一篇我们继续学习了 DDD 的战略设计,主要整理了通用语言和上下文映射。

简单总结一下:

  • 通用语言用于统一同一个限界上下文内部的业务表达;
  • 通用语言要进入需求、模型、接口和代码,不能只停留在会议上;
  • 同一个词在不同限界上下文中可以有不同含义;
  • 上下文映射用于描述不同限界上下文之间的依赖和协作关系;
  • 防腐层可以隔离外部模型,公开服务契约可以稳定上下文之间的协作;
  • 上下文映射不等于微服务调用图,更不能代替真实业务边界分析。

到这里,我们已经知道了怎么划分边界、怎么统一边界内的语言,以及边界之间怎么协作。

不过这些结果不能只靠几个人坐在会议室里面拍脑袋决定。下一篇文章,我们继续学习事件风暴,看看怎么让业务、产品、开发和测试一起把业务流程、关键事件和边界梳理出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 7:52:14

高压安全隔离系统设计与ISOM8710+PIC18F应用

1. 高压安全隔离系统设计概述在工业控制、电力电子和医疗设备等领域,高压安全隔离是确保人员和设备安全的关键技术。使用ISOM8710数字隔离器与PIC18F24K50微控制器的组合方案,能够有效实现高达5kVrms的电气隔离,同时保持高速数据传输能力。这…

作者头像 李华
网站建设 2026/7/13 7:51:49

双节锂电池均衡充电方案:MP2672A与STM32F732IE设计

1. 项目背景与核心需求在便携式电子设备和储能系统中,多节锂电池串联使用时存在一个普遍问题:由于制造工艺差异和使用环境不同,各单体电池的电压会出现不均衡现象。这种不均衡会导致电池组整体容量下降、充电效率降低,甚至引发安全…

作者头像 李华
网站建设 2026/7/13 7:49:30

锂电池组主动均衡方案设计与BQ25887应用

1. 项目背景与核心器件选型在锂电池组应用中,电池单元之间的电压不平衡是影响系统性能和寿命的关键问题。当串联电池组中单体电池存在容量差异时,充电过程中会出现部分电池过充而其他电池未充满的情况。传统被动均衡方案虽然简单,但存在能量浪…

作者头像 李华
网站建设 2026/7/13 7:46:10

工业负载控制方案:TPD2017FN与dsPIC33EP的实战设计

1. 工业负载控制的核心挑战与方案选型在工业自动化领域,电感和电阻负载的控制一直是工程师面临的关键技术难题。以纺织机械为例,其控制系统需要同时驱动数十个电磁阀(电感负载)和加热元件(电阻负载)&#x…

作者头像 李华
网站建设 2026/7/13 7:45:23

HsMod:炉石传说终极增强插件 - 55项功能全面解锁游戏新体验

HsMod:炉石传说终极增强插件 - 55项功能全面解锁游戏新体验 【免费下载链接】HsMod Hearthstone Modification Based on BepInEx 项目地址: https://gitcode.com/GitHub_Trending/hs/HsMod HsMod是基于BepInEx框架开发的炉石传说多功能增强插件,为…

作者头像 李华
网站建设 2026/7/13 7:44:30

锂电池组电压平衡与充电管理方案设计

1. 项目背景与核心器件选型在锂电池组应用中,电池单元之间的电压不平衡是影响系统性能和寿命的关键问题。当多个电池串联时,由于制造工艺差异、温度分布不均等因素,各单体电池的充放电特性会出现偏差。这种不平衡会导致部分电池过充或过放&am…

作者头像 李华