What is the onion architecture?

Part 2 of the functional architecture series. When we're structuring our functional software, we want to isolate the actions from the calculations. We can do that using the Onion Architecture, which has layers like an onion. The center of the onion is your domain model, then around that are your business rules. Finally, around that is your interaction layer, which talks with the outside world, including the database, web requests, api endpoints, and the UI.

Transcript

Eric Normand: What is the onion architecture? Hi, my name is Eric Normand. These are my thoughts on functional programming.

This is part two of a three-part series I'm doing on functional architecture. What does functional programming have to say about architecture? We went over the first thing, which was the stratified design. This is where you build layers of meaning on top of more fundamental layers of meaning. You're building meaning up.

This onion architecture also has layers. That's how onions are used for these layer metaphors, but these are in circles. Instead of top-down you're going outside in. How does this thing work?

In a functional onion architecture, you're going to have really three layers. The inner layer is where you put your domain model. This is a functional implementation of your domain model. By functional I mean its calculations and data, pure functions and immutable data. This is how you represent the ideas in your domain.

If you're doing accounting software, this is where you implement the rules of accounting. How do I define a transaction? What are the operations I can do on transactions? All that goes in your domain model.

The second layer is where you put your business rules. If you're an accounting business, and you're developing software to help you run your business, there's going to be rules that aren't really set by how accounting systems work like everyone uses the same double entry bookkeeping accounting system.

How much money do you charge for an overdraft on an account? How much is the monthly fee on an account? How long do you let an account stay open before you close it if there's no activity on it? These are rules that businesses like banks and accounting firms have that aren't really accounting, but they're part of their business. These are things that change a lot, a lot more than accounting, that is.

Accounting rules have been around for hundreds of years, but every business has different policies about how they deal with late fees and things like that. Those go in this other layer. Just like in the stratified design, you're putting stuff that changes more frequently on an outer layer. Inner layer, stuff doesn't change.

Now, these business rules are also pure functions with immutable data. We haven't done any actions yet. They're built on top of the accounting rules. They can use the concepts inside the domain model.

Third layer is where you start doing actions. This layer is interacting with the outside world. This layer is where you're going to put your web server that converts a web request into either business rule, operation or a domain operation. It's going to pierce through the layers and operate on the model.

That's going to have some effect again on the outside world. That's going to bubble out. It's all pure on the inside. Once it starts bubbling out, this outer interaction layer is going to start doing stuff. It's going to start sending emails. It's going to store stuff somewhere. It's going to make API requests. It's going to respond to the web request.

Let's look at how this onion architecture does some very common operations. We already talked about the web request. I'm going to do it again, but I'm going to put it off. Before we do that, let's look at the persistence layer. This is the database.

A lot of apps are built with the database at the center. The onion architecture is built with the database on the outside, because the database...This is what architecture is about. Architecture is about isolating yourself from your mistakes, isolating the mistakes, not yourself, isolating the different mistakes that you could make from each other.

You have to make a lot of decisions when you're building software. What database do we use? What language do we choose? How do we process a certain message? Do we need a cue? Can we do it as a monolith, or as micro services? There's all these questions. All of them might be wrong. Certainly, some of them are going to be wrong. They can't be right all the time.

We want to isolate those changes so that for instance, if I made a mistake in database, I don't have to do a whole rewrite. I've done so much right. I made one mistake. Do I really have to start over? I'm sorry, this light is really bad on the video.

Do I really have to start over? It seems unreasonable. Then a lot of systems you do the database, the queries and the SQL statements are all smeared all over the code. You haven't isolated it. If you ever wanted to change the database, let's say, you go from SQL to no SQL, oh man, you're going to have to go every line of code inspected.

Did I make an assumption here about the database? What's going on? You want to just move all that out because the database is something you can make a mistake on. It's much more likely that you choose the wrong database, than you choose the wrong accounting system, the accounting process.

The accounting process, it's been around for hundreds of years. It's not something that there's any real debate on anymore. How do you do this? Database is on the outside. It means when your system decides something needs to be stored, this is a business rule that says, "I need to store this. That this check came in."

The business rules generate some representation in immutable data of that storage. In [inaudible 8:31] world, they call this a command object. It's just some representation of this thing that needs to be stored. When that bubbles out to the outer layer, the interaction layer, something grabs that and says, "I know how to store that," and puts it in the database. That's it.

The business rules decide what gets stored, but they don't do the storing. This is an example of a pattern that I'm going to have, actually, I have this on my to-do list, an episode about this. It's a dumb name, so I might change the name. I'm calling it the plan then act pattern. In plan then act, you make a plan, and then you act.

Usually, we write software in an imperative way. We write software that makes decisions as we go. You loop through all the checks. You get a query from that. You query the database. You get all the checks. You loop through all of them. You figure out which ones inside the for-loop.

You figure out which ones need to send an email about. You don't know how many emails you're going to send until you get to the end of the loop, and then you've sent them already. That's how imperative programming tends to work. You don't have to do it that way, but that's a very common pattern.

In the plan then act situation, what you do is you have a pure function that takes all of the checks, and the immutable data. It goes through those checks and makes a plan, I need to send this email, this email, this email, this email, and whatever. Now you have a list of emails, also immutable data. That's a pure function from checks to emails.

Then you have your outer layer, your interaction layer, that's what does the query, it calls this business rule that determines the plan for what emails to send. It does a query, it gets immutable data. This is the current state of the checks that we have.

It calls that pure function that's in the business rule layer. It returns a list of emails to send. Then the interaction layer iterates through that and sends them, but there's no decisions anymore. There's no decisions to be made at that point. It's simply acting.

You've extracted all of the decisions out into your plan. That's the idea of the plan and then act instead of acting and planning intertwined. That's how persistence happens. The business rules will bubble out a plan, and part of that plan is what needs to get stored.

How does a web request happen? Well, the interaction layer has a web server with an open port, etc. The web request comes in, it converts that into some kind of request that the business rules understand. This is a transfer from one account to another. It represents that as data, pure data.

Then it calls the business rules for that. The business rules will return, well this is the new state that has to be stored, or now we have to send an email. It's just a plan for what to do. That interaction layer is just going to iterate through the plan and do it.

It's actually dumb, and that's a good thing because you want the business rules to decide what happens. You don't want the email server to decide what happens, the email system.

This is part two. I'm going to talk about part three, in the next one, more architecture. The reason I only have three is because I think only have three thoughts on functional architecture. I'd love to have more, so please, if you know something, I would greatly appreciate it.

I'd love to get into the discussion with you. On Twitter, I'm @ericnormand. On LinkedIn, just search for Eric Normand. Or on email, if you want some longer form discussion, I love that too, I'm eric@lispcast.com.

Subscribe, if you're on YouTube, you can get notified of new videos if you subscribe and click the little alarm, the little bell icon. Please subscribe on podcast. Tell your friends about it. Awesome, thanks so much. See you next time.