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.