扩展点
VEF 的大多数扩展点都是显式的 FX group。
本页覆盖通过 FX group 注册、通过 fx.Decorate 替换默认实现、以及通过 fx.Supply 提供单例值的 DI extension helpers。
Helper 机制
helper 名称前缀不足以判断实际 wiring 方式,要看具体机制:
| 机制 | Helpers |
|---|---|
fx.Provide + fx.ResultTags 追加到 group | ProvideAPIResource, ProvideAuthStrategy, ProvideAuthenticator, ProvideMiddleware, ProvideSPAConfig, ProvideCQRSBehavior, ProvideChallengeProvider, ProvideMCPTools, ProvideMCPResources, ProvideMCPResourceTemplates, ProvideMCPPrompts, ProvideEventTransport, ProvideEventPublishMiddleware, ProvideEventConsumeMiddleware, ProvideApprovalLifecycleHook, ProvideApprovalAggregator, ProvideApprovalAssigneeResolver, ProvideApprovalCCResolver, ProvideApprovalInitiatorResolver, ProvideDataSourceProvider, ProvideJSLib, ProvideCronJobHandler, ProvideIntegrationOutboundAuthScheme, ProvideIntegrationInboundAuthScheme, ProvideIntegrationInboundHandler, ProvideSessionRevocationListener |
带 group tag 的 fx.Supply | SupplySPAConfigs |
fx.Decorate 替换默认实现 | SupplyFileACL, SupplyURLKeyMapper, SupplyBusinessRefProvider, SupplyBusinessRefResolver, ProvideEventMetricsRecorder, ProvideEventErrorSink, ProvideApprovalFormSchemaParser, ProvideApprovalGlobalsResolver |
普通 fx.Supply 值 | SupplyMCPServerInfo |
替换型 helper 是单服务 override,不是 append-only extension point。除非你明确希望后面的 FX option 替换前面的实现,否则同一个默认服务只注册一个替代实现。
API 与应用级 group
vef:api:resourcesvef:api:auth_strategiesvef:app:middlewares
Helpers:
vef.ProvideAPIResource(...)vef.ProvideAuthStrategy(...)vef.ProvideMiddleware(...)
ProvideAuthStrategy 会把自定义 api.AuthStrategy 追加到认证策略 group。资源或操作通过 api.AuthConfig.Strategy 选择其 Name() 返回的策略名;内置策略是 none、bearer、signature、ip、api_key 和 http_basic。
app.Middleware 契约
vef.ProvideMiddleware(...) 会追加进 vef:app:middlewares,构造函数必须返回公开的 app.Middleware:
type Middleware interface {
Name() string
Order() int
Apply(router fiber.Router)
}
负数 order 注册在路由处理器之前,正数注册在之后,各自按升序排列;默认的零值归入"之前"这一组。
这个契约放在公开包里,理由适用于这里的每一个扩展点:fx 按精确类型匹配 group,类型对不上的 provider 会被静默丢弃而不报错——应用照常启动,扩展只是永远不会运行。结构上等价也救不了:方法集相同的两个具名接口是两个不同的类型。在做这次拆分之前,ProvideMiddleware 对每一个真实宿主都是不可用的,而故障表现是"一条路由莫名其妙没注册上"。
注册了真实路由的中间件要自己负责认证:API 认证中间件只在操作分发内部运行,因此在 /api 之外调用 contextx.Principal(ctx) 永远返回 nil。请自行分发 security.AuthManager。
最小模块示例
var Module = vef.Module(
"app:user",
vef.ProvideAPIResource(NewUserResource),
vef.ProvideMiddleware(NewAuditMiddleware),
)
API 参数注入
vef:api:handler_param_resolversvef:api:factory_param_resolvers
这两个 group 分别扩展请求期和启动期的 handler 注入。
CQRS
vef:cqrs:behaviors
Helper:
vef.ProvideCQRSBehavior(...)
安全
vef:security:challenge_providersvef:security:session_revocation_listeners
Helper:
vef.ProvideChallengeProvider(...)——把一个security.ChallengeProvider追加进security/auth按Order()评估的 group。构造函数必须直接返回security.ChallengeProvider:fx 按类型区分 group,返回具体 provider 类型的构造 函数会被静默丢弃、不报任何错误。provider 默认适用于每一次登录;要限定到部分登录 方式,返回用security.NewFilteredChallengeProvider包装后的 provider(见 认证:按登录方式限定挑战)vef.ProvideAuthenticator(...)——把一个security.Authenticator追加进security.AuthManager聚合的认证器 group。认证器通过Supports(authType)认领某个登录type;内置类型有password、当前配置的令牌机制 (jwt_token或opaque_token)、refresh、signature,以及在信任登录 开启时的trust_codevef.ProvideSessionRevocationListener(...)——追加security.SessionRevocationListener,观察登出、并发登录驱逐与管理员 踢出;见会话管理
// security.SessionRevocationListener
OnSessionsRevoked(ctx context.Context, revocations []SessionRevocation)
type SessionRevocation struct {
SessionID string
UserID string
}
定时任务处理器
vef:cron:job_handlers
Helper:
vef.ProvideCronJobHandler(...)
向调度存储注册持久化 cron.JobHandler——每个任务名恰好一个处理器,重名
导致启动失败。处理器可经 cron.WithDefaultSchedule 附带默认调度。见
持久化调度。
// cron.JobHandler
Name() string
Execute(ctx context.Context, execution Execution) error
type Execution struct {
RunID string
ScheduleID string
ScheduleName string
JobName string
ScheduledAt time.Time
Params json.RawMessage
}
JS 引擎库
vef:js:libs
Helper:
vef.ProvideJSLib(...)
向共享 js.Engine 贡献 js.Lib:与内置同名的库在其所在档位(常驻 vs
opt-in 目录)内替换,新名称加入 opt-in 目录。见
JS 引擎。
集成引擎(需启用 vef.IntegrationModule)
vef:integration:outbound_auth_schemesvef:integration:inbound_auth_schemesvef:integration:inbound_handlers
Helper:
vef.ProvideIntegrationOutboundAuthScheme(...)—— 自定义integration.OutboundAuthScheme;与内置同名则替换vef.ProvideIntegrationInboundAuthScheme(...)—— 自定义integration.InboundAuthScheme;同样的替换规则vef.ProvideIntegrationInboundHandler(...)—— 服务一个入站契约的业务 处理器;每个契约编码恰好一个
当路由不在 itg_route 表中维护时,用普通 fx.Decorate 替换默认的
integration.RouteResolver。见集成引擎。
事件总线
vef:event:transportsvef:event:publish-middlewaresvef:event:consume-middlewares
Helpers:
vef.ProvideEventTransport(...)vef.ProvideEventPublishMiddleware(...)vef.ProvideEventConsumeMiddleware(...)
transport helper 用于注册自定义 event/transport.Transport 实现。publish middleware 在事件 frame 交给 transport 之前运行;consume middleware 包裹在 subscriber handler 周围。
另外两个事件集成点是替换框架默认实现,而不是追加 group 成员:
vef.ProvideEventMetricsRecorder(...)替换默认的event.MetricsRecorder。vef.ProvideEventErrorSink(...)替换异步 publish 的 error sink。
数据源 provider
vef:datasource:providers
Helper:
vef.ProvideDataSourceProvider(...)
datasource.Provider 会在启动期加载额外的数据源 spec,此时 primary 和静态 TOML 数据源已经注册完成。每个返回的 datasource.Spec 都会注册进 datasource.Registry;如果和 TOML 或另一个 provider 发生名称冲突,启动会失败。
主数据源固定保留在 datasource.PrimaryName("primary")下。它来自 vef.data_sources.primary,作为全框架 orm.DB 暴露,不能通过动态 registry API 修改。静态 TOML 非 primary 条目会先于 provider spec 注册。provider 顺序未定义;Provider.Load 返回错误或 registry 名称冲突都会让应用启动失败,Provider.Name 会出现在诊断信息里。
datasource 顶层 API:
| API | 契约 |
|---|---|
datasource.ConnectionInfo | Registry.TestConnection 的返回结果;exported field datasource.ConnectionInfo.Version 是 string。 |
datasource.ErrClosed | registry 开始 shutdown 后,datasource.Registry.Register、datasource.Registry.Update、datasource.Registry.Unregister 返回该错误。 |
datasource.ErrExists | datasource.Registry.Register 遇到已注册名称时返回该错误。 |
datasource.ErrNameInvalid | datasource.Registry.Register 或 datasource.Registry.Update 遇到空名称、包含 whitespace/control characters 的名称时返回该错误。 |
datasource.ErrNotFound | datasource.Registry.Get、datasource.Registry.Kind、datasource.Registry.Update、datasource.Registry.Unregister 遇到未注册名称时返回该错误。 |
datasource.ErrPrimaryReserved | datasource.Registry.Register、datasource.Registry.Update、datasource.Registry.Unregister 操作 datasource.PrimaryName 时返回该错误。 |
datasource.PrimaryName | 常量 "primary",TOML primary 数据源的保留名称。 |
datasource.Provider | 启动期 provider interface,包含 datasource.Provider.Name 和 datasource.Provider.Load。 |
datasource.ReconcileOption | Registry.Reconcile 的 functional option 类型。 |
datasource.ReconcileOptions | option 状态,exported field 是 datasource.ReconcileOptions.DryRun(bool)。 |
datasource.ReconcileReport | reconcile 结果,exported fields 是 datasource.ReconcileReport.Added、datasource.ReconcileReport.Updated、datasource.ReconcileReport.Removed、datasource.ReconcileReport.Errors。 |
datasource.RegisterOption | Registry.Update 和 Registry.Unregister 的 functional option 类型。 |
datasource.RegisterOptions | option 状态,exported field 是 datasource.RegisterOptions.CloseGrace(time.Duration)。 |
datasource.Registry | 可注入的 registry interface,用于 primary lookup、named lookup、mutation、reconcile、probe 和 health check。 |
datasource.Spec | 期望状态或 provider 返回的数据源,exported fields 是 datasource.Spec.Name 和 datasource.Spec.Config。 |
datasource.WithCloseGrace(d) | 仅在 d > 0 时设置 RegisterOptions.CloseGrace;延迟关闭异步执行,并会被 shutdown 提前截断。 |
datasource.WithReconcileDryRun() | 设置 ReconcileOptions.DryRun,让 Reconcile 只报告 diff,不打开或关闭连接。 |
datasource.Registry 的方法:
| Method | 契约 |
|---|---|
datasource.Registry.Primary | 返回 primary orm.DB;等价于 Get(datasource.PrimaryName),但不返回 error。 |
datasource.Registry.Get | 返回已注册 orm.DB;未知名称返回 datasource.ErrNotFound。 |
datasource.Registry.Has | 判断名称当前是否已注册。 |
datasource.Registry.Names | 返回全部已注册名称,包括 primary,顺序为稳定 lexical order。 |
datasource.Registry.Kind | 返回配置的 config.DBKind;未知名称返回 datasource.ErrNotFound。 |
datasource.Registry.Register | 先打开并 ping 新的非 primary 数据源,再插入 registry。重复名称返回 datasource.ErrExists;冲突路径会关闭新连接池。 |
datasource.Registry.Update | 先打开并 ping 替换连接,再交换进去。失败时保留旧 entry;成功后异步关闭旧连接池,并支持 datasource.WithCloseGrace。 |
datasource.Registry.Unregister | 移除非 primary 数据源,然后异步关闭旧连接池,并支持 datasource.WithCloseGrace。 |
datasource.Registry.Reconcile | 串行化 reconcile 调用,忽略空名称和 primary specs,对 add/update/remove bucket 排序;局部失败写入 ReconcileReport.Errors,不会因为 partial failure 返回 top-level error。 |
datasource.Registry.TestConnection | 打开临时连接、查询 server version、关闭连接、返回 datasource.ConnectionInfo,且绝不修改 registry。probe 有内部 5s timeout 上限,同时仍尊重调用方 cancellation/deadline。 |
datasource.Registry.HealthCheck | 并行 ping primary 和全部非 primary entries,返回 name-to-error map;nil error 表示该数据源可达。 |
SPA
vef:spa
Helpers:
vef.ProvideSPAConfig(...)vef.SupplySPAConfigs(...)
存储集成
Helpers:
vef.SupplyFileACL(...)vef.SupplyURLKeyMapper(...)
SupplyFileACL 替换默认的 storage.FileACL。默认实现只允许读取 pub/ 下的 key;如果应用要存储私有文件,应提供业务自己的 ACL。
SupplyURLKeyMapper 替换默认的 storage.ProxyURLKeyMapper,它会把 /storage/files/<key> 代理 URL 映射回对象 key。当富文本或 markdown 内容嵌入的是 CDN URL 或其他需要映射回对象 key 的 URL 形式时,覆盖它。只有内容直接嵌入裸 object key 时才使用 storage.IdentityURLKeyMapper。这个默认值针对的是框架 DI graph;直接调用 storage.NewFiles(...) 时,nil mapper 会被规整为 storage.IdentityURLKeyMapper。
MCP
vef:mcp:toolsvef:mcp:resourcesvef:mcp:templatesvef:mcp:prompts
Helpers:
vef.ProvideMCPTools(...)vef.ProvideMCPResources(...)vef.ProvideMCPResourceTemplates(...)vef.ProvideMCPPrompts(...)vef.SupplyMCPServerInfo(...)
审批生命周期 hook
vef:approval:lifecycle_hooks
Helper:
vef.ProvideApprovalLifecycleHook(...)
approval.InstanceLifecycleHook 的实现会在审批 engine 事务内同步运行——OnInstanceCreated 在实例创建时、OnInstanceTransition(from, to) 在每一次实例状态迁移时。返回 error 会回滚外层的审批命令。多个 hook 之间的调用顺序未定义(FX value group 不携带顺序)。需要在提交后才运行的异步集成应使用事件订阅或 approval.BindCommand。
审批 aggregator 与表单 schema
vef:approval:aggregators
Helpers:
vef.ProvideApprovalAggregator(...)vef.ProvideApprovalFormSchemaParser(...)
ProvideApprovalAggregator 为审批字段条件注册自定义的 detail-table aggregator,与内置的 sum / count / avg 并存。constructor 必须返回 approval.Aggregator;条件求值器按其 AggregateKind 选用。如果内置聚合类型未注册对应实现,启动会失败。
ProvideApprovalFormSchemaParser 替换框架默认的 approval.FormSchemaParser(内置实现是 vef-framework-react 表单编辑器解析器)。这个替换是整体覆盖,不是追加:每一次部署的表单 schema 都会经过它,因此它必须理解宿主提交的每一种设计器文档。解析只在流程部署时运行一次;更早部署的版本仍保留部署时持久化的 form_fields。
审批主体解析种类
vef:approval:assignee_resolversvef:approval:cc_resolversvef:approval:initiator_resolvers
Helper:
vef.ProvideApprovalAssigneeResolver(...)vef.ProvideApprovalCCResolver(...)vef.ProvideApprovalInitiatorResolver(...)
谁可以审批、谁会被抄送、谁可以发起流程是三张开放注册表:可部署的种类,恰好就是注册了 resolver 的那些。种类与内置项相同的 resolver 会就地替换它并保留设计器的选项顺序;其他种类按 kind 升序追加——因为 fx value group 的到达顺序是随机的,按到达顺序追加会让设计器的下拉框每次重启都重新洗牌。两个宿主 resolver 争夺同一个种类会在启动时失败,而不是"后者胜出"。
每个 resolver 的 Describe() 返回的 approval.KindDescriptor 同时驱动设计器的输入与保存时的校验——参见事件与集成。
审批条件全局变量
Helper:
vef.ProvideApprovalGlobalsResolver(...)
替换默认的空实现 approval.InstanceGlobalsResolver,它的结果在实例启动时于服务端解析,并快照到 Instance.Globals 上。globals 会左右条件分支,所以它刻意不属于 start-instance 的请求载荷:能够自行提供 globals 的申请人,就能左右自己的审批走向。这个 helper 通过 fx.Decorate 组合,因此需要 orm.DB 或任何其他依赖的 resolver,把它声明成构造函数参数即可。
审批业务绑定
Helpers:
vef.SupplyBusinessRefProvider(...)vef.SupplyBusinessRefResolver(...)
SupplyBusinessRefProvider 替换默认的 no-op approval.BusinessRefProvider。它在 Flow.BindingMode == BindingBusiness 的 start_instance 事务内运行,让宿主解析或创建业务行,并返回 opaque 的 Instance.BusinessRef。
SupplyBusinessRefResolver 替换默认的 approval.BusinessRefResolver——它把 Instance.BusinessRef 解析成与流程 BusinessBindingConfig.KeyColumns 匹配的 approval.BusinessRecordKey(单列 ref 按原文,复合 ref 按 JSON 对象)。当 ref 采用别的形状或解析需要宿主查询时,注册一个自定义实现。
业务状态投影本身由 engine 拥有。宿主可以用 approval.InstanceLifecycleHook、事件订阅或 approval.BindCommand 在其周围扩展,但不再替换投影路径本身。
日志
Helper:
vef.NamedLogger(name)
当集成代码需要在依赖注入之外获取框架的 logx.Logger 时,使用这个 root-package 便捷函数。它返回 logx.Logger;logx package 本身公开 Level 常量、Level.String()、Logger interface 契约,以及 LoggerConfigurable[T](供 immutable component 通过 WithLogger 返回一个配置了 logger 的副本)。
ORM 事务提交 hook
orm.OnCommit(ctx, fn)——注册一个回调函数,在拥有该上下文的orm.DB.RunInTx作用域提交后运行。回调接收一个与取消无关的上下文,且不能报告失败;到它运行时事务已经持久化。在活跃的RunInTx/RunInReadOnlyTx作用域之外调用会返回orm.ErrNoCommitScope。
这是"当且仅当外层工作单元已持久化时才执行"的接缝——例如分发进程内事件、失效缓存——而这项工作不能使事务失败。它是 tx_memory 事件传输在提交后投递事件的底层机制。
一个简单的判断原则
决定如何扩展 VEF 时,先问自己:
- 框架是否已经为这个概念预留了 group?
- 这个扩展是否应该纳入启动和生命周期管理?
- 你是否希望模块之间的依赖保持显式、可测试?
如果答案是肯定的,通常应该走 FX group,而不是依赖隐式全局状态或手写单例。
延伸阅读
- 模块与依赖注入:这些 group 如何融入应用装配流程
- 扩展 Handler 参数:handler 注入扩展的具体做法