# Evently **Repository Path**: ymjake/evently ## Basic Information - **Project Name**: Evently - **Description**: Evently是一个模块化单体项目,与米兰Jovanovic的Evently课程材料一致。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-02 - **Last Updated**: 2026-07-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Evently `Evently` 是一个模块化单体实践项目,最初灵感来自 Milan Jovanovic 的 Evently 课程,但在基础设施选型上做了不少主动调整,目标不是照搬课程源码,而是探索一套更轻量、更显式、也更容易演进的实现方式。 当前项目围绕四个核心业务模块展开: - `Users` - `Events` - `Ticketing` - `Attendance` ## 项目定位 这个项目的重点不是把所有微服务模式都搬进来,而是围绕“模块化单体”这个边界做取舍: - 单体部署 - 模块内聚 - 跨模块异步通信 - 显式的可靠性机制 - 尽量减少框架黑盒 你可以把它理解成一个偏架构实验型的 .NET 项目:在保留业务复杂度的同时,持续验证消息、工作流、CQRS、Inbox/Outbox 和模块边界的组合方式。 ## 解决方案结构 ```text src |- Aspire | |- Aspire.AppHost | |- Aspire.BareWire | \- Aspire.ServiceDefaults |- Bootstrapper | \- Evently.Api |- Shared | |- Evently.Shared.Domain | |- Evently.Shared.Application | |- Evently.Shared.Infrastructure | \- Evently.Shared.Presentation \- Modules |- Users |- Events |- Ticketing \- Attendance ``` 每个业务模块内部继续保持统一分层: - `Api` - `Application` - `Domain` - `Infrastructure` ## Shared 层职责 ### `Evently.Shared.Domain` - DDD 基础抽象 - 领域事件 - 结果模型 ### `Evently.Shared.Application` - Command / Query 契约 - Dispatcher 抽象 - 集成事件契约 - 领域事件与集成事件处理器抽象 - CQRS 装饰器与应用层公共能力 ### `Evently.Shared.Infrastructure` - 认证与授权基础设施 - BareWire + RabbitMQ 消息集成 - Inbox / Outbox - Quartz 作业 - 持久化与基础数据访问设施 - 自定义轻量 CQRS 分发器 ### `Evently.Shared.Presentation` - 端点注册帮助方法 - HTTP API 公共能力 ## 当前技术方向 这个仓库不是课程源码的逐行翻版,当前基础设施方向大致如下: 1. **优先坚持模块化单体** 2. **使用 RabbitMQ 承载跨模块集成事件** 3. **通过 Inbox / Outbox 保证消息可靠性** 4. **使用 DBOS 处理长事务与工作流编排,而不是回到 MassTransit Saga** 5. **使用 BareWire 作为消息运行时,并仅保留一层很薄的 Aspire 风格封装** ## 消息与可靠性设计 ### BareWire 消息运行时 项目当前没有继续走手写 RabbitMQ Runtime,也没有回到 MassTransit / Rebus,而是选择了 **BareWire** 作为消息运行时。 共享基础设施里只保留了很薄的一层发布抽象: - `IEventBus` - `BareWireEventBus` - `Aspire.BareWire` BareWire 主要负责: - RabbitMQ Transport 接入 - 消息发布 - Consumer 运行时分发 - Exchange / Queue / Endpoint 绑定 而应用层仍然显式保留消息策略: - 一个共享 Exchange:`evently` - 按模块拆分 Inbox Queue - 每个集成事件显式绑定 Queue - 消费者显式注册到 DI 关键文件: - [BareWireEventBus](src/Shared/Evently.Shared.Infrastructure/EventBus/BareWireEventBus.cs) - [InfrastructureConfiguration](src/Shared/Evently.Shared.Infrastructure/InfrastructureConfiguration.cs) - [Aspire.BareWire README](src/Aspire/Aspire.BareWire/README.md) ### 模块级队列 当前总线使用的是**按模块拆分的 Inbox Queue**,而不是一个全局消费者队列。 当前队列包括: - `events-inbox` - `ticketing-inbox` - `attendance-inbox` 这样做的好处是,跨模块事件消费更贴近模块边界,同时仍然共享同一个 RabbitMQ Broker 和同一个 BareWire 运行时。 ## 领域事件与集成事件 项目有意把 **领域事件** 和 **集成事件** 拆成两个阶段。 ### 领域事件 领域事件只在模块内部表达“领域里发生了什么”。 例如: - 用户注册 - 活动发布 - 票种价格变更 这些事件由聚合根抛出,并在当前事务里被收集。 ### 集成事件 集成事件是跨模块异步通信的消息,它们在领域状态成功持久化之后再被发布。 例如: - `UserRegisteredIntegrationEvent` - `EventPublishedIntegrationEvent` - `TicketTypePriceChangedIntegrationEvent` - `EventCanceledIntegrationEvent` ## Outbox 发送流程 发送侧使用 **Outbox 模式**。 ### 执行过程 1. Command 修改领域状态 2. 聚合根抛出领域事件 3. `InsertOutboxMessagesInterceptor` 将领域事件写入 `outbox_messages` 4. Quartz `ProcessOutboxJob` 扫描待处理 Outbox 消息 5. Outbox 作业解析模块 `IDomainEventHandler` 6. 领域事件处理器通过 `IEventBus` 发布集成事件 也就是说,集成事件不会直接在修改领域状态的事务内同步发送,而是通过持久化的 Outbox 记录异步发布。 关键文件: - [InsertOutboxMessagesInterceptor](src/Shared/Evently.Shared.Infrastructure/Outbox/InsertOutboxMessagesInterceptor.cs) - 各模块 `ProcessOutboxJob` - 示例领域事件处理器: - [UserRegisteredDomainEventHandler](src/Modules/Users/Evently.Modules.Users.Application/Users/RegisterUser/UserRegisteredDomainEventHandler.cs) - [EventPublishedDomainEventHandler](src/Modules/Events/Evently.Modules.Events.Application/Events/PublishEvent/EventPublishedDomainEventHandler.cs) ### Outbox 路径 ```text Command -> 聚合状态变化 -> DomainEvent -> OutboxMessage 持久化 -> ProcessOutboxJob -> DomainEventHandler -> IEventBus.PublishAsync(...) -> RabbitMQ ``` ## Inbox 接收流程 接收侧使用 **Inbox 模式**。 ### 为什么要有 Inbox 项目刻意把“消息投递”和“业务处理”分开: - RabbitMQ Consumer 只负责把收到的集成事件落到模块 Inbox - 真正的业务处理由后续 Inbox 作业完成 这样可以让模块边界更清晰,也更容易处理重复投递。 ### 当前形态 每个订阅模块会注册通用技术型 Consumer,例如: - `IntegrationEventConsumer` - `IntegrationEventConsumer` - `IntegrationEventConsumer` 这些 Consumer 实现 BareWire 的 `IConsumer`,它们不是业务处理器,而是消息接入适配器,只负责把消息写入模块自己的 `inbox_messages` 表。 ### 执行过程 1. RabbitMQ 收到集成事件 2. BareWire 将事件分发到模块 Queue 对应的 Consumer 3. Consumer 使用 `ON CONFLICT DO NOTHING` 把消息插入 Inbox 4. Quartz `ProcessInboxJob` 扫描待处理 Inbox 消息 5. Inbox 作业解析真正的 `IIntegrationEventHandler` 6. 集成事件处理器执行实际业务逻辑 关键文件: - 各模块 Inbox Consumer - 各模块 `ProcessInboxJob` - [IntegrationEventHandlersFactory](src/Shared/Evently.Shared.Infrastructure/Inbox/IntegrationEventHandlersFactory.cs) ### Inbox 路径 ```text RabbitMQ -> BareWire Consumer Runtime -> Module IntegrationEventConsumer -> inbox_messages -> ProcessInboxJob -> IntegrationEventHandler -> Command / Workflow / Business Logic ``` ## 实际集成事件处理 真正的跨模块业务响应仍然发生在各模块的集成事件处理器中。 示例: - [Ticketing UserRegisteredIntegrationEventHandler](src/Modules/Ticketing/Evently.Modules.Ticketing.Api/IntegrationEvents/Customers/UserRegisteredIntegrationEventHandler.cs) - [Ticketing EventPublishedIntegrationEventHandler](src/Modules/Ticketing/Evently.Modules.Ticketing.Api/IntegrationEvents/Events/EventPublishedIntegrationEventHandler.cs) - [Attendance UserRegisteredIntegrationEventHandler](src/Modules/Attendance/Evently.Modules.Attendance.Api/IntegrationEvents/Attendees/UserRegisteredIntegrationEventHandler.cs) - [Events EventCanceledIntegrationEventHandler](src/Modules/Events/Evently.Modules.Events.Api/IntegrationEvents/EventCanceledIntegrationEventHandler.cs) 这些处理器才是真正执行业务反应的地方,例如分发命令、触发流程、推进状态等。 ## 使用 DBOS 工作流替代 MassTransit Saga 项目早期也探索过 MassTransit Saga,但当前方向改成了 **DBOS 工作流**。 目前这一能力主要应用在 `Events` 模块的“活动取消编排”场景里。 ### 当前工作流 `CancelEventWorkflow` 用于协调整个活动取消过程: 1. 发布 `EventCancellationStartedIntegrationEvent` 2. 等待所需完成信号 3. 发布 `EventCancellationCompletedIntegrationEvent` 关键文件: - [CancelEventWorkflow](src/Modules/Events/Evently.Modules.Events.Api/Events/CancelEventSaga/CancelEventWorkflow.cs) - [CancelEventWorkflowSteps](src/Modules/Events/Evently.Modules.Events.Api/Events/CancelEventSaga/CancelEventWorkflowSteps.cs) - [CancelEventWorkflowOrchestrator](src/Modules/Events/Evently.Modules.Events.Api/Events/CancelEventSaga/CancelEventWorkflowOrchestrator.cs) ### 为什么这里选 DBOS 当前更偏向 DBOS 的原因是: - 编排模型更显式 - Saga 行为保持在模块内部 - 避免为了工作流引入更重的消息框架 换句话说,RabbitMQ 主要聚焦在集成事件,工作流编排交给 DBOS,各自职责更清楚。 ## 跨模块通信策略 这个项目并没有强迫所有事情都走 RabbitMQ。 当前大致遵循这套规则: - 模块内应用行为优先走 Command / Query - 模块内领域反应用领域事件 - 跨模块异步协作用集成事件 - 长事务或流程编排场景使用 DBOS 工作流 这也是它和“盲目照搬微服务模式”最大的区别之一。 ## 当前基础设施积木 - ASP.NET Core Minimal APIs - PostgreSQL - RabbitMQ - Redis - Quartz.NET - Keycloak - DBOS Transact - BareWire - 手写轻量 CQRS Dispatcher - Inbox / Outbox 可靠性模式 ## 解决方案 ```text Evently.slnx ``` ## 项目说明 - 这是一个偏学习与架构实践用途的项目 - 一些实现选择有意偏离 Milan 课程源码,用来探索更轻量、更显式的基础设施设计 - 当前设计更重视可读性、边界清晰和后续演进成本,而不是尽可能堆叠框架能力 - 当前项目并没有真正落地规约模式,查询组织仍以更直接的方式为主,后续如果出现复杂查询复用场景再考虑引入