Dependency Isolation in Go

Hamed Momeni
4 min readOct 22, 2019

--

If you’ve never written any Go code, grasping the mechanics of its interfaces could be burdensome at first. The fact that you don’t need to explicitly declare that a struct implements a certain interface will make newcomers a little more than confused. But let me tell you this; it’s a tremendously powerful feature.

But first a little bit of history…

Here is a little goofy gopher for you :)

If you’re a serious programmer you must be familiar with SOLID principles. The 5 principle which Uncle Bob coined and published as a guide for us to write good software. The fifth of those guidelines is the Dependency Inversion Principle which states:

High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

But that’s for Object Oriented Programming right?! Is Go even an object oriented language? No it’s not. And you should never think of it as one.

But even though Go is not an OO language, many of our previous practices holds true here too. Specially the Dependency Inversion Principle . So what’s Go’s interfaces have to do anything with it?

Let’s continue with an example. Imagine that you’re building a package which needs to store some data, but it doesn’t not care about where or how this data is stored. Let’s call it foo. Let’s also have Fooler interface which will have a Save func that another package will use.

As you can see the foo struct conforms to the Fooler interface. But where do we store the data? Let’s add an Storage interface so that we can use it to store some data.

But wait…!

We have no idea what functions that Storage interface is going to have. What if it names its Save function something like Store? What if it accepts different kinds of parameters in different order?

The cool thing here is, that we don’t really care about how or what methods that external persistence service will have. We define our own Storage interface and as long as it -the external service- conforms to our standards we can use it. Without any imports for external interfaces. As a matter of fact that other package doesn’t need to import anything from us too. That is called Duck Typing [1]. If it walks like duck and it quacks like a duck, then it is a duck.

So let’s define our interface like this.

Storage interface in Go with Store and Fetch functions

Now that we have our Storage we can refactor our foo struct.

foo struct in Go that implements a Save function

We’re not done yet. We need a way to pass in the Storage instance to the foo package. It is conventional in Go to have an exported function which instantiates things for other packages to use. This is how it goes…

A function to create a concrete implementation of the Fooler interafce

So now let’s create a sample storage package to see how it goes.

I purposefully named this package `datastore` to avoid confusing the less experienced

As you can see there is no import anywhere in both our packages. The only entity which needs to be aware of our individual interfaces is the higher level package which will pass the Datastore to the NewFooler function. Here is the main package that will glue these two packages together.

Go’s main function
Notice that `NewFooler` doesn’t complain about the type of datastore

main is the only package which needs to be aware of those 2 components. You now may begin to understand how powerful and flexible this feature is in creating highly decoupled software components.

Go is a simple enough language to learn but it is difficult master. It has many intricate features such as this one that only practice can make a perfect gopher.

Footnotes:

[1] Technically in Go Duck Typing is called Structural Typing since it is done at compile time whilst Duck Typing is done at run time.

--

--

Hamed Momeni

A programmer who’s fond of Android/Kotlin, Go, PHP and DevOps (Docker/K8s) among other things