微服务架构基础与 DDD 实践
2026年1月23日...大约 8 分钟
微服务架构基础与 DDD 实践
从 12 个服务到清晰边界,用 DDD 思想重构团队微服务架构。
💡 背景
团队当前维护 12 个后端服务,采用 Spring Cloud 微服务架构。但随着业务复杂度提升,原有的服务划分方式(按"测试设计、执行,分析"等动词维度)逐渐暴露问题:
- 服务边界模糊 — 同一数据结构在多个服务中被修改
- 代码重复 — 通用逻辑散落在各处
- 维护困难 — 新人理解成本高
优化目标: 转向 DDD(领域驱动设计),围绕"领域实体"(名词)划分服务。
📚 DDD 基础知识
什么是 DDD?
DDD(Domain-Driven Design,领域驱动设计) 是由 Eric Evans 在 2003 年提出的软件开发方法论,核心思想是:
以业务领域为核心,通过构建通用语言,让技术团队和业务团队对业务达成一致理解。
DDD 核心概念
┌─────────────────────────────────────────────────────────────┐
│ DDD 核心概念体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 限界上下文 │ ← 领域的边界,每个上下文有自己的语言 │
│ │ (Bounded │ │
│ │ Context) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ 聚合根 │ ← 聚合的根,具有全局唯一标识 │
│ │ (Aggregate) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ ┌─────────────┐ │
│ │ 实体 │───▶│ 值对象 │ │
│ │ (Entity) │ │ (Value Obj) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 领域服务 │ │
│ │ (Domain Service) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 仓储模式 │ │
│ │ (Repository) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘核心概念详解
1. 限界上下文(Bounded Context)
定义: 领域边界,每个边界内有自己独立的通用语言和模型。
特点:
- 每个限界上下文是独立的业务范围
- 上下文之间通过上下文映射进行通信
- 避免跨上下文直接访问数据
示例:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 用例上下文 │ │ 执行上下文 │ │ 报告上下文 │
│ TestCase BC │ │ Execution BC │ │ Report BC │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ 上下文映射(ACL/防腐层) │
└────────────────────┴──────────────────────┘2. 聚合根(Aggregate Root)
定义: 聚合的根节点,是外部访问聚合的唯一入口。
职责:
- 保证聚合内的一致性
- 维护聚合的不变规则
- 控制聚合的生命周期
示例:
// 测试用例聚合根
public class TestCaseAggregate {
private TestCaseId id; // 全局唯一标识
private List<TestStep> steps; // 聚合内的实体
private TestCaseStatus status; // 值对象
// 聚合根负责维护聚合内的不变规则
public void addStep(TestStep step) {
if (this.status == TestCaseStatus.EXECUTING) {
throw new BusinessException("执行中不能添加步骤");
}
this.steps.add(step);
}
}3. 实体(Entity)
定义: 具有唯一标识的领域对象,其标识在生命周期内保持不变。
特点:
- 有唯一标识(ID)
- 可以修改状态
- 生命周期内标识不变
4. 值对象(Value Object)
定义: 没有唯一标识,描述事物的某个特征。
特点:
- 无唯一标识
- 不可变
- 通过属性值相等
示例:
// 值对象示例
public class TestCaseStatus {
private final String value;
public static final TestCaseStatus DRAFT = new TestCaseStatus("DRAFT");
public static final TestCaseStatus ACTIVE = new TestCaseStatus("ACTIVE");
// 值对象相等比较属性值,而非引用
}5. 领域服务(Domain Service)
定义: 当某个操作不属于任何实体或值对象时,放在领域服务中。
特点:
- 不持有状态
- 代表领域中的重要操作
- 可以调用多个聚合
6. 仓储(Repository)
定义: 封装聚合的持久化和检索逻辑。
职责:
- 提供聚合的持久化接口
- 实现聚合的重建
- 不包含业务逻辑
📊 当前架构分析
服务清单
团队当前维护的 12 个服务:
| 服务名称 | 功能描述 | 维护难度 |
|---|---|---|
| Config-Server | 配置中心,多环境配置管理 | ⭐☆☆☆☆ |
| Eureka | 服务注册中心 | ⭐☆☆☆☆ |
| Gateway | 网关转发、权限校验 | ⭐☆☆☆☆ |
| OldGateway | 老网关(待废弃) | ⭐☆☆☆☆ |
| TMSS | 测试管理系统 | ⭐☆☆☆☆ |
| DataAnalyzer | 硬件趋势分析 | ⭐☆☆☆☆ |
| CriterionService | 判据管理 | ⭐★☆☆☆ |
| Analysis | 云图,前端质量分析 | ⭐★☆☆☆ |
| Logging | 日志服务 | ⭐★☆☆☆ |
| SmallTools | 测试小工具集合 | ⭐★☆☆☆ |
| Uttest | UT测试、告警测试 | ⭐★☆☆☆ |
| TestCaseGenerate | 用例生成、测试策略 | ⭐★☆☆☆ |
| UserCenter | 用户中心、角色权限 | ⭐★☆☆☆ |
问题诊断
┌─────────────────────────────────────────────────────────────┐
│ 当前架构问题 │
├─────────────────────────────────────────────────────────────┤
│ 1. 按"动词"划分 → 服务边界模糊 │
│ 2. 跨服务修改数据 → 一致性难以保证 │
│ 3. 通用逻辑散落 → 代码重复率高 │
│ 4. 新人上手难 → 领域知识不清晰 │
└─────────────────────────────────────────────────────────────┘🏗️ DDD 重构方案
核心思想
传统划分(按动词) DDD 划分(按名词)
┌─────────────────┐ ┌─────────────────┐
│ 测试设计服务 │ │ 测试用例领域 │
│ 测试执行服务 │ → │ 执行任务领域 │
│ 测试分析服务 │ │ 测试报告领域 │
└─────────────────┘ └─────────────────┘服务重新划分
| DDD 领域 | 包含服务 | 核心实体 |
|---|---|---|
| 用例域 | TestCase、判据管理 | TestCase, Criterion |
| 执行域 | Uttest, SmallTools | ExecutionTask, TestResult |
| 报告域 | Analysis, Logging | Report, LogEntry |
| 资源域 | UserCenter, Config | User, Configuration |
| 网关域 | Gateway, Eureka | (横切关注点) |
🔧 DDD 实践过程
阶段一:战略设计(Strategic DDD)
目标: 确定限界上下文和上下文映射
1.1 识别核心域(Core Domain)
核心域是业务成功的关键,是团队的核心竞争力所在。
┌─────────────────────────────────────────────┐
│ 领域分层 │
├─────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 核心域(Core Domain) │ │
│ │ 测试用例 + 执行引擎 + 报告分析 │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 支撑域(Supporting Domain) │ │
│ │ 配置中心、用户中心 │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 通用域(Generic Domain) │ │
│ │ 日志、监控、通知 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘1.2 定义限界上下文
根据业务边界和团队组织划分限界上下文:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 用例域 │ │ 执行域 │ │ 报告域 │
│ │ │ │ │ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
│ │ TestCase│ │ │ │Execution│ │ │ │ Report │ │
│ └─────────┘ │ │ │ Task │ │ │ └─────────┘ │
│ │ │ └─────────┘ │ │ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
│ │Criterion│ │ │ │ Result │ │ │ │Analysis │ │
│ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└──────────────────┼───────────────────┘
│
┌──────▼──────┐
│ 防腐层 │
│ (ACL) │
└─────────────┘1.3 上下文映射
| 映射类型 | 说明 | 使用场景 |
|---|---|---|
| 共享内核 | 两个上下文共享部分模型 | 通用枚举、常量 |
| 客户-供应商 | 上游提供服务,下游消费 | 内部服务调用 |
| 防腐层 | 隔离外部系统影响 | 外部系统对接 |
| 开放主机服务 | 通过 API 暴露服务 | 服务间通信 |
阶段二:战术设计(Tactical DDD)
目标: 设计聚合、实体、值对象、领域服务
2.1 聚合设计原则
┌─────────────────────────────────────────────────────────────┐
│ 聚合设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 聚合要小 — 只包含最小必需的对象 │
│ │
│ 2. 聚合内强一致性 — 聚合内数据必须一致 │
│ │
│ 3. 聚合间最终一致性 — 聚合间通过事件异步同步 │
│ │
│ 4. 聚合根是唯一入口 — 外部只能通过聚合根访问 │
│ │
└─────────────────────────────────────────────────────────────┘2.2 聚合设计示例
// ==================== 用例聚合 ====================
/**
* 测试用例聚合根
* 封装用例的所有业务规则
*/
public class TestCaseAggregate {
@Id
private TestCaseId id;
private TestCaseName name;
private TestCaseStatus status;
private List<TestStep> steps; // 聚合内实体
private TestCaseConfig config; // 值对象
// 聚合根负责维护业务不变规则
public void addStep(TestStep step) {
// 不变规则:执行中的用例不能修改
if (this.status == TestCaseStatus.EXECUTING) {
throw new IllegalStateException("用例执行中不能修改");
}
this.steps.add(step);
}
// 聚合根控制生命周期
public void submit() {
if (this.steps.isEmpty()) {
throw new BusinessException("用例至少需要一个步骤");
}
this.status = TestCaseStatus.SUBMITTED;
// 发布领域事件
DomainEvents.publish(new TestCaseSubmittedEvent(this.id));
}
}
// ==================== 实体 ====================
/**
* 测试步骤实体
* 属于 TestCaseAggregate
*/
public class TestStep {
@Id
private TestStepId id;
private StepOrder order;
private StepAction action;
private ExpectedResult expected;
// ...
}
// ==================== 值对象 ====================
/**
* 用例状态值对象
*/
public class TestCaseStatus {
public static final TestCaseStatus DRAFT = new TestCaseStatus("DRAFT");
public static final TestCaseStatus SUBMITTED = new TestCaseStatus("SUBMITTED");
public static final TestCaseStatus EXECUTING = new TestCaseStatus("EXECUTING");
public static final TestCaseStatus COMPLETED = new TestCaseStatus("COMPLETED");
private final String value;
// ...
}2.3 领域服务示例
/**
* 测试执行领域服务
* 当操作跨越多个聚合时使用
*/
@DomainService
public class TestExecutionService {
public ExecutionResult execute(Long testCaseId, ExecutionContext context) {
// 1. 获取用例
TestCaseAggregate testCase = testCaseRepository.findById(testCaseId);
// 2. 创建执行任务
ExecutionTask task = ExecutionTask.create(testCase, context);
// 3. 执行业务逻辑
ExecutionResult result = doExecute(testCase, task);
// 4. 发布领域事件
DomainEvents.publish(new TestExecutedEvent(task.getId(), result));
return result;
}
}阶段三:微服务拆分
3.1 拆分决策
| 考量因素 | 说明 |
|---|---|
| 团队边界 | 独立的团队负责独立的上下文 |
| 变更频率 | 变更频率相同的放一起 |
| 技术差异 | 需要不同技术栈的分开 |
| 数据一致性 | 强一致性要求高的在同一个服务 |
3.2 拆分后的服务架构
┌─────────────────────────────────────────────────────────────┐
│ 微服务架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 用例服务 │ │ 执行服务 │ │ 报告服务 │ │
│ │ │ │ │ │ │ │
│ │ - TestCase │ │ - Execution │ │ - Report │ │
│ │ - Criterion│ │ - Result │ │ - Analysis │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ API Gateway │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘📈 API 演进路线
Richardson 成熟度模型
参考 Richardson 成熟度模型,分四个 Level 演进:
Level 0: RPC 沼泽 ← 避免!
Level 1: REST 资源 ← 现状
Level 2: HTTP 动词 ← 优化重点
Level 3: HATEOAS ← 未来方向各 Level 说明
| Level | 特点 | 示例 |
|---|---|---|
| L0 | 所有请求到一个端点 | POST /api |
| L1 | 资源拆分 | GET /test-cases |
| L2 | 动词规范 | GET/POST/PUT/PATCH/DELETE |
| L3 | 超媒体控制 | 响应包含下一步链接 |
规范示例
// Level 3: HATEOAS 响应示例
{
"id": 123,
"name": "性能测试用例",
"status": "PASSED",
"_links": {
"self": { "href": "/test-cases/123" },
"rerun": { "href": "/execution-tasks", "method": "POST" },
"report": { "href": "/reports/test-case/123" }
}
}HTTP 状态码规范
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 成功 | GET/PUT/PATCH 成功 |
| 201 Created | 创建成功 | POST 新建资源 |
| 400 Bad Request | 请求错误 | 参数校验失败 |
| 404 Not Found | 资源不存在 | 查询不存在的资源 |
| 429 Too Many Requests | 限流 | 请求过于频繁 |
🛠️ 工具链
| 工具 | 用途 |
|---|---|
| Spring Cloud Gateway | API 网关 |
| Spring Cloud Config | 配置中心 |
| Nacos | 服务注册与发现 |
| Sentinel | 服务限流降级 |
| Swagger/OpenAPI | API 文档 |
| APM (SkyWalking) | 链路追踪 |
| Axon Framework | DDD 框架支持 |
| EventStorming | 领域建模工具 |
📊 重构收益
| 指标 | 改进前 | 改进后 | 提升 |
|---|---|---|---|
| 服务内聚性 | 低 | 高 | ✅ |
| 代码重复率 | 30%+ | <10% | ↓ 67% |
| 新人上手时间 | 2 周 | 3 天 | ↓ 80% |
| API 一致性 | 混乱 | 统一 | ✅ |
| 业务响应速度 | 慢 | 快 | ✅ |
💡 总结
核心要点
- DDD 战略设计 — 确定限界上下文和上下文映射
- DDD 战术设计 — 设计聚合、实体、值对象、领域服务
- API 演进 — 逐步提升 REST 成熟度等级
- 工具支撑 — 用 Spring Cloud 全家桶构建微服务基础设施
DDD 实践心得
┌─────────────────────────────────────────────────────────────┐
│ DDD 实践心得 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ DDD 不是银弹 — 适用于复杂业务,不适合简单 CRUD │
│ │
│ ✅ 渐进式实践 — 不要试图一次性完成所有改造 │
│ │
│ ✅ 团队共识最重要 — DDD 是团队协作的方法论 │
│ │
│ ✅ 持续重构 — 领域模型需要不断演进 │
│ │
└─────────────────────────────────────────────────────────────┘后续计划
欢迎交流讨论!
相关阅读:
欢迎交流讨论,我的 blog:sunrong.site