XiHan Framework 处于预览阶段

查看获取方式
跳转到内容

能力包与清单 ​

本章说明框架的装配单元:特性(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) 加入已创建的实例:

csharp
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否入口类型全名

一个应用能力包的清单:

csharp
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.knowledgeAI 端口ai.tools 由 AIToolsFeature 提供
protocols.http 等 protocols.*协议适配器对应的 Protocols.* 包

完整的已登记能力见包总览。引用能力时带整数接口版本,写作 persistence.relational@1;求解时接口版本必须相等。接口变更只增加新版本并存(@2),不改旧版本的语义,因此按 @1 写的 Provider 在当前大版本内始终可用。

依赖标为可选(optional: true)时,缺失不阻止启动,声明它的特性进入 Degraded,在健康端点报告。

能力图谱的求解 ​

AddXiHan 的回调结束后,AppBuilder.Build() 依次执行:

  1. 收集清单:内核清单(xihan.kernel.core,提供全部 kernel.* 能力)加上所有经 UseFeature 加入的特性,校验每份清单的格式与 ID 唯一。
  2. 为每条 Requires 选出唯一的提供者(规则见下一节);没有被任何特性依赖的能力也按同一规则选出提供者。
  3. 把选择结果回填到图谱节点:每个节点知道自己被选中提供哪些能力(ResolvedNode.SelectedCapabilities)。声明了能力却一个都没被选中的节点标为 shadowed,不装配、不启动;特性实现了 IShadowAwareFeature 时改调它的 ConfigureShadowed,只登记与能力选择无关的服务(对象存储的命名实例 Provider 就是这样)。节点还区分每项选中是不是被要求的(ResolvedNode.IsRequestedFor):有特性依赖这项能力,或 XiHan:Providers / UseProvider 指定了提供者,是被要求的;没人依赖也没人指定、只按上一步兜底选出的不是。兜底选出照样算选中,图谱不变;特性可据此只为被要求的能力承担跨能力的约束(对象存储的「缺省三个契约同一存储」就只约束被要求的)。
  4. 拓扑排序:被依赖者在前,UseFeature 的顺序只决定同一层次内的次序。
  5. 解析资源绑定,校验每个特性的必需绑定(缺失报 KERNEL_HOSTING_001)。
  6. 按拓扑顺序调用各特性的 Configure,再把 App、内置中间件、权限表、操作登记表与宿主服务注册进容器。

求解失败时 Build() 抛出 FrameworkException,错误信息列出冲突双方:

错误码情形
KERNEL_CAPABILITY_001必需依赖没有任何清单提供
KERNEL_CAPABILITY_002依赖成环
KERNEL_CAPABILITY_003有清单提供同名能力,但接口版本不符
KERNEL_CAPABILITY_004同一能力有多个候选且没有指定
KERNEL_CAPABILITY_005清单 ID 重复

已解析图在启动日志(Information 级别)里打印,命令行宿主的 graph 命令输出同一份内容,(default) 标出以默认角色被选中的节点:

text
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 ​

同一能力有多个候选时,按以下顺序选出一个:

  1. 显式指定:代码里的 AppBuilder.UseProvider(capabilityId, manifestId),其次配置 XiHan:Providers:<能力 ID>;两者都有时代码优先。指定的清单不存在或版本不符时,必需依赖直接失败。
  2. 唯一的 Role = Provider 候选。
  3. 唯一的 Role = Default 候选。默认实现只在没有任何显式 Provider 时参与竞争。
  4. 以上都不成立:报 KERNEL_CAPABILITY_004 并列出候选。

因此加入一个 Provider 就会顶替同一能力的默认实现,不需要移除组合包里的 UseFeature。默认实现若还提供别的、未被顶替的能力,仍然装配,但只为被选中的能力注册服务:Caching.InMemory 同时提供 caching.local 与 caching.distributed,加入 Caching.Redis 后它只负责 caching.local。特性在 Configure 里用 context.Node.IsSelectedFor(capabilityId) 判断自己被选中提供哪些能力。

两个显式 Provider 提供同一能力时必须指定:

json
{
  "XiHan": {
    "Providers": {
      "caching.distributed": "xihan.caching.redis"
    }
  }
}
csharp
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)见配置与选项。

相关 ​

Released under The MIT License