

Similar to counterfeiter
we use another tool called ifacecodegen that allows us to automatically generate code from interface definitions using a Go template.
ifacecodegen
is used to generate code that satisfies an interface type and wraps the existing type implementing said interface, think of middleware code that will be used for logging purposes (using logurs) or collecting metrics (using NewRelic).
For example, assuming you have code that follows the DDD paradigm (where there’s a Repository/Data Store and some sort of Service/Use Case):
// 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
}
And you’re planning to add logging support to determine how long each method takes when calling the remote data store method, you would need to do two things:
generate
directive for generating that file.For the first thing, the template, the content of that one will look like:
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 }}
Last, Adding the following directive:
//go:generate ifacecodegen -source main.go -template logger.tmpl -destination logger.gen.go -package main
With the generated code we should be able to still satisfy the contract defined by UserFinder
and add logging support!
$ ./ifacecodegen-example
2019/07/17 23:28:09 Find took 100.109011ms
2019/07/17 23:28:10 Find took 200.418756ms
Obviously this example is extremely simple but imagine the real human time you will save when you have hundreds of data store methods and you need to collect metrics for all them!
Feel free to browse the complete program and play with it!
ifacecodegen
is a simple yet powerful tool, highly recommended.