Hector Yeomans Blog

Golang and interfaces misuse

January 18, 2020

One of my favorite things about Golang is the concept of interface. It’s also one of my grievances every time I see them used as C#/Java interfaces.

It’s typical to see a colossal interface defined at a package level file, for example, a package that defines CRUD operations for a User.

package db

import "context"

// User --
type User struct {
 ID int
 Email string
}

// UsersDb pointless interface
type UsersDb interface {
 Get(ctx context.Context, id int) User
 Create(ctx context.Context, user User) User
 Update(ctx context.Context, id int, user User) User
 Delete(ctx context.Context, id int) bool
}
//... then we have the actual struct that "implements" the interface

What benefit does this bring to the package? Some say mocking capabilities. From what I’ve seen to mock that kind of package, you need a generator that generates and updates the mocks.

A better approach is to follow what Golang wiki describes:

Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values.

In the case of our db package, we defined an interface without knowing if another consumer implements it. But how do we test? And I think that’s the wrong question to ask, the right question is, how does my business logic require User information?

Let’s assume that we have a simple business requirement: when retrieving the name, the name its always uppercase.

You define a dumb business package, so you follow separation of concerns and get something like:

package business

import (
 "context"
 "strings"

 "github.com/hyeomans/interface-misuses/db"
)

// User --
type User struct {
 Name string
}

// Service --
type Service struct {
 //Here you will have logging
 //Counts, etc
 userService db.UserService
}

// NewService --
func NewService(userService db.UserService) Service {
 return Service{
 userService: userService,
 }
}

// GetUser --
func (s Service) GetUser(ctx context.Context, id int) User {
 dbUser := s.userService.Get(ctx, id)
 return User{Name: strings.ToUpper(dbUser.Name)}
}

You followed dependency injection and because you defined an interface of db.UserService you can generate mocks and get testing.

However, there is much boilerplate code to get testing and mocking. There is a more straightforward way to do it, taking advantage of what Golang wiki says:

Do not define interfaces on the implementor side of an API “for mocking”; instead, design the API so that it can be tested using the public API of the real implementation. https://github.com/golang/go/wiki/CodeReviewComments#interfaces

Let’s define an interface with only one method in business.Service, then instead of injecting the whole db.UserService, substitute that with your new interface.

package business

import (
 "context"
 "strings"

 "github.com/hyeomans/interface-misuses/db"
)

// User --
type User struct {
 Name string
}

// UserGetter <---- This interface is defined in the
// consumer side.
// "Do not define interfaces before they are used"
type UserGetter interface {
 Get(ctx context.Context, id int) db.User
}

// Service --
type Service struct {
 userGetter UserGetter // <--- Change this
}

// NewService --
func NewService(userGetter UserGetter) Service {
 return Service{
 userGetter: userGetter, // <----- Change this
 }
}

// GetUser --
func (s Service) GetUser(ctx context.Context, id int) User {
 dbUser := s.userGetter.Get(ctx, id) // You still get a dbUser
 return User{Name: strings.ToUpper(dbUser.Name)}
}

Your main.go file looks something like:

package main

import (
 "context"
 "fmt"

 "github.com/hyeomans/interface-misuses/business"
 "github.com/hyeomans/interface-misuses/db"
)

func main() {
 ctx := context.Background()
 userService := db.NewUserService()
 service := business.NewService(userService)

 user := service.GetUser(ctx, 1)
 fmt.Println(user.Name)
}

Conclusion

Golang allows the perfect inversion of control, thanks to this pattern. In its essence, it is abstract, but once you start playing with it, it’s powerful.

If you need more information about this pattern, there are other great blog posts:

https://github.com/golang/go/wiki/CodeReviewComments#interfaces https://dave.cheney.net/2016/08/20/solid-go-design https://twitter.com/davecheney/status/1030790804011245569?lang=en https://www.ardanlabs.com/blog/2017/07/interface-semantics.html https://github.com/ardanlabs/gotraining/blob/master/topics/go/design/composition/pollution/example1/example1.go


Hector Yeomans

Written by Hector Yeomans who lives in Mexico and works remotely for Invision. Follow him on Twitter, or add him on LinkedIn.