跳到主要内容

CQRS

VEF 内置了一套轻量级 CQRS bus,用来承载 command、query 和统一行为(behavior)中间件。

面向用户的公共接口

应用层最常用的是这些:

  • cqrs.BaseCommand
  • cqrs.BaseQuery
  • cqrs.Register(...)
  • cqrs.Send(...)
  • cqrs.Behavior
  • cqrs.BehaviorFunc
  • vef.ProvideCQRSBehavior(...)

其他公开 API:

API作用
cqrs.NewBus(behaviors)创建独立 bus,主要用于测试或自定义装配
cqrs.Bushandler registry 和 dispatcher 抽象
cqrs.Action / ActionKindaction 契约;CommandQuery 是公开 kind 常量
cqrs.Handler[TAction, TResult] / HandlerFunc[...]类型化 handler 契约
cqrs.Behavior / BehaviorFunccommand/query 执行管线
cqrs.Ordered可选的 behavior 排序 hook
cqrs.Unitcommand 常用的空结果类型
ErrHandlerNotFoundaction 类型没有注册 handler
ErrResultTypeMismatchhandler 返回值无法转换成请求的结果类型

Action kind 契约

Action.Kind() 返回 action discriminator。BaseCommand.Kind() 返回 Command0),BaseQuery.Kind() 返回 Query1)。

定义 action

command 嵌入 cqrs.BaseCommand,query 嵌入 cqrs.BaseQuery

type CreateUser struct {
cqrs.BaseCommand
Name string
}

type GetUser struct {
cqrs.BaseQuery
ID string
}

注册 handler

handler 按 action 类型注册,Send 也是类型安全的:

package useractions

import (
"context"

"github.com/coldsmirk/vef-framework-go/cqrs"
)

type CreateUser struct {
cqrs.BaseCommand
Name string
}

type CreateUserHandler struct{}

func (CreateUserHandler) Handle(ctx context.Context, cmd CreateUser) (cqrs.Unit, error) {
return cqrs.Unit{}, nil
}

func RegisterHandlers(bus cqrs.Bus) {
cqrs.Register(bus, CreateUserHandler{})
}

func Run(ctx context.Context, bus cqrs.Bus) error {
_, err := cqrs.Send[CreateUser, cqrs.Unit](ctx, bus, CreateUser{Name: "alice"})
return err
}

Register 使用具体的 TAction 类型作为 registry key;如果同一个 action type 已经注册过 handler,会 panic。Send 按同一个 action type 分发。没有 handler 时返回可用 errors.Is 匹配 ErrHandlerNotFound 的错误,并返回 zero-value result。handler 自身返回的 error 会原样向外传播。

HandlerFuncHandler 的函数适配器;它的 Handle 方法只是调用被包装的函数。

Behavior pipeline

CQRS bus 支持在 command/query 执行前后包裹 middleware 风格的 behavior。

使用 vef.ProvideCQRSBehavior(...) 把 behavior 注册进运行时:

vef.ProvideCQRSBehavior(NewLoggingBehavior)

比较适合用 behavior 做的事情:

  • 日志
  • tracing
  • metrics
  • 统一校验
  • 事务包装

最小 behavior 示例:

func NewLoggingBehavior() cqrs.Behavior {
return cqrs.BehaviorFunc(func(ctx context.Context, action cqrs.Action, next func(context.Context) (any, error)) (any, error) {
return next(ctx)
})
}

BehaviorFuncBehavior 的函数适配器;它的 Handle 方法只是调用被包装的函数。 behavior 会收到原始 action,也可以不调用 next 而直接 short-circuit。如果返回 nilSend 会返回 TResult 的 zero value。如果 short-circuit 返回了非 nil 但 concrete type 不是 TResult 的值,Send 会返回可用 errors.Is 匹配 ErrResultTypeMismatch 的错误,而不是 panic。

bus 创建时会对 behavior 排序一次。Ordered.Order() 控制包裹顺序:较小的值包在 较大的值外面。不实现 Ordered 的 behavior 默认 order 是 0;独立调用 NewBus 时相同 order 会保留输入顺序,但 FX value group 的输入顺序不稳定。框架 约定的 order band 是:

Order band用途
0..99transactional / contextual setup,需要包住所有逻辑
100..199audit / collector lifecycle
200..299event publish / outbox side effects
1000+custom host behaviors

什么时候用它

CQRS 是可选的。当你需要以下能力时会比较合适:

  • 明确的 command/query 边界
  • 类型安全的分发
  • 在 HTTP resource 层之外,围绕应用层 action 建立一条 pipeline

它不会帮你做什么

CQRS bus 不会自动扫描 handler。你仍然需要在自己的模块图里明确把 handler 注册进共享 bus。

下一步

如果你的 behavior 需要把命令执行包进数据库边界,继续阅读 事务