Golang Interfaces Simplified
Understanding A Misunderstood Beast
TL;DR
Golang interfaces are like electrical outlets and plugs and should be treated as such. See the code section below.
Ugh… Boring Details
Full disclosure: I am dead set against willy-nilly creation of interfaces. They introduce overhead and possible obfuscation of code and I hate hidden code (especially when on an outage call and trying to unravel spaghetti code). Just because you can do it, does not mean you should…
So a simple analogy to the Golang interface is an electrical outlet - commonly referred to as the receptacle. Our implementation of the interface is like the plug which fits into the receptacle. This electrical interface can accept any device which has the correct plug and meets the voltage and amperage requirements. You want to use a different device, unplug it and plug in another one. Voila! It is that easy.
Let’s say our device is a coffee pot. If we hard wire the coffee pot - or worse yet, make it a part of the wall, cabinet, electrical, and plumbing systems then it becomes time consuming to replace the coffee pot. However, if we implement interfaces to the wall and cabinet (with feet or independent mounts), to the plumbing (with quick disconnect fittings), and to the electrical (with the proper plug), then removing or replacing the coffee pot becomes somewhat trivial. Just implement the same interfaces to the new coffee pot, plug it all in, and the go-juice is flowing.
Interfaces make our Golang programming lives easier by allowing plug and play. In my rule book, there are only 3 reasons to create an interface.
In Jeff Foxworthy style:
- If there are certain methods you want to access without rewriting a lot of code, then you might need an interface.
- If you need to mock up databases or external APIs in unit tests, then you might need an interface.
- If you need to quickly plug different packages in and out of the code (like a logger) without a complete logic rewrite, then you might need an interface.
Other than those possible reasons above, YOU PROBABLY DO NOT NEED TO CREATE AN INTERFACE!
Those are just my rules. They work for me but they may not for you. The point is you should make rules which fit your purposes; by no means should interfaces be used without discretion and purpose. Salt is nice in small doses. Too much can be bad. The same goes for interfaces.
Code
Here is a simple logger interface:
I took thoughts from Chris Hines’ slide deck and gokit’s log package , but I did not agree with everything — their oversimplification obfuscates too much. I wanted a structured logger which did not hide how to use it, was simple (only implements a few methods), and allowed all the complexity of what package was being used (e.g. zap, logrus, etc.) to be handled in a “New” function and a single brain-dead call function — aka “Print”.
Now onto the implementation detail of adding zap logger into the above interface:
And this is how we use it:
The expected output should look something like:
The full code can be found here: https://github.com/cavebeavis/coolog
Happy Interfacing!