Software Architecture: About Dependencies

Niklas Wulf
grandcentrix
Published in
5 min readFeb 6, 2019

We at grandcentrix are often tasked with solving complex problems. In order to be able to build solutions for those problems we apply modern but proven methodologies, technologies and also architectures. Today I want to talk about two important aspects of software architectures, namely boundaries and (in-)dependencies within an implementation.

Several quite similar architectural styles influence us today, two of which I take most inspiration from.

The Hexagonal Architecture (aka Plugs & Adapters) is one style of preference for many practitioners of Domain Driven Design and one of our methodologies of choice.

The Clean Architecture by Uncle Bob is really similar to it, though — at least for me — it is more about deriving architecture from principles in contrast to providing you with a ready-made “architecture to rule them all”.

Good Architecture

What makes a software architecture “good”? The Clean Architecture blog specifies some goals that architectures should strive to achieve. Systems with good architectures are:

  • Independent of Frameworks
  • Testable
  • Independent of UI
  • Independent of Database
  • Independent of any external agency

“But Niklas”, you say. “This is obvious”, you say. And I say: “You’re right!”.

And still we often find ourselves in situations where we did not do the obvious.

Story time!

See if the following three short-stories sound familiar to you:

Sprint 1: Evaluation… “Let’s choose Azure Functions, because they fulfill our requirements.”

Sprint 2: Implementation… *hack hack hack*

Sprint N: Realization… “Azure Functions do not in fact fulfill our requirements.”

…or…

Sprint 1: “Let’s choose the hapi.js web framework.”

Sprint N: Breaking changes… “We need to update to version 17, which includes breaking API changes.”

…or…

Sprint 1: “Let’s choose Realm to store local copies of backend entities.”

Sprint N: Bugs… “Realm leads to crashes when used wrongly together with threads.”

Those are sad stories. We made mistakes. We made bad decisions. The thing is: We will always make mistakes.

Much more important is how the story continues. Consider these two possible endings:

Sprint N+1: “Oh. Now we have to re-implement/change everything. It will take N/2 sprints.”

Been there, done that. My main point today is, that it is much nicer to go down the following path:

Sprint N+1: “Easy: We replace/update/fix the implementation. We only have to adjust our abstraction layer.”

This is what independence is about (non-exclusively): Reducing technical risks.

The Clean Architecture

In order to achieve the required independence, the Clean Architecture’s most striking feature are onion-like layers.

The most important rule that governs this architecture is the Dependency Rule:

Dependencies must only point inwards.

Onion layers and the Dependency Rule sound easy enough, but we already established above that it does not come naturally for most of us.

Dependencies

First of all: What exactly are dependencies?

We are talking about code dependencies. Code depends on other code, if it uses it in any way: Imports/requires, references, function calls, you name it (another nice read: “Clean Architecture Guide: Data Flow != Dependency Rule”).

Sometimes (especially in untyped languages) dependencies happen implicitly and often accidentally. Example code is worth a thousand words (please keep in mind that this is a bad example):

The core building and database components both use the request object of the web framework, and therefore depend on it. The web- and data-frameworks accidentally leak into the implementation:

We made exactly this mistake some time ago and upgrading the hapi web framework became basically impossible when they released some breaking API changes. It took us weeks (or was it months?) of refactorings until we were able to upgrade…

Pointing inwards

The second part of the Dependency Rule is even harder to understand at first. From the onion diagram you have probably already guessed what “inwards” means.

Rule of thumb: The farther away code is from the inputs and outputs of a system, the more “inside” it is (also referenced to as “higher-level”).

The code example above demonstrates a typical case where developers fail at this: Data access. Not only does the core code call directly into the data-access layer (and therefore depends on it), even worse: The database result object is returned and passed around to the other layers. Sounds familiar? ;-)

Let me repeat: Both the web framework and the data access layers should be the “lowest level” code! This is especially confusing because N-tier architectures are often depicted top-to-bottom.

Putting it all together

In order to let the dependency point in the correct direction, we apply the Dependency Inversion principle (the D in SOLID). For the data access layer the Repository Pattern is a nice way to go. It can interpreted as a special case of the Plugs & Adapters architecture by the way.

Let’s see, how a better implementation of our example could look like:

A lot of things happened here (and I hope that the weird half-TypeScript does not stand in the way of understanding):

  • The web framework details are handled in the web layer. No other layers depend on it. (lines 6–9)
  • The core domain defines only what is important to the domain, no longer depending on anything external (lines 14–27)
  • By implementing dependency inversion with the Repository Pattern the data-access layer depends on the core domain and no longer the other way around (line 25–27 and 30)
  • The database object is no longer returned to “upper” layers and instead transformed into a domain object (line 33)

The software architecture, albeit being far from perfect, is now much nicer to look at and follows some of the ideas of the Clean Architecture:

Better, but not perfect yet. It’s a journey…

The take away

There’s obviously a lot you have to keep in mind when designing software. This blog post only scratched the surface of what . Let’s recap:

  • Goals of a good architecture: Testable, independent of frameworks, UI, databases, …
  • Dependencies lead to technical risks
  • Think about clear boundaries in your implementation
  • Be aware of dependencies and let them point in the right direction

Especially as a less experienced programmer you might be tempted to argue that…

  • …this is more work? — Yes.
  • …this is tedious work? — Oh, yes.
  • …this is this hard to get right? — Often!

But especially if in doubt: Try to do it nonetheless. You will reap the benefits after a few sprints.

Further reading

If you’re interested in the grand scheme of things you should read The Clean Architecture. If you’re a bit like me and like to obtain knowledge in a dense and short text, read the rather excellent “DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together”. Then of course there’s Martin Fowler’s Catalog of Patterns of Enterprise Application Architecture and the corresponding book.

--

--