4

I have been reading about clean architecture and following Domain-Driven Design. In all the examples I have found, the interfaces of the repository are defined in the domain.

However, it is in the application layer where the repository interface is injected, and the interface is implemented in the infrastructure layer.

From what I understand, the interface should be defined in the layer that needs the resource. In this case, the repository is needed to retrieve data from the database. This layer is the application layer. The domain shouldn’t know where the data is coming from; it should just receive the data it needs to perform its tasks.

Therefore, the domain should be agnostic about persistence. In theory, the domain doesn’t know if the data will be saved or not. The application could run in memory and not save anything. The domain shouldn’t concern itself with persistence.

Given this, I was wondering why the repository interfaces are not defined in the application layer.

Furthermore, why should the domain define any interface if it is supposed to be isolated, being the core of the application? Shouldn’t the application layer provide the domain with all the data it needs to perform its tasks?

In summary, I would like to understand the logic behind defining the interfaces in the domain layer. I am also curious if it is a good or bad idea to define the interfaces in the application layer.

Thank you very much.

1
  • 1
    You’re right, to keep the domain pure, the repository interfaces don’t belong in the domain layer. The examples you found are ‘wrong’ in that sense.
    – Rik D
    Commented Jan 31 at 22:27

2 Answers 2

7

There is no "one true answer" here. Different people favor different approaches. This answer is intentionally arguing a different opinion than the one you already have. Not because it implies your opinion is wrong, but to showcase why your opinion is not the only correct one. Keep that in mind when you read this answer as I'm not going to explicitly point out the contextuality of every statement that I make (it would be a drag to read).

The domain shouldn’t know where the data is coming from; it should just receive the data it needs to perform its tasks.

That is precisely what the interface already provides for you. Even if the domain knows the interface, it has no idea which concrete class is being injected, so while your consumer works with an IPersonRepository, it has no idea what kind of persistence technology is being used (e.g. MyDbContext : IPersonRepository, PersonCosmosDbClient : IPersonRepository, ...)

This argument you've made does not meaningfully lead to favoring keeping the interface out of the domain rather than in the domain.

Therefore, the domain should be agnostic about persistence. [..] The application could run in memory and not save anything. The domain shouldn’t concern itself with persistence.

Point made above, it's already agnostic due to the interface abstraction.

In theory, the domain doesn’t know if the data will be saved or not.

This depends on what you're building. If your domain is purely a calculation model that has no other concept, then sure. But that's not always a given.

For example, I've built an application where the domain logic contained a bunch of clever decisions to be made about if something was changed and therefore if such a change would need to be persisted or not. This made the decision to persist part of the domain's remit, and therefore it made sense for the domain to access the relevant Save() method in the persistence interface.

But this is not universally true. Some domains do not have this kind of responsibility, and therefore they have no need to know whether their state is/can be persisted or not.
Even though it's not necessary in these cases, I wouldn't particularly say that it's outright wrong to still keep the interfaces in the same location. This is more so an argument of having a consistent architecture across your company and not deviating from said architecture unless it's actively harmful (which this isn't).

From what I understand, the interface should be defined in the layer that needs the resource.

I disagree with this. I consider the domain layer as the "shared language" of the codebase, i.e. what is being used to communicate between layers (as far as the internal codebase goes, e.g. interaction between application logic and persistence logic, not external communication such as API endpoints!)

From that point of view, anything that is used for communication between layers can be added to the domain, because the domain is the shared language. It can be added to the domain if the application and persistence layer both make use of it; whether it's an interface (where one consumes it and the other implements it), a helper (just some reusable toolbox), or a shared model (most notably the data classes used by the interfaces, but also e.g. a custom exception type that one throws and the other catches).

Given this, I was wondering why the repository interfaces are not defined in the application layer.

Because who says that the application layer is the only consumer of these interfaces; or (even if that is currently the case) that you should design it in a way that no other consumers could ever be created?

This sounds to me like a shortcut that is made because it's possible, rather than because it's the correct design. This is very analogous to a more common argument such as "why do I need an interface? Only one class implements it anyway, so I can just refer to that class directly". The key feedback to that argument is that it is cutting corners on the hopes that what is true today will never change in the future, and that's just not good practice.

Good practice (and design) focuses on change-friendliness in the future, not just building the current requirements and locking them down. If you're building a car, it will be a lot faster if you weld the components together instead of drilling holes, threading them, finding bolts/screws, cutting gaskets, ... But when it comes to maintaining your already built car, it would take tremendously more effort to fix a broken part that's welded into the car as opposed to one that can be screwed loose and taken out. "But we can build this car faster and easier" is just not an acceptable argument when it trades away future maintainability.

Essentially, you're arguing the same principle when you try to combine the application layer and the definition of your persistence interfaces just because right now these happen to overlap.

The correct design here is that the persistence layer should be accessible to anyone (within the codebase, not external consumers), and therefore the interfaces should be stored in a location that is accessible to anyone (again, within the codebase).
This is why you commonly see interfaces as a separate project that others can freely depend on; but if you consider the domain layer as a shared codebase language, then it too is a valid location for these interfaces to be housed in.

Shouldn’t the application layer provide the domain with all the data it needs to perform its tasks?

In the sense that this should be done (i.e. it's the only way), no. In the sense that this is one of several ways to tackle it, that is correct. Personally, I don't like it, but I know several colleagues who very much prefer this "application feeds the domain" approach.

Personally, my issue with this is that when you disconnect your domain from your persistence layer, when you look at the diagram, you get your domain, persistence and API layers which do not directly interact with each other, and the application layer acts as a central hub that connects all of these together.

I'm not saying that's inherently bad; but what I am going to say is that this is then not a domain-centric codebase, but rather an application-centric codebase (as the application layer is now the central component).

You've tagged your question with the DDD tag, and DDD is inherently a domain-centric approach. To me, it doesn't make sense to then advocate for a design where the domain is not actually central to the codebase.

3
  • Thanks for the detail answer. But to clear when I tell that the domain should be agnostic about persistence, I wanted to mean that the domain shouldn't know if the data is persisted or not. If the repository is defined in the domain, the domain is asumming that the data will be persisted, and if it defines an interface it means it will use in some moment, so the domain will decide when to persist the data. So the interface it makes the domain agnostic about technology, but not about if the data is persisted or not. Commented Feb 3 at 12:37
  • About if this is a aplication layer centric instead of domain centric solution. I could be wrong, but for me the application layer is the layer that coordinate different elements. In a basic action, 1.- get the data from the repository, 2.- call the methods of the domain and 3.- save the changes. If the domain use the interface to get the data and persist it using the interface, which is the responsability of the application layer? Commented Feb 3 at 12:47
  • @ÁlvaroGarcía: First comment: I understand and explicitly address this in the answer when I mention it depends on whether it's contextually relevant for the domain or not. However, even if it isn't, just because the interface exists in Domain does not mean that it must invariably get consumed there as well. This is not a must. Second comment: draw the diagram (api, application, domain, persistence) and look what is in the middle. It's not the domain. To me this whole "no interface in the domain" argument sounds a lot like the arguments that used to advocate for anemic domain models as well.
    – Flater
    Commented Feb 4 at 11:22
1

I think, domain layer should know/dictate if some data should be saved or not, unless nothing/everything is being saved. But it should not know how the data is being saved.

Other than the repository interface within the domain layer, another way to achieve that is marking data to be saved. The marking can be done using a flag property or a generic wrapper class. Then the application layer will check the mark and initiate the needful to save/persist the data.

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.