2

Why does the infrastructure layer depend on the application layer, and the application layer depend on the domain layer in the onion architecture ?

enter image description here

In its definition, the dependency flow inwards. But I can't see the advantages of having the application layer depending on the domain layer, and the infrastructure layer depending on the application layer.

This makes the infrastructure layer unusable for other projects, same thing for the application layer. They depend on the domain layer which is business oriented.

This is expected as it is a DDD design.

But I can see the benefits of having the flow outwards instead inwards.

For ex:

If the application is an e-commerce website, the user can track an item he purchased. When the user buys an item, the system automatically tracks this item and send some notifications to the user. If he is logged in a push notification on the website, and always an email.

Using the onion architecture we might implement it as follows:

The UI has an action which calls an api on the application layer with the request body:

{

    "actionName": "buyItem",
    "actionDetails": {
        "itemType": "shirt"
    }
}

This api searches in a list of IActionExecutor services for the one with name = "buyItem" and when found it calls IActionExecutor.execute(_actionDetails). All the implementations of IActionExecutor are in the domain layer. Let's say the mapping resolved to the class ItemBuyer: IActionExecutor (which is in the domain layer).

ItemBuyer uses 2 abstract service called IEntityTracker and INotificationService declared in that domain layer.

Their implementations EntityTracker and NotificationService are defined in the application layer and uses 2 abstract services IDataService and INotificationService declared in that application layer.

The implementation of these 2 services are in the infrastructure layer.

So each layer uses abstraction declared inside of it. And the implementation of these abstractions are defined in the outer layer. So the outer layer depends on the inner layer.

Let's say we have another application that is not an e-commerce. The application layer doesn't need to implement IEntityTracker and INotificationService and the infrastructure layer doesn't need to implement INotificationService.

We can't just reuse these 2 layers. They will have missing references. I can see this case happening a lot especially when the these 2 layers grow and mature.

But if we flip the dependency direction, we can just insert another domain layer and use whatever service we already support in the outer layers.

I am having a problem understanding the benefit of a general layer (application, infrastructure) being dependent on a business specific layer (domain layer)

Am I missing something ?

3
  • I've never heard of clear architecture. And onion architecture is different from clean architecture. May 10, 2023 at 20:31
  • Edited the question so it involves only the onion architecture
    – EEAH
    May 10, 2023 at 20:38
  • Note that, these sorts of practices or principles (strong dependency principle) have a reason to exist that you probably have not suffered. By the time they were conceived, software was built in such a way that infrastructure, business and application boundaries were tightly coupled. Coupled enough to render "changes" almost unaffordable or not fast enough to adapt to the market, causing companies to suffer a great loss of money and opportunities. Today, onion or layered architectures are not necessarily layers belonging to a monolith. They span several applications and systems.
    – Laiv
    May 15, 2023 at 8:08

3 Answers 3

4

The fundamental idea is that you should not depend on something which could change. Or, put another way: only depend on something that changes slower than you do.

Infrastructures change every couple of years: from mainframes to minicomputers to microcomputers to servers to workstations to PCs to portables to laptops to notebooks to phones and back to the cloud, from monolithic machines to desktops to client/server to the cloud, from batch processing to interactive processing, from monolithic server applications to monolithic client applications to client/server to virtualization to microservices to FaaS, and so on and so forth.

User Interfaces change every couple of years: from batch processing (i.e. no user interface at all) to teletype to green screen terminals to color terminals to GUIs to web sites to dynamic web sites to web apps to SPAs to mobile apps to IoT devices (where the interface is so ubiquitous it almost vanishes).

A bank account hasn't changed in 400 years. A physics model hasn't changed ever and will never change.

If your bank account depends on your infrastructure, it needs to adapt whenever the infrastructure changes, even though nothing about banking has changed. If it's the other way around, the infrastructure would theoretically need to change every time the bank account changes … but the bank account doesn't change.

7
  • "The fundamental idea is that you should not depend on something which could change." That is my main concern. Saying that means that the infrastructure and application layers are expected to change thus we don't depend on them. But why ? Aren't they meant to be reusable for other applications ? The application should be the one that can change. Isn't this the purpose of SaaS or engine or etc. (represented by the infrastructure and application layers) The bank account can use and depend on the infrastructure layer by abstraction. This contract should never change.
    – EEAH
    May 10, 2023 at 21:58
  • What if we are creating another application that does not involve banking. We would copy a part of the infrastructure ? We would end up with 2 repos with the same code. And any change in one should be made in the other. If that's the case how is google for ex maintaining a large monorepo ? It is surely using the same infrastructure for all its modules
    – EEAH
    May 10, 2023 at 22:08
  • I'm not saying your underlying approach is necessarily wrong, but the whole "only depend on something that changes slower than you do" is massively prone to misunderstanding. What I suspect you're trying to get at is that when it happens, you want your application layer to withstand e.g. a change of data provider. That part is correct. But during development, developers will tend to stick with a given storage technology while they continually develop and change the application logic, which appears to be the exact opposite of your advice. [..]
    – Flater
    May 10, 2023 at 23:35
  • [..] I suspect this advice could be significantly improved by being more nuanced in what it means by "change". Your advice seems to be focusing on a change in external dependency (e.g. database tech) after the main development phase of the application logic. It cannot possibly be referring to the phase where the application layer is under development - if it did, I would vehemently oppose the advice.
    – Flater
    May 10, 2023 at 23:37
  • @Flater, it is actually the rate of change in the requirements. The core domain requirements (how a bank account works) change much slower than the application requirements (how can a user interact with their bank account), which in turn changes slower than the UX requirements (look&feel, button placement/color, etc.). May 11, 2023 at 7:25
3

TL;DR

If you're expecting that the "domain logic" (i.e. what distinguishes this application from the next application you'll build) depends on the reusable infrastructure logic, this is sounding more like you're wanting to do N-tier architecture.

I am not able to judge whether you actually do need a domain-centric architecture and have misunderstood its implementation; or if you are correct about what you want to build and are wrongly choosing to use a domain-centric architecture to help get you there.

Any attempt by me (or any other answerer) to give you the answers you need in a single go (as back and forth is off-topic on StackExchange) would effectively devolve into a learner course on onion architecture, which is similarly off-topic for StackExchange.


The meaning of "dependency"

There is a different meaning to "depending on" in a technical context. I suspect you are reading the following:

Why does the infrastructure layer depend on the application layer?

To mean the following:

Why does the infrastructure layer require the application layer in order to do its work?

And this isn't quite the right interpretation. It's not incorrect per se (as you will see by the end of this answer, but a more accurate interpretation would be:

Why does the infrastructure layer need to be aware of certain things in the application layer as part of its overall responsibility?

The point here I'm trying to get at is that the application layer doesn't factor into the actual "how do I (infra) interact with my data store" logic. It does factor into some things on the side, such as data models which the data store models will eventually will get mapped to (which still happens inside of the infrastructure layer).


Reusability across codebases?

This makes the infrastructure layer unusable for other projects, same thing for the application layer.

This observation is correct, but what it's pointing out is not actually a problem, it is an intentional design.

Reusability of this order is a very tall order and not something you should be focusing on for your average codebase. You haven't even built this application and you're already limiting yourself to reusability that would apply for the next application? That's putting the cart well before the horse.

But let's entertain this notion. Let's say that you know for a fact that your infrastructure layer will be using a HttpClient that e.g. uses some company-wide headers that you want to reuse across your company's codebases.

This doesn't change the original advice. The infrastructure project you've been talking about should remain to be unique to this application. However, this scenario opens the door to having an additional general library which contains the reusable logic, and then all of your applications can depend on this general library in their Infrastructure projects.

Anything specific to one codebase in particular is part of that application's infrastructure project. If you want there to be reusable logic across Infrastructure projects for multiple codebases, then you create an additional library, put it in there, and reuse that library across all of your Infrastructure projects.


A domain is not a framework

But if we flip the dependency direction, we can just insert another domain layer and use whatever service we already support in the outer layers.

What you're telling me here is that you're in the business of framework building. You are creating a soulless husk with your application and infrastructure layers, and then you intend to build many of these husks, each with a different soul (i.e. the domain logic).

Far be it from me to tell you that has no technical value. However, this is not a domain-centric approach. Quite literally, onion architecture puts the domain as the centerpiece of your application.

Your intended approach is almost entirely orthogonal to your chosen architecture. This is why you're experiencing this tension that's leading to your issues.

If you're expecting that the "domain logic" (i.e. what distinguishes this application from the next application you'll build) depends on the reusable infrastructure logic, this is sounding more like you're wanting to do N-tier architecture.

I am not able to judge whether you actually do need a domain-centric architecture and have misunderstood its implementation; or if you are correct about what you want to build and are wrongly choosing to use a domain-centric architecture to help get you there.

However, your question has convinced me that your current idea on how to approach it is not agreeing with what onion architecture prescribes.

I suspect the best way to resolve this is to either get a senior/consultant profile involved to help you through your questions, or to further study onion architecture and DDD in order for you to make your own decisions.

Any attempt by me (or any other answerer) to give you the answers you need in a single go (as back and forth is off-topic on StackExchange) would effectively devolve into a learner course on onion architecture, which is similarly off-topic for StackExchange.

2
  • Thank you. I think I misjudged the meaning/purpose of the onion architecture. It's meant to be domain centric, and the infrastructure layer should be aware of that. I guess what I wanted is just that shared library / another infrastructure layer but scoped to the whole company rather that this single project.
    – EEAH
    May 11, 2023 at 16:26
  • @EEAH: What you want is definitely viable, it's just unrelated to Onion specifically.
    – Flater
    May 11, 2023 at 23:48
2

I am having a problem understanding the benefit of a general layer (application, infrastructure) being dependent on a business specific layer (domain layer)

The main benefit is having a place to think about your business rules without caring who made the database.

You're absolutely right that you don't have to point your dependency arrows this way. Pointing them the other way tends to give you what's called an 'anemic domain model' which, despite the criticism it receives, remains fairly popular. It's easier to write. And the costs that come up later aren't enough to force people to change. And it's what the people selling you the tools would really prefer you do since it makes you depend on them. It's called vender lock in. So guess what most of their example code encourages? Fighting that is not easy. It's work.

To care about any of that you really have to take the long view. And many projects are created by people who won't be here long enough to care about the long view. Especially when the boss wants it yesterday.

So, all that being true, what benefit do you get from faithfully pointing your static dependency arrows inward? You get to write code without caring what others wrote. You can pretend something convenient exists even when it doesn't and make it exist later. You can push all the volatility and uncertainty of the rest of the world away from your core idea of what this thing should do. You can define what you want without understanding how to get it. You can procrastinate understanding how to get it.

That's right, once you get good at coding this way, you can be lazy.

But at the end of the day, it's still just a coding style. Certainly not the only way to work.

Call it Onion, Clean, Hex, or Ports and Adapters, the idea is to use Dependency Inversion so that the way you point your static dependencies isn't dictated by the flow of control you happen to need. With that freedom the choice is to either point them in directions that isolate your code from the details of the outside world or make it dependent on them.

Of course, at some point you end up caring about the outside world. Something somewhere has to know the database. The outside adapters take care of that. It comes down to deciding if you think it's easier when code that knows those details is contained in a small space or if you just assume those details everywhere. Isolating knowledge is a good principle that can save you later but there's no getting around the fact that it's work today.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.