类似于 counterfeiter我们使用另一个工具,叫做 ifacecodegen的工具,它允许我们使用Go模板从接口定义中自动生成代码。
ifacecodegen 是用来生成满足接口类型的代码,并包装实现所述接口的现有类型,想想看,中间件代码将用于记录目的(使用logurs)或收集指标(使用NewRelic)。
例如,假设你有遵循DDD范式的代码(其中有一个存储库/数据存储和某种服务/用例)。
// User ...
type User struct {
Role string
}
// UserFinder finds an existing User from a remote data store.
type UserFinder interface {
Find(id int64) (User, error)
}
// UserValidator determines if the requested user has specific roles.
type UserValidator struct {
f UserFinder
}
// NewUserValidator ...
func NewUserValidator(f UserFinder) UserValidator {
return UserValidator{f}
}
// IsAdmin determines if the user with the specified id is an admin.
func (v *UserValidator) IsAdmin(id int64) (bool, error) {
u, _ := v.f.Find(id) // XXX error validation omitted
if u.Role == "admin" {
return true, nil
}
return false, nil
}
// IsGuest determines if the user with the specified id is a guest.
func (v *UserValidator) IsGuest(id int64) (bool, error) {
u, _ := v.f.Find(id) // XXX error validation omitted
if u.Role == "guest" {
return true, nil
}
return false, nil
}
而且你打算添加日志支持,以确定在调用远程数据存储方法时每个方法需要多长时间,你需要做两件事。
- 实现一个模板,做你想实现的事情,和
- 添加一个
generate指令来生成该文件。
对于第一件事,即模板,那个的内容将看起来像。
type logger{{ .Name }} struct {
s {{ .Name }}
}
func NewLogger{{ .Name }}(s {{ .Name }}) {{ .Name }} {
return &logger{{ .Name }}{
s: s,
}
}
{{ $ifaceRef := . }}
{{ range .Methods }}
func (m *logger{{ $ifaceRef.Name }}) {{ .Name }}({{ input_parameters . }}) {{ $methodRef := . }}{{ output_parameters . }} {
now := time.Now()
defer func() {
log.Printf("%s took %s", "{{ .Name }}", time.Since(now))
}()
{{ return . }} m.s.{{ .Name }}({{ input_calls . }})
}
{{ end }}
最后,添加以下指令。
//go:generate ifacecodegen -source main.go -template logger.tmpl -destination logger.gen.go -package main
通过生成的代码,我们应该仍然能够满足UserFinder 所定义的契约,并增加对日志的支持!
$ ./ifacecodegen-example
2019/07/17 23:28:09 Find took 100.109011ms
2019/07/17 23:28:10 Find took 200.418756ms
显然,这个例子是非常简单的,但是想象一下,当你有数百个数据存储方法,并且需要为所有这些方法收集指标时,你将节省的真正的人力时间!
请随意浏览完整的程序并玩一玩!
ifacecodegen 是一个简单而强大的工具,强烈推荐。