跳到主要内容

模块化单体

IGeekFan.FreeKit.Modularity 提供一套很小的模块启动协议。模块仍然编译进同一个进程,由 Host 作为组合根决定加载哪些程序集;它不是运行时插件系统,也不会为每个模块创建独立的中间件管线。

当前业务模块通常拆成两个项目:

  • Core:实体、Domain Manager、Application Service、仓储使用和后台任务可复用能力。
  • HttpApi:Controller、SignalR Hub、Hub Filter 等仅 HTTP 宿主需要的能力。

CmsKit、Identity、Member、Platform 都采用这个边界。对应源码位于 src/Services/*,主组合根位于 src/Services/Host/FreeKit.Host/Program.cs

启动协议

模块启动类实现 IModuleStartup

using IGeekFan.FreeKit.Modularity;

public sealed class ExampleModuleStartup : IModuleStartup
{
public void ConfigureServices(
IServiceCollection services,
IConfiguration configuration)
{
// 在 WebApplication 构建前注册服务。
}

public void Configure(
WebApplication app,
IWebHostEnvironment env)
{
// 在 WebApplication 构建后执行模块初始化或映射端点。
}
}

接口的真实定义在 src/FreeKit/src/IGeekFan.FreeKit.Modularity/IModuleStartup.cs。启动类必须可由无参构造函数创建,依赖应在方法参数或 app.Services 中获取。

AddModule 做了什么

services.AddModule(startupType, routePrefix, configuration) 会按顺序完成三件事:

  1. Startup 所在程序集 加入 MVC ApplicationPart,让该程序集中的 Controller 被发现。
  2. 立即执行该 Startup 的 ConfigureServices
  3. 注册一条 ModuleInfo,等待应用构建后由 ConfigureModules() 执行 Configure

实现位于 src/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleServiceCollection.cs。因此只调用 UseFreeKitAddModule 并不会执行模块的 Configure

WebApplication app = builder.Build();

app.ConfigureModules();

app.MapControllers();
app.Run();

ConfigureModules() 会依注册顺序遍历全部 ModuleInfo。同一业务模块的 Core Startup 和 HttpApi Startup 可以同时存在,并分别执行各自的 Configure

主宿主的真实组合方式

FreeKit.Host 先注册四个 Core Startup,再把 E.Modules 中对应条目替换成 HttpApi Startup:

builder.Services.AddModule<CmsKitModuleStartup>("cmskit", builder.Configuration);
builder.Services.AddModule<IdentityModuleStartup>("identity", builder.Configuration);
builder.Services.AddModule<PlatformModuleStartup>("platform", builder.Configuration);
builder.Services.AddModule<MemberModuleStartup>("member", builder.Configuration);

var hostModules = new Dictionary<string, Type>(
E.Modules,
StringComparer.OrdinalIgnoreCase)
{
["cmskit"] = typeof(CmsKitHttpApiModuleStartup),
["identity"] = typeof(IdentityHttpApiModuleStartup),
["member"] = typeof(MemberHttpApiModuleStartup),
["platform"] = typeof(PlatformHttpApiModuleStartup)
};

builder.UseFreeKit(
moduleTypeMap: hostModules,
extraAssemblies:
[
typeof(Program).Assembly,
typeof(CmsKitModuleStartup).Assembly,
typeof(IdentityModuleStartup).Assembly,
typeof(MemberModuleStartup).Assembly,
typeof(PlatformModuleStartup).Assembly
]);

WebApplication app = builder.Build();
app.ConfigureModules().Init();

这里有两个不能省略的细节:

  • HttpApi Startup 的程序集负责 Controller 发现;Core 程序集通过 extraAssemblies 参加 Autofac 的 Application Service、Domain Service 和工作单元扫描。
  • identity-inf 没有被替换,仍由 IdentityInfrastructureModuleStartup 注册文件、设置等能力。

E.Modules 定义在 src/Services/Host/FreeKit.DI/E.cs。Job 和 Message Host 直接使用其中的 Core Startup,不引用业务 HttpApi 项目。

Core 与 HttpApi Startup 的职责

以 CmsKit 为例:

Startup所在项目当前职责
CmsKitModuleStartupFreeKit.CmsKit领域/应用服务、运行时设置、HTTP Client、MeiliSearch、开发环境建表
CmsKitHttpApiModuleStartupFreeKit.CmsKit.HttpApiSignalR、ValidTokenHubFilter、实时通知实现、/hubs/notifications

Platform 和 Member 的 HttpApi Startup 当前没有额外服务或端点,但仍承担“Controller 所在程序集入口”的作用,不能因此省略。

路由前缀的准确含义

模块 key 只有在路由模板使用 [module] 时才参与 URL 替换:

[Route("api/[module]/orders")]
public sealed class OrderController : FreeKitController;

若以 "order" 注册该 Controller 所在程序集,路由会变成 /api/order/orders。约定只作用于该 Startup 的程序集,一个 Controller 程序集只能对应一个模块前缀。

当前业务 API 大多使用更清晰的显式路由,例如:

  • api/cms/articles
  • api/identity/users
  • api/plat/todo

模块 key、Swagger GroupName[Area] 和 URL 前缀是四个不同概念,不要假设它们会自动互相推导。

新模块接入检查表

新增采用 Core + HttpApi 拆分的模块时,需要同时完成:

  1. Core 和 HttpApi 两个项目,且 HttpApi 引用 Core。
  2. 两个完整的 IModuleStartup 实现,均包含 ConfigureServicesConfigure
  3. FreeKit.DI.csproj 引用 Core,并在 E.Modules 中登记 Core Startup。
  4. FreeKit.Host.csproj 引用 HttpApi。
  5. 主 Host 直接 AddModule<CoreStartup>,并在 hostModules 中将该 key 替换成 HttpApi Startup。
  6. 将 Core 程序集加入 extraAssemblies
  7. 根据需要补充 Swagger 分组、权限定义、测试和本地化资源。

完整可执行示例见 从零创建业务模块

源码索引

  • src/FreeKit/src/IGeekFan.FreeKit.Modularity/IModuleStartup.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleServiceCollection.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleApplicationBuilderExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/ModuleCollectionExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/WebApplicationBuilderExtensions.cs
  • src/Services/Host/FreeKit.Host/Program.cs

相关文档