BitzOrcas.Modern 是一个模块化单体。多个限界上下文——Files、Notifications、Webhooks、PlatformBilling、Catalog、Tickets、Chat 及未来的业务模块——存在于一个仓库中,构建一组容器,部署为一个进程。每个模块都是一个限界上下文,拥有自己的 Contracts、Ports、Adapters 和 Feature Slices。跨模块通信仅通过 *.Contracts 程序集进行。
整体结构
src/├── BuildingBlocks/ 共享框架(10 个库)│ ├── BitzOrcas.Domain/ DDD 原语、`Result<T>`、实体接口│ ├── BitzOrcas.Application/ Mediator 管道、授权、多租户、审计│ ├── BitzOrcas.Infrastructure/ ORM 无关:审计、缓存、种子、存储│ ├── BitzOrcas.Infrastructure.SqlSugar/ 主 ORM(简洁干净)│ ├── BitzOrcas.Infrastructure.EfCore/ 可选 ORM 适配器│ ├── BitzOrcas.Infrastructure.Dapper/ 只读查询适配器│ ├── BitzOrcas.Infrastructure.Persistence.Models/ 共享实体模型│ ├── BitzOrcas.Modularity/ IAppModule、依赖图、边界校验器│ ├── BitzOrcas.CodeGeneration.Abstractions/ 代码生成元数据模型│ └── BitzOrcas.CodeGeneration.Scriban/ Scriban 模板引擎│├── Platform/ 平台级服务│ ├── BitzOrcas.Platform.Application/ 模块服务、端口、默认适配器│ ├── BitzOrcas.Platform.Contracts/ 平台契约定义│ └── BitzOrcas.SaaS.Contracts/ SaaS 全局契约(跨模块共享)│├── Hosts/ 入口点│ ├── BitzOrcas.Api/ HTTP API(组合根)│ ├── BitzOrcas.AppHost/ .NET Aspire 编排器(仅开发环境)│ ├── BitzOrcas.JobHost/ Quartz.NET 后台工作器│ └── BitzOrcas.ServiceDefaults/ OTel + 健康检查│└── Modules/ 未来业务模块(ADR 0017) └── <Category>/<Module>/ ├── <Module>.Contracts/ ├── <Module>.Domain/ ├── <Module>.Application/ ├── <Module>.Infrastructure/ └── <Module>.Endpoints/模块治理 — IAppModule
每个模块通过实现 IAppModule 并通过 DI 注册来声明自己:
public sealed class CasesAppModule : IAppModule{ public string Name => "Cases"; public string BaseNamespace => "BitzOrcas.Cases"; public IReadOnlyList<string> Dependencies => ["Tenancy", "FileAsset"]; public IReadOnlyList<string> PublishedEvents => ["CaseOpenedIntegrationEvent", "CaseRelatedPartyChangedIntegrationEvent"]; public IReadOnlyList<string> SubscribedEvents => []; public IReadOnlyList<string> PublicContractNamespaces => ["BitzOrcas.Cases.Contracts"]; public IReadOnlyList<string> OwnedPermissions => ["cases.case.view", "cases.case.create", "cases.case.update"]; public IReadOnlyList<string> OwnedFeatures => ["cases.advanced-filter"];}注册方式:
builder.Services.AddSingleton<IAppModule, CasesAppModule>();治理管道
- 注册 — 模块实现
IAppModule,通过 DI 注册 - 发现 —
AppModuleRegistry收集所有描述符 - 依赖图 —
AppModuleDependencyGraph构建邻接表,提供拓扑排序(Kahn 算法)和环检测 - 边界校验 —
AppModuleBoundaryVerifier检测:重复名称、缺失依赖、循环依赖 - 架构测试 — ArchUnitNET 在编译/CI 时强制命名空间边界
- 运维 API —
GET /api/operations/governance暴露运行时模块治理报告
边界规则
模块之间仅通过 *.Contracts 程序集通信:
| 允许 | 不允许 |
|---|---|
BitzOrcas.Cases.Application 引用 BitzOrcas.Files.Contracts | BitzOrcas.Cases.Application 引用 BitzOrcas.Files.Infrastructure |
Platform.Application 引用 SaaS.Contracts | 任何模块引用另一个模块的 Domain 或 Application |
Contracts 可引用 Domain 抽象 | Contracts 引用 ORM 类型(SqlSugar、EF Core) |
核心规则:运行时模块永不互相引用。 如果模块 A 需要模块 B 的某些东西,它通过模块 B 的契约请求——一个由命令、查询、事件和 DTO 组成的薄层。
模块间通信
1. 端口与接口
平台模块在其 Contracts 或 Application 层暴露服务接口。下游模块通过 DI 解析,无需引用运行时。默认适配器(InMemory/Null)预注册;生产适配器(SqlSugar/Local)在启动时覆盖。
2. 领域事件(模块内)
领域事件对聚合所属模块是私有的。DomainEventDispatchPipelineBehavior 在处理器运行后分发它们。处理器在同一模块内;跨模块依赖为零。
3. 集成事件(跨模块)
标准模式。模块将 IIntegrationEvent 写入 Outbox(CAP 的 Cap.Published 表,在同一事务中提交);CAP 后台分发器将其发布到 RabbitMQ;任何具有 [CapSubscribe] 处理器的模块接收副本,支持基于 Inbox 的幂等性。
接收方不知道生产方。生产方不知道接收方。这就是纪律。
五件套模块结构
根据 ADR 0017,每个未来模块遵循:
src/Modules/<Category>/<Module>/ BitzOrcas.<Module>.Contracts/ # 公共表面(DTO、集成事件、常量) BitzOrcas.<Module>.Domain/ # 领域模型(实体、值对象、领域事件) BitzOrcas.<Module>.Application/ # 用例(命令、查询、处理器、管道) BitzOrcas.<Module>.Infrastructure/ # 持久化 / 外部集成 BitzOrcas.<Module>.Endpoints/ # Minimal API 端点 + AddModule 扩展依赖方向(严格单向):
Endpoints → Application → DomainEndpoints → Application → ContractsInfrastructure → Application → DomainEndpoints → Infrastructure(仅 DI 注册)跨模块 → 仅引用 .Contracts限界上下文地图
系统定义了 18 个限界上下文,分布在四个层次:
平台层(底层):Tenancy、Identity、Authorization、Modularity SaaS 能力层:Auditing、PlatformBilling、Notification、Webhooks、FileStorage、Tickets、Chat 业务层(未来):Cases、Billing、Workflow 基础设施层:Search(Lucene)、Reporting(Mart/OLAP)、LocalResourceSync、CodeGeneration、Operations
当模块需要独立扩展时
你已画好的边界就是提取的契约:
- 模块的运行时移入独立服务
- 其
*.Contracts程序集共享(NuGet 包或复制) - 跨模块通信从进程内 Mediator 和 CAP 内存模式切换为 HTTP / RabbitMQ
- 消费方无需更改——仍然通过
IEventBus发布,仍然通过 DI 解析服务
你几乎肯定在很长一段时间内不需要这个。重点是第一天不必为此做出承诺。