Applying The Adapter Design Pattern To Decouple Libraries From Go Apps
A major benefit of Go is the explicitness of the code. There is no magic going on, no annotations doing implicit work. The flow is clear about what it is happening with the data while elegantly minimizing boilerplate code and maximizing readability. Maybe, that’s the reason why frameworks are not popular among Go developers. We prefer using libraries that do one thing and one thing only, so we can compose them as we think they fit. This is great for mature developers and painful for beginners who expect an opinionated archetype to start with, but Mandalorians think this is not the way.
Using libraries directly may look simple with go get
and import
, but we need more than that to keep applications weakly coupled with third-party dependencies. We want to prevent too many changes in the application when a library is discontinued, a faster and secure alternative is available, or even when the license becomes incompatible with the project.
To illustrate that, we are going to apply the Adapter Design Pattern to abstract a caching mechanism. The rest of the code should profit from caching features, but should not be aware of what we are using to cache data. The Adapter Pattern is good for that because it makes available just enough features from the third-party library for the precise needs of the application. Everything else is hidden. The Cache
interface defines the required features that can be provided by ourselves or third-party libraries.
The follow implementation of the Cache
interface is specialized on Redis. It encapsulates all the complexity by adapting the Redigo interface to the interface known by the application. There is a function implementation for each Cache
function definition.
It amazes me how much complexity is hidden by using an adapter. A good deal of knowledge about the library is encapsulated in a single place and we don’t want the rest of the code to know it is using Redis. So we use a factory function to provide the supported caching implementation to callers.
This factory connects to Redis, stores the connection in an instance of RedisCache
and returns the instance to the caller, but the caller only sees it as Cache
. Given the name of the factory function, it is unclear for the caller what it is using as a cache.
A complete example of this code is available in my repo of Examples. Do not hesitate to submit a pull request if you find something that can be improved.
Using the adapter pattern for every dependency is very important in Go. It puts the developer in control of the design instead of succumbing to frameworks impositions. It also allows mocking the dependencies to enable effective unit testing. Your future self and colleagues can’t thank you enough for using adapters.