Extensible GO using interfaces
How can interfaces make my code easier to maintain and extend?
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)
}