Skip to content

Instantly share code, notes, and snippets.

@kevinmichaelchen
Created September 7, 2022 23:11
Show Gist options
  • Save kevinmichaelchen/6afae8ecf0bfb28248877c39abaf0ced to your computer and use it in GitHub Desktop.
Save kevinmichaelchen/6afae8ecf0bfb28248877c39abaf0ced to your computer and use it in GitHub Desktop.
go interfaces and proverbs

πŸ‘‹πŸΌ here's what I'm thinking regarding my earlier comments about interfaces (inspired by here and mainly here).

This is a really subtle point that I'm still trying to wrap my head around and understand the tradeoffs πŸ˜†

I think at small scale it makes no difference whichever approach we take.

I definitely think we want and need interfaces (for mocking, multiple implementations, etc).

But it's just the where that is the focus here.

Say we've got the following directory structure:

β”œβ”€β”€ redis
β”‚  └── redis.go
β”œβ”€β”€ supply
β”‚  β”œβ”€β”€ supply.go
β”‚  └── supply_test.go
β”œβ”€β”€ demand
β”‚  β”œβ”€β”€ demand.go
β”‚  └── demand_test.go

Say our Redis client can do a lot of different things:

// redis.go
type Cache interface {
  GetName() string
  Put(ctx context.Context, key string, value string) error
  Get(ctx context.Context, key string) (value string, err error)
  Remove(ctx context.Context, key string) (err error)
  ContainsKey(ctx context.Context, key string) (containsKey bool)
}

And say the supply and demand packages both use the client, but only a subset of the client's functionality. And those subsets of functionality might not be overlapping.

This is where defining interfaces on the client-/consumer-side has the benefit of smallness and keeps more with the proverb: "The bigger the interface, the weaker the abstraction."

// supply.go

// a small interface - means we won't 
// have to mock as much in the unit test
type RedisCache interface {
  GetName()
}

func NewService(rc RedisCache) *Service {
  return &Service{
    RedisCache: rc,
  }
}

type Service struct {
  RedisCache RedisCache
}

func (s *Service) BusinessLogic() {
  s.RedisCache.GetName()
}

In main.go we can inject the implementation itself:

func main() {
  rc := redis.NewCache()
  supplyService := supply.NewService(rc)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment