Discussion: Onion Architecture

I’m curious to hear people’s opinions on implementing the Onion Architecture in an Elixir application.

Jeffrey Palermo, the originator of the Onion Architecture, states that it is “unashamedly biased toward object-oriented programming, and it puts objects before all others”. However, there has been some discussion on implementing the Onion Architecture in Functional Programming.

The diagram depicts the Onion Architecture. The main premise is that it controls coupling. The fundamental rule is that all code can depend on layers more central, but code cannot depend on layers further out from the core. In other words, all coupling is toward the center. This architecture is unashamedly biased toward object-oriented programming, and it puts objects before all others.

Has anybody in the Elixir community implemented the Onion Architecture in Elixir or another functional language? Has anybody implemented the Onion Architecture in an OOP language? If you have, what are your thoughts on implementing this architecture?

Do you think this architecture could be useful to the Elixir community? Is using this architecture a bad idea? A good idea? A waste of time? Do Elixir apps tend to exhibit this architecture anyway?

I’m looking forward to hearing people’s thoughts!

2 Likes

This might be a slightly controversial point but I don’t think Elixir code needs to be very different from OO code. A user struct is not very different from an immutable user object. The domain core being functionally pure is I link a good idea because it makes units very easy to test.

By the time you step out to application services and infrastructure, your code will no longer be pure. However at this level a Elixir program will probably be coordinating multiple process and they encapsulate state so again many of the OO lessons are still relevant.

So in summary I think that the onion architecture in Elixir is a good idea.

2 Likes

I would say that my main problems with that architecture are that it is badly defined

  1. If it is about code and use dependency injection, then… it is deeply single threaded. Which… urgh
  2. if it is about higher level stuff… Why not but it is highly specific. Why even need something that complex that limits you that much ? And why talk about Objects or DI ?

All in all… I still do not understand all these talks about architecture. Think about your system as a whole, build from there, rewrite regularly, decouple stuff and please make it works safely before diving in complex architecture.

1 Like

I did not dive into the onion thing. My time is limited, and I just read that tip of mr. Kunikow: [Functional Programming] Domain Modeling Made Functional (Pragprog) (F#). The writer of that book is also a nice humoristic and clear presenter. I can recommend
https://www.youtube.com/watch?v=AG3KuqDbmhM on the subject of a comparison of oo and fp dependency injection (it comes a bit later in the presentation).

2 Likes

I thought that looked familiar as it’s basically Alistair Cockburn’s Hexagonal Architecture.

I’m not even sure why it claims that its “unashamedly biased toward object-oriented programming, and it puts objects before all others” because it basically just takes the general notion of “separation of concerns” and transforms that into pleasingly shaped boundaries - manifesting themselves as “objects” at the lowest level of granularity and bending a layered architecture into an out-in-out circular shape on the highest level of granularity.

And it also seems that somehow the Onion Architecture got associated with DDD (Domain-Driven Design with Onion Architecture). Which is interesting because I suspect that similar criticism (Jim Coplien — Symmetry in Design) can be levelled against it - it relies heavily on symmetry (and hierarchy) which may not be inherent in the problem domain which can ultimately lead to accidental complexity.

From that viewpoint Coplien’s DCI (Data, Context and Interaction) may actually be a more profitable place to pilfer ideas from. While he’s still an object-oriented guy, he eshews class-orientation and his “network of objects” seem reminiscent of communicating BEAM processes.

2 Likes

If someone were to implement this in Elixir it may not make sense to bring over all aspects from the original article. I agree that dependency injection is one of my least favorite aspects.

However, I am intrigued by the clear separation between domain, application, and infrastructure code. Most apps that I encounter in the wild are “database first” and place the infrastructure layer in the middle. This tends to set the stage for a “Traditional Layered Architecture” which leads to a hopeless amount of coupling.

A lot of this may be obvious to others but judging from the projects I’ve worked on it doesn’t seem obvious to a good number of smart people I’ve worked with.

In the end, the reason that many applications in the wild are extremely tightly coupled is possibly because it takes a conscious effort to not introduce coupling.

That for some reason the term ‘model’ started becoming confounded (moving from ‘conceptual model’, also known as ‘business logic’, to take on the extra meaning: ‘database model’, resulting in programmers only being able to think about both concepts simultaneously) did not help in this regard, as this resulted in very tight coupling of the database with the application logic.

In your application, the main piece that should dictate how everything else works, should be your business domain logic: The abstract representation of the part of real world your application attempts to help with (to ‘model’).

What database you use is an implementation detail.
What user-interface the business logic can be manipulated through is an implementation detail.

Yes, there can be multiple different kinds of databases in a single application, or none at all. Yes, you might both have a web interface, a command-line interface and an Application Programming Interface.

These decisions do not have impact on your business logic, but the inverse is true: Your business logic strongly impacts in what ways it make sense to store certain pieces of data, and in what ways it makes sense to interact with it.


I believe that above realization is the core of all these different kinds of architecture ideas. Whether you call the result Domain Driven Design, Onion Architecture, Hexagonal Architecture, Data Context and Interaction, SOLID, etc. does not really matter. Their goals are the same: To decouple the multiple pieces of your application, making sure that your code remains structured (understandable, maintainable and adaptable) over time.

Rather, I have somewhat the feeling that all these different names steal the wind out of each other’s sails a little bit, making all of them less valuable as a result (in a way similar to this).

2 Likes

However, I am intrigued by the clear separation between domain, application, and infrastructure code.

I’m not against being organized (i.e. observing separation of concerns) - in fact most of the success stories are probably based on the fact that projects went from not managing boundaries and dependencies appropriately to starting to having to think about them - but that doesn’t give the onion architecture “special powers”.

Most apps that I encounter in the wild are “database first” and place the infrastructure layer in the middle.

To certain degree that is simply an matter of perspective - and typically there are multiple perspectives. The onion architecture’s primary claim to fame is the direction of the interfaces - something that can’t even be discerned in the primary graphic. The domain logic gets to dictate the interfaces and types and the infrastructure has to implement everything based on those constraints.

So for example in Java-land that would mean that none of the classes in Java.sql would be allowed in the domain code, even something as convenient as ResultSet because that would ultimately couple the domain logic with the notion of a relational database. From the perspective of the Dependency Inversion Principle this has always been the right thing to do:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

By extension Ecto.Changeset has no business appearing anywhere in your domain logic because Ecto is ultimately only part of your infrastructure - not your domain. So according to DDD you would have to build a repository around Ecto’s repo and transform all inbound and outbound data to basic and domain datatypes - no leaking of changesets and schemas allowed.

it doesn’t seem obvious to a good number of smart people I’ve worked with.

Loose coupling adds effort upfront. So a lot of those “smart people” will accept tight coupling either of their own accord (laziness) or because of pressure by management to produce something (we need this yesterday).

8 Likes