Golang Weekly had a link to a thoughtful post about interfaces. It got me thinking about my use of interfaces in Go, and how I could improve here. I’ve been struggling with this a little recently. I think there’s still a bit I’ve got to unlearn.
In the Java world, where I got my start, the principal to maintainable systems was a component-based approach: small, internally coherent, units of functionality that you stick together. These units were self contained, dependencies were well defined, and everything was tested in isolation. This meant lots of interfaces, usually defined up front before you even start writing the code that uses them.
I can see why this is popular. The appeal of component design is reuse: build one component for your system and you can use it in another system. I think the principals come from electrical engineering, where you build isolated components, like a transistor or an IC, that when put together produces the working electrical system. So it’s no surprise that this was adopted by the Java and object orientated community, which took such ideals of reuse to extreme levels, as if you could build a system out of the components of another system (this seemed like the whole motivation behind J2EE). Nevertheless, it was a patten of design that appealed to me, especially my sense of coming up with grand designs of abstraction layers.
But recently, I’ve been experiencing a bit of loss in religion. As the post points out, the idea of component design have merit in principal, but they start to break down in reality. Code reuse isn’t free, and if taken too far, you end up spending so much effort on the costs of abstraction (rewriting models, maintaining interfaces, dealing with unit test mocks) for very little benefit. Are you really going to use that Go service in the “catalogue manager” in something else?
So I agree with the post but I come away from it wondering what an alternative to component design actually looks like. I’m still trying to figure this out, and it might be that I’ll need to read up on this some. But maybe it’s to take the idea of self contain units, and throw away the imagined idea of reuse. In concrete terms, ditch the interfaces and replace them with direct method calls.
As for testing, maybe focus less on testing individual units and more of the system as a whole. I’m already onboard with the idea of not mocking out the database in unit tests, but I’m starting to come around to the idea of a “unit” being more than just a single type tested in isolation. I’m guessing this is inevitable as you throw away your interfaces and start depending on other types explicitly. That is, until you begin seeing areas where reuse is possible. But maybe this gets back to the original idea of Go interfaces: they’re more discovered than prescribed.
Anyway, it might be worth trying this approach for the next personal project I have in mind. Of course, that’s the easy part. Finding a way to adopt this pattern at work would be interesting.