learn.lianglianglee.com/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html
2022-05-11 19:04:14 +08:00

920 lines
51 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>034 实践 确定限界上下文的协作关系.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/领域驱动设计实践(完)/001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html">001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html">002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/003 领域驱动设计概览.md.html">003 领域驱动设计概览.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/004 深入分析软件的复杂度.md.html">004 深入分析软件的复杂度.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/005 控制软件复杂度的原则.md.html">005 控制软件复杂度的原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/006 领域驱动设计对软件复杂度的应对(上).md.html">006 领域驱动设计对软件复杂度的应对(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/007 领域驱动设计对软件复杂度的应对(下).md.html">007 领域驱动设计对软件复杂度的应对(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/008 软件开发团队的沟通与协作.md.html">008 软件开发团队的沟通与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/009 运用领域场景分析提炼领域知识(上).md.html">009 运用领域场景分析提炼领域知识(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/010 运用领域场景分析提炼领域知识(下).md.html">010 运用领域场景分析提炼领域知识(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/011 建立统一语言.md.html">011 建立统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/012 理解限界上下文.md.html">012 理解限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/013 限界上下文的控制力(上).md.html">013 限界上下文的控制力(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/014 限界上下文的控制力(下).md.html">014 限界上下文的控制力(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/015 识别限界上下文(上).md.html">015 识别限界上下文(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/016 识别限界上下文(下).md.html">016 识别限界上下文(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/017 理解上下文映射.md.html">017 理解上下文映射.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/018 上下文映射的团队协作模式.md.html">018 上下文映射的团队协作模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/019 上下文映射的通信集成模式.md.html">019 上下文映射的通信集成模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/020 辨别限界上下文的协作关系(上).md.html">020 辨别限界上下文的协作关系(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/021 辨别限界上下文的协作关系(下).md.html">021 辨别限界上下文的协作关系(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/022 认识分层架构.md.html">022 认识分层架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/023 分层架构的演化.md.html">023 分层架构的演化.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/024 领域驱动架构的演进.md.html">024 领域驱动架构的演进.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/025 案例 层次的职责与协作关系(图文篇).md.html">025 案例 层次的职责与协作关系(图文篇).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/026 限界上下文与架构.md.html">026 限界上下文与架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/027 限界上下文对架构的影响.md.html">027 限界上下文对架构的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/028 领域驱动设计的代码模型.md.html">028 领域驱动设计的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/029 代码模型的架构决策.md.html">029 代码模型的架构决策.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/030 实践 先启阶段的需求分析.md.html">030 实践 先启阶段的需求分析.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/031 实践 先启阶段的领域场景分析(上).md.html">031 实践 先启阶段的领域场景分析(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/032 实践 先启阶段的领域场景分析(下).md.html">032 实践 先启阶段的领域场景分析(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">033 实践 识别限界上下文.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html">034 实践 确定限界上下文的协作关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">035 实践 EAS 的整体架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html">036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/037 「战术篇」开篇词:领域驱动设计的不确定性.md.html">037 「战术篇」开篇词:领域驱动设计的不确定性.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/038 什么是模型.md.html">038 什么是模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/039 数据分析模型.md.html">039 数据分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/040 数据设计模型.md.html">040 数据设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/041 数据模型与对象模型.md.html">041 数据模型与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/042 数据实现模型.md.html">042 数据实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">043 案例 培训管理系统.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html">044 服务资源模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">045 服务行为模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/046 服务设计模型.md.html">046 服务设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/047 领域模型驱动设计.md.html">047 领域模型驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/048 领域实现模型.md.html">048 领域实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/049 理解领域模型.md.html">049 理解领域模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/050 领域模型与结构范式.md.html">050 领域模型与结构范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/051 领域模型与对象范式(上).md.html">051 领域模型与对象范式(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/052 领域模型与对象范式(中).md.html">052 领域模型与对象范式(中).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/053 领域模型与对象范式(下).md.html">053 领域模型与对象范式(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/054 领域模型与函数范式.md.html">054 领域模型与函数范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/055 领域驱动分层架构与对象模型.md.html">055 领域驱动分层架构与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/056 统一语言与领域分析模型.md.html">056 统一语言与领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/057 精炼领域分析模型.md.html">057 精炼领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/058 彩色 UML 与彩色建模.md.html">058 彩色 UML 与彩色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/059 四色建模法.md.html">059 四色建模法.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/060 案例 订单核心流程的四色建模.md.html">060 案例 订单核心流程的四色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/061 事件风暴与业务全景探索.md.html">061 事件风暴与业务全景探索.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/062 事件风暴与领域分析建模.md.html">062 事件风暴与领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/063 案例 订单核心流程的事件风暴.md.html">063 案例 订单核心流程的事件风暴.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/064 表达领域设计模型.md.html">064 表达领域设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/065 实体.md.html">065 实体.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/066 值对象.md.html">066 值对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/067 对象图与聚合.md.html">067 对象图与聚合.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/068 聚合设计原则.md.html">068 聚合设计原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/069 聚合之间的关系.md.html">069 聚合之间的关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/070 聚合的设计过程.md.html">070 聚合的设计过程.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/071 案例 培训领域模型的聚合设计.md.html">071 案例 培训领域模型的聚合设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/072 领域模型对象的生命周期-工厂.md.html">072 领域模型对象的生命周期-工厂.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/073 领域模型对象的生命周期-资源库.md.html">073 领域模型对象的生命周期-资源库.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/074 领域服务.md.html">074 领域服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/075 案例 领域设计模型的价值.md.html">075 案例 领域设计模型的价值.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/076 应用服务.md.html">076 应用服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/077 场景的设计驱动力.md.html">077 场景的设计驱动力.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">078 案例 薪资管理系统的场景驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html">079 场景驱动设计与 DCI 模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">080 领域事件.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/081 发布者—订阅者模式.md.html">081 发布者—订阅者模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/082 事件溯源模式.md.html">082 事件溯源模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/083 测试优先的领域实现建模.md.html">083 测试优先的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/084 深入理解简单设计.md.html">084 深入理解简单设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/085 案例 薪资管理系统的测试驱动开发(上).md.html">085 案例 薪资管理系统的测试驱动开发(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">086 案例 薪资管理系统的测试驱动开发(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">087 对象关系映射(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">088 对象关系映射(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">089 领域模型与数据模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/090 领域驱动设计对持久化的影响.md.html">090 领域驱动设计对持久化的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/091 领域驱动设计体系.md.html">091 领域驱动设计体系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/092 子领域与限界上下文.md.html">092 子领域与限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/093 限界上下文的边界与协作.md.html">093 限界上下文的边界与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/094 限界上下文之间的分布式通信.md.html">094 限界上下文之间的分布式通信.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/095 命令查询职责分离.md.html">095 命令查询职责分离.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/096 分布式柔性事务.md.html">096 分布式柔性事务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/097 设计概念的统一语言.md.html">097 设计概念的统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/098 模型对象.md.html">098 模型对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/099 领域驱动设计参考过程模型.md.html">099 领域驱动设计参考过程模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/100 领域驱动设计的精髓.md.html">100 领域驱动设计的精髓.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/101 实践 员工上下文的领域建模.md.html">101 实践 员工上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/102 实践 考勤上下文的领域建模.md.html">102 实践 考勤上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/103 实践 项目上下文的领域建模.md.html">103 实践 项目上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/104 实践 培训上下文的业务需求.md.html">104 实践 培训上下文的业务需求.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/105 实践 培训上下文的领域分析建模.md.html">105 实践 培训上下文的领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/106 实践 培训上下文的领域设计建模.md.html">106 实践 培训上下文的领域设计建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/107 实践 培训上下文的领域实现建模.md.html">107 实践 培训上下文的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/108 实践 EAS 系统的代码模型.md.html">108 实践 EAS 系统的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/109 后记:如何学习领域驱动设计.md.html">109 后记:如何学习领域驱动设计.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>034 实践 确定限界上下文的协作关系</h1>
<p>通过上下文映射来确定限界上下文之间的协作关系,是识别限界上下文之后至为关键的一步。每个限界上下文都仅仅展示了整体架构全局视图的一角,只有将它们联合起来,才能产生合力,满足业务场景的需要。若这种协作关系处理不当,这种联合的合力反倒成了一种阻力,清晰的架构不见,限界上下文带给我们的“价值”就会因此荡然无存。</p>
<p>如果单从确定限界上下文之间的协作关系而论,要考量的设计要素包括:</p>
<ul>
<li>限界上下文的通信边界</li>
<li>采用命令、查询还是事件的协作机制</li>
<li>定义协作接口</li>
</ul>
<p>通信边界、协作机制与上下文映射模式的选择息息相关。例如,通信边界采用进程内通信,就可能无需采用<strong>开放主机服务</strong>模式,甚至为了保证架构的简单性,也无需采用<strong>防腐层</strong>模式。如果采用命令和查询的协作机制,可能会采用<strong>客户方/供应方</strong>模式,如果采用事件的协作机制,则需要采用<strong>发布者/订阅者</strong>模式。</p>
<p>在识别限界上下文协作关系的阶段,是否需要定义协作的接口呢?我认为是必要的。一方面接口的定义直接影响到协作模式,也属于架构中体现“组件关系”的设计内容;另一方面通过要求对协作接口的定义,可以强迫我们思考各种协作的业务场景,避免做出错误的上下文映射。如果在这个阶段还未做好框架的技术选型,<strong>接口的设计就不应该与具体的框架技术绑定</strong>,而是给出体现业务价值的领域模型,换言之,就是定义好当前限界上下文的应用服务,因为应用服务恰好体现了用例的应用逻辑。</p>
<h3>识别 EAS 的上下文映射</h3>
<p>在领域驱动设计中,以“领域”为核心的设计思想应当贯穿整个过程始终,确定系统的上下文映射自然也不例外。实际上,整个领域驱动的战略设计实践是存在连贯关系的,我们不能因为进入一个新的阶段,就忘记了前面获得的成果。决定上下文映射的重要输入就包括基于领域场景分析获得的用例图,基于用例图获得的限界上下文。</p>
<h4>根据用例识别协作关系</h4>
<p>为避免出现上下文映射的疏漏,我们应该根据业务场景来分析各种限界上下文协作的关系。这时,先启阶段领域场景分析获得的用例图就派上用场了。为了确保设计的严谨,我们<strong>应该“遍历”所有的主用例</strong>,理解用例的目标与流程,再结合我们已经识别出来的限界上下文判断它们之间的关系。</p>
<p>由于用例图中的用例传递的信息量有限,我们在识别协作关系时,可以进一步确定详细的流程,绘制更为详细的用例图甚至活动图。用例的好处在于不会让你遗漏重要的业务场景,而用例图中的包含用例与扩展用例,往往是存在上下文协作的信号。当然,在识别上下文协作关系时,还需要注意其中的陷阱。正如在[第 3-9 课:辨别限界上下文的协作关系(上)]中提到的那样,要理解<strong>协作即依赖</strong>的本质,正确辨别这种依赖关系到底是领域行为或领域模型的依赖,还是数据导致的依赖,又或者与限界上下文的边界彻底无关。</p>
<p>以“创建需求订单”用例为例,它的完整用例图如下所示:</p>
<p><img src="assets/156bb3b0-d3a3-11e8-a9a3-fd92848cdb84" alt="enter image description here" /></p>
<p>主用例“创建需求订单”属于<strong>订单上下文</strong>,“指定客户需求承担者”属于<strong>客户上下文</strong>,“通知承担者”用例是“指定客户需求承担者”的扩展用例,但它实际上会通过 <strong>OA 集成上下文</strong>发送消息通知。若满足于这样的表面现象,可得出上下文映射(图中使用了六边形图例来表达限界上下文,但并不说明该限界上下文一定为微服务):</p>
<p><img src="assets/24b48360-d3a3-11e8-83c4-93b72872a9ed" alt="img" /></p>
<p>然而事实上,在指定客户需求承担者时,<strong>订单上下文</strong>并非该用例的真正发起者,而是市场人员通过用户界面获得客户信息,再将选择的客户 ID 传递给了订单,<strong>订单上下文</strong>并不知道<strong>客户上下文</strong>。如此一来,消息通知的发送也将转为由订单上下文发起。于是,上下文映射变为:</p>
<p><img src="assets/39e7e970-d3a3-11e8-83c4-93b72872a9ed" alt="img" /></p>
<p>目前获得的上下文映射自然不会是最终方案。不同的用例代表不同的场景,产生的协作关系自然会有所不同。在“跟踪需求订单”用例中,需要在用户界面呈现需求订单状态,同时还将显示需求订单下所有客户需求的客户信息和承担者信息,这就需要分别求助于<strong>客户上下文</strong><strong>员工上下文</strong>。因此,<strong>订单上下文</strong>的上下文映射就修改为:</p>
<p><img src="assets/4ddae040-d3a3-11e8-b055-6fdf72668cfc" alt="img" /></p>
<p>“创建市场需求”用例图如下所示:</p>
<p><img src="assets/5fd625c0-d3a3-11e8-abac-396c1f0bcec5" alt="enter image description here" /></p>
<p>除了需要在<strong>订单上下文</strong>中创建市场需求之外,还要通过<strong>文件共享上下文</strong>完成附件的上传。此外,操作订单时需要对用户进行身份认证。最终,<strong>订单上下文</strong>的上下文映射就演变为:</p>
<p><img src="assets/71be3160-d3a3-11e8-b055-6fdf72668cfc" alt="img" /></p>
<p>有些限界上下文之间的关系是隐含的,需要透过用例去理解内在的业务流程才能探明这种关系。例如,“制定招聘计划”用例:</p>
<p><img src="assets/8143bbf0-d3a3-11e8-83c4-93b72872a9ed" alt="enter image description here" /></p>
<p>当招聘专员制定好招聘计划时,会发送消息通知招聘计划审核人,这个审核人就是人力资源总监。然而此时的<strong>招聘上下文</strong>并不知道谁是人力资源总监,只能通过招聘专员所属部门的组织层级去获得人力资源总监(用户角色)的信息,再通过该角色对应的 EmployeeId 到<strong>员工上下文</strong>获取人力资源总监的联系信息,包括手机和邮箱地址。得到的上下文映射为:</p>
<p><img src="assets/93465470-d3a3-11e8-83c4-93b72872a9ed" alt="img" /></p>
<p>通过识别上下文映射,还会帮助我们<strong>甄别一些错误的限界上下文职责边界划定</strong>。例如,针对“添加项目成员”用例:</p>
<p><img src="assets/a5a60110-d3a3-11e8-b055-6fdf72668cfc" alt="enter image description here" /></p>
<p>通过前面对限界上下文的识别,我们认为项目成员作为一种用户角色,项目组作为一个组织层级,从概念关联性看更适合放在<strong>组织上下文</strong>。当项目经理通过用户界面添加项目成员时,其流程为:</p>
<ul>
<li>前置条件与项目关联的项目组已经创建好</li>
<li>选择要加入的项目组</li>
<li>列出符合条件的员工清单</li>
<li>选择员工加入到当前项目组</li>
<li>通知该员工已成为项目组的项目成员</li>
<li>将当前项目的信息追加到项目成员的项目经历中</li>
</ul>
<p>注意,列出员工清单的功能属于<strong>员工上下文</strong>,但该操作是通过用户界面发起对<strong>员工上下文</strong>的调用,<strong>组织上下文</strong>并不需要获取员工清单,而是用户界面传递给它的。在员工加入到当前项目组后,<strong>组织上下文</strong>需要通过 <strong>OA 集成</strong>发送通知消息,还要通过<strong>员工上下文</strong>来追加项目经历功能。基于这样的流程,得到的上下文映射为:</p>
<p><img src="assets/b74bac80-d3a3-11e8-abac-396c1f0bcec5" alt="img" /></p>
<p>然而考虑<strong>认证上下文</strong>,它又需要调用<strong>组织上下文</strong>提供的服务来判断用户是否属于某个部门或团队,这就在二者之间产生了上下游关系。由于<strong>认证上下文</strong>比较特殊,如果系统没有采用 API 网关,则作为通用子领域的限界上下文,会被多个核心子领域的限界上下文调用,其中也包括<strong>员工上下文</strong><strong>项目上下文</strong>,于是上下文映射就变为:</p>
<p><img src="assets/c59052a0-d3a3-11e8-abac-396c1f0bcec5" alt="img" /></p>
<p>为了更好地体现协作关系,我在上图增加了箭头,加粗了相关连线。可以清晰地看到,上图粗线部分形成了认证、组织与员工三个限界上下文之间的循环依赖,这是设计上的“坏味道”。导致这种循环依赖的原因,是因为与项目成员有关的用例被放到了<strong>组织上下文</strong>中,从而导致了它与<strong>员工上下文</strong>产生协作关系,这充分说明了之前识别的限界上下文仍有不足之处。组织结构是一种领域,管理的是部门、部门层次、角色等更为普适性的特性。换言之,即使不是在 EAS 系统,只要存在组织结构的需求,仍然需要该限界上下文。如此看来,项目成员的管理应属于更加特定的业务领域。在添加项目成员时,领域逻辑仍然属于<strong>项目上下文</strong>,但建立成员与项目组之间的关系,则应交给更为通用的<strong>组织上下文</strong>,形成二者的上下游关系。经过这样的更改后,“追加项目成员的项目经历”用例就由<strong>项目上下文</strong><strong>员工上下文</strong>直接发起调用请求:</p>
<p><img src="assets/4bb9bc90-d796-11e8-a846-1515ba7379c6" alt="img" /></p>
<p>这个场景体现了<strong>上下文映射对限界上下文设计的约束和驱动作用</strong>。在调整了限界上下文的职责之后,避免了限界上下文之间的循环依赖,使得限界上下文的边界更加清晰,保证了它们之间的松散耦合,有利于整个系统架构的演化。</p>
<h4>确定上下文协作模式</h4>
<p>要确定上下文协作模式,首先需要明确限界上下文的通信边界,即确定为进程内通信还是进程间通信。采用进程间通信的限界上下文就是一个微服务。在[第 4-8 课:代码模型的架构决策]中我总结了微服务的优势与不足。EAS 系统作为一个企业的内部系统,对并发访问与低延迟的要求并不高,可用性固然是一个系统该有的特质,但毕竟它不是“生死攸关”的一线生产系统,短时间出现故障不会给企业带来致命的打击或难以估量的损失。整体来看,在质量属性方面,除了安全与可维护性之外,系统并无特别高的要求。综上所述,我看不到需要建立微服务架构的任何理由。既然无需创建微服务架构,就不必遵守一个限界上下文一个数据库的约束,满足架构的简单原则,可以为整个 EAS 系统创建一个集中的数据库。</p>
<p>这一设计决策直接影响到<strong>决策分析上下文</strong>的实现方案。就目前的需求而言,我们似乎没有必要为实现该上下文的功能专门引入数据仓库。<strong>决策分析上下文</strong>具有如下特征:</p>
<ul>
<li>访问的数据涵盖所有的核心子领域</li>
<li>决策分析仅针对数据执行查询统计操作</li>
</ul>
<p>虽然<strong>决策分析上下文</strong>属于核心子领域,但针对这两个特征,我们决定“斩断”该上下文和其他上下文之间的业务耦合关系,让它直接访问数据库,并借鉴 CQRS 架构模式,不为它定义领域模型,而是创建一个薄的数据访问层,通过执行 SQL 语句完成高效直接的数据处理。</p>
<p>既然决定限界上下文之间采用进程内通信,我们该选择何种上下文映射模式呢?到上下文映射的“武器库”中看一看,原来我们不知不觉已经使用了“共享内核”模式,提取了<strong>文件共享</strong>上下文,同时还引入了扮演“防腐层”功能的 <strong>OA 集成上下文</strong></p>
<p>作为提供垂直领域功能的限界上下文,需要为前端的用户界面或其他客户端提供 RESTful 服务,于是为如下限界上下文建立“开放主机服务”:</p>
<ul>
<li>订单上下文</li>
<li>合同上下文</li>
<li>客户上下文</li>
<li>员工上下文</li>
<li>考勤上下文</li>
<li>招聘上下文</li>
<li>储备人才上下文</li>
<li>培训上下文(该上下文是项目开发中期针对需求变更引入)</li>
<li>项目上下文</li>
<li>决策分析上下文</li>
<li>资源上下文</li>
<li>组织上下文</li>
</ul>
<p>既然采用了进程内通信,且针对这样的企业系统,演变为微服务架构的可能性较低,为了架构的简单性,针对以上限界上下文之间的协作,并无必要引入间接的防腐层。至于它与外部的 OA 系统之间的协作,已经由 <strong>OA 集成上下文</strong>提供了“防腐”功能。</p>
<p>我们是否需要采用“遵奉者”模式实现限界上下文之间的模型重用呢同样是设计的取舍简单还是灵活重用还是清晰这是一个问题限界上下文的边界控制力会在架构中产生无与伦比的价值它可以有效地保证系统架构的清晰度。如果为了简单与重用而纵容对模型的“滥用”可能会导致系统变得越来越糟糕。对于采用进程内通信的限界上下文运用“遵奉者”模式重用领域模型就会失去限界上下文存在的意义使之与战术设计中的模块Module没有什么区别了。说好的限界上下文保证领域概念的一致性呢例如<strong>合同上下文</strong><strong>项目上下文</strong><strong>订单上下文</strong>都需要通过<strong>员工上下文</strong>获得员工的联系信息,那么最好的方式不是直接重用<strong>员工上下文</strong>中的 Employee 模型对象,而是各自建立自己的模型对象 Employee 或 TeamMember除了具有 EmployeeId 之外,可以只包含一个 Contact 属性:</p>
<p><img src="assets/edd10a70-d3a3-11e8-abac-396c1f0bcec5" alt="enter image description here" /></p>
<p>我们还需要确定限界上下文之间的调用机制,究竟是通过命令、查询还是事件?由于采用了进程内通信,限界上下文之间的协作方式应以同步的查询或命令机制为主。唯一的例外是将 <strong>OA 集成上下文</strong>定义为进程间通信的限界上下文,毕竟它的实现本身就是要跨进程调用 OA 系统。这个限界上下文要实现的功能都与通知有关,无论是短信通知、邮件通知还是站内通知,都没有副作用,且允许以异步形式调用,适合使用事件的调用机制。这种方式一方面解除了 <strong>OA 系统上下文</strong>与大多数限界上下文之间的耦合,另一方面也能够较好地保证 EAS 系统的响应速度,减轻主应用服务器的压力。唯一不足的是需要增加一台部署消息队列的服务器,并在一定程度增加了架构的复杂度。采用事件机制,意味着 <strong>OA 集成上下文</strong>采用了“发布者/订阅者”模式,其中 <strong>OA 集成上下文</strong>为订阅者:</p>
<p><img src="assets/fdaf1540-d3a3-11e8-abac-396c1f0bcec5" alt="img" /></p>
<h4>定义协作接口</h4>
<p>定义协作接口的重要性在于<strong>保证开发不同限界上下文的特性团队能够并行开发</strong>,这相当于为团队规定了合作的契约。集成是痛苦的,无论团队成员能力有多么强,只要没有规定好彼此之间协作的接口,就有可能导致系统模块无法正确地集成,或者隐藏的缺陷无法及时发现,最严重的是破坏了限界上下文的边界。我们需要像保卫疆土一样去守护限界上下文的边界,如果不加以控制,任何风吹草动都可能酿成“边疆”的风云突变。</p>
<p>注意,现在定义的是限界上下文之间协作的接口,并非限界上下文所有的服务接口,也不包括限界上下文对外部资源的访问接口。协作接口完全可以根据之前确定的上下文映射获得。在上下文映射图中,每个协作关系都意味着一个接口,不同的上下文映射模式可能会影响到对这些接口的设计。例如,如果下游限界上下文通过<strong>开放主机服务</strong>模式与上游协作,就需要定义 RESTful 或 RPC 接口;如果下游限界上下文直接调用上游,意味着需要定义应用服务接口;如果限界上下文之间采用<strong>发布者/订阅者</strong>模式需要定义的接口其实是事件Event</p>
<p>对于 EAS 系统而言,我们已经确定除与 <strong>OA 集成上下文</strong>之间采用“发布者/订阅者”模式之外,其余限界上下文之间的协作都是“客户方/供应方”模式,且无需引入防腐层和开放主机服务,因此,要定义的协作接口其实就是各个限界上下文的应用服务接口。在定义协作接口时,我们只需要规定作为供应方的上游应用服务即可。如果采用事件机制,协作接口就应该是对事件的定义。</p>
<p>以订单上下文为例,它的上下文映射图为(与前面上下文映射的不同之处是将订单与 OA 集成之间的协作改为了事件机制):</p>
<p><img src="assets/106d0e80-d3a4-11e8-83c4-93b72872a9ed" alt="img" /></p>
<p>记录与<strong>订单上下文</strong>相关的协作接口如下表所示:</p>
<p><img src="assets/fd0d7030-d797-11e8-a846-1515ba7379c6" alt="enter image description here" /></p>
<p>在这个接口表中我使用生产者Producer与消费者Consumer来抽象客户方/供应方模式与发布者/订阅者模式。表中的模式自然就是上下文映射模式。如有必要,也可以是多个模式的组合,比如客户方/供应方与开放主机服务之间的组合。当然,如果为开放主机服务,且发布语言为 RESTful则后面的服务定义就应该是遵循 RESTful 服务定义的接口。</p>
<p>对于<strong>订单上下文</strong><strong>OA 集成</strong>上下文之间的协作,正如前所述,我们采用了发布者/订阅者模式。因此,这里的协作接口实际上是对<strong>事件</strong>的定义。最初为了表达订单的领域概念,我将该事件定义为 OrderCompleted。回顾 <strong>OA 集成上下文</strong>的上下文映射,作为订阅者的 <strong>OA 集成上下文</strong>在接收到事件后,要做的事情<strong>都是</strong>将事件持有的内容转换为要发送消息通知的内容以及送达的地址,然后发送消息通知。显然,它订阅的事件应该是相同的,因为处理事件的逻辑完全相同。故而应该将 OrderCompleted 修改为 NotificationReady 事件。除了订单发布该事件外,合同、项目、组织等限界上下文都将发布该事件。</p>
<p>协作接口表格式并非固定或唯一。例如,我们也可以为每个接口定义详尽的描述:</p>
<ul>
<li><strong>接口,</strong> AuthenticationService</li>
<li><strong>描述,</strong> 对操作用户进行身份认证</li>
<li><strong>命名空间,</strong> paracticeddd.eas.authcontext.application</li>
<li><strong>方法,</strong> authenticate(userId): AuthenticatedResult</li>
<li><strong>模式</strong>,客户方/供应方模式</li>
<li><strong>接口类型,</strong> 命令</li>
</ul>
<p>协作接口定义的格式不是重要的,关键还是在战略设计阶段需要重视对它们的定义。只有这样才能更好地保证限界上下文的边界,因为除了这些协作接口,限界上下文之间是不允许直接协作的。协作接口的定义也是上下文映射的一种落地实践,要避免上下文映射在战略设计中沦为一幅幅中看不中用的设计图。同时,通过它还可以更好地遵循统一语言,保证设计模型与领域模型的一致性。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997e41396d3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>