跳到主要内容

扩展点

VEF 的大多数扩展点都是显式的 FX group。

本页覆盖通过 FX group 注册、通过 fx.Decorate 替换默认实现、以及通过 fx.Supply 提供单例值的 DI extension helpers。

Helper 机制

helper 名称前缀不足以判断实际 wiring 方式,要看具体机制:

机制Helpers
fx.Provide + fx.ResultTags 追加到 groupProvideAPIResource, ProvideAuthStrategy, ProvideMiddleware, ProvideSPAConfig, ProvideCQRSBehavior, ProvideChallengeProvider, ProvideMCPTools, ProvideMCPResources, ProvideMCPResourceTemplates, ProvideMCPPrompts, ProvideEventTransport, ProvideEventPublishMiddleware, ProvideEventConsumeMiddleware, ProvideApprovalLifecycleHook, ProvideApprovalAggregator, ProvideDataSourceProvider
带 group tag 的 fx.SupplySupplySPAConfigs
fx.Decorate 替换默认实现SupplyFileACL, SupplyURLKeyMapper, SupplyBusinessRefProvider, SupplyBusinessRefResolver, ProvideEventMetricsRecorder, ProvideEventErrorSink, ProvideApprovalFormSchemaParser
普通 fx.SupplySupplyMCPServerInfo

替换型 helper 是单服务 override,不是 append-only extension point。除非你明确希望后面的 FX option 替换前面的实现,否则同一个默认服务只注册一个替代实现。

API 与应用级 group

  • vef:api:resources
  • vef:api:auth_strategies
  • vef:app:middlewares

Helpers:

  • vef.ProvideAPIResource(...)
  • vef.ProvideAuthStrategy(...)
  • vef.ProvideMiddleware(...)

ProvideAuthStrategy 会把自定义 api.AuthStrategy 追加到认证策略 group。资源或操作通过 api.AuthConfig.Strategy 选择其 Name() 返回的策略名;内置策略是 nonebearersignatureip

最小模块示例

var Module = vef.Module(
"app:user",
vef.ProvideAPIResource(NewUserResource),
vef.ProvideMiddleware(NewAuditMiddleware),
)

API 参数注入

  • vef:api:handler_param_resolvers
  • vef:api:factory_param_resolvers

这两个 group 分别扩展请求期和启动期的 handler 注入。

CQRS

  • vef:cqrs:behaviors

Helper:

  • vef.ProvideCQRSBehavior(...)

安全

  • vef:security:challenge_providers

Helper:

  • vef.ProvideChallengeProvider(...)

事件总线

  • vef:event:transports
  • vef:event:publish-middlewares
  • vef: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.ConnectionInfoRegistry.TestConnection 的返回结果;exported field datasource.ConnectionInfo.Versionstring
datasource.ErrClosedregistry 开始 shutdown 后,datasource.Registry.Registerdatasource.Registry.Updatedatasource.Registry.Unregister 返回该错误。
datasource.ErrExistsdatasource.Registry.Register 遇到已注册名称时返回该错误。
datasource.ErrNameInvaliddatasource.Registry.Registerdatasource.Registry.Update 遇到空名称、包含 whitespace/control characters 的名称时返回该错误。
datasource.ErrNotFounddatasource.Registry.Getdatasource.Registry.Kinddatasource.Registry.Updatedatasource.Registry.Unregister 遇到未注册名称时返回该错误。
datasource.ErrPrimaryReserveddatasource.Registry.Registerdatasource.Registry.Updatedatasource.Registry.Unregister 操作 datasource.PrimaryName 时返回该错误。
datasource.PrimaryName常量 "primary",TOML primary 数据源的保留名称。
datasource.Provider启动期 provider interface,包含 datasource.Provider.Namedatasource.Provider.Load
datasource.ReconcileOptionRegistry.Reconcile 的 functional option 类型。
datasource.ReconcileOptionsoption 状态,exported field 是 datasource.ReconcileOptions.DryRunbool)。
datasource.ReconcileReportreconcile 结果,exported fields 是 datasource.ReconcileReport.Addeddatasource.ReconcileReport.Updateddatasource.ReconcileReport.Removeddatasource.ReconcileReport.Errors
datasource.RegisterOptionRegistry.UpdateRegistry.Unregister 的 functional option 类型。
datasource.RegisterOptionsoption 状态,exported field 是 datasource.RegisterOptions.CloseGracetime.Duration)。
datasource.Registry可注入的 registry interface,用于 primary lookup、named lookup、mutation、reconcile、probe 和 health check。
datasource.Spec期望状态或 provider 返回的数据源,exported fields 是 datasource.Spec.Namedatasource.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(...)

存储集成

扩展 group:

  • vef:api:resources
  • vef:app:middlewares

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:tools
  • vef:mcp:resources
  • vef:mcp:templates
  • vef: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) 在每一次实例状态迁移时(v0.38)。返回 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

审批业务绑定

Helpers:

  • vef.SupplyBusinessRefProvider(...)
  • vef.SupplyBusinessRefResolver(...)

SupplyBusinessRefProvider 替换默认的 no-op approval.BusinessRefProvider。它在 Flow.BindingMode == BindingBusinessstart_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.Loggerlogx package 本身公开 Level 常量、Level.String()Logger interface 契约,以及 LoggerConfigurable[T](供 immutable component 通过 WithLogger 返回一个配置了 logger 的副本)。

一个简单的判断原则

决定如何扩展 VEF 时,先问自己:

  • 框架是否已经为这个概念预留了 group?
  • 这个扩展是否应该纳入启动和生命周期管理?
  • 你是否希望模块之间的依赖保持显式、可测试?

如果答案是肯定的,通常应该走 FX group,而不是依赖隐式全局状态或手写单例。

延伸阅读