能力包与清单
本章说明框架的装配单元:特性(IFeature)、清单(Manifest)、能力 ID 与能力图谱的求解,以及同一能力有多个实现时如何选择 Provider。适用于所有基于框架写应用或扩展的开发者。
完整 API 见 Kernel 包与 Primitives 包。
特性与能力包
特性是实现 IFeature 的类,是内核装配的最小单元。它只有两个成员:
| 成员 | 作用 |
|---|---|
Manifest Manifest { get; } | 清单:身份、提供与依赖的能力、资源绑定、贡献点 |
void Configure(FeatureContext context) | 注册服务、中间件、权限与操作;按能力图谱的拓扑顺序调用一次 |
需要在应用启动与停止时做事的特性再实现 IHostedFeature(它继承 IFeature),多出 StartAsync(IServiceProvider, CancellationToken) 与 StopAsync(CancellationToken),详见生命周期与健康。
能力包是包含一个或多个特性的 NuGet 包。框架里每个子系统的默认实现与 Provider 都是能力包,应用自己的业务模块也写成能力包:例如示例应用的 OrdersFeature 声明依赖持久化、消息、授权与多租户四个能力,并登记订单仓储、消息处理器、权限与操作。
特性没有基类、没有生命周期钩子,内核也不扫描程序集。应用用 UseFeature<T>() 显式加入每一个特性(要求公开的无参构造函数),或用 UseFeature(instance) 加入已创建的实例:
using XiHan.Framework.Caching.Redis;
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Standard;
using XiHan.Framework.Protocols.Http;
var builder = WebApplication.CreateBuilder(args);
builder.AddXiHan(xihan => xihan
.UseStandardProfile() // 组合包:一组 UseFeature 的简写
.UseFeature<RedisCachingFeature>() // 显式 Provider:caching.distributed 改由 Redis 提供
.UseFeature<HttpProtocolFeature>());
var app = builder.Build();
app.UseXiHan();
app.Run();组合包(Profiles.Minimal、Profiles.Standard、Profiles.Full)只是预先写好的一串 UseFeature,例如 UseStandardProfile() 等于 Minimal 再加持久化、缓存、多租户、安全、消息五个子系统的内存默认实现。
清单
清单是装配的事实源,特性以 Manifest 属性给出,内核在构建期校验格式(Manifest.Validate())。清单都是手写的:RFC-0002 设想的 [Capability] 清单生成器顺延,内核里的 CapabilityAttribute 运行期不读。字段与 XiHan Spec 的 manifest.schema.json 一一对应:
| 字段 | 必填 | 含义 |
|---|---|---|
Id | 是 | 清单 ID,xihan.<领域>.<名称>,应用内唯一,重复报 KERNEL_CAPABILITY_005 |
Package | 否 | 所属包 ID |
Version | 是 | 特性版本,SemVer |
Primitives | 是 | 依赖的原语层大版本,当前为 1 |
Stability | 是 | Stable / Preview / Experimental / Internal / Deprecated |
Kind | 是 | Capability、Provider、Plugin、Host、Protocol |
Role | 否 | Provider(缺省)或 Default:没有显式 Provider 时才被选中 |
Provides | 否 | 提供的能力与接口版本,CapabilityDeclaration |
Requires | 否 | 依赖的能力、接口版本与是否可选,CapabilityRequirement |
Bindings | 否 | 需要宿主注入的资源(连接串、端点),Binding |
Contributes | 否 | 贡献点:管道中间件、操作、配置节、权限码、健康项 |
Activation | 否 | 激活事件,插件按需激活时使用 |
Isolation / Permissions | 插件 | 插件的隔离级别与所需权限 |
Entry | 否 | 入口类型全名 |
一个应用能力包的清单:
using XiHan.Framework.Kernel;
using XiHan.Framework.Kernel.Hosting;
using XiHan.Framework.Messaging.Abstractions;
using XiHan.Framework.Persistence.Abstractions;
using XiHan.Framework.Primitives;
using XiHan.Framework.Primitives.Manifests;
namespace Shop.Orders;
public sealed class OrdersFeature : IFeature
{
public Manifest Manifest { get; } = new()
{
Id = "xihan.shop.orders",
Package = "Shop.Orders",
Version = "1.0.0",
Primitives = 1,
Stability = Stability.Preview,
Kind = ManifestKind.Capability,
Requires =
[
new CapabilityRequirement(KernelCapabilities.Hosting, KernelCapabilities.Version),
new CapabilityRequirement(PersistenceCapabilities.Relational, PersistenceCapabilities.Version),
// 可选依赖:没有消息能力时特性进入 Degraded,应用照常启动
new CapabilityRequirement(MessagingCapabilities.Queue, MessagingCapabilities.Version, optional: true),
],
Contributes = new Contributions { Permissions = ["orders.read", "orders.write"] },
Entry = typeof(OrdersFeature).FullName,
};
public void Configure(FeatureContext context)
{
// 在这里显式注册服务、权限与操作
}
}每个子系统的抽象包都有一个 <领域>Capabilities 静态类,给出能力 ID 常量与接口版本(PersistenceCapabilities.Relational、CachingCapabilities.Distributed、SecurityCapabilities.Authorization 等),清单里引用常量,不手写字符串。
Contributes 是声明:命令行的 graph 命令、插件宿主与架构测试读取它,内核不据此注册任何东西。中间件、权限、操作仍要在 Configure 里经 FeatureContext 登记。Contributes.Health 例外地有强约束:声明的每个健康条目都必须由同一个包实现并登记为 IHealthContributor,架构测试核对两边一致。
能力 ID 与接口版本
能力 ID 的文法是 <领域>.<能力>,领域是术语表领域词的小写形态,能力用小写字母、数字与连字符:
| 能力 ID | 含义 | 默认实现 |
|---|---|---|
kernel.hosting 等 kernel.* | 宿主、能力图谱、管道、诊断、安全原语、插件抽象 | 内核自身 |
persistence.relational | 仓储与工作单元 | Persistence.InMemory |
caching.local / caching.distributed | 进程内缓存 / 共享缓存与分布式锁 | Caching.InMemory |
messaging.queue / messaging.stream | 队列 / 有序流与重放 | Messaging.InMemory(只提供队列) |
security.authentication / security.authorization | 认证 / 授权 | Security.InMemory |
multitenancy.context / multitenancy.sharding | 租户上下文 / 租户分片 | MultiTenancy.InMemory |
localization.resources | 本地化资源 | Localization.InMemory |
objectstorage.blob | 对象存储 | ObjectStorage.FileSystem |
scheduling.jobs / scheduling.leader | 作业调度 / 领导选举 | Scheduling.InMemory |
auditing.trail | 审计写入与查询 | Auditing.InMemory |
workflow.execution | 工作流 | Workflow.InMemory |
ai.chat / ai.embedding / ai.tools / ai.knowledge | AI 端口 | ai.tools 由 AIToolsFeature 提供 |
protocols.http 等 protocols.* | 协议适配器 | 对应的 Protocols.* 包 |
完整的已登记能力见包总览。引用能力时带整数接口版本,写作 persistence.relational@1;求解时接口版本必须相等。接口变更只增加新版本并存(@2),不改旧版本的语义,因此按 @1 写的 Provider 在当前大版本内始终可用。
依赖标为可选(optional: true)时,缺失不阻止启动,声明它的特性进入 Degraded,在健康端点报告。
能力图谱的求解
AddXiHan 的回调结束后,AppBuilder.Build() 依次执行:
- 收集清单:内核清单(
xihan.kernel.core,提供全部kernel.*能力)加上所有经UseFeature加入的特性,校验每份清单的格式与 ID 唯一。 - 为每条
Requires选出唯一的提供者(规则见下一节);没有被任何特性依赖的能力也按同一规则选出提供者。 - 把选择结果回填到图谱节点:每个节点知道自己被选中提供哪些能力(
ResolvedNode.SelectedCapabilities)。声明了能力却一个都没被选中的节点标为 shadowed,不装配、不启动;特性实现了IShadowAwareFeature时改调它的ConfigureShadowed,只登记与能力选择无关的服务(对象存储的命名实例 Provider 就是这样)。节点还区分每项选中是不是被要求的(ResolvedNode.IsRequestedFor):有特性依赖这项能力,或XiHan:Providers/UseProvider指定了提供者,是被要求的;没人依赖也没人指定、只按上一步兜底选出的不是。兜底选出照样算选中,图谱不变;特性可据此只为被要求的能力承担跨能力的约束(对象存储的「缺省三个契约同一存储」就只约束被要求的)。 - 拓扑排序:被依赖者在前,
UseFeature的顺序只决定同一层次内的次序。 - 解析资源绑定,校验每个特性的必需绑定(缺失报
KERNEL_HOSTING_001)。 - 按拓扑顺序调用各特性的
Configure,再把App、内置中间件、权限表、操作登记表与宿主服务注册进容器。
求解失败时 Build() 抛出 FrameworkException,错误信息列出冲突双方:
| 错误码 | 情形 |
|---|---|
KERNEL_CAPABILITY_001 | 必需依赖没有任何清单提供 |
KERNEL_CAPABILITY_002 | 依赖成环 |
KERNEL_CAPABILITY_003 | 有清单提供同名能力,但接口版本不符 |
KERNEL_CAPABILITY_004 | 同一能力有多个候选且没有指定 |
KERNEL_CAPABILITY_005 | 清单 ID 重复 |
已解析图在启动日志(Information 级别)里打印,命令行宿主的 graph 命令输出同一份内容,(default) 标出以默认角色被选中的节点:
xihan.kernel.core <版本>
provides kernel.hosting@1
...
xihan.persistence.inmemory <版本> (default)
provides persistence.relational@1
requires kernel.hosting@1 -> xihan.kernel.core
xihan.caching.inmemory <版本> (default)
provides caching.local@1
provides caching.distributed@1
requires kernel.hosting@1 -> xihan.kernel.core代码里可以从容器取 App,用 App.Graph(ResolvedGraph)或 App.DescribeGraph() 读同样的信息。
选择 Provider
同一能力有多个候选时,按以下顺序选出一个:
- 显式指定:代码里的
AppBuilder.UseProvider(capabilityId, manifestId),其次配置XiHan:Providers:<能力 ID>;两者都有时代码优先。指定的清单不存在或版本不符时,必需依赖直接失败。 - 唯一的
Role = Provider候选。 - 唯一的
Role = Default候选。默认实现只在没有任何显式 Provider 时参与竞争。 - 以上都不成立:报
KERNEL_CAPABILITY_004并列出候选。
因此加入一个 Provider 就会顶替同一能力的默认实现,不需要移除组合包里的 UseFeature。默认实现若还提供别的、未被顶替的能力,仍然装配,但只为被选中的能力注册服务:Caching.InMemory 同时提供 caching.local 与 caching.distributed,加入 Caching.Redis 后它只负责 caching.local。特性在 Configure 里用 context.Node.IsSelectedFor(capabilityId) 判断自己被选中提供哪些能力。
两个显式 Provider 提供同一能力时必须指定:
{
"XiHan": {
"Providers": {
"caching.distributed": "xihan.caching.redis"
}
}
}builder.AddXiHan(xihan => xihan
.UseStandardProfile()
.UseFeature<RedisCachingFeature>() // 提供 caching.distributed@1
.UseFeature<RemoteCachingFeature>() // 同样提供 caching.distributed@1
// 与上面的配置等价,代码优先
.UseProvider("caching.distributed", "xihan.caching.redis"));没有被选中的 xihan.caching.remote 一个能力都不提供,在图谱里标为 shadowed,不装配、不启动。
各 Provider 的清单 ID 写在对应包页的信息表里。
FeatureContext
Configure 收到的 FeatureContext 给出本特性在图谱里的节点与登记入口:
| 成员 | 用途 |
|---|---|
Services | 宿主的 IServiceCollection,显式注册服务 |
Configuration | 宿主配置 |
Manifest / Node | 本特性的清单与图谱节点(Node.IsSelectedFor、Node.IsRequestedFor、Node.IsDegraded、Node.MissingOptional) |
AddMiddleware<T>(stage, placement) | 把中间件挂到某个阶段,归属本特性的清单 ID,并以单例注册中间件类型 |
AddPermission(definition) | 登记权限码,归属本特性的清单 ID;操作契约引用的权限码在装配完成时核对,未登记即启动失败(KERNEL_VALIDATION_005);端点引用的未登记权限码在宿主建请求管道时核对,启动失败(SECURITY_VALIDATION_004) |
AddOperation<TOperation, TRequest, TResponse>(...) | 登记操作契约,通常由生成的 AddGeneratedOperations() 代劳 |
AddStartupCheck(check) | 登记启动核对:要等容器建好才能做的装配核对(如授权能力核对契约引用的策略),在任何特性启动之前执行,抛出即终止启动、不归失败策略管,见生命周期与健康 |
GetBinding(name) / TryGetBinding(name) | 读取宿主解析出的资源绑定 |
Pipeline、Permissions、Operations、Bindings | 底层的管道构建器、权限表、操作登记表与绑定表 |
资源绑定的来源(配置节 XiHan:Bindings 与 Service Binding 目录 SERVICE_BINDING_ROOT)见配置与选项。
