OVO Tech Blog
OVO Tech Blog

Our journey navigating the technosphere

Software Engineer



Onion Architecture - Cutting onions, without the tears!

Creating a good architecture and structuring software is similar to cooking a good meal with a solid base of ingredients, but when it comes to cutting those onions, don't go shedding tears!

In this post I'll be giving an insight into Onion Architecture and how it can be used as a well structured alternative to traditional architecture models.

Ingredients of an Onion Architecture

The idea for an Onion Architecture was first introduced by Jeffrey Palermo and is a concept closely related to Hexagonal and Layered architectures. The difference being that it uses an alternate layered approach made up of four layers with the fourth acting as a lightweight or replaceable layer of components:

Onions have layers

The image below shows the layers and the overlap between each one.


The relationship between each layer is one way and follows the rule that outer layers can depend on inner layers but not the other way around. When we use Onion Architecture we're essentially applying the Dependency Inversion Principle in the form of a whole architecture instead of one or multiple classes.

The Domain Model, Domain Services and Application Services layers make up the Application Core. The application core is the structure that should stay the same and contains all the logic to run the outer layer. The outer layer is interchangeable through the use of interfaces, inverting the dependencies.

Cooking with onions

In a project we can structure our code into packages, these packages act as layers.


In a Scala project we can further enforce the relationship between each layer by scoping our classes to the packages themselves with private[domain] final class MyClass() and defining the dependencies by using dependsOn when creating projects in our build.sbt file.

lazy val model = Project("onion-model", file("model"))

lazy val domain = Project("onion-domain", file("domain"))
lazy val application = Project("onion-application", file("application"))

lazy val persistence = Project("onion-persistence", file("persistence"))
  .dependsOn(domain, application)

By enforcing these rules of dependencies, outer layers can interact with inner layers but those inner layers aren't dependent on the interacting outer layers.

Inner domain and application layers will define criteria through interfaces, in the outer layers implementations are created. Implementation behaviour is dependent on the signature its domain interface. A common example of this would be for database interaction - an interface is defined in the domain services layer. This isn't dependent on the type of database or the data-source meaning we won't break any application logic by moving from using a Postgres database to MongoDB as a source.

Arguing about onions

There are a number of pros and cons to Onion Architecture.



Eating onions

To summarize, Onion Architecture provides a well thought out approach to structuring software and is easy to visualize. While it doesn't solve problems of interfaces interfering with the readability of code and changes to inner layers can surface breaking changes to dependent outer layers. Onion Architecture is simple to implement and enforces good relationships with one way coupling between software components. The interchangeability and separation of peripherals ensures that core application processes aren't interfered with and can keep up with changing requirements, data sources and improving tools in the engineering world.

Next time you're building a project, why not throw in an onion!

Software Engineer

View Comments