mcpField
T
The Standard Architecture

The Standard Architecture

github.com/hassanhabib开发2058 次安装13 周前更新
代码仓库

关于 The Standard Architecture

The Standard Architecture 是用于在系统设计和评审中应用 The Standard 架构规则的技能。它定义模型、Brokers、服务、暴露层、API 与用户界面的职责边界和依赖方向。

主要功能

  1. 01按目的、依赖和暴露拆分系统
  2. 02定义模型与 Brokers 的职责
  3. 03设定服务角色和依赖规则
  4. 04设计暴露层、通信协议与 API
  5. 05在重构中应用架构规则

适用场景

  1. 01评审系统架构和分层边界
  2. 02规划服务拆分与依赖方向
  3. 03设计 API 或用户界面架构
  4. 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.json
contracts/
contracts.json
examples/bad/
example_bad_broker.cs
example_bad_service.cs
examples/good/
example_logging_broker.cs
example_processing_service.cs
example_broker.cs
example_foundation_service.cs
rules/
rules.json
rules.md
templates/
logging_broker_template.cs
foundation_service_template.cs
broker_template.cs
processing_service_template.cs
validations/
anti-patterns.md
checklist.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 合约变更、准备发布版本,或必须弃用现有代码时

全系统架构规则

  1. 每个系统都必须按用途、依赖和暴露方式进行分解。
  2. 在底层,这意味着:
    • 代理 = 依赖
    • 服务 = 用途
    • 暴露器 = 暴露方式
  3. 保持流程单向向前:
    • 暴露器 -> 服务 -> 代理 -> 外部资源
  4. 绝不反转流程。
  5. 绝不模糊层级边界。
  6. 绝不通过神奇的抽象隐藏架构。

建模规则

数据载体模型
  1. 主模型是系统的支柱。
    • 它们在物理上独立完备。
  2. 次级模型依赖主模型。
  3. 关系模型连接主模型,且应主要保存引用。
  4. 仅当关系本身必须携带状态或详细信息时,才允许使用混合模型。
  5. 保持物理模型贫血且扁平。
  6. 仅当编排确实需要时,才使用虚拟合约。
操作模型
  1. 集成模型是代理。
  2. 处理模型是服务。
  3. 暴露模型是暴露器。
  4. 配置模型用于组合系统,并管理启动、DI、中间件或特定平台的配置。

Broker 规则

  1. Broker 是业务逻辑与外部世界之间的联络方。
  2. Broker 必须实现本地接口。
  3. Broker 不得包含业务逻辑。
  4. Broker 不得包含流程控制。
    • 不得使用 if 语句作出业务决策。
    • 不得使用循环作出业务决策。
    • 不得使用 switch 分支作出业务决策。
  5. Broker 不得处理异常。
    • 让原生异常传播到相邻的 Broker 服务。
  6. Broker 必须拥有自己的配置。
  7. Broker 可以根据基本类型输入构造原生模型。
  8. Broker 必须使用其技术领域的语言。
    • 存储 -> Insert / Select / Update / Delete
    • 队列 -> Enqueue / Dequeue
    • REST -> Get / Post / Put / Delete / 必要时使用自定义 HTTP 方法
  9. Broker 不能调用其他 Broker。
  10. Broker 不能依赖服务。
  11. 一个资源对应一个 Broker。
  12. 对于时间和日志等通用能力,应使用支持型 Broker。
  13. 对于资源或实体特定的集成,应使用实体 Broker / API Broker。
  14. 优先使用分部接口和分部类来组织多实体 Broker。
  15. 优先在 Broker 根分部中使用泛型辅助方法,以便实体分部无需直接接触原生客户端。
  16. 始终如一地使用异步抽象。
  17. 在符合实现配置的情况下,Standard 示例和抽象应优先使用 ValueTask。
  18. Broker 应位于 Brokers/ 目录及其命名空间下。
  19. Broker 配置应位于 appsettings.json 或等效的配置存储中
  20. 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 补充说明
  1. Broker 的范围比 Repository 更广。
  2. Provider 与 Broker 并不相同。
  3. 设计上允许原生异常透过 Broker 泄漏;基础服务会将其本地化处理。
  4. 多实体 Broker 优先采用分部类,因为这样可以集中管理配置所有权。
  5. 确有需要时,应在项目级别抑制警告;否则应修复警告。
SPAL 规则
  1. 标准化 Provider 抽象库必须可扩展。
  2. 必须可配置。
  3. 必须可分发。
  4. 必须支持外部模拟,以便在本地或飞行模式下运行。
  5. 在可行的情况下,应从本地需求发展为全局可复用的库。
  6. 它们是子系统,本身也应遵循 Brokers / Services / Exposers 架构。

服务级规则

  1. 服务包含业务逻辑。
  2. 服务操作分为验证、处理和集成。
  3. 服务类型分为验证器、编排器和聚合器。
  4. 自行处理或委派处理,但不能两者兼做。
  5. 适用时执行 Florance Pattern。
  6. 暴露层必须只有一个与业务逻辑交互的入口。
  7. 服务应接受并返回相同的契约,或该契约的基本类型/聚合。
    • 方法的基本类型输入数量超过三项时,应考虑使用契约模型。
  8. 每个服务都要验证自己的输入和输出。
  9. 服务不能调用同级的其他服务。
  10. 服务方法不能调用同级的其他服务方法。
    • 如果存在共享逻辑,应将其提取为私有方法,供两个公共方法调用。
  11. 公共 API 不能调用同级的公共 API。
  12. 流程只能向前推进。

基础服务规则

  1. 基础服务是与代理相邻的服务。
  2. 其用途是验证、抽象、映射和表达基本业务语言。
  3. 必须保持纯基本类型。
    • 不包含多步骤的高阶业务逻辑。
  4. 必须只与一个实体代理集成。
  5. 允许使用日志和时间等支持代理。
  6. 必须将技术语言转换为业务语言。
    • Insert → Add
    • Select → Retrieve
    • Update → Modify
    • Delete → Remove
  7. 必须使用 TryCatch/异常噪声消除包装逻辑。
  8. 必须在调用依赖项前执行验证。
  9. 它们是核心业务逻辑之前的最后一层抽象。
  10. 基础服务位于 Services/Foundations 下。
  11. 基础服务模型位于 Models/Foundations/{Entity Plural}/{Entity}.cs
  12. 基础服务异常模型位于 Models/Foundations/{Entity Plural}/Exceptions/
验证规则
  1. 必须遵循以下验证顺序:
    • 结构
    • 逻辑
    • 外部
  2. 熔断验证应立即退出。
  3. 连续验证应累积错误,然后熔断。
  4. 对累积的验证数据使用可更新异常。
  5. 使用同时包含条件和人类可读消息的动态规则。
  6. 使用规则收集器进行汇总,然后抛出异常。
  7. 对嵌套模型使用混合式连续验证。
  8. 当前例程使用输出数据时,应验证该数据。
  9. 将原生故障映射为本地异常模型。
基础服务的异常规则
  1. 将原生异常本地化。
    • 必须将原生异常的数据字典赋给本地化异常的数据字典。
    • 尽可能将原生异常设为本地化异常的内部异常。
  2. 将异常分类为验证、依赖项验证、依赖项和服务异常。
  3. 向上游传递时保留内部本地化异常。
  4. 按适当的严重级别记录日志。
  5. 关键依赖项故障属于基础设施/配置故障。
  6. 不要将原生异常语义泄漏到上游纯业务逻辑中。

处理服务规则

  1. 处理服务承载更高阶的单实体业务逻辑。
  2. 它们可以组合一个基础服务中的原语操作。
  3. 它们可以使用工具代理。
  4. 它们不得直接使用实体代理。
  5. 它们只能依赖一个基础服务。
  6. 它们的命名必须包含实体名称和 Processing 后缀。
  7. 它们只验证输入中实际使用的内容。
  8. 它们可以将结果转换为 bool 或 int 等原语类型。
  9. 它们可以将多个原语例程组合为一个更高阶的例程。
  10. 它们将基础层异常映射为处理层异常类别。
  11. 它们必须对基础层异常进行本地化和分类。
  12. 处理服务位于 Services/Processings 下。
  13. 处理服务虚拟模型位于 Models/Processings/{Entity Plural}/{Entity}.cs
  14. 处理服务异常模型位于 Models/Processings/{Entity Plural}/Exceptions/

编排服务规则

  1. 编排服务组合多实体操作。
  2. 它们可以依赖基础服务或处理服务,但实体/业务依赖不能混合使用两者。
  3. 此外允许使用工具代理。
  4. 必须遵循 Florance Pattern。
  5. 实体/业务服务依赖必须遵循 Two-Three 规则。
  6. 如果依赖项超出规则,则进行规范化。
    • 完全规范化
    • 半规范化
    • 仅在最后不得已时采用不规范化
  7. 每次拆分都必须有意义。
  8. 在保持契约纯净的前提下,编排服务可以仅做透传。
  9. 编排服务可以公开物理契约或虚拟契约。
  10. 它们负责映射和分支处理。
  11. 它们负责按正确顺序调用依赖项。
  12. 当依赖项要求特定顺序时,优先采用自然顺序。
  13. 如果调用依赖关系未通过输入/输出关系自然体现,则必须测试强制执行的顺序。
  14. 在事件/监听器场景中,死胡同式编排是有效的。
  15. 变体包括协调、管理和超级管理服务。
  16. 保持工作单元思维。
  17. 如果使用事件能安全地降低编排复杂度,则优先采用事件机制。
  18. 它们将基础层或处理层异常映射为编排层异常类别。
  19. 它们必须对基础层或处理层异常进行本地化和分类。
  20. 编排服务位于 Services/Orchestrations 下。
  21. 编排服务虚拟模型位于 Models/Orchestrations/{Entity Plural}/{Entity}.cs
  22. 编排服务异常模型位于 Models/Orchestrations/{Entity Plural}/Exceptions/
  23. 选择死胡同式事件机制时(规则 13 和 16),启用 the-standard-events 技能——它负责规范事件层的事件代理、事件服务、验证、异常处理、DI 生命周期和启动激活。

聚合服务规则

  1. 聚合服务是核心业务逻辑边界处的纽结。
  2. 当多个同变体服务共享同一契约时,它们提供单一的暴露入口。
  3. 它们不添加业务逻辑。
  4. 它们可以有多个同变体依赖项。
  5. 它们不验证调用顺序。
  6. 它们只执行基本的结构验证。
  7. 它们可以通过多次调用例程或透传方法进行聚合。
  8. 它们是可选的。
  9. 它们的依赖项必须属于同一契约系列。
  10. 它们必须像编排类服务一样聚合异常。
  11. 它们必须像编排类服务一样对异常进行本地化和分类。
  12. 聚合服务位于 Services/Aggregations 下。
  13. 聚合服务异常模型位于 Models/Aggregations/{Entity Plural}/Exceptions/

暴露器规则

  1. 暴露器是一次性的映射层。
  2. 它们的用途是在核心业务逻辑的输入和输出之间进行双向映射。
  3. 它们只负责映射。
  4. 它们不得与消息代理通信。
  5. 它们不得包含业务逻辑。
  6. 它们只能依赖一个服务。
  7. 该依赖必须是服务,而不是消息代理。
  8. 它们必须提供单一联系点。
暴露组件类型
  1. 通信协议
  2. 用户界面
  3. I/O 组件 / 后台守护进程

通信协议规则

  1. 按照协议规定的形式返回核心业务结果。
  2. 如实返回错误报告,并使用标准化代码。
  3. 支持结果通信和错误报告。
  4. 保持映射精简、明确。

RESTful API 规则

  1. 控制器使用 HTTP 动词。
    • Post / Get / Put / Delete
  2. 当业务操作超出 CRUD 范围且标准动词不适用时,可以使用自定义动词。
  3. 不同例程可以使用相似的动词,只要名称和路由有所区分。
  4. 路由中不得包含动词。
  5. 路由必须使用名词。
  6. 遵循单数名词原则。
  7. 优先采用资源交集,避免名词冲突。
  8. 控制器类名使用复数形式。
  9. 控制器路由通常应遵循 api/[controller]。
  10. 特定方法的路由应扩展控制器路由。
  11. 嵌套资源交集的路由应遵循复数-单数-复数模式。
  12. 对于可查询的读取操作,适当使用查询参数和 OData。
  13. 表单样式的端点输入可以使用 x-www-form-urlencoded。
  14. 控制器方法的参数不应超过 3 个;超过时应设计一个模型。
  15. 控制器只依赖一个服务。
  16. 控制器遵循单一契约系列。
  17. 控制器位于 Controllers 下。
  18. 每个系统都应有一个 HomeController 心跳端点。
  19. HomeController 不应要求安全验证,且只能表示系统是否存活。
REST 响应代码规则
  1. 成功响应:
    • 成功的 GET / PUT / DELETE 类操作返回 200 OK
    • 成功的 POST 返回 201 Created
    • 委托处理或最终一致性提交返回 202 Accepted
  2. 用户错误响应:
    • 对于映射为用户可修正的输入或领域问题的验证及依赖项验证问题,返回 400 BadRequest
    • 对于未找到的情况,返回 404 NotFound
    • 对于资源已存在的情况,返回 409 Conflict
    • 对于资源被锁定的情况,返回 423 Locked
    • 对于无效引用的情况,返回 424 FailedDependency
  3. 系统错误响应:
    • 对于依赖项或服务故障,返回 500 InternalServerError
    • 适用时,对于内部存储问题,返回 507 InsufficientStorage
  4. 保持安全边界。
    • 除非协议要求提供经过清理的表示,否则不要暴露依赖项或服务故障的内部细节。

用户界面规则

  1. UI 交互组件必须映射进度、结果和错误。
  2. 切勿伪造进度。
  3. 有意识地选择进度报告级别:
    • 基本进度
    • 剩余进度
    • 详细进度
  4. 有意识地选择结果报告级别:
    • 简要
    • 部分详情
    • 完整详情
  5. 提交后若未告知用户发生了什么就将其重定向,属于违规行为。
  6. 错误报告必须告知用户发生了什么、为何发生,以及可能的后续处理方式。
  7. 错误报告类型:
    • 信息型
    • 参考型 / 隐式操作
    • 可操作型
  8. 将技术性错误表述转换为适合用户理解的语言。
  9. 每个 UI 交互组件只能有一个依赖项。
  10. UI 结构必须区分:
    • 基础组件
    • 组件
    • 容器
  11. 基础组件封装原生或第三方 UI 元素。
  12. 核心组件只能与一个视图服务集成。
  13. 容器/页面仅负责协调 UI 组件和路由。
  14. 容器不得包含 UI 逻辑。
  15. 容器不得直接使用基础组件。
  16. 在可行的情况下,基础组件封装的非本地组件不得超过一个。
  17. 基础组件不得包含业务逻辑、异常处理、验证或计算。
  18. 页面是路由容器,无需包含业务逻辑。
  19. 将标记、代码隐藏和样式文件分开。
  20. 将 UI 组织在 Views/Bases、Views/Components 和 Views/Pages 下。
  21. 按领域驱动的方式组织 UI。
Web 应用特定规则
  1. 基础组件的行为类似代理。
  2. 核心组件的行为类似服务与控制器的混合体。
  3. 核心组件采用测试驱动方式开发。
  4. 核心组件由以下部分构成:
    • 元素
    • 样式
    • 操作
  5. 元素测试必须涵盖:
    • 是否存在
    • 属性
    • 操作
  6. 存在性测试可使用:
    • 属性赋值
    • 按 id 搜索
    • 常规搜索
  7. 样式主要属于核心组件,而非基础组件。
  8. 页面组合组件并表示路由。
  9. 页面通常无需单元测试。
  10. 保持 UI 简洁;不要将 CSS、C# 和标记放在同一个文件中。
  11. 同一层级的核心组件不得相互调用。
  12. 一个视图服务对应一个核心组件和一个视图模型。

版本控制与破坏性变更

此技能规定每一层的正确结构应是什么。 当结构变更破坏现有契约时——例如新增或移除模型属性、更改服务签名,或变更 API 路由或响应结构——这就不再只是架构决策。 此时启用 the-standard-versioning 技能。

版本控制技能规定:

  • 何时以及如何递增发布版本
  • 如何在 Vn 子文件夹下引入新的模型版本,同时不覆盖早期版本
  • 如何在保留早期服务的同时引入新服务版本
  • 如何对 API 路由进行版本控制,以免破坏早期使用方
  • 如何标示模型、服务和路由的弃用状态

架构技能和版本控制技能相辅相成:

  • 架构回答:结构是否正确?
  • 版本控制回答:如何安全地进行变更?

架构审查清单

审查或生成架构时,请验证以下所有内容:

  1. 明确说明目的。
  2. 模型的范围与目的相符。
  3. 依赖项和暴露方式与目的相互独立。
  4. Broker 的职责精简且可弃用。
  5. 服务负责业务逻辑。
  6. 验证顺序正确。
  7. 服务层流程只向前推进。
  8. 同一层级的服务之间不会互相调用。
  9. 遵循 Florance Pattern。
  10. 暴露层具有单一联系点。
  11. API 遵循路由和状态码规则。
  12. UI 遵循单一依赖、非侵入性和结构规则。
  13. 架构保持可读、自主且可重写。
  14. 如果使用 Cul-De-Sac 事件机制,则必须已启用 the-standard-events 技能,且符合其规则。
  15. 如果模型、服务或 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 方法:

方法日志级别
LogInformationAsyncInformation
LogTraceAsyncTrace
LogDebugAsyncDebug
LogWarningAsyncWarning
LogErrorAsyncError
LogCriticalAsyncCritical

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检测到重复键
FailedStorageLegacyUserDependencyExceptionSQL/存储级别失败
FailedLegacyUserServiceException意外的运行时故障
外层(分类)异常
异常类别包装的内部异常
LegacyUserValidationExceptionValidationNullLegacyUserException、InvalidLegacyUserException
LegacyUserDependencyExceptionDependencyFailedStorageLegacyUserDependencyException
LegacyUserDependencyValidationExceptionDependencyValidationAlreadyExistsLegacyUserException、InvalidLegacyUserException(来自 DbUpdateException)
LegacyUserServiceExceptionServiceFailedLegacyUserServiceException
4.2.2 基于 API 的异常(Person)
内层(本地)异常
异常用途
NullPersonException实体为 null
InvalidPersonException一个或多个属性级验证失败,或 API 返回 BadRequest
AlreadyExistsPersonExceptionAPI 返回 Conflict(409)
FailedPersonDependencyExceptionHTTP 级别故障(任何 HTTP 错误或 HttpRequestException)
FailedPersonServiceException意外的运行时故障
外层(分类)异常
异常类别包装的内部异常
PersonValidationExceptionValidationNullPersonException、InvalidPersonException
PersonDependencyValidationExceptionDependencyValidationInvalidPersonException(来自 BadRequest)、AlreadyExistsPersonException(来自 Conflict)
PersonDependencyExceptionDependency(严重)FailedPersonDependencyException(来自 Unauthorized、Forbidden、NotFound、UrlNotFound、HttpRequestException)
PersonDependencyExceptionDependency(非严重)FailedPersonDependencyException(来自 InternalServerError、ServiceUnavailable)
PersonServiceExceptionServiceFailedPersonServiceException

所有异常均继承自 Xeption(来自 Xeption NuGet 包),它提供 UpsertDataList 和 ThrowIfContainsErrors,用于汇总验证数据。


5. 服务 — 基础

基础服务直接位于 Brokers 之上,是它们的唯一使用者。

5.1 部分类布局

该服务拆分为三个部分类文件:

部分类文件职责
{Entity}Service.cs构造函数、DI 字段、公开业务逻辑
{Entity}Service.Validations.cs所有 Validate* 和 IsInvalid* 方法
{Entity}Service.Exceptions.csTryCatch 委托模式、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* 辅助方法都会:

  1. 将内部异常包装在分类外层异常中。
  2. 通过 ILoggingBroker 在相应级别记录(LogErrorAsync 或 LogCriticalAsync)。
  3. 返回外层异常,以便 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

ElementPatternExample
Broker interfaceI{Resource}BrokerIStorageBroker, IModernApiBroker, ILoggingBroker
Broker class{Resource}BrokerStorageBroker, ModernApiBroker, LoggingBroker
Broker method{Action}{Entity}AsyncInsertLegacyUserAsync, PostPersonAsync
Service interfaceI{Entity}ServiceILegacyUserService
Service class{Entity}ServiceLegacyUserService
Service methodAdd{Entity}AsyncAddLegacyUserAsync
Inner exception{Adjective}{Entity}ExceptionNullLegacyUserException
Outer exception{Entity}{Category}ExceptionLegacyUserValidationException
Test class{Entity}ServiceTestsLegacyUserServiceTests
Test methodShould{Action}Async / ShouldThrow{Exception}On{Action}…ShouldAddLegacyUserAsync

评论

支持 Markdown