Review of the dependency graph

Throughout this book, we have used the dependency graph as a way to discover potential issues. Here is how it looked when we started:

For a small service with only three endpoints, it's kind of complicated. From this graph, we also noticed that there were a lot of arrows pointing to the data, config, and logging packages.

Working under the assumption that more arrows going into or coming out of a package meant the more risk, complexity, and coupling, we set about trying to reduce these relationships.

The highest impact change was our adoption of the config injection, which included the definition of local config interfaces (as discussed in the previous section). This removed all of the arrows going into the config package, except for the one from main(), which we cannot remove.

Furthermore, during our config injection work, we also removed all the references to the global logging instance, and instead injected the logger. This, however, did not change the graph. This was due to our decision to re-use the Logger interface defined in that package.

We could have defined a copy of this interface inside every package and removed this coupling, but decided not to, given that the logger definition was probably not going to change. Copying the interface everywhere would add code for no gain beyond removing arrows from the graph.

After all of our refactoring and decoupling work, our dependency graph looks like the following diagram:

It's better, but sadly, it's still rather messy. To address this and the issue regarding the logging interface that we mentioned earlier, I have one more trick to show you.

So far, we have been generating the graphs with a command like the following:

$ BASE_PKG=github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch12/acme
godepgraph -s -o $BASE_PKG $BASE_PKG | dot -Tpng -o depgraph.png

We can remove the logging package from the chart by using Godepgraph's exclusions feature, changing the command to the following form:

$ BASE_PKG=github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch12/acme
godepgraph -s -o $BASE_PKG -p $BASE_PKG/internal/logging $BASE_PKG | dot -Tpng -o depgraph.png

This finally gives us the nice clear pyramid of a graph that we had been aiming for:

You might be wondering if we can further flatten the graph by removing the links between the REST and model packages (get, list, and register).

We are currently injecting the model code into the REST package; however, the one remaining link between the two is the output format of the model packages. Let's take a look at this now.

Our list model API looks like this:

// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
cfg Config
data myLoader
}

// Exchange will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
// code removed
}

We are returning a slice of the *data.Person type, which forces our local interface in the REST package to be defined as follows:

type ListModel interface {
Do() ([]*data.Person, error)
}

Given that data.Person is a data transfer object (DTO), I am inclined to be pragmatic and leave it. We could, of course, remove it. To do so, we would need to change our ListModel definition to expect a slice of interface{}, and then define an interface into which we could cast our *data.Person when we need to use it.

There are two major issues with this. Firstly, it's a lot of extra work that only removes one line from the dependency graph, but makes the code messier. Secondly, we are effectively bypassing the type system and creating a way for our code to fail at runtime, should the return type of our model layer become different from the REST package's expectations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.26.138