关于 The Standard Events
The Standard Events 是用于按照 The Standard 实现和评审事件驱动通信的 Skill,规定事件服务与 Broker 的结构、验证和异常规则,以及编排、启动注册和测试约定。
主要功能
- 01CulDeSac 事件模式指南
- 02事件 Broker 与服务结构
- 03发布和订阅操作约定
- 04验证与异常映射规则
- 05启动激活和依赖注入规则
- 06按操作划分的测试约定
适用场景
- 01设计服务间单向领域事件流程
- 02评审事件服务的验证与错误处理
- 03在应用启动时注册订阅
- 04组织发布和订阅操作的测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-events安装后在 Claude Code 中输入/the-standard-events确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsoncontracts/contracts.jsonexamples/bad/example_bad_event_service.csexamples/good/example_event_service_orchestration_di.csexample_event_service_publish.csexample_event_service_subscribe.csrules/rules.jsonrules.mdtemplates/brokers/event_broker_template.cstemplates/foundations/event_service_template.csevent_service_test_subscribe_template.csevent_service_test_class_subscribe_template.csevent_service_test_class_root_template.csevent_service_test_publish_template.csevent_service_test_class_publish_template.csvalidations/checklist.mdanti-patterns.md详细介绍
标准事件
此技能的内容
此技能规定了在符合 The Standard 的系统中如何架构、实现和测试事件。 涵盖 CulDeSac 模式、事件代理、基础事件服务、验证、异常映射,以及发布和订阅操作的测试结构。
明确的涵盖范围
此技能明确涵盖:
- CulDeSac 模式及其在符合 Standard 的系统中存在的原因
- 事件代理的结构和实现
- 基础事件服务的实现:main、Validations 和 Exceptions 文件
- Publish 和 SubscribeTo 操作契约及命名约定
- 对 null 实体和 null 事件处理程序的验证规则
- 异常映射:仅映射 Validation 和 Service 异常(不映射 Dependency 异常)
- 代理、服务和测试的文件及 partial class 结构
- 测试结构:每个操作都包含 Logic、Validations 和 Exceptions
- 在整个事件层中强制执行的命名约定
- 编排集成:发布者和订阅者编排服务如何使用事件服务
- 依赖注入注册:事件代理和事件服务的生命周期要求
- 启动激活:应用程序启动时必须调用 SubscribeTo 的位置和方式
使用时机
在实现、审查、扩展或测试服务之间的事件驱动通信时,请使用此技能。 在决定如何发布领域事件、如何订阅事件,或如何在事件服务中验证和处理故障时,请使用此技能。 在确定某项服务是否适合采用 CulDeSac 模式时,请使用此技能。
CulDeSac 模式
当服务需要向外发送领域事件,且不期待任何订阅者同步返回结果时,适用 CulDeSac 模式。 它是调用图中的死胡同:数据单向流动,调用方不会被阻塞以等待结果,订阅者可以独立响应。
为什么使用事件
- 在有界上下文之间解耦服务,避免产生直接的服务间依赖。
- 允许多个订阅者独立响应同一个领域事件。
- 避免创建必须了解所有下游使用者的编排服务。
- 支持即发即弃的流程,让发布者无需获得订阅者的确认。
- 使下游服务能够扩展和演进,而不影响发布者。
- 防止任何单个编排器随着时间推移积累过多职责。
事件服务原则
- 事件服务始终是基础服务。
- 事件服务位于领域模型与事件基础设施的边界。
- 事件服务不得依赖其他服务,只能依赖事件代理。
- 每个实体的事件服务恰好公开两个操作:Publish[Entity]Async 和 SubscribeTo[Entity]Event。
- 事件服务不注入日志代理——其设计本身不依赖日志记录。
- 事件服务使用两个 TryCatch 委托,将异常处理与业务逻辑隔离。
- 事件服务不得捕获 Dependency 或 CriticalDependency 异常——它们不会调用 HTTP 或存储 API。
代理实现原则
- 事件代理是事件服务唯一的基础设施依赖项。
- 事件代理封装所选的任何事件基础设施,不强制使用特定事件库。
- 每个实体都拥有自己的事件客户端实例,该实例以该实体为类型,并在代理构造函数中声明。
- 代理拆分为四个文件:基础接口、实体接口 partial、基础实现、实体实现 partial。
- 代理操作只是对底层事件基础设施的简单转发——代理中不包含任何逻辑。
编排集成
事件服务仅由编排服务使用。 控制器、处理服务和其他基础服务不得直接依赖事件服务。
发布方
需要引发领域事件的编排服务会调用事件服务上的 Publish[Entity]Async。
编排服务负责组合实体,并将发布调用委托给事件服务。
发布方编排服务不知道哪些服务会订阅,只知道自己必须发布事件。
订阅方
需要对领域事件做出响应的编排服务会公开一个名为 SubscribeTo[Entity]Events(复数)的方法。
该方法会调用 this.[entity]EventService.SubscribeTo[Entity]Event(handler),其中 handler 是一个私有异步方法。
handler 接收实体并执行编排响应——根据需要调用基础服务。
只有订阅方编排服务知道该如何处理事件;发布方不知道订阅方的存在。
命名区别:复数与单数
- 事件服务方法始终使用单数形式:
SubscribeTo[Entity]Event。 - 编排服务包装方法始终使用复数形式:
SubscribeTo[Entity]Events。 - 使用复数形式的包装方法,是因为编排服务可能会将事件路由到多个子操作。
- 启动时调用的是复数形式的包装方法——绝不能直接调用单数形式的事件服务方法。
依赖注入与启动激活
注册规则
- 所有发布或订阅事件的编排服务都必须注册到 DI 中。
IEventBroker和EventBroker所需的 DI 生命周期取决于所使用的事件基础设施。- 对于内存事件基础设施(例如 LeVent):必须使用单例以确保正确性——客户端实例会在内存中保存订阅处理程序注册;使用作用域或瞬时注册会创建未注册处理程序的新实例,因此事件永远不会送达。
- 对于外部事件基础设施(例如 Azure Service Bus 或 EventHighway):遵循客户端库自身的生命周期建议——订阅保存在外部服务中,独立于客户端实例而存在,但客户端通常需要使用单例生命周期来复用连接并保持消息处理器处于活动状态。
I[Entity]EventService和[Entity]EventService的注册生命周期必须与其依赖的 broker 相匹配——绝不能将服务注册为比 broker 生命周期更长。- 如有疑问,优先使用单例——这对所有已知的事件基础设施都安全,也可避免订阅无声地失败。
启动激活规则
- 事件订阅不会自动激活——必须在应用程序启动时显式启动。
- DI 容器构建完成后,必须调用每个订阅方编排服务的
SubscribeTo[Entity]Events()方法。 - 激活调用必须出现在
Configure()(Startup.cs 风格)或最小化 API 主机设置中的等效位置。 - 在 Startup.cs 中:使用
app.ApplicationServices.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events()。 - 在最小化 API(Program.cs)中:在
app = builder.Build()后使用app.Services.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events()。 - 如果缺少启动激活调用,则不会有任何订阅方收到事件——订阅将无声地无法注册。
- 每个订阅方编排服务的每个订阅都必须且只能有一个启动激活调用。
事件服务的验证规则
发布验证
- 发布前验证实体不为 null。
- 实体为 null 时必须立即抛出 NullEventException(中断处理)。
- 实体为 null 的验证不得收集其他错误——必须立即中断。
订阅验证
- 订阅前验证事件处理程序委托不为 null。
- handler 为 null 时必须立即抛出 NullEventHandlerException(中断处理)。
- handler 为 null 的验证不得收集其他错误——必须立即中断。
异常处理规则
- 所有验证失败都必须生成一个包装内部异常的 [Entity]EventValidationException。
- 所有意外异常都必须先包装在 FailedEventServiceException 中,再包装在 [Entity]EventServiceException 中。
- FailedEventServiceException 必须将原始异常作为 innerException,并包含其 Data 集合。
- 事件服务不使用 DependencyValidationException 或 DependencyException 类别。
- 必须使用 TryCatch 委托,将异常处理与核心逻辑分离。
命名约定
- 接口:I[Entity]EventService。
- 实现:[Entity]EventService(internal partial class)。
- 发布操作:Publish[Entity]Async(返回 ValueTask,异步)。
- 订阅操作:SubscribeTo[Entity]Event(返回 void,同步)。
- Subscribe 绝不能命名为 ListenTo[Entity]Event。
- Broker 接口:IEventBroker(共享),包含位于 IEventBroker_[Entity].cs 中的实体专用 partial。
- 异常命名:Null[Entity]EventException、Null[Entity]EventHandlerException、Failed[Entity]EventServiceException、[Entity]EventValidationException、[Entity]EventServiceException。
事件服务测试规则
- 首先测试 SubscribeTo[Entity]Events 的成功路径。
- 其次测试 Publish[Entity]Async 的成功路径。
- 第三步测试验证失败(处理程序为 null、实体为 null)。
- 第四步测试服务异常(两个操作中出现的意外错误)。
- 事件服务测试不得声明 loggingBrokerMock——这里没有 logging broker。
- 订阅处理程序的 Mock 必须使用 Mock<Func<[Entity], ValueTask>>。
- 每个测试都必须以 eventBrokerMock.VerifyNoOtherCalls() 结束。
- 对预期调用使用 Times.Once,对因验证失败而跳过的调用使用 Times.Never。