整洁架构:实体、用例与接口适配
好的,这是为您生成的“软件架构整洁之道:实体、用例与接口适配”教程页面内容,可直接展示。
软件架构整洁之道:实体、用例与接口适配
欢迎来到整洁架构的核心区域。本教程将带你深入理解 Robert C. Martin 提出的整洁架构(Clean Architecture)中最关键的三个层次:实体、用例与接口适配。我们将摒弃空洞的理论,用最通俗的语言和实例,帮你建立起抵御框架变更、工具演进和界面替换的稳固代码核心。
什么是整洁架构?
在开始之前,让我们先对齐一个核心理念:好的架构,其中心是业务逻辑,而非技术实现。 整洁架构是一种以业务价值为中心的软件设计方法,它通过严格的分层和依赖规则,让代码变得可测试、可维护且独立于外部细节。
核心原则:依赖规则——源代码的依赖方向,必须始终指向内部,从外部细节指向内部核心。
整洁架构中的同心圆,从内到外依次是:实体 > 用例 > 接口适配 > 框架与驱动。今天我们聚焦于前三层,它们是系统核心业务的承载者。
第一层:实体(Enterprise Business Rules)
实体是系统最核心、最稳定的一层。它封装了企业级的关键业务规则和数据。这一层的变化,往往意味着企业本身业务逻辑的根本性改变。
实体到底是什么?
实体既可以是拥有方法的对象,也可以是纯粹的数据结构加函数。关键在于,它不依赖任何外部事物。
- 它封装最关键的商业规则:如“一笔订单金额不能为负”、“一个用户必须拥有唯一的邮箱”。
- 它定义核心业务对象:如
Order、User、Product等。 - 它可以是其他层的数据载体:但它本身不知道如何被持久化,如何被展示。
实体的设计准则
- 零依赖原则:实体不应该导入任何框架、数据库、网络相关的库。它只依赖语言自身的基础类型和工具库。
- 高内聚:与某个核心概念相关的规则和数据应放在一起。例如,
Order实体自己应该拥有addItem()方法,而不是让外部服务去操作一堆散落的数据。 - 用例无关性:实体不知道谁在调用它,也不知道调用者的目的是什么。它只是纯粹的、自解释的业务规则容器。
示例:一个简洁的 User 实体
# Python示例,同样适用于其他语言
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: Optional[int] # ID可能由持久化层生成,实体本身不关心
email: str
name: str
def change_email(self, new_email: str) -> None:
"""核心业务规则:邮箱必须包含‘@’"""
if "@" not in new_email:
raise ValueError("无效的邮箱地址")
self.email = new_email
这个实体不知道它将被如何储存(数据库、文件),也不知道哪些界面会用到它。它只负责保护“邮箱必须有@”这条企业级规则。
第二层:用例(Application Business Rules)
用例层位于实体层的外围,它包含应用特定的业务规则。如果说实体回答“什么是正确的”,那么用例回答“在特定场景下,我们需要做什么”。
用例是什么?
用例是软件系统能够完成的一个具体的、面向用户的任务。它编排实体,实现一个完整的业务目标。例如:“用户注册”、“提交订单”、“导出报表”。
- 它依赖于实体:用例会获取、创建和修改实体。
- 它定义输入和输出:通过输入端口(Input Port)接收请求数据,通过输出端口(Output Port)传递结果或外部依赖。
- 它不关心外界如何触发它:可以是HTTP请求、命令行、或者测试用例。
用例中的端口与适配器概念
用例层最重要的设计是依赖倒置,它通过定义接口(端口)来断开与外部层的直接耦合。
- 输入端口 (Input Port):用例类本身实现的接口,供外层调用。
- 输出端口 (Output Port):用例需要的、由外层实现的接口。例如,用例需要保存实体,它会定义一个
UserRepository接口,而不关心具体的数据库实现。
设计准则
- 单一职责:每个用例只做一件事。避免庞大的“上帝服务”。
- 依赖倒置:用例绝不直接实例化外部类。它声明所需的抽象接口,由外层在运行时注入具体实现。
- 数据隔离:用例的输入输出应该使用与特定界面、传输协议无关的简单数据结构(DTO),而不是
HttpRequest对象。
示例:“注册新用户”用例
# 定义输出端口(抽象的接口)
class UserRepository:
def save(self, user: User) -> User:
raise NotImplementedError
def exists_by_email(self, email: str) -> bool:
raise NotImplementedError
# 用例的请求和响应模型(简单的数据类)
class RegisterUserRequest:
email: str
name: str
class RegisterUserResponse:
success: bool
user_id: Optional[int]
message: str
# 用例实现(输入端口本身)
class RegisterUserUseCase:
def __init__(self, user_repo: UserRepository):
self._user_repo = user_repo
def execute(self, request: RegisterUserRequest) -> RegisterUserResponse:
# 1. 应用特定规则:检查邮箱是否已注册
if self._user_repo.exists_by_email(request.email):
return RegisterUserResponse(False, None, "邮箱已被注册")
# 2. 创建领域实体,实体自身会进行核心验证
try:
user = User(id=None, email=request.email, name=request.name)
except ValueError as e:
return RegisterUserResponse(False, None, str(e))
# 3. 通过输出端口保存实体
saved_user = self._user_repo.save(user)
# 4. 返回成功响应
return RegisterUserResponse(True, saved_user.id, "注册成功")
注意,这个用例完全不知道 HTTP、数据库操作。它可以被纯单元测试覆盖,而无需启动任何框架。
第三层:接口适配(Interface Adapters)
这一层是核心与外部世界的“翻译官”。它负责将外层传入的数据转换为用例可以理解的格式,并将用例的输出结果转换为外层可用的格式。
接口适配层的职责
- 控制反转容器:将用例所需的实现(如
UserRepository的具体数据库实现)注入到用例中。 - 数据格式转换:将
http.Request中的 JSON 字段提取出来,构造成RegisterUserRequest;将RegisterUserResponse转换成 JSON 响应、HTML 视图或命令行打印。 - 跨边界通信:管理 UI、存储、外部服务等具体技术。
三种常见的适配器
1. 控制器/表现层适配器(MVC、REST)
它将 Web 框架(如 Flask、Express、Spring MVC)的路由处理程序转换为用例调用。
# Flask 示例:一个适配器
from flask import Flask, request, jsonify
app = Flask(__name__)
# 依赖组装(通常在工厂或启动脚本中)
sql_repo = SQLUserRepository(session) # 实现UserRepository
register_usecase = RegisterUserUseCase(sql_repo)
@app.route('/users', methods=['POST'])
def register():
data = request.get_json()
# 将外部HTTP数据转换为用例请求结构
req = RegisterUserRequest(email=data['email'], name=data['name'])
# 调用用例
response = register_usecase.execute(req)
# 将用例响应转换为HTTP JSON
return jsonify({
"id": response.user_id,
"message": response.message
}), 201 if response.success else 400
控制器极其纤薄,它不含业务逻辑,只负责格式转换和委托。
2. 持久层适配器(Repository 实现)
它实现用例层定义的 UserRepository 接口,使用具体 ORM、SQL 语句或文件系统。
class SQLUserRepository(UserRepository):
def __init__(self, db_session):
self.session = db_session
def save(self, user: User) -> User:
# 将领域实体转换为数据库模型(假如使用ORM)
db_user = UserModel(email=user.email, name=user.name)
self.session.add(db_user)
self.session.commit()
# 返回带ID的实体
return User(id=db_user.id, email=db_user.email, name=db_user.name)
def exists_by_email(self, email: str) -> bool:
return self.session.query(UserModel).filter_by(email=email).first() is not None
3. 外部服务适配器(邮件、短信等)
适配邮件发送接口,将其转化为用例可以调用的抽象端口。
适配层的设计守则
- 绝对不能包含领域逻辑:任何与业务决策相关的代码都不能出现在控制器或视图里。
- 可替换的即插即用设备:适配器应该像电源插头一样,可以随时更换而不影响核心电器运转。从 REST 换成 GraphQL 或 gRPC,只需更换适配器。
- 透明度:适配器层代码应该直观地显示“它在做什么转换”,让任何一个开发者都能快速理解数据的边界。
它们如何协同工作:一个完整的请求流程
当一个“用户注册”请求到达时,数据流动路径如下:
- Web 框架接收 HTTP 请求,将其委托给控制器适配器。
- 控制器提取 JSON 数据,创建
RegisterUserRequest,并调用用例RegisterUserUseCase.execute()。 - 用例通过依赖注入握有
UserRepository实例(实际为SQLUserRepository适配器)。 - 用例检查
exists_by_email(),然后创建User实体,执行change_email等操作(如果存在)。 - 用例调用
repository.save(user),将实体传递给持久层适配器。 - 持久层适配器将实体转换为数据库记录,保存,并返回带有
id的新实体。 - 用例将结果封装为
RegisterUserResponse,返回给控制器。 - 控制器将
RegisterUserResponse转换为 HTTP JSON 响应,返回给客户端。
整个过程中,实体和用例完全不涉及任何框架、协议或特定数据库。它们可以脱离运行环境被单独测试和演化。
总结:整洁架构带来的价值
通过将系统划分为实体、用例、接口适配,你获得了:
- 可测试性:业务规则可以在毫秒级内完成单元测试,无需启动服务器。
- 可维护性:需求变更往往只影响某一层。修改邮件服务不会触及核心业务逻辑。
- 可扩展性:想要支持新的前端、新的数据库?只需编写一个新的适配器。
- 延迟决策:你可以先专注于核心业务建模,再决定使用哪种框架或数据库。
整洁架构不是繁文缛节,而是一种让你在软件开发这场“战争”中,始终掌握主动权、不被外部工具绑架的战略。从今天起,尝试在你的项目中应用这三层划分,哪怕只是一个模块,你都会立刻感受到代码重获的自由与清晰。
实践是理解整洁架构的唯一道路。 一旦你亲手将实体、用例与适配器剥离,你便再也无法忍受业务逻辑与框架代码交织的混乱状态。祝你在整洁架构的道路上越走越远!