ππΌ 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)
}