实现经过验证的后端架构模式,包括整洁架构、六边形架构和领域驱动设计。在设计新微服务的整洁架构、重构单体应用以使用限界上下文、实现六边形或洋葱架构模式,或调试应用层之间的依赖循环时,使用此技能。
架构模式
掌握经过验证的后端架构模式,包括整洁架构、六边形架构和领域驱动设计,以构建可维护、可测试和可扩展的系统。
给定: 需要架构设计的服务边界或模块。
产出: 具有清晰依赖规则、接口定义和测试边界的分层结构。
何时使用此技能
- 从头设计新的后端服务或微服务
- 重构单体应用,其中业务逻辑与ORM模型或HTTP关注点纠缠在一起
- 在将系统拆分为服务之前建立限界上下文
- 调试基础设施代码渗入领域层的依赖循环
- 创建可测试的代码库,其中用例测试不需要运行数据库
- 实现领域驱动设计的战术模式(聚合、值对象、领域事件)
核心概念
1. 整洁架构(Uncle Bob)
层(依赖向内流动):
- 实体:核心业务模型,无框架导入
- 用例:应用业务规则,编排实体
- 接口适配器:控制器、展示器、网关——在用例和外部格式之间转换
- 框架与驱动:UI、数据库、外部服务——均在最外层
关键原则:
- 依赖仅向内指向;内层对外层一无所知
- 业务逻辑独立于框架、数据库和交付机制
- 每一层边界通过抽象接口跨越
- 无需UI、数据库或外部服务即可测试
2. 六边形架构(端口与适配器)
组件:
- 领域核心:业务逻辑在此,无框架依赖
- 端口:定义核心如何与外部世界交互的抽象接口(驱动和驱动端)
- 适配器:端口的具体实现(PostgreSQL适配器、Stripe适配器、REST适配器)
优势:
- 无需触及核心即可切换实现(例如,将PostgreSQL替换为DynamoDB)
- 在测试中使用内存适配器——无需Docker
- 技术决策推迟到边缘
3. 领域驱动设计(DDD)
战略模式:
- 限界上下文:为一个子域隔离一个连贯的模型;避免在整个系统中共享单一模型
- 上下文映射:定义上下文之间的关系(防腐层、共享内核、开放主机服务)
- 通用语言:代码中的每个术语与领域专家使用的术语一致
战术模式:
- 实体:具有稳定标识且随时间变化的对象
- 值对象:由其属性标识的不可变对象(Email、Money、Address)
- 聚合:一致性边界;只有根可从外部访问
- 仓库:持久化和重建聚合;抽象存储机制
- 领域事件:捕获领域内发生的事情;用于跨聚合协调
详细模式和工作示例
详细的模式文档位于 references/details.md。当上述导航层级不足时,请阅读该文件。
测试——内存适配器
正确应用整洁架构的标志是每个用例都可以在纯单元测试中执行,无需真实数据库、Docker或网络:
# tests/unit/test_create_user.py
import asyncio
from typing import Dict, Optional
from domain.entities.user import User
from domain.interfaces.user_repository import IUserRepository
from use_cases.create_user import CreateUserUseCase, CreateUserRequest
class InMemoryUserRepository(IUserRepository):
def __init__(self):
self._store: Dict[str, User] = {}
async def find_by_id(self, user_id: str) -> Optional[User]:
return self._store.get(user_id)
async def find_by_email(self, email: str) -> Optional[User]:
return next((u for u in self._store.values() if u.email == email), None)
async def save(self, user: User) -> User:
self._store[user.id] = user
return user
async def delete(self, user_id: str) -> bool:
return self._store.pop(user_id, None) is not None
async def test_create_user_succeeds():
repo = InMemoryUserRepository()
use_case = CreateUserUseCase(user_repository=repo)
response = await use_case.execute(CreateUserRequest(email="alice@example.com", name="Alice"))
assert response.success
assert response.user.email == "alice@example.com"
assert response.user.id is not None
async def test_duplicate_email_rejected():
repo = InMemoryUserRepository()
use_case = CreateUserUseCase(user_repository=repo)
await use_case.execute(CreateUserRequest(email="alice@example.com", name="Alice"))
response = await use_case.execute(CreateUserRequest(email="alice@example.com", name="Alice2"))
assert not response.success
assert "already exists" in response.error
故障排除
用例测试需要运行数据库
业务逻辑已泄漏到基础设施层。将所有数据库调用移到 IRepository 接口后面,并在测试中注入内存实现(参见上面的测试部分)。用例构造函数必须接受抽象端口,而不是具体类。
层之间的循环导入
常见症状是 use_cases 和 adapters 之间的 ImportError: cannot import name X。这是因为用例导入了具体的适配器类而不是抽象端口。强制执行规则:use_cases/ 仅从 domain/(实体和接口)导入。绝不能从 adapters/ 或 infrastructure/ 导入。
框架装饰器出现在领域实体中
如果领域实体上出现了 SQLAlchemy Column() 或 Pydantic Field() 注解,则该实体不再纯净。在 adapters/repositories/ 中创建单独的ORM模型,并在仓库的 _to_entity() 方法中与领域实体进行映射。
所有逻辑最终都放在控制器中
当控制器超出HTTP解析和响应格式化的范围时,将逻辑提取到用例类中。控制器方法应仅做三件事:解析请求、调用用例、映射响应。
值对象过晚抛出错误
在 __post_init__(Python)或构造函数中验证不变量,以便无法构造无效的 Email 或 Money。这会在边界处暴露不良数据,而不是在业务逻辑深处。
跨限界上下文的上下文泄漏
如果 Order 上下文从 Identity 上下文导入 User 实体,则引入防腐层。Order 上下文应持有自己的轻量级 CustomerId 值对象,并仅通过显式接口调用 Identity 上下文。
高级模式
有关详细的DDD限界上下文映射、完整的多服务项目树、防腐层实现和洋葱架构比较,请参阅:
相关技能
microservices-patterns— 在将单体分解为服务时应用这些架构模式cqrs-implementation— 使用整洁架构作为CQRS命令/查询分离的结构基础saga-orchestration— Saga需要定义良好的聚合边界,DDD战术模式提供了这一点event-store-design— 聚合产生的领域事件直接馈送到事件存储中






