模块化单体
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) 会按顺序完成三件事:
- 将 Startup 所在程序集 加入 MVC
ApplicationPart,让该程序集中的 Controller 被发现。 - 立即执行该 Startup 的
ConfigureServices。 - 注册一条
ModuleInfo,等待应用构建后由ConfigureModules()执行Configure。
实现位于 src/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleServiceCollection.cs。因此只调用 UseFreeKit 或 AddModule 并不会执行模块的 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 | 所在项目 | 当前职责 |
|---|---|---|
CmsKitModuleStartup | FreeKit.CmsKit | 领域/应用服务、运行时设置、HTTP Client、MeiliSearch、开发环境建表 |
CmsKitHttpApiModuleStartup | FreeKit.CmsKit.HttpApi | SignalR、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/articlesapi/identity/usersapi/plat/todo
模块 key、Swagger GroupName、[Area] 和 URL 前缀是四个不同概念,不要假设它们会自动互相推导。
新模块接入检查表
新增采用 Core + HttpApi 拆分的模块时,需要同时完成:
- Core 和 HttpApi 两个项目,且 HttpApi 引用 Core。
- 两个完整的
IModuleStartup实现,均包含ConfigureServices和Configure。 FreeKit.DI.csproj引用 Core,并在E.Modules中登记 Core Startup。FreeKit.Host.csproj引用 HttpApi。- 主 Host 直接
AddModule<CoreStartup>,并在hostModules中将该 key 替换成 HttpApi Startup。 - 将 Core 程序集加入
extraAssemblies。 - 根据需要补充 Swagger 分组、权限定义、测试和本地化资源。
完整可执行示例见 从零创建业务模块。
源码索引
src/FreeKit/src/IGeekFan.FreeKit.Modularity/IModuleStartup.cssrc/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleServiceCollection.cssrc/FreeKit/src/IGeekFan.FreeKit.Modularity/ModuleApplicationBuilderExtensions.cssrc/BuildingBlocks/IGeekFan.FreeKit.Web/DI/ModuleCollectionExtensions.cssrc/BuildingBlocks/IGeekFan.FreeKit.Web/DI/WebApplicationBuilderExtensions.cssrc/Services/Host/FreeKit.Host/Program.cs