关于 The Standard Architecture
The Standard Architecture 是用于在系统设计和评审中应用 The Standard 架构规则的技能。它定义模型、Brokers、服务、暴露层、API 与用户界面的职责边界和依赖方向。
主要功能
- 01按目的、依赖和暴露拆分系统
- 02定义模型与 Brokers 的职责
- 03设定服务角色和依赖规则
- 04设计暴露层、通信协议与 API
- 05在重构中应用架构规则
适用场景
- 01评审系统架构和分层边界
- 02规划服务拆分与依赖方向
- 03设计 API 或用户界面架构
- 04按 The Standard 调整代码结构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-architecture安装后在 Claude Code 中输入/the-standard-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsoncontracts/contracts.jsonexamples/bad/example_bad_broker.csexample_bad_service.csexamples/good/example_logging_broker.csexample_processing_service.csexample_broker.csexample_foundation_service.csrules/rules.jsonrules.mdtemplates/logging_broker_template.csfoundation_service_template.csbroker_template.csprocessing_service_template.csvalidations/anti-patterns.mdchecklist.md详细介绍
标准架构
此技能的内容
此技能将 The Standard 的架构章节付诸实践。 它规定系统如何拆分、如何分配职责,以及依赖关系如何流动。 它涵盖模型、代理、服务、聚合器、暴露器、通信协议、API 和用户界面的架构。
明确的涵盖范围
此技能明确涵盖:
- 0.1.2 建模
- 数据载体模型
- 主模型
- 次级模型
- 关系模型
- 混合模型
- 操作模型
- 集成模型(Broker)
- 处理模型(服务)
- 暴露模型(暴露器)
- 配置模型
- 数据载体模型
- 1 Broker
- 简介
- 全景图
- 特征
- 实现本地接口
- 无流控制
- 无异常处理
- 自主管理配置
- 基于原语构建
- 命名约定
- 语言
- 向上与横向扩展
- 一个资源,一个 Broker
- 组织
- Broker 类型
- 实体 Broker
- 支持型 Broker
- 实现
- 异步化抽象
- 常见问题 / 澄清
- 标准化提供方抽象库(SPAL)
- 可扩展性
- 可配置性
- 可分发性
- 外部可模拟性(云无关)
- 从本地到全局
- 2 服务
- 简介
- 服务操作
- 验证
- 处理
- 集成
- 服务类型
- 验证器
- 编排器
- 聚合器
- 总体规则
- 执行或委托
- 二至三(Florance 模式)
- 单一暴露点
- 相同或原语 I/O 模型
- 每个服务各司其职
- 流程向前推进
- 面向 API
- 基础服务(邻近 Broker)
- 简介
- 全景图
- 特征
- 纯原语
- 单实体集成
- 业务语言
- 职责
- 抽象
- 验证
- 熔断验证
- 持续验证
- 可更新异常
- 动态规则
- 规则与验证收集器
- 混合持续验证
- 结构验证
- 逻辑验证
- 外部验证
- 依赖验证
- 映射
- 非本地模型
- 异常处理
- 异常映射
- 将外部异常本地化
- 异常分类
- 日志记录
- 异常映射
- 处理服务(高阶业务逻辑)
- 简介
- 全景图
- 特征
- 语言
- 函数语言
- 直通
- 类级语言
- 依赖项
- 单一基础服务
- 仅验证所用数据
- 职责
- 高阶逻辑
- 转换器
- 组合
- 签名映射
- 非异常本地模型
- 异常处理
- 解包并本地化基础服务异常
- 异常分类
- 日志记录
- 编排服务(复杂高阶逻辑)
- 简介
- 全景图
- 特征
- 语言
- 函数语言
- 直通
- 类级语言
- 依赖项
- 依赖平衡(Florance 模式)
- 二至三
- 完全规范化
- 半规范化
- 不规范化
- 有意义的拆分
- 契约
- 物理契约
- 虚拟契约
- 死胡同
- 职责
- 高级逻辑
- 流程组合
- 调用顺序
- 自然顺序
- 强制顺序
- 异常处理
- 解包并本地化基础服务异常
- 异常分类
- 日志记录
- 变体
- 协调
- 管理
- 超级管理
- 工作单元
- 聚合服务(结点)
- 简介
- 全景图
- 特征
- 不限制依赖项
- 不验证顺序
- 基本验证
- 直通
- 可选性
- 例程级聚合
- 纯依赖契约
- 职责
- 抽象
- 异常聚合
- 3 暴露器
- 简介
- 目的
- 纯映射
- 暴露组件类型
- 通信协议
- 用户界面
- I/O 组件
- 单一联系点
- 总结
- 3.1 通信协议
- 原则与规则
- 结果通信
- 错误报告
- RESTful API
- 语言
- 超越 CRUD 例程
- 类似动词
- 路由约定
- 名词与动词
- 控制器路由
- 例程/方法专用路由
- 复数-单数-复数
- 查询参数与 OData
- X-WWW-Form-UrlEncoded 参数
- 代码与响应
- 成功代码(2xx)
- 用户错误代码(4xx)
- 系统错误代码(5xx)
- 所有代码
- 单一依赖项
- 单一契约
- 组织
- 主页控制器
- 原则与规则
- 3.2 用户界面
- 原则与规则
- 进度(加载)
- 基本进度
- 剩余进度
- 详细进度
- 结果
- 简单
- 部分详情
- 完整详情
- 错误报告
- 信息性
- 参照式 / 隐式操作
- 可操作
- 单一依赖项
- 结构
- 基础
- 组件
- 容器
- UI 组件类型
- Web 应用程序
- 移动应用程序
- 其他类型
- Web 应用程序
- 结构
- 基础组件
- 实现
- 使用
- 限制
- 核心组件
- 元素
- 存在性
- 属性赋值
- 按 Id 搜索
- 常规搜索
- 属性
- 操作
- 存在性
- 样式
- 操作
- 限制
- 元素
- 页面
- 不显眼性
- 组织
以下主题由专门技能管理,这些技能会扩展本技能。
请参阅
## Related skills了解激活指引:
- 基础组件
- 进度(加载)
- CulDeSac 事件代理、事件服务、发布/订阅、DI 注册和启动激活 →
the-standard-events - 发布版本管理、文件版本管理、API 版本管理和弃用 →
the-standard-versioning
- 原则与规则
适用场景
在进行系统设计、架构评审、分解、重构、API 设计、UI 架构、依赖流设计,或决定行为应归属何处时,使用本技能。
相关技能
本技能定义了整个系统的结构规则。 选定特定架构模式后,由专门技能管理其详细实现。 一旦识别出相关模式,就立即激活相应技能——不要等到开始实现后才激活。
| 模式 | 技能 | 激活时机 |
|---|---|---|
| CulDeSac 事件处理:事件代理、事件服务、发布/订阅、DI 注册、启动激活 | the-standard-events | 编排服务需要发布领域事件,或订阅领域事件且不需要同步返回值时 |
| 发布版本管理、文件版本管理、API 版本管理、弃用 | the-standard-versioning | 引入模型、服务或 API 合约变更、准备发布版本,或必须弃用现有代码时 |
全系统架构规则
- 每个系统都必须按用途、依赖和暴露方式进行分解。
- 在底层,这意味着:
- 代理 = 依赖
- 服务 = 用途
- 暴露器 = 暴露方式
- 保持流程单向向前:
- 暴露器 -> 服务 -> 代理 -> 外部资源
- 绝不反转流程。
- 绝不模糊层级边界。
- 绝不通过神奇的抽象隐藏架构。
建模规则
数据载体模型
- 主模型是系统的支柱。
- 它们在物理上独立完备。
- 次级模型依赖主模型。
- 关系模型连接主模型,且应主要保存引用。
- 仅当关系本身必须携带状态或详细信息时,才允许使用混合模型。
- 保持物理模型贫血且扁平。
- 仅当编排确实需要时,才使用虚拟合约。
操作模型
- 集成模型是代理。
- 处理模型是服务。
- 暴露模型是暴露器。
- 配置模型用于组合系统,并管理启动、DI、中间件或特定平台的配置。
Broker 规则
- Broker 是业务逻辑与外部世界之间的联络方。
- Broker 必须实现本地接口。
- Broker 不得包含业务逻辑。
- Broker 不得包含流程控制。
- 不得使用 if 语句作出业务决策。
- 不得使用循环作出业务决策。
- 不得使用 switch 分支作出业务决策。
- Broker 不得处理异常。
- 让原生异常传播到相邻的 Broker 服务。
- Broker 必须拥有自己的配置。
- Broker 可以根据基本类型输入构造原生模型。
- Broker 必须使用其技术领域的语言。
- 存储 -> Insert / Select / Update / Delete
- 队列 -> Enqueue / Dequeue
- REST -> Get / Post / Put / Delete / 必要时使用自定义 HTTP 方法
- Broker 不能调用其他 Broker。
- Broker 不能依赖服务。
- 一个资源对应一个 Broker。
- 对于时间和日志等通用能力,应使用支持型 Broker。
- 对于资源或实体特定的集成,应使用实体 Broker / API Broker。
- 优先使用分部接口和分部类来组织多实体 Broker。
- 优先在 Broker 根分部中使用泛型辅助方法,以便实体分部无需直接接触原生客户端。
- 始终如一地使用异步抽象。
- 在符合实现配置的情况下,Standard 示例和抽象应优先使用 ValueTask。
- Broker 应位于 Brokers/ 目录及其命名空间下。
- Broker 配置应位于 appsettings.json 或等效的配置存储中
- Broker 配置类应位于 Brokers/ 目录及其命名空间下。
异步化抽象(§1.5.1)
所有公开暴露的接口方法——包括 Broker、服务和暴露器上的方法——都必须返回 ValueTask 或 ValueTask<T>,即使当前实现内部没有等待任何操作。
这是 The Standard §1.5.1 的要求:公开契约统一采用异步形式,这样当实现以后真正变为异步时,调用方无需更改。
| 模式 | 判定 |
|---|---|
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message); | 正确 — 使用 async 关键字,直接调用 |
public ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => ValueTask.FromResult(...) | 正确 — ValueTask.FromResult 包装同步结果 |
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => await this.SelectAllAsync<Student>(); | 正确 — 通过泛型辅助方法使用 async/await |
public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking(); | 错误 — 同步方法,未使用 ValueTask |
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => ...)); | 错误 — Task.Run 不必要地包装了同步调用 |
对服务的影响: CreateAndLog* 辅助方法也必须是异步的,因为 ILoggingBroker.LogErrorAsync 返回 ValueTask。Catch 块必须使用 throw await:
// WRONG
private StudentValidationException CreateAndLogValidationException(Xeption exception)
{
...
this.loggingBroker.LogError(studentValidationException); // no such sync method
return studentValidationException;
}
// CORRECT
private async ValueTask<StudentValidationException> CreateAndLogValidationException(
Xeption exception)
{
var studentValidationException = new StudentValidationException(...);
await this.loggingBroker.LogErrorAsync(studentValidationException);
return studentValidationException;
}
// CORRECT call site in TryCatch
catch (NullStudentException nullStudentException)
{
throw await CreateAndLogValidationException(nullStudentException);
}
Broker 补充说明
- Broker 的范围比 Repository 更广。
- Provider 与 Broker 并不相同。
- 设计上允许原生异常透过 Broker 泄漏;基础服务会将其本地化处理。
- 多实体 Broker 优先采用分部类,因为这样可以集中管理配置所有权。
- 确有需要时,应在项目级别抑制警告;否则应修复警告。
SPAL 规则
- 标准化 Provider 抽象库必须可扩展。
- 必须可配置。
- 必须可分发。
- 必须支持外部模拟,以便在本地或飞行模式下运行。
- 在可行的情况下,应从本地需求发展为全局可复用的库。
- 它们是子系统,本身也应遵循 Brokers / Services / Exposers 架构。
服务级规则
- 服务包含业务逻辑。
- 服务操作分为验证、处理和集成。
- 服务类型分为验证器、编排器和聚合器。
- 自行处理或委派处理,但不能两者兼做。
- 适用时执行 Florance Pattern。
- 暴露层必须只有一个与业务逻辑交互的入口。
- 服务应接受并返回相同的契约,或该契约的基本类型/聚合。
- 方法的基本类型输入数量超过三项时,应考虑使用契约模型。
- 每个服务都要验证自己的输入和输出。
- 服务不能调用同级的其他服务。
- 服务方法不能调用同级的其他服务方法。
- 如果存在共享逻辑,应将其提取为私有方法,供两个公共方法调用。
- 公共 API 不能调用同级的公共 API。
- 流程只能向前推进。
基础服务规则
- 基础服务是与代理相邻的服务。
- 其用途是验证、抽象、映射和表达基本业务语言。
- 必须保持纯基本类型。
- 不包含多步骤的高阶业务逻辑。
- 必须只与一个实体代理集成。
- 允许使用日志和时间等支持代理。
- 必须将技术语言转换为业务语言。
- Insert → Add
- Select → Retrieve
- Update → Modify
- Delete → Remove
- 必须使用 TryCatch/异常噪声消除包装逻辑。
- 必须在调用依赖项前执行验证。
- 它们是核心业务逻辑之前的最后一层抽象。
- 基础服务位于 Services/Foundations 下。
- 基础服务模型位于 Models/Foundations/{Entity Plural}/{Entity}.cs
- 基础服务异常模型位于 Models/Foundations/{Entity Plural}/Exceptions/
验证规则
- 必须遵循以下验证顺序:
- 结构
- 逻辑
- 外部
- 熔断验证应立即退出。
- 连续验证应累积错误,然后熔断。
- 对累积的验证数据使用可更新异常。
- 使用同时包含条件和人类可读消息的动态规则。
- 使用规则收集器进行汇总,然后抛出异常。
- 对嵌套模型使用混合式连续验证。
- 当前例程使用输出数据时,应验证该数据。
- 将原生故障映射为本地异常模型。
基础服务的异常规则
- 将原生异常本地化。
- 必须将原生异常的数据字典赋给本地化异常的数据字典。
- 尽可能将原生异常设为本地化异常的内部异常。
- 将异常分类为验证、依赖项验证、依赖项和服务异常。
- 向上游传递时保留内部本地化异常。
- 按适当的严重级别记录日志。
- 关键依赖项故障属于基础设施/配置故障。
- 不要将原生异常语义泄漏到上游纯业务逻辑中。
处理服务规则
- 处理服务承载更高阶的单实体业务逻辑。
- 它们可以组合一个基础服务中的原语操作。
- 它们可以使用工具代理。
- 它们不得直接使用实体代理。
- 它们只能依赖一个基础服务。
- 它们的命名必须包含实体名称和 Processing 后缀。
- 它们只验证输入中实际使用的内容。
- 它们可以将结果转换为 bool 或 int 等原语类型。
- 它们可以将多个原语例程组合为一个更高阶的例程。
- 它们将基础层异常映射为处理层异常类别。
- 它们必须对基础层异常进行本地化和分类。
- 处理服务位于 Services/Processings 下。
- 处理服务虚拟模型位于 Models/Processings/{Entity Plural}/{Entity}.cs
- 处理服务异常模型位于 Models/Processings/{Entity Plural}/Exceptions/
编排服务规则
- 编排服务组合多实体操作。
- 它们可以依赖基础服务或处理服务,但实体/业务依赖不能混合使用两者。
- 此外允许使用工具代理。
- 必须遵循 Florance Pattern。
- 实体/业务服务依赖必须遵循 Two-Three 规则。
- 如果依赖项超出规则,则进行规范化。
- 完全规范化
- 半规范化
- 仅在最后不得已时采用不规范化
- 每次拆分都必须有意义。
- 在保持契约纯净的前提下,编排服务可以仅做透传。
- 编排服务可以公开物理契约或虚拟契约。
- 它们负责映射和分支处理。
- 它们负责按正确顺序调用依赖项。
- 当依赖项要求特定顺序时,优先采用自然顺序。
- 如果调用依赖关系未通过输入/输出关系自然体现,则必须测试强制执行的顺序。
- 在事件/监听器场景中,死胡同式编排是有效的。
- 变体包括协调、管理和超级管理服务。
- 保持工作单元思维。
- 如果使用事件能安全地降低编排复杂度,则优先采用事件机制。
- 它们将基础层或处理层异常映射为编排层异常类别。
- 它们必须对基础层或处理层异常进行本地化和分类。
- 编排服务位于 Services/Orchestrations 下。
- 编排服务虚拟模型位于 Models/Orchestrations/{Entity Plural}/{Entity}.cs
- 编排服务异常模型位于 Models/Orchestrations/{Entity Plural}/Exceptions/
- 选择死胡同式事件机制时(规则 13 和 16),启用
the-standard-events技能——它负责规范事件层的事件代理、事件服务、验证、异常处理、DI 生命周期和启动激活。
聚合服务规则
- 聚合服务是核心业务逻辑边界处的纽结。
- 当多个同变体服务共享同一契约时,它们提供单一的暴露入口。
- 它们不添加业务逻辑。
- 它们可以有多个同变体依赖项。
- 它们不验证调用顺序。
- 它们只执行基本的结构验证。
- 它们可以通过多次调用例程或透传方法进行聚合。
- 它们是可选的。
- 它们的依赖项必须属于同一契约系列。
- 它们必须像编排类服务一样聚合异常。
- 它们必须像编排类服务一样对异常进行本地化和分类。
- 聚合服务位于 Services/Aggregations 下。
- 聚合服务异常模型位于 Models/Aggregations/{Entity Plural}/Exceptions/
暴露器规则
- 暴露器是一次性的映射层。
- 它们的用途是在核心业务逻辑的输入和输出之间进行双向映射。
- 它们只负责映射。
- 它们不得与消息代理通信。
- 它们不得包含业务逻辑。
- 它们只能依赖一个服务。
- 该依赖必须是服务,而不是消息代理。
- 它们必须提供单一联系点。
暴露组件类型
- 通信协议
- 用户界面
- I/O 组件 / 后台守护进程
通信协议规则
- 按照协议规定的形式返回核心业务结果。
- 如实返回错误报告,并使用标准化代码。
- 支持结果通信和错误报告。
- 保持映射精简、明确。
RESTful API 规则
- 控制器使用 HTTP 动词。
- Post / Get / Put / Delete
- 当业务操作超出 CRUD 范围且标准动词不适用时,可以使用自定义动词。
- 不同例程可以使用相似的动词,只要名称和路由有所区分。
- 路由中不得包含动词。
- 路由必须使用名词。
- 遵循单数名词原则。
- 优先采用资源交集,避免名词冲突。
- 控制器类名使用复数形式。
- 控制器路由通常应遵循 api/[controller]。
- 特定方法的路由应扩展控制器路由。
- 嵌套资源交集的路由应遵循复数-单数-复数模式。
- 对于可查询的读取操作,适当使用查询参数和 OData。
- 表单样式的端点输入可以使用 x-www-form-urlencoded。
- 控制器方法的参数不应超过 3 个;超过时应设计一个模型。
- 控制器只依赖一个服务。
- 控制器遵循单一契约系列。
- 控制器位于 Controllers 下。
- 每个系统都应有一个 HomeController 心跳端点。
- HomeController 不应要求安全验证,且只能表示系统是否存活。
REST 响应代码规则
- 成功响应:
- 成功的 GET / PUT / DELETE 类操作返回 200 OK
- 成功的 POST 返回 201 Created
- 委托处理或最终一致性提交返回 202 Accepted
- 用户错误响应:
- 对于映射为用户可修正的输入或领域问题的验证及依赖项验证问题,返回 400 BadRequest
- 对于未找到的情况,返回 404 NotFound
- 对于资源已存在的情况,返回 409 Conflict
- 对于资源被锁定的情况,返回 423 Locked
- 对于无效引用的情况,返回 424 FailedDependency
- 系统错误响应:
- 对于依赖项或服务故障,返回 500 InternalServerError
- 适用时,对于内部存储问题,返回 507 InsufficientStorage
- 保持安全边界。
- 除非协议要求提供经过清理的表示,否则不要暴露依赖项或服务故障的内部细节。
用户界面规则
- UI 交互组件必须映射进度、结果和错误。
- 切勿伪造进度。
- 有意识地选择进度报告级别:
- 基本进度
- 剩余进度
- 详细进度
- 有意识地选择结果报告级别:
- 简要
- 部分详情
- 完整详情
- 提交后若未告知用户发生了什么就将其重定向,属于违规行为。
- 错误报告必须告知用户发生了什么、为何发生,以及可能的后续处理方式。
- 错误报告类型:
- 信息型
- 参考型 / 隐式操作
- 可操作型
- 将技术性错误表述转换为适合用户理解的语言。
- 每个 UI 交互组件只能有一个依赖项。
- UI 结构必须区分:
- 基础组件
- 组件
- 容器
- 基础组件封装原生或第三方 UI 元素。
- 核心组件只能与一个视图服务集成。
- 容器/页面仅负责协调 UI 组件和路由。
- 容器不得包含 UI 逻辑。
- 容器不得直接使用基础组件。
- 在可行的情况下,基础组件封装的非本地组件不得超过一个。
- 基础组件不得包含业务逻辑、异常处理、验证或计算。
- 页面是路由容器,无需包含业务逻辑。
- 将标记、代码隐藏和样式文件分开。
- 将 UI 组织在 Views/Bases、Views/Components 和 Views/Pages 下。
- 按领域驱动的方式组织 UI。
Web 应用特定规则
- 基础组件的行为类似代理。
- 核心组件的行为类似服务与控制器的混合体。
- 核心组件采用测试驱动方式开发。
- 核心组件由以下部分构成:
- 元素
- 样式
- 操作
- 元素测试必须涵盖:
- 是否存在
- 属性
- 操作
- 存在性测试可使用:
- 属性赋值
- 按 id 搜索
- 常规搜索
- 样式主要属于核心组件,而非基础组件。
- 页面组合组件并表示路由。
- 页面通常无需单元测试。
- 保持 UI 简洁;不要将 CSS、C# 和标记放在同一个文件中。
- 同一层级的核心组件不得相互调用。
- 一个视图服务对应一个核心组件和一个视图模型。
版本控制与破坏性变更
此技能规定每一层的正确结构应是什么。
当结构变更破坏现有契约时——例如新增或移除模型属性、更改服务签名,或变更 API 路由或响应结构——这就不再只是架构决策。
此时启用 the-standard-versioning 技能。
版本控制技能规定:
- 何时以及如何递增发布版本
- 如何在
Vn子文件夹下引入新的模型版本,同时不覆盖早期版本 - 如何在保留早期服务的同时引入新服务版本
- 如何对 API 路由进行版本控制,以免破坏早期使用方
- 如何标示模型、服务和路由的弃用状态
架构技能和版本控制技能相辅相成:
- 架构回答:结构是否正确?
- 版本控制回答:如何安全地进行变更?
架构审查清单
审查或生成架构时,请验证以下所有内容:
- 明确说明目的。
- 模型的范围与目的相符。
- 依赖项和暴露方式与目的相互独立。
- Broker 的职责精简且可弃用。
- 服务负责业务逻辑。
- 验证顺序正确。
- 服务层流程只向前推进。
- 同一层级的服务之间不会互相调用。
- 遵循 Florance Pattern。
- 暴露层具有单一联系点。
- API 遵循路由和状态码规则。
- UI 遵循单一依赖、非侵入性和结构规则。
- 架构保持可读、自主且可重写。
- 如果使用 Cul-De-Sac 事件机制,则必须已启用
the-standard-events技能,且符合其规则。 - 如果模型、服务或 API 契约发生变更,或正在发布新版本,则必须已启用
the-standard-versioning技能,且符合其规则。
根据所提供的实现规范纳入的 .NET 实现配置
以下附录保留了所提供的实现配置,使该技能既能落实抽象的 Standard,也能落实你所附的具体 .NET 实现风格。
1. 概述
Standard 是由 Hassan Habib 编写的一套观点鲜明的软件工程标准。 它规定了一种由三种性质构成的架构:
| 层级 | 职责 |
|---|---|
| Brokers | 对任何外部依赖(数据库、API、日志)的轻量抽象 |
| Services | 所有业务逻辑——验证、编排、协调 |
| Exposers | 暴露服务的入口点(控制器、端点) |
该项目目前为 LegacyUser 实体(基于存储)和 Person 实体(基于 API)实现了 Brokers 和 Foundation Services,并提供了一个用于向外部 API 发送 Person 实体的 API Broker。
2. 项目结构
RedRhino.Core.Synchronizer/
├── Brokers/
│ ├── Apis/
│ │ ├── IModernApiBroker.cs (partial interface — base)
│ │ ├── IModernApiBroker.Persons.cs (partial interface — entity)
│ │ ├── ModernApiBroker.cs (partial class — base)
│ │ └── ModernApiBroker.Persons.cs (partial class — entity)
│ ├── Loggings/
│ │ ├── ILoggingBroker.cs
│ │ └── LoggingBroker.cs
│ └── Storages/
│ ├── IStorageBroker.cs (partial interface — base)
│ ├── IStorageBroker.LegacyUsers.cs (partial interface — entity)
│ ├── StorageBroker.cs (partial class — base)
│ └── StorageBroker.LegacyUsers.cs (partial class — entity)
├── Models/
│ ├── Foundations/
│ ├── Persons/
│ │ ├── Person.cs
│ │ ├── PersonType.cs
│ │ ├── PersonRecordState.cs
│ │ └── Exceptions/
│ │ ├── NullPersonException.cs
│ │ ├── InvalidPersonException.cs
│ │ ├── AlreadyExistsPersonException.cs
│ │ ├── FailedPersonDependencyException.cs
│ │ ├── FailedPersonServiceException.cs
│ │ ├── PersonValidationException.cs
│ │ ├── PersonDependencyException.cs
│ │ ├── PersonDependencyValidationException.cs
│ │ └── PersonServiceException.cs
│ └── LegacyUsers/
│ ├── LegacyUser.cs
│ └── Exceptions/
│ ├── NullLegacyUserException.cs
│ ├── InvalidLegacyUserException.cs
│ ├── AlreadyExistsLegacyUserException.cs
│ ├── FailedStorageLegacyUserDependencyException.cs
│ ├── FailedLegacyUserServiceException.cs
│ ├── LegacyUserValidationException.cs
│ ├── LegacyUserDependencyException.cs
│ ├── LegacyUserDependencyValidationException.cs
│ └── LegacyUserServiceException.cs
├── Services/
│ └── Foundations/
│ ├── LegacyUsers/
│ │ ├── ILegacyUserService.cs
│ │ ├── LegacyUserService.cs (partial — logic)
│ │ ├── LegacyUserService.Validations.cs (partial — validations)
│ │ └── LegacyUserService.Exceptions.cs (partial — exception handling)
│ └── Persons/
│ ├── IPersonService.cs
│ ├── PersonService.cs (partial — logic)
│ ├── PersonService.Validations.cs (partial — validations)
│ └── PersonService.Exceptions.cs (partial — exception handling)
└── Program.cs
RedRhino.Core.Synchronizer.Tests.Units/
└── Services/
└── Foundations/
├── LegacyUsers/
│ ├── LegacyUserServiceTests.cs (partial — setup & helpers)
│ ├── LegacyUserServiceTests.Logic.{Method}.cs (partial — happy-path tests)
│ ├── LegacyUserServiceTests.Validations.{Method}.cs (partial — validation tests)
│ └── LegacyUserServiceTests.Exceptions.{Method}.cs (partial — exception tests)
└── Persons/
├── PersonServiceTests.cs (partial — setup & helpers)
├── PersonServiceTests.Logic.{Method}.cs (partial — happy-path tests)
├── PersonServiceTests.Validations.{Method}.cs (partial — validation tests)
└── PersonServiceTests.Exceptions.{Method}.cs (partial — exception tests)
3. Brokers
Brokers 是外部资源的轻量封装层。它们不包含任何业务逻辑。
规则——通用辅助方法: 每个 Broker 基础 partial 必须公开私有泛型辅助方法(例如,
InsertAsync<T>、PostAsync<T>),用于封装底层客户端调用。实体 partial 文件绝不能直接引用私有客户端成员(例如,this.apiClient、this.Entry(...)),而应委托给这些泛型辅助方法。这样可使实体 partial 与具体客户端解耦,并能在单一位置替换底层实现。
3.1 存储 Broker
| 方面 | 实现 |
|---|---|
| 基类 | EFxceptionsContext(来自 EFxceptions 库——为 DbContext 封装有意义的 EF 异常) |
| 接口 | partial interface IStorageBroker——按实体拆分 |
| 类 | partial class StorageBroker——按实体拆分 |
| 通用 CRUD 辅助方法 | 基类分部中的私有辅助方法:InsertAsync<T>、SelectAllAsync<T>、SelectAsync<T>、UpdateAsync<T>、DeleteAsync<T> |
| 配置 | 从 IConfiguration 读取 DefaultConnection;在构造时调用 this.Database.Migrate() |
基类分部——StorageBroker.cs
此文件负责:IConfiguration、OnConfiguring、构造函数(调用 Database.Migrate()),
以及私有通用 CRUD 辅助方法。除此之外,不负责其他内容。
arch-014——基类分部中不得包含
DbSet<>。DbSet<Student> Students位于StorageBroker.Students.cs,而不在此文件中。 基类分部绝不得声明特定于实体的成员。
public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
// No DbSet<> properties here. Each entity partial declares its own.
private readonly IConfiguration configuration;
public StorageBroker(IConfiguration configuration)
{
this.configuration = configuration;
// arch-011: Must be called — applies pending migrations at startup.
// Omitting this means the schema is never applied.
this.Database.Migrate();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString =
this.configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
// arch-012: All EF operations live here. Entity partials call these helpers only.
private async ValueTask<T> InsertAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Added;
await this.SaveChangesAsync();
return entity;
}
// SelectAllAsync<T> wraps IQueryable in a ValueTask so all entity methods
// follow the uniform async/await pattern. AsNoTracking() is applied here.
private ValueTask<IQueryable<T>> SelectAllAsync<T>() where T : class =>
ValueTask.FromResult<IQueryable<T>>(this.Set<T>().AsNoTracking());
private async ValueTask<T> SelectAsync<T>(Guid entityId) where T : class =>
await this.FindAsync<T>(entityId);
private async ValueTask<T> UpdateAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Modified;
await this.SaveChangesAsync();
return entity;
}
private async ValueTask<T> DeleteAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Deleted;
await this.SaveChangesAsync();
return entity;
}
}
实体分部——StorageBroker.Students.cs
arch-014:
DbSet<Student>在此处的实体分部中声明,而不在StorageBroker.cs中。
public partial class StorageBroker
{
// DbSet<Student> belongs in this entity partial file, not in StorageBroker.cs.
public DbSet<Student> Students { get; set; }
public async ValueTask<Student> InsertStudentAsync(Student student) =>
await this.InsertAsync(student);
// arch-009: SelectAll* must be async ValueTask<IQueryable<T>>.
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking();
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Set<Student>();
// CORRECT: delegate to SelectAllAsync<T>() — never touch DbSet or EF members directly.
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() =>
await this.SelectAllAsync<Student>();
public async ValueTask<Student> SelectStudentByIdAsync(Guid studentId) =>
await this.SelectAsync<Student>(studentId);
public async ValueTask<Student> UpdateStudentAsync(Student student) =>
await this.UpdateAsync(student);
public async ValueTask<Student> DeleteStudentAsync(Student student) =>
await this.DeleteAsync(student);
}
**规则:**每个实体的接口和类都各自使用一个分部文件。
规则——先确定范围再生成脚手架(arch-013):名为
BROKERS-student-insert的分支仅实现InsertStudentAsync。如果提示词对所需操作含糊不清, Agent 必须先询问,然后才能创建分支或编写代码。一个 Broker 分支 = 一个操作。**规则——分支操作命名(prac-016):**Broker 分支操作使用基础设施动词——
insert、select-all、select-by-id、update、delete。绝不要在BROKERS分支名称中使用业务动词(add、retrieve、modify、remove)。
3.2 API Broker
| 方面 | 实现 |
|---|---|
| HTTP 客户端 | IRESTFulApiFactoryClient(来自 RESTFulSense 库——封装 HttpClient) |
| 接口 | partial interface IModernApiBroker——按实体拆分 |
| 类 | partial class ModernApiBroker——按实体拆分 |
| 通用 HTTP 辅助方法 | 基类分部中的私有 PostAsync<T>(),供不同实体复用 |
| 配置 | 从 IConfiguration 读取 ApiConfigurations:Url,以设置 HttpClient.BaseAddress |
基类分部——ModernApiBroker.cs
public partial class ModernApiBroker : IModernApiBroker
{
private readonly IRESTFulApiFactoryClient apiClient;
public ModernApiBroker(IConfiguration configuration)
{
var httpClient = new HttpClient()
{
BaseAddress =
new Uri(configuration.GetValue<string>("ApiConfigurations:Url"))
};
this.apiClient = new RESTFulApiFactoryClient(httpClient);
}
private async ValueTask<T> PostAsync<T>(string relativeUrl, T content) =>
await this.apiClient.PostContentAsync<T>(relativeUrl, content);
}
实体分部——ModernApiBroker.Persons.cs
public partial class ModernApiBroker
{
private const string PersonsRelativeUrl = "api/persons";
public async ValueTask<Person> PostPersonAsync(Person person) =>
await PostAsync(PersonsRelativeUrl, person);
}
**规则:**实体分部将请求委托给通用辅助方法(
PostAsync<T>)——绝不直接调用this.apiClient。
3.3 日志代理
对 ILogger<T> 的专用抽象。代理仅提供与标准日志级别对应的 异步 ValueTask 方法:
| 方法 | 日志级别 |
|---|---|
LogInformationAsync | Information |
LogTraceAsync | Trace |
LogDebugAsync | Debug |
LogWarningAsync | Warning |
LogErrorAsync | Error |
LogCriticalAsync | Critical |
LoggingBroker.cs
public class LoggingBroker : ILoggingBroker
{
private readonly ILogger<LoggingBroker> logger;
public LoggingBroker(ILogger<LoggingBroker> logger) =>
this.logger = logger;
public async ValueTask LogInformationAsync(string message) =>
this.logger.LogInformation(message);
public async ValueTask LogTraceAsync(string message) =>
this.logger.LogTrace(message);
public async ValueTask LogDebugAsync(string message) =>
this.logger.LogDebug(message);
public async ValueTask LogWarningAsync(string message) =>
this.logger.LogWarning(message);
public async ValueTask LogErrorAsync(Exception exception) =>
this.logger.LogError(exception, exception.Message);
public async ValueTask LogCriticalAsync(Exception exception) =>
this.logger.LogCritical(exception, exception.Message);
}
ILoggingBroker.cs
public interface ILoggingBroker
{
ValueTask LogInformationAsync(string message);
ValueTask LogTraceAsync(string message);
ValueTask LogDebugAsync(string message);
ValueTask LogWarningAsync(string message);
ValueTask LogErrorAsync(Exception exception);
ValueTask LogCriticalAsync(Exception exception);
}
规则 — 异步表达式主体,不使用 Task.Run: 每个方法都使用
async关键字,并直接委托给ILogger<T>。 切勿将调用包装在Task.Run()或new ValueTask(Task.Run(...))中。ILogger<T>是同步的;将其包装在Task.Run()中会带来不必要的线程池开销, 并生成低效的、在堆上分配的ValueTask。错误:
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => this.logger.LogWarning(message)));正确:
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message);
规则 — DI 注册: 必须在
Program.cs中显式注册日志代理。 还必须包含AddLogging()(或宿主构建器的默认配置),以便正确解析ILogger<LoggingBroker>。builder.Services.AddLogging(); builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();省略任意一行都会导致运行时 DI 解析失败。
4. 模型
4.1 实体模型
实体类是普通的 POCO,位于 Models/{Service Type}/{Entity}/ 下。它们不包含行为,只有属性。属性上的领域注释用于说明验证意图
(例如,可空规则、电子邮件格式、电话号码格式)。
LegacyUser 类位于 Models/Foundations/LegacyUsers/ 下。
Person 类位于 Models/Foundations/Persons/ 下。
4.2 异常模型
异常模型位于 Models/{Service Type}/{Entity}/Exceptions/ 中,并按照 The Standard 形成两层异常层次结构。
LegacyUser 类位于 Models/Foundations/LegacyUsers/Exceptions/ 下。
Person 类位于 Models/Foundations/Persons/Exceptions/ 下。
内部/外部异常层次结构因 Foundation Service 所使用的代理类型而异。基于存储的服务(例如 LegacyUserService)处理 SQL/EF 异常,而基于 API 的服务(例如 PersonService)处理 RESTFulSense HTTP 异常。
4.2.1 基于存储的异常(LegacyUser)
内部(本地)异常
| 异常 | 用途 |
|---|---|
NullLegacyUserException | 实体为 null |
InvalidLegacyUserException | 一个或多个属性级验证失败 |
AlreadyExistsLegacyUserException | 检测到重复键 |
FailedStorageLegacyUserDependencyException | SQL/存储级别失败 |
FailedLegacyUserServiceException | 意外的运行时故障 |
外层(分类)异常
| 异常 | 类别 | 包装的内部异常 |
|---|---|---|
LegacyUserValidationException | Validation | NullLegacyUserException、InvalidLegacyUserException |
LegacyUserDependencyException | Dependency | FailedStorageLegacyUserDependencyException |
LegacyUserDependencyValidationException | DependencyValidation | AlreadyExistsLegacyUserException、InvalidLegacyUserException(来自 DbUpdateException) |
LegacyUserServiceException | Service | FailedLegacyUserServiceException |
4.2.2 基于 API 的异常(Person)
内层(本地)异常
| 异常 | 用途 |
|---|---|
NullPersonException | 实体为 null |
InvalidPersonException | 一个或多个属性级验证失败,或 API 返回 BadRequest |
AlreadyExistsPersonException | API 返回 Conflict(409) |
FailedPersonDependencyException | HTTP 级别故障(任何 HTTP 错误或 HttpRequestException) |
FailedPersonServiceException | 意外的运行时故障 |
外层(分类)异常
| 异常 | 类别 | 包装的内部异常 |
|---|---|---|
PersonValidationException | Validation | NullPersonException、InvalidPersonException |
PersonDependencyValidationException | DependencyValidation | InvalidPersonException(来自 BadRequest)、AlreadyExistsPersonException(来自 Conflict) |
PersonDependencyException | Dependency(严重) | FailedPersonDependencyException(来自 Unauthorized、Forbidden、NotFound、UrlNotFound、HttpRequestException) |
PersonDependencyException | Dependency(非严重) | FailedPersonDependencyException(来自 InternalServerError、ServiceUnavailable) |
PersonServiceException | Service | FailedPersonServiceException |
所有异常均继承自 Xeption(来自 Xeption NuGet 包),它提供 UpsertDataList 和 ThrowIfContainsErrors,用于汇总验证数据。
5. 服务 — 基础
基础服务直接位于 Brokers 之上,是它们的唯一使用者。
5.1 部分类布局
该服务拆分为三个部分类文件:
| 部分类文件 | 职责 |
|---|---|
{Entity}Service.cs | 构造函数、DI 字段、公开业务逻辑 |
{Entity}Service.Validations.cs | 所有 Validate* 和 IsInvalid* 方法 |
{Entity}Service.Exceptions.cs | TryCatch 委托模式、CreateAndLog* 辅助方法 |
5.2 依赖注入
Foundation 服务只依赖 Broker,绝不依赖其他服务。
基于存储的服务(LegacyUserService):
public partial class LegacyUserService : ILegacyUserService
{
private readonly IStorageBroker storageBroker;
private readonly ILoggingBroker loggingBroker;
public LegacyUserService(
IStorageBroker storageBroker,
ILoggingBroker loggingBroker) { ... }
}
基于 API 的服务(PersonService):
public partial class PersonService : IPersonService
{
private readonly IModernApiBroker modernApiBroker;
private readonly ILoggingBroker loggingBroker;
public PersonService(
IModernApiBroker modernApiBroker,
ILoggingBroker loggingBroker) { ... }
}
5.3 业务逻辑 — TryCatch 模式
每个公共方法都会委托给 TryCatch 包装器,由它捕获、包装、记录并重新抛出分类异常:
基于存储:
public ValueTask<LegacyUser> AddLegacyUserAsync(LegacyUser legacyUser) =>
TryCatch(async () =>
{
ValidateLegacyUser(legacyUser);
return await this.storageBroker.InsertLegacyUserAsync(legacyUser);
});
基于 API:
public ValueTask<Person> AddPersonAsync(Person person) =>
TryCatch(async () =>
{
ValidatePerson(person);
return await this.modernApiBroker.PostPersonAsync(person);
});
5.4 验证
验证采用动态规则 + 聚合模式:
Validate(
(IsInvalid(legacyUser.Id), nameof(LegacyUser.Id)),
(IsInvalid(legacyUser.UserPK), nameof(LegacyUser.UserPK)),
(IsInvalid(legacyUser.UserName), nameof(LegacyUser.UserName)),
...);
每个 IsInvalid 重载都会返回一个匿名对象,其中包含 Condition(bool)和 Message(string)。
Validate 方法会通过 UpsertDataList 汇总所有失败项,生成单个 InvalidLegacyUserException,并调用
ThrowIfContainsErrors()。
针对特定领域规则的自定义验证器包括:
| 验证器 | 规则 |
|---|---|
IsInvalid(Guid) | 不能是 Guid.Empty |
IsInvalid(int) | 不能是 default (0) |
IsInvalid(string) | 不能为 null、空字符串或空白字符 |
IsInvalidLob(int) | 必须大于 0 |
IsInvalidEmail | 基于正则表达式检查电子邮件格式 |
6. 异常处理
每个 {Entity}Service.Exceptions.cs 中的 TryCatch 方法都会实现 The Standard 的
异常映射模式。具体的 catch 块会根据服务使用的 Broker 类型而有所不同。
6.1 基于存储的异常映射(LegacyUser)
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception
──────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullLegacyUserException → LegacyUserValidationException
(validation rules fail) → InvalidLegacyUserException → LegacyUserValidationException
SqlException → FailedStorage...Exception → LegacyUserDependencyException (Critical)
DuplicateKeyException → AlreadyExists...Exception → LegacyUserDependencyValidationException
DbUpdateException → InvalidLegacy...Exception → LegacyUserDependencyValidationException
Exception (catch-all) → FailedService...Exception → LegacyUserServiceException
6.2 基于 API 的异常映射(Person)
对于通过 RESTFulSense 调用外部 API 的服务,异常映射会涵盖所有 HTTP 响应异常。TryCatch 中 catch 块的顺序很重要——更具体的异常必须出现在其基类之前。
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception Log Level
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullPersonException → PersonValidationException LogError
(validation rules fail) → InvalidPersonException → PersonValidationException LogError
HttpResponseBadRequestException → InvalidPersonException → PersonDependencyValidationException LogError
HttpResponseConflictException → AlreadyExistsPersonException → PersonDependencyValidationException LogError
HttpResponseUnauthorizedException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseForbiddenException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseUrlNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseInternalServerErrorException → FailedPersonDependencyException → PersonDependencyException LogError
HttpResponseServiceUnavailableException → FailedPersonDependencyException → PersonDependencyException LogError
HttpRequestException → FailedPersonDependencyException → PersonDependencyException LogCritical
Exception (catch-all) → FailedPersonServiceException → PersonServiceException LogError
规则 — 关键与非关键依赖项: 表示 API 端点不可达或无法访问的异常(
Unauthorized、Forbidden、NotFound、UrlNotFound、HttpRequestException)会通过LogCriticalAsync记录,因为它们意味着配置或基础设施故障。服务器端错误(InternalServerError、ServiceUnavailable)会通过LogErrorAsync记录,因为它们可能是暂时性的。
每个 CreateAndLog* 辅助方法都会:
- 将内部异常包装在分类外层异常中。
- 通过
ILoggingBroker在相应级别记录(LogErrorAsync或LogCriticalAsync)。 - 返回外层异常,以便
TryCatch能够throw它。
7. 依赖注册(Exposers)
所有依赖关系都在 Program.cs 中按照 The Standard 的显式注册风格进行配置:
builder.Services.AddDbContext<StorageBroker>();
builder.Services.AddTransient<IStorageBroker, StorageBroker>();
builder.Services.AddTransient<IModernApiBroker, ModernApiBroker>();
builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();
builder.Services.AddTransient<ILegacyUserService, LegacyUserService>();
builder.Services.AddTransient<IPersonService, PersonService>();
Broker 和 Foundation 服务均注册为瞬时。
10. Naming Conventions
| Element | Pattern | Example |
|---|---|---|
| Broker interface | I{Resource}Broker | IStorageBroker, IModernApiBroker, ILoggingBroker |
| Broker class | {Resource}Broker | StorageBroker, ModernApiBroker, LoggingBroker |
| Broker method | {Action}{Entity}Async | InsertLegacyUserAsync, PostPersonAsync |
| Service interface | I{Entity}Service | ILegacyUserService |
| Service class | {Entity}Service | LegacyUserService |
| Service method | Add{Entity}Async | AddLegacyUserAsync |
| Inner exception | {Adjective}{Entity}Exception | NullLegacyUserException |
| Outer exception | {Entity}{Category}Exception | LegacyUserValidationException |
| Test class | {Entity}ServiceTests | LegacyUserServiceTests |
| Test method | Should{Action}Async / ShouldThrow{Exception}On{Action}… | ShouldAddLegacyUserAsync |