5. Hook

Kesa...大约 2 分钟golang

day5-hooksopen in new window

1. Hook

Hook(钩子),主要思想是提前在可能增加功能的地方埋好(预设)一个钩子,当需要重新修改或者增加这个地方的逻辑的时候,把扩展的类或者方法挂载到这个点即可。

例如:

  • Github 支持的 travis 持续集成服务,当有 git push 事件发生时,会触发 travis 拉取新的代码进行构建。
  • 在IDE 中当按下 Ctrl + s 后,自动格式化代码。
  • 前端常用的 hot reload 机制,前端代码发生变更时,自动编译打包,通知浏览器自动刷新页面,实现所写即所得

钩子机制设计的好坏,取决于扩展点选择的是否合适。例如对于持续集成来说,代码如果不发生变更,反复构建是没有意义的,因此钩子应设计在代码可能发生变更的地方,比如 MR、PR 合并前后。

对于 ORM 框架来说,Hook 适合放在 CRUD 操作的前后。例如:查询的数据中包含敏感数据,可以在查询后对其进行脱敏后再返回。

2. 实现

Hook 需要和结构体绑定,交给用户实现,ORM 框架则在 CRUD 操作中插入这些 hook。

session/hooks.go

package session

import (
	"geeorm/log"
	"reflect"
)

// Hooks
const (
	BeforeQuery  = "BeforeQuery"
	AfterQuery   = "AfterQuery"
	BeforeUpdate = "BeforeUpdate"
	AfterUpdate  = "AfterUpdate"
	BeforeDelete = "BeforeDelete"
	AfterDelete  = "AfterDelete"
	BeforeInsert = "BeforeInsert"
	AfterInsert  = "AfterInsert"
)

// CallMethod calls the registered hooks
func (s *Session) CallMethod(method string, value any) {
	var fm reflect.Value
	if value == nil {
		fm = reflect.ValueOf(s.RefTable().Model).MethodByName(method)
	} else {
		fm = reflect.ValueOf(value).MethodByName(method)
	}

	param := []reflect.Value{reflect.ValueOf(s)}
	if fm.IsValid() {
		if v := fm.Call(param); len(v) > 0 {
			if err, ok := v[0].Interface().(error); ok {
				log.Error(err)
			}
		}
	}

	return
}
  1. 判断当前操作对象value,若为空则默认设置为Session.refTable.Model
  2. 获取 hook 方法
  3. 获取 hook 方法的参数,类型为*Session
  4. 调用 hook

2.1 插入 Hook

在 CRUD 操作中插入 Hook,例如Find:

func (s *Session) Find(vals any) error {
	s.CallMethod(BeforeQuery, nil)
	...
	for rows.Next() {
		...
		s.CallMethod(AfterQuery, dst.Addr().Interface())
		dstSlice.Set(reflect.Append(dstSlice, dst))
	}
	return rows.Close()
}

3. 单元测试

package session

import (
	"geeorm/log"
	"testing"
)

type Account struct {
	ID       int `geeorm:"PRIMARY KEY"`
	Password string
}

const (
	pass = "******"
)

func (a *Account) BeforeInsert(s *Session) error {
	log.Info("before insert", a)
	a.ID += 1000
	return nil
}

func (a *Account) AfterQuery(s *Session) error {
	log.Info("after query", a)
	a.Password = pass
	return nil
}

func TestSession_CallMethod(t *testing.T) {
	s := newSession().Model(&Account{})
	_ = s.DropTable()
	_ = s.CreateTable()
	_, _ = s.Insert(&Account{1, "123"}, &Account{2, "abc"})

	u := &Account{}

	err := s.First(u)
	if err != nil || u.ID != 1001 || u.Password != pass {
		t.Fatal("failed to call after query hooks")
	}

}

Reference

  1. https://geektutu.com/post/geeorm-day5.htmlopen in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2