Skip to content
bitzorcas
EN

Concept

模块化单体

BitzOrcas.Modern 如何通过 IAppModule 治理、依赖图校验和架构测试强制模块边界,将模块组合为单一可部署进程。

Last updated

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>();

治理管道

  1. 注册 — 模块实现 IAppModule,通过 DI 注册
  2. 发现AppModuleRegistry 收集所有描述符
  3. 依赖图AppModuleDependencyGraph 构建邻接表,提供拓扑排序(Kahn 算法)和环检测
  4. 边界校验AppModuleBoundaryVerifier 检测:重复名称、缺失依赖、循环依赖
  5. 架构测试 — ArchUnitNET 在编译/CI 时强制命名空间边界
  6. 运维 APIGET /api/operations/governance 暴露运行时模块治理报告

边界规则

模块之间仅通过 *.Contracts 程序集通信:

允许不允许
BitzOrcas.Cases.Application 引用 BitzOrcas.Files.ContractsBitzOrcas.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 → Domain
Endpoints → Application → Contracts
Infrastructure → Application → Domain
Endpoints → 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

当模块需要独立扩展时

你已画好的边界就是提取的契约:

  1. 模块的运行时移入独立服务
  2. *.Contracts 程序集共享(NuGet 包或复制)
  3. 跨模块通信从进程内 Mediator 和 CAP 内存模式切换为 HTTP / RabbitMQ
  4. 消费方无需更改——仍然通过 IEventBus 发布,仍然通过 DI 解析服务

你几乎肯定在很长一段时间内不需要这个。重点是第一天不必为此做出承诺。

相关