Extensible GO using interfaces

Interfaces in go really allow you to think about the functionality of your code, rather than a specific implementation of that functionality.

For example, you might want a function to get an account from somewhere. It doesn’t matter whether the source is a database, cache, or even a filesystem you expect the return value to be of the same type.

Use an interface…

In the snippet below we define an AccountGetter interface that allows us to get an account from it’s ID. We have been able to define the functionality of GetAccount without worrying about the implementation of our repository.

package account

type Account struct{
	ID int64
	Name string
}

type AccountGetter interface {
	Get(id int64) (*Account, error)
}

func GetAccount(id int64, getter AccountGetter) (*Account, error) {
	return getter.Get(id)
}

We can now pass any implementation of this interface to GetAccount.

type DbRepo struct{
	db *sql.DB
}

func (g *DbRepo) Get(id int64) (*Account, error) {
	// Do something with g.db
	return &Account{
		ID: id,
		Name: "Account from db",
	}, nil
}

type CacheRepo struct{
}

func (g *CacheRepo) Get(id int64) (*Account, error) {
	// Do something with cache
	return &Account{
		ID: id,
		Name: "Account from cache",
	}, nil
}

_, _ = GetAccount(1, &DbRepo{})
_, _ = GetAccount(1, &CacheRepo{})

Combining interfaces

As well as providing succinct interfaces, we can create combinations of existing interfaces.

package account

type Account struct{
	ID int64
	Name string
}

type AccountGetter interface {
	Get(id int64) (*Account, error)
}

type AccountSetter interface {
	Set(id int64, name string) (*Account, error)
}

type AccountRepo interface {
	AccountGetter
	AccountSetter
}

func DoSomething(repo AccountRepo) {
	_, _ = repo.Set(1, "set acc")
	_, _ = repo.Get(1)
}
comments powered by Disqus