03 · 战略设计(下):限界上下文与上下文映射
划完子域,接下来回答:每块业务内部的模型边界在哪?边界之间怎么打交道?本篇讲限界上下文的判断方法、最容易犯的"大一统模型"错误,以及六种上下文映射模式。
3.1 限界上下文:模型的户口所在地
限界上下文(Bounded Context)是模型的适用边界------在这条边界内,通用语言的每个词都有唯一、明确的含义。
判断方法一句话:当同一个词在不同场景下含义或规则不同时,就该切上下文了。
3.2 经典案例:一个"商品",三种形态
初学者最容易犯的错:建一个巨大的 Product 类,所有模块共用。看看它的下场:
#mermaid-svg-xQWEQboHeHI9GILp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xQWEQboHeHI9GILp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xQWEQboHeHI9GILp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xQWEQboHeHI9GILp .error-icon{fill:#552222;}#mermaid-svg-xQWEQboHeHI9GILp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xQWEQboHeHI9GILp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xQWEQboHeHI9GILp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xQWEQboHeHI9GILp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xQWEQboHeHI9GILp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xQWEQboHeHI9GILp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xQWEQboHeHI9GILp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xQWEQboHeHI9GILp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xQWEQboHeHI9GILp .marker.cross{stroke:#333333;}#mermaid-svg-xQWEQboHeHI9GILp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xQWEQboHeHI9GILp p{margin:0;}#mermaid-svg-xQWEQboHeHI9GILp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xQWEQboHeHI9GILp .cluster-label text{fill:#333;}#mermaid-svg-xQWEQboHeHI9GILp .cluster-label span{color:#333;}#mermaid-svg-xQWEQboHeHI9GILp .cluster-label span p{background-color:transparent;}#mermaid-svg-xQWEQboHeHI9GILp .label text,#mermaid-svg-xQWEQboHeHI9GILp span{fill:#333;color:#333;}#mermaid-svg-xQWEQboHeHI9GILp .node rect,#mermaid-svg-xQWEQboHeHI9GILp .node circle,#mermaid-svg-xQWEQboHeHI9GILp .node ellipse,#mermaid-svg-xQWEQboHeHI9GILp .node polygon,#mermaid-svg-xQWEQboHeHI9GILp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xQWEQboHeHI9GILp .rough-node .label text,#mermaid-svg-xQWEQboHeHI9GILp .node .label text,#mermaid-svg-xQWEQboHeHI9GILp .image-shape .label,#mermaid-svg-xQWEQboHeHI9GILp .icon-shape .label{text-anchor:middle;}#mermaid-svg-xQWEQboHeHI9GILp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xQWEQboHeHI9GILp .rough-node .label,#mermaid-svg-xQWEQboHeHI9GILp .node .label,#mermaid-svg-xQWEQboHeHI9GILp .image-shape .label,#mermaid-svg-xQWEQboHeHI9GILp .icon-shape .label{text-align:center;}#mermaid-svg-xQWEQboHeHI9GILp .node.clickable{cursor:pointer;}#mermaid-svg-xQWEQboHeHI9GILp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xQWEQboHeHI9GILp .arrowheadPath{fill:#333333;}#mermaid-svg-xQWEQboHeHI9GILp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xQWEQboHeHI9GILp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xQWEQboHeHI9GILp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xQWEQboHeHI9GILp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xQWEQboHeHI9GILp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xQWEQboHeHI9GILp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xQWEQboHeHI9GILp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xQWEQboHeHI9GILp .cluster text{fill:#333;}#mermaid-svg-xQWEQboHeHI9GILp .cluster span{color:#333;}#mermaid-svg-xQWEQboHeHI9GILp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xQWEQboHeHI9GILp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xQWEQboHeHI9GILp rect.text{fill:none;stroke-width:0;}#mermaid-svg-xQWEQboHeHI9GILp .icon-shape,#mermaid-svg-xQWEQboHeHI9GILp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xQWEQboHeHI9GILp .icon-shape p,#mermaid-svg-xQWEQboHeHI9GILp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xQWEQboHeHI9GILp .icon-shape .label rect,#mermaid-svg-xQWEQboHeHI9GILp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xQWEQboHeHI9GILp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xQWEQboHeHI9GILp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xQWEQboHeHI9GILp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 上帝类 Product(300+ 字段)
标题 · 详情 · 图片 · SKU · 库存数量
成交价快照 · 仓库位置 · 营销文案......
商品目录页:只需要标题图片详情
但被迫加载和维护全部字段
库存模块:只关心 SKU 和数量
但改库存要动整个 Product 类
订单模块:只需要成交快照
但商品改价,历史订单语义被污染
DDD 的做法是拆成三个模型,各归各的上下文:
#mermaid-svg-FAWpKQEN5d2Uc75A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FAWpKQEN5d2Uc75A .error-icon{fill:#552222;}#mermaid-svg-FAWpKQEN5d2Uc75A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FAWpKQEN5d2Uc75A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FAWpKQEN5d2Uc75A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FAWpKQEN5d2Uc75A .marker.cross{stroke:#333333;}#mermaid-svg-FAWpKQEN5d2Uc75A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FAWpKQEN5d2Uc75A p{margin:0;}#mermaid-svg-FAWpKQEN5d2Uc75A .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster-label text{fill:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster-label span{color:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster-label span p{background-color:transparent;}#mermaid-svg-FAWpKQEN5d2Uc75A .label text,#mermaid-svg-FAWpKQEN5d2Uc75A span{fill:#333;color:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A .node rect,#mermaid-svg-FAWpKQEN5d2Uc75A .node circle,#mermaid-svg-FAWpKQEN5d2Uc75A .node ellipse,#mermaid-svg-FAWpKQEN5d2Uc75A .node polygon,#mermaid-svg-FAWpKQEN5d2Uc75A .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FAWpKQEN5d2Uc75A .rough-node .label text,#mermaid-svg-FAWpKQEN5d2Uc75A .node .label text,#mermaid-svg-FAWpKQEN5d2Uc75A .image-shape .label,#mermaid-svg-FAWpKQEN5d2Uc75A .icon-shape .label{text-anchor:middle;}#mermaid-svg-FAWpKQEN5d2Uc75A .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FAWpKQEN5d2Uc75A .rough-node .label,#mermaid-svg-FAWpKQEN5d2Uc75A .node .label,#mermaid-svg-FAWpKQEN5d2Uc75A .image-shape .label,#mermaid-svg-FAWpKQEN5d2Uc75A .icon-shape .label{text-align:center;}#mermaid-svg-FAWpKQEN5d2Uc75A .node.clickable{cursor:pointer;}#mermaid-svg-FAWpKQEN5d2Uc75A .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FAWpKQEN5d2Uc75A .arrowheadPath{fill:#333333;}#mermaid-svg-FAWpKQEN5d2Uc75A .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FAWpKQEN5d2Uc75A .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FAWpKQEN5d2Uc75A .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FAWpKQEN5d2Uc75A .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FAWpKQEN5d2Uc75A .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FAWpKQEN5d2Uc75A .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster text{fill:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A .cluster span{color:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FAWpKQEN5d2Uc75A .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FAWpKQEN5d2Uc75A rect.text{fill:none;stroke-width:0;}#mermaid-svg-FAWpKQEN5d2Uc75A .icon-shape,#mermaid-svg-FAWpKQEN5d2Uc75A .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FAWpKQEN5d2Uc75A .icon-shape p,#mermaid-svg-FAWpKQEN5d2Uc75A .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FAWpKQEN5d2Uc75A .icon-shape .label rect,#mermaid-svg-FAWpKQEN5d2Uc75A .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FAWpKQEN5d2Uc75A .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FAWpKQEN5d2Uc75A .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FAWpKQEN5d2Uc75A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 商品已上架/改价事件
下单时同步快照
库存已扣减事件
订单上下文
OrderItem
· 成交价快照
· 数量
· 购买时名称快照
库存上下文
StockItem
· SKU 编码
· 占用数量
· 可售数量
· 预警阈值 · 仓库位置
商品上下文
Product
· 标题 / 详情 / 图片
· 分类 / 标签
· 营销文案
· 上下架状态
关键认知:
- 三个类都叫"商品相关的模型",但没有一个是同一个东西,也不应该是;
- 订单上下文的
OrderItem绝不引用商品上下文的Product对象------只存下单那一刻的快照。否则商品改价、改图,历史订单的语义就被污染; - 上下文之间靠领域事件或 API 同步数据,互不侵入对方的模型。
3.3 上下文数量怎么把握
#mermaid-svg-2osZ6XqGxzgHJHFy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2osZ6XqGxzgHJHFy .error-icon{fill:#552222;}#mermaid-svg-2osZ6XqGxzgHJHFy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2osZ6XqGxzgHJHFy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2osZ6XqGxzgHJHFy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2osZ6XqGxzgHJHFy .marker.cross{stroke:#333333;}#mermaid-svg-2osZ6XqGxzgHJHFy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2osZ6XqGxzgHJHFy p{margin:0;}#mermaid-svg-2osZ6XqGxzgHJHFy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster-label text{fill:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster-label span{color:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster-label span p{background-color:transparent;}#mermaid-svg-2osZ6XqGxzgHJHFy .label text,#mermaid-svg-2osZ6XqGxzgHJHFy span{fill:#333;color:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy .node rect,#mermaid-svg-2osZ6XqGxzgHJHFy .node circle,#mermaid-svg-2osZ6XqGxzgHJHFy .node ellipse,#mermaid-svg-2osZ6XqGxzgHJHFy .node polygon,#mermaid-svg-2osZ6XqGxzgHJHFy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2osZ6XqGxzgHJHFy .rough-node .label text,#mermaid-svg-2osZ6XqGxzgHJHFy .node .label text,#mermaid-svg-2osZ6XqGxzgHJHFy .image-shape .label,#mermaid-svg-2osZ6XqGxzgHJHFy .icon-shape .label{text-anchor:middle;}#mermaid-svg-2osZ6XqGxzgHJHFy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2osZ6XqGxzgHJHFy .rough-node .label,#mermaid-svg-2osZ6XqGxzgHJHFy .node .label,#mermaid-svg-2osZ6XqGxzgHJHFy .image-shape .label,#mermaid-svg-2osZ6XqGxzgHJHFy .icon-shape .label{text-align:center;}#mermaid-svg-2osZ6XqGxzgHJHFy .node.clickable{cursor:pointer;}#mermaid-svg-2osZ6XqGxzgHJHFy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2osZ6XqGxzgHJHFy .arrowheadPath{fill:#333333;}#mermaid-svg-2osZ6XqGxzgHJHFy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2osZ6XqGxzgHJHFy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2osZ6XqGxzgHJHFy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2osZ6XqGxzgHJHFy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2osZ6XqGxzgHJHFy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2osZ6XqGxzgHJHFy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster text{fill:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy .cluster span{color:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2osZ6XqGxzgHJHFy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2osZ6XqGxzgHJHFy rect.text{fill:none;stroke-width:0;}#mermaid-svg-2osZ6XqGxzgHJHFy .icon-shape,#mermaid-svg-2osZ6XqGxzgHJHFy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2osZ6XqGxzgHJHFy .icon-shape p,#mermaid-svg-2osZ6XqGxzgHJHFy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2osZ6XqGxzgHJHFy .icon-shape .label rect,#mermaid-svg-2osZ6XqGxzgHJHFy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2osZ6XqGxzgHJHFy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2osZ6XqGxzgHJHFy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2osZ6XqGxzgHJHFy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 上下文数量怎么定?
信号:过少
· 一个上下文里模型字段大量可空
· 同一个词在代码里有两个含义
· 改一处影响多个不相关模块
信号:过多
· 一个上下文只有一个类
· 跨上下文调用占请求耗时大头
· 团队规模撑不起独立维护
合并
拆分或合并
经验值:一个限界上下文对应一个小团队(两披萨以内)能在数周内完整理解并维护的模型。宁少勿滥------上下文越少,集成的成本越低。
3.4 上下文映射:边界之间的六种契约
上下文之间必然要协作,**上下文映射(Context Mapping)**定义协作的"契约"。六种模式:
| 模式 | 一句话解释 | 典型场景 | 协作成本 |
|---|---|---|---|
| 防腐层(ACL) | 两上下文间加一层翻译,保护自己模型不被外部污染 | 对接第三方支付、遗留系统 | 中 |
| 客户-供应商 | 下游定义需求,上游按需供给,双方协商排期 | 库存服务于订单 | 低 |
| 开放主机服务(OHS) | 上游提供标准化协议/SDK,供所有下游接入 | 统一认证服务 | 一次性高、边际低 |
| 发布语言(PL) | 双方约定公开的报文/事件格式(常与 OHS 搭配) | 领域事件 Schema | 低 |
| 共享内核 | 两上下文共享一小部分模型,需协同变更 | 同团队维护的紧密模块 | 高(慎用) |
| 各行其道 | 互不集成,靠人工/文件同步 | 一次性数据导出 | 最低 |
最常用的是防腐层
规则:凡是调用"不受你控制的系统"(三方接口、遗留系统),中间必须垫一层翻译对象,绝不让外部报文字段直接穿透进你的领域模型。
java
// 三方支付回调原文:字段含义由别人定义,随时会变
class ThirdPartyPayNotify { String tranStat; String amt; String merId; }
// 防腐层:翻译成自己领域的事件
class PaymentAcl {
PaymentReceived translate(ThirdPartyPayNotify notify) {
return new PaymentReceived(
OrderId.of(notify.merId),
Money.of(notify.amt),
mapStatus(notify.tranStat)); // "01"=成功 → PAID,翻译规则集中一处
}
}
好处:三方改字段、换服务商,只改 ACL 一个类;领域模型全程不认识三方的报文格式。
一个完整电商的上下文映射图
#mermaid-svg-2KQ9rZVRRCeGb4Yu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .error-icon{fill:#552222;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .marker.cross{stroke:#333333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu p{margin:0;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster-label text{fill:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster-label span{color:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster-label span p{background-color:transparent;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .label text,#mermaid-svg-2KQ9rZVRRCeGb4Yu span{fill:#333;color:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .node rect,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node circle,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node ellipse,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node polygon,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .rough-node .label text,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node .label text,#mermaid-svg-2KQ9rZVRRCeGb4Yu .image-shape .label,#mermaid-svg-2KQ9rZVRRCeGb4Yu .icon-shape .label{text-anchor:middle;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .rough-node .label,#mermaid-svg-2KQ9rZVRRCeGb4Yu .node .label,#mermaid-svg-2KQ9rZVRRCeGb4Yu .image-shape .label,#mermaid-svg-2KQ9rZVRRCeGb4Yu .icon-shape .label{text-align:center;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .node.clickable{cursor:pointer;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .arrowheadPath{fill:#333333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2KQ9rZVRRCeGb4Yu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2KQ9rZVRRCeGb4Yu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster text{fill:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .cluster span{color:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2KQ9rZVRRCeGb4Yu rect.text{fill:none;stroke-width:0;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .icon-shape,#mermaid-svg-2KQ9rZVRRCeGb4Yu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .icon-shape p,#mermaid-svg-2KQ9rZVRRCeGb4Yu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .icon-shape .label rect,#mermaid-svg-2KQ9rZVRRCeGb4Yu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2KQ9rZVRRCeGb4Yu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2KQ9rZVRRCeGb4Yu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2KQ9rZVRRCeGb4Yu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户-供应商
下单扣库存
客户-供应商
支付成功发货
客户-供应商
领券用券
开放主机服务
统一发 Token
开放主机服务
防腐层 ACL
三方报文翻译
交易上下文
(核心域)
库存上下文
物流上下文
营销上下文
认证上下文
支付网关(外部系统)
读图要点:
- 箭头方向 = 依赖方向。交易上下文是核心域,依赖它的支撑域按它的节奏供给;
- 对外部系统(支付网关)一定经过 ACL,图中能看到明确的"隔离带";
- 认证是典型 OHS:一个上游服务所有下游,避免每个上下文各自对接登录逻辑。
3.5 上下文映射与团队协作
上下文映射同时是组织架构的镜子(康威定律):
| 映射模式 | 背后的团队关系 |
|---|---|
| 客户-供应商 | 两个团队,下游有话语权,排期可协商 |
| 防腐层 | 两个团队互不信任或外部无法协商,各管各的 |
| 共享内核 | 同一个团队或 pair 极紧密的两个团队 |
| 各行其道 | 没有正式协作渠道(警惕:多半是组织问题) |
实用推论:如果上下文映射图上出现"共享内核",先检查是不是两个团队在互相改对方的代码------多半该拆团队或拆上下文,而不是共享模型。
3.6 常见疑问
Q:限界上下文 = 微服务吗?
不是,但关系密切。限界上下文是逻辑边界;微服务是物理部署单元。理想状态下一个上下文一个服务,但落地时可以是"多个上下文先住一个单体,各自独立模块"(09 篇详细展开)。
Q:怎么验证上下文边界划对了?
两个体检项:① 每个上下文内部能否用 5~10 句通用语言讲清自己的职责;② 跨上下文的调用/事件是否在真实业务流程中自然出现,还是为了凑数据硬拉的。后者说明边界划错了。
本篇小结:同一个词含义不同就切上下文;"商品"三种形态是最经典的判例;上下文之间用六种映射模式定契约,其中防腐层是保护模型的头号利器;映射图同时反映组织结构。战略设计到此完成,下一篇进入战术设计------边界内部的类怎么写。
关于作者
985 计算机硕士,十年头部大厂技术经历,现任金融公司高级架构师。面试近千人,培养 50+ 新人。
我维护着一个全栈技术博客(yuandianit.com),把踩过的坑整理成了实战文档,覆盖 Java 全栈、AI 应用(RAG 链路 / Agent / 多模型接入)、微信支付完整链路与对账、Wiki 知识库。