OOro

Hooks 与事件

模型生命周期 Hook、全局事件订阅、SQL 事件、事务事件和跳过策略。

Oro 同时提供模型 Hook 和全局事件。

  • Hook 写在模型上,适合模型内部不变量和字段整理;
  • 事件注册在 DB 上,适合日志、审计、缓存失效、领域事件发布等横切逻辑。

Oro 明确区分“模型行为”和“全局订阅”:Hook 贴近模型,事件贴近基础设施。

模型 Hook

Hook 是模型上的方法。只有模型查询会触发 Hook,例如 db.Use[Product]().Create(...)

func (p *Product) BeforeCreate(ctx context.Context, h *oro.Hook) error {
    if p.Code == "" {
        return errors.New("code is required")
    }
    p.Code = strings.TrimSpace(p.Code)
    return nil
}

func (p *Product) AfterCreate(ctx context.Context, h *oro.Hook) error {
    return nil
}

TableRaw 不执行模型 Hook,因为它们没有模型实例。

支持的 Hook

Hook 触发时机
BeforeCreate(ctx, hook) 模型创建前
AfterCreate(ctx, hook) 模型创建后
BeforeUpdate(ctx, hook) 模型更新前
AfterUpdate(ctx, hook) 模型更新后
BeforeDelete(ctx, hook) 模型删除前
AfterDelete(ctx, hook) 模型删除后
BeforeRestore(ctx, hook) 软删除恢复前
AfterRestore(ctx, hook) 软删除恢复后
AfterFind(ctx, hook) 模型查询并扫描后

Hook 返回错误时,当前操作中止,并包装为 oro.ErrHook

Hook 上下文

type Hook struct {
    DB           *oro.DB
    Operation    string
    Values       oro.Map
    RowsAffected int64
    SoftDelete   bool
}
字段 含义
DB 当前数据库会话,事务内就是事务 DB
Operation 当前操作名称
Values 更新、删除、恢复等操作值
RowsAffected 已影响行数,通常在 after hook 可用
SoftDelete 当前删除是否为软删除

推荐用途

适合放在 Hook 里的逻辑:

  • 字段 trim、大小写标准化;
  • 校验模型内部不变量;
  • 自动填充派生字段;
  • 创建后补充同事务内的模型相关数据;
  • 查询后做轻量转换。

不建议放在 Hook 里的逻辑:

  • 大量外部 HTTP 调用;
  • 复杂消息发布;
  • 和模型无关的审计逻辑;
  • 需要全局开关的横切行为。

这些更适合全局事件或应用服务层。

跳过 Hook

_, err := db.Use[Product]().
    SkipHooks().
    Create(ctx, product)

适合导入脚本、数据修复、可信内部任务。业务入口默认不建议跳过 Hook。

全局事件

事件通过 db.On 订阅。

off := db.On(oro.AfterCreate, func(ctx context.Context, event *oro.Event) error {
    // 审计、缓存失效、发送领域事件
    return nil
})
defer off()

db.On 返回取消订阅函数。测试或短生命周期任务中建议调用 off()

支持的事件

事件 说明
BeforeCreate / AfterCreate 创建前后
BeforeUpdate / AfterUpdate 更新前后
BeforeDelete / AfterDelete 删除前后
BeforeRestore / AfterRestore 恢复前后
AfterFind 查询后
BeforeSQL / AfterSQL SQL 执行前后
AfterCacheHit / AfterCacheMiss 查询缓存命中/未命中
AfterCommit / AfterRollback 事务提交/回滚后

Event 结构

type Event struct {
    Name      oro.EventName
    DB        *oro.DB
    ModelName string
    Table     string
    Model     any

    Operation    string
    Values       oro.Map
    RowsAffected int64
    SoftDelete   bool

    SQL      string
    Args     []any
    Duration time.Duration
    Err      error
}

事件处理器返回错误时,ORM 会返回 oro.ErrEvent

跳过事件

_, err := db.Use[Product]().
    SkipEvents().
    Update(ctx, oro.Map{"Price": 120})

SkipEvents 不跳过模型 Hook。需要同时跳过时显式写两个:

_, err := db.Use[Product]().
    SkipHooks().
    SkipEvents().
    Create(ctx, product)

SQL 日志优先用 Logger

虽然 Oro 有 BeforeSQL / AfterSQL 事件,但 SQL 日志更推荐使用 Config.Logger

db, err := oro.Open(oro.Config{
    Logger:   logger,
    LogLevel: oro.LogLevelInfo,
})

事件适合审计、指标、trace 和缓存失效;日志接口适合统一格式化和接入日志系统。

事务事件

err := db.Transaction(ctx, func(tx *oro.DB) error {
    _, err := tx.Use[Product]().Create(ctx, product)
    return err
})

事务提交后触发 AfterCommit,回滚后触发 AfterRollback。这类事件适合做事务完成后的后置处理。

执行顺序

单条模型创建的大致顺序:

BeforeCreate hook
BeforeCreate event
SQL insert
AfterCreate hook
AfterCreate event

SQL 事件围绕具体 SQL 执行触发。事务事件在事务结束后触发。

实战建议

需求 推荐
模型字段整理 Hook
模型内部校验 Hook
审计日志 Event
缓存失效 Event
SQL 日志 Logger
Trace/metrics Event 或 Logger
导入脚本提速 SkipHooks / SkipEvents
编辑此页