Skip to content

Hooks (简体中文)

jiangz222 edited this page Sep 18, 2021 · 15 revisions

About Hooks:

一般的Hook会通过被操作文档实现hook 回调方法,以实现Hook功能。但是Qmgo的很多操作入参没有文档,同时我们认为这是一种很简洁的操作方式.

所以QmgoHook v1和一般的Hook实现会有不同:

  • 用户自己实现Hook的结构体方法,通过具体某个操作的options传递给QmgoQmgo 自动回调

  • 这几个API InsertOneInsertManyUpsert and ReplaceOne 的回调可以更简单,不依赖于options

  • 如果Hook操作失败,暂时没有做回滚数据库操作的功能

  • 某操作前和操作后的hook生效是互相独立的,即只需要实现BeforeInsert() error就能实现Insert前的hook

Insert Hook

实现Insert Hook,用户需要实现结构体方法:

BeforeInsert(ctx context.Context) error // 实现本方法让insert前的hook生效
AfterInsert(ctx context.Context) error  // 实现本方法让insert后的hook生效

InsertOne

InsertOne流程中

  • 通过自定义结构(下例的User)实现InsertHook的方法

  • 通过options.InsertOneOptions传入来实现Hook回调

  • 如果第二个参数doc实现了InsertHook的方法,那么可以不传入options.InsertOneOptions

  • 如果使用文档User来直接实现Hook的方法,在BeforeInsert()中修改文档,修改后的文档会被插入数据库

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}
func (u *User) AfterInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}

u := &User{Name: "Alice", Age: 7}

_, err := cli.InsertOne(context.Background(), u)

// 下面的例子也可以工作
// _, err := cli.InsertOne(context.Background(), u, options.InsertOneOptions{
//  InsertHook: u,
// })

InsertMany

InsertMany流程中

  • 通过自定义结构(下例的User)实现InsertHook方法,

  • 通过options.InsertManyOptions传入来实现Hook回调。

  • 如果第二个参数docs实现了InsertHook的方法,那么可以不传入options.InsertManyOptions

  • 如果使用文档User来直接实现Hook方法,在BeforeInsert()中修改文档,修改后的文档会被插入数据库

  • 因为插入的是多个文档,而hook是针对每个文档都会发生,所以Hook会根据插入文档数量被回调多次

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}
func (u *User) AfterInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}

u1 := &User{Name: "Lucas", Age: 7}
u2 := &User{Name: "Alice", Age: 7}
us := []*User{u1, u2}
_, err := cli.InsertMany(ctx, us, options.InsertManyOptions{
  InsertHook: us,
})

// 下面的代码也能够工作
// _, err := cli.InsertMany(ctx, us, options.InsertManyOptions{
//  InsertHook: us,
// })

Update Hook

实现Update操作的Hook,用户需要实现结构体方法:

BeforeUpdate(ctx context.Context) error // 实现本方法让update前的hook生效
AfterUpdate(ctx context.Context) error // 实现本方法让update后的hook生效

UpdateOne/UpdateAll

  • 通过自定义结构(下例中的MyUpdateHook)实现UpdateHook方法

  • 通过options.UpdateOptions传入来实现Hook回调。

  • 如果使用文档User来直接实现Hook方法,在方法中对文档进行操作,不会 对写入数据库中的文档产生影响。

type MyUpdateHook struct {
	beforeUpdateCount int
	afterUpdateCount  int
}
func (u *MyUpdateHook) BeforeUpdate(ctx context.Context) error {
	u.beforeUpdateCount++
	return nil
}
func (u *MyUpdateHook) AfterUpdate(ctx context.Context) error {
	u.afterUpdateCount++
	return nil
}
u := User{Name: "Lucas", Age: 7}
uh := &MyUpdateHook{}
_, err := cli.InsertOne(context.Background(), u)
ast.NoError(err)

err = cli.UpdateOne(ctx, bson.M{"name": "Lucas"}, bson.M{operator.Set: bson.M{"age": 27}}, options.UpdateOptions{
  UpdateHook: uh,
})

cli.UpdateAll(ctx, bson.M{"name": "Lucas"}, bson.M{operator.Set: bson.M{"age": 27}}, options.UpdateOptions{
  UpdateHook: uh,
})

ReplaceOne

  • 通过自定义结构(下例中的User)实现UpdateHook方法

  • 通过options.UpdateOptions传入来实现Hook回调。

  • 如果第3个参数doc实现了UpdateHook方法,则options.UpdateOptions可以不传入

  • 如果使用文档User来直接实现Hook方法,在方法中对文档进行操作,会将改写的文档更新到数据库。

type User struct {
	Name string `bson:"name"`
	Age  int    `bson:"age"`

	beforeUpdate int
	afterUpdate  int
}

func (u *User) BeforeUpdate(ctx context.Context) error {
	u.beforeUpdate++
	return nil
}

func (u *User) AfterUpdate(ctx context.Context) error {
	u.afterUpdate++
	return nil
}

u := &User{}
err = cli.ReplaceOne(ctx, bson.M{"name": "Lucas"}, &u)

// 下面的代码也可以工作
// err = cli.ReplaceOne(ctx, bson.M{"name": "Lucas"}, &u, options.UpdateOptions{
//		UpdateHook: u,
// })

Upsert Hook

实现Upsert操作的Hook,用户需要实现结构体方法:

BeforeUpsert(ctx context.Context) error // 实现本方法让upsert前的hook生效
AfterUpsert(ctx context.Context) error // 实现本方法让upser后的hook生效

Upsert流程中

  • 通过自定义结构(下例的User)实现UpsertHook的方法

  • 通过options.UpsertOptions传入来实现Hook回调

  • 如果第二个参数doc实现了UpsertHook的方法,那么可以不传入options.UpsertOptions

  • 如果使用文档User来直接实现Hook的方法,在BeforeUpsert()中修改文档,修改后的文档会被插入数据库

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeUpsert(ctx context.Context) error {
	return nil
}

func (u *User) AfterUpsert(ctx context.Context) error {
	return nil
}

u := &User{Name: "Alice", Age: 7}
_, err = cli.Upsert(context.Background(), bson.M{"name": "Lucas"}, u)

// 下面的代码也可以运行
// _, err = cli.Upsert(context.Background(), bson.M{"name": "Lucas"}, u, options.UpsertOptions{
//	UpsertHook: myHook,
//})

Remove Hook

实现Remove操作的Hook,用户需要实现结构体方法:

BeforeRemove(ctx context.Context) error // 实现本方法让remove前的hook生效
AfterRemove(ctx context.Context) error // 实现本方法让remove后的hook生效

Remove/RemoveAll

  • 自定义结构(下例中的MyRemoveHook)实现RemoveHook方法
  • 通过options.RemoveOptions传入来实现Hook回调。
type MyRemoveHook struct {
	beforeCount int
	afterCount  int
}

func (m *MyRemoveHook) BeforeRemove(ctx context.Context) error {
	m.beforeCount++
	return nil
}

func (m *MyRemoveHook) AfterRemove(ctx context.Context) error {
	m.afterCount++
	return nil
}

rh := &MyRemoveHook{}
err = cli.Remove(ctx, bson.M{"age": 17}, options.RemoveOptions{
  RemoveHook: rh,
})

rh = &MyRemoveHook{}
_, err = cli.RemoveAll(ctx, bson.M{"age": "7"}, options.RemoveOptions{
  RemoveHook: rh,
})

QueryHook

实现Query操作的Hook,用户需要实现结构体方法:

BeforeQuery(ctx context.Context) error // 实现本方法让query前的hook生效
AfterQuery(ctx context.Context) error // 实现本方法让query后的hook生效

Find().One/Find().All()

  • 自定义结构(下例中的MyQueryHook)实现QueryHook方法
  • 通过options.FindOptions传入来实现Hook回调。
type MyQueryHook struct {
	beforeCount int
	afterCount  int
}

func (q *MyQueryHook) BeforeQuery(ctx context.Context) error {
	q.beforeCount++
	return nil
}

func (q *MyQueryHook) AfterQuery(ctx context.Context) error {
	q.afterCount++
	return nil
}

qk := &MyQueryHook{}
err = cli.Find(ctx, bson.M{"age": 17}, options.FindOptions{
  QueryHook: qk,
}).One(ur)

err = cli.Find(ctx, bson.M{"age": 17}, options.FindOptions{
  QueryHook: qh,
}).All(&ur)