10

There are several posts on stackoverflow about this question, but I'm still not able to understand it fully.

Different architectures like the layered architecture in the blue book, Ports and Adapters, Onion Architecture or Clean Architecture are proposed. Despite after much reading I still don't fully understand why other approaches are proposed, but each one isolates the domain as postulated by Eric Evans.

In my Web API project I use an Web API layer that holds the controllers, an application layer that handles and orchestrates the business use cases, the domain layer, and a persistence layer that implements the repositories using EF Core to access the database. The API layer calls commands of the application layer that handlers process. Later other API layers may be added for GraphQL or gRPC.

Eric Evans Domain-Driven Design:

"The infrastructure layer usually does not initiate action in the domain layer. Being “below” the domain layer, it should have no specific knowledge of the domain it is serving."

I understand that the infrastructure layer usually does not initiate action in the domain layer, but I don't understand how it should have no specific knowledge of the domain. How can the repository save an entity if it doesn't have knowledge of the entity?

Eric Evans Domain-Driven Design:

The application and domain layers call on the SERVICES provided by the infrastructure layer.

In other posts on stackoverflow and articles it is stated that the domain layer should not depend on the repository or other services and that rather the application service will access those and provide the results to the domain layer. For example an application command handler will get an aggregate by id from the repository and then calls the domain commands on that aggregate and then updates the entity in the repository.

If I understand Clean Architecture and Onion Architecture correctly, the domain layer is at the center and does not access any outer layers. Does this contradict Evans or is it just an improvement?

I often miss information about the relationships in the infrastructure layer. Looking at the different architectures I would consider my Web API layer part of the infrastructure layer. If the Web API controller and the repository implementation are in the same layer, the API could call the repository directly and bypass the application service. This makes no sense to me. I rather have the API depend on the application layer only.

This matches also the image of the onion architecture: enter image description here

In that case it makes no sense to me to have the repository interface in the domain layer. I would have it in the application layer. According to Uncle Bob the interface is owned by the client. Therefore I would have the repository interfaces in the application layer.

Commonly it is stated that the repository interface is part of the domain layer. Is this only related to the different architecture styles? If I understand correctly, in Evans approach the domain layer can access the infrastructure and in clean or onion architecture it does not. Is that correct?

Can someone help me to clear this mess in my head and especially explain why which approach is used? In what cases would it make sense that domain layers calls infrastructure and in what cases not?

M. Koch
  • 525
  • 4
  • 20
  • Probably this link will be useful: https://stackoverflow.com/questions/71996305/repository-implementation-in-application-domain-and-infrastructure-layer-ddd/72036702#72036702 – Eugene May 14 '22 at 11:18
  • 1
    Eugene, I'm aware of this link. In his book Vaugn first references to the trad. 4-layer structure that Evans used in his book. Then he uses dependency inversion to move the infrastructure layer up. If the respository interface is defined in the domain layer the it can access the repository through that interface. Evans stated that the domain layer can access the repository. In many posts it is stated that the domain layer should not know about the persistence, only application layer accesses persistence. I don't understand the reason for this different view. – M. Koch May 15 '22 at 08:32
  • The repository is the contract between the domain model and the persistence. It should be written only in terms of the Domain and without a thought to the underlying persistence. The contract of a repository is more than just a CRUD interface. It is an extension of the domain model and is written in terms that the domain expert understands. Your repository should be built from the needs of the application use cases rather than from a CRUD‐like data access standpoint. – Eugene May 15 '22 at 10:54
  • 1
    Eugene, I don't quite understand. "A repository represents all objects of a certain type as a conceptual set." (Evans). So you can retrieve, add or remove objects from that set. It is the retrieval that has search criteria reflected by use cases. I found the following article about "clean DDD": [link](https://blog.jacobsdata.com/2020/02/19/a-brief-intro-to-clean-architecture-clean-ddd-and-cqrs). Here the repo interface is put in the application layer. It looks like a further development from Evan's layered architecture. Only application and not domain layer is calling the repository. – M. Koch May 16 '22 at 11:07
  • DDD is not strict rules rather than just recommendations. You can put Repository contract to Application layer, but what advantages you will get for your project? I think this is main question. I see that in this case Domain Layer will be restricted, you need to think how to pass the whole required model to domain service for logic execution, in huge domain it can be a problem. But you can think of some workarounds. – Eugene May 16 '22 at 18:30
  • 1
    I finally understood the benefit of having repository interface in application layer: testing. If you have the domain layer access infrastructure, e.g. repository, even through an interface you need to mock it to be able to test it. If your domain layer has no dependencies, it is easier to test the business rules. All required data is fetched by the application layer and passed into the domain layer. – M. Koch May 16 '22 at 23:30

1 Answers1

7

Why do we need to introduce Repository Interface in the domain layer?

Answer:

  • For abstraction and loose coupling purpose. Thanks to the interface declaration in the domain layer, detailed implementation in infrastructure layer can be flexibly modified without worrying about changing domain logic.
  • For dependency integrity (outer-depends-on-inner), domain entities and domain services sometimes DO need to query data for some specific operations. By introducing Repository Interface, the domain entities and domain services just need to depend on the interfaces and access the implementation instances by using Dependency Injection or Service Factory.

Why not let GraphQL Resolvers, Restful Controllers, RPC Handlers, etc (request handlers in general) directly use the infra-Repositories-Implementation-Classes for better simplicity, as they're both in the most-outer layers? Why make the process flow to be too complex?

Answer:

  • Because they just accept the clients request and don't know anything about the use cases (belong to application service) and domain business logic (belong to domain layer). This is especially true for "write" operation (e.g: bank transfer).
  • Even for "read" operations, there are some cases we need business logics involved like authorization check, remove some sensitive data fields (e.g: password from account) before returning to clients. These cannot be done in the outer layer.

A couple of things to be clear

  • "Access / Call" is different to "Depend / Import". Domain services and entities "access / call" repository instances to find the aggregates, doesn't mean they "depend on / import" repository implementation.
  • Domain services and entities sometimes DO NEED to query data to complete their operations.

(Updated) Answer to M.Koch question

An example of domain layer need to query data from a repository.

// UserAccountApplicationService is an application service belonging to application layer.
class UserAccountApplicationService {
    ...

    public PublicAccountDescriptor registerNewUser(email, password) {
        try {
            Account newUserData = this.accountFactory.buildUserAccount(email, password);
            Account createdAccount = this.accountRepository.save(newUserData);
            return new PublicAccountDescriptor(createdAccount);
        } catch (e) {
            // Exception handling
        }
    }
}

// AccountFactory belongs to domain layer and acts as a builder.
// AccountFactory need to call AccountRepository method to query data.
class AccountFactory {

    ...
    public Account buildUserAccount(email, password) {
        // Password validation, password encryption, etc.
        ...

        Account existingAccount = this.accountRepository.accountOfEmail(email);
        if (existingAccount != null) {
            throw new Exception("email registered")
        }

        // Create account instance.
        // Publish "account_creation" domain event.
        ...
        return account;
    }
}
haotang
  • 5,520
  • 35
  • 46
  • 2
    Thanks for your answer. The reasons for using an interface is clear. Your wrote: "domain entities and domain services sometimes DO need to query data for some specific operations". Can you provide examples where it would not be possible to provide the data from the application layer, passing it to the domain layer as a parameter in a method call. When the domain layer is calling repositories they need to be mocked to test the domain layer. Without outside dependencies it is easier to test. Onion and clean architecture show this implementation. – M. Koch Jun 15 '22 at 09:35
  • @M.Koch I updated my answer above to include code example demonstrating this case. – haotang Jun 17 '22 at 03:03
  • 1
    @hoatang Thank you very much for your example! I realized that one of my earlier questions is related to this one. The answer showed how to pass the data into the domain layer and adovated to keep the domain layer free from repositories: https://stackoverflow.com/questions/69615432/ddd-how-to-implement-validation-against-database . Why in your example can't the application layer use case call the repository to check the uniqueness of the email and then call the domain layer to create a new user? This will keep the domain layer dependency free. – M. Koch Jun 20 '22 at 08:55
  • "User account email must be unique" is a business logic and should be part of the domain layer. It's not the job of application to check this logic. – haotang Jun 21 '22 at 07:34
  • 1
    I understand your argument. Checking the uniquness of stored emails can be viewed as a service. The application layer can just pass the result to the domain layer, which then decides what to do or just what error message to return. Than the application layer is just the orchestrator. It seems there are two different schools (Evans' orignal DDD and clean architecture) and I'm still trying to understand the differences. It like the idea that the domain layer has no dependencies. It makes things easier. Still I agree that business logic should be solely in the domain layer. – M. Koch Jun 21 '22 at 09:39
  • I just found this article: https://enterprisecraftsmanship.com/posts/email-uniqueness-as-aggregate-invariant/ It expresses my feeling that an invariant of one entity is different than an invariant of a set of entities. It always bothered me that this difference is not addressed. Here set invariants are handled in the application layer. If I understand clean architecture correctly, the application layer is the use case, so it could/should handle those set invariants. So far I see clean architecture as an improvement over Evan's DDD layers and it achieves a dependency free domain layer. – M. Koch Jun 21 '22 at 10:04
  • Here is another post of Vladimir talking about it: https://khorikov.org/posts/2021-05-17-domain-model-purity/ – M. Koch Jun 21 '22 at 10:52
  • 2
    You're right about the 2 differences between Clean Architecture and Domain Driven Design. Indeed, DDD doesn't care about the Architecture style (Lean, Hexagonal, MVC, Event-Driven, Micro-services, Monolith, etc.) - hence we have a lot of flexibility. It's correct that domain layer doesn't have dependencies on out layers, that's why we have to introduce Repository Interface. There is no sin to put unique email checking logic in application layer, but in general I'm more favor of "fat domain - anemic application" rather than "anemic domain - fat application". – haotang Jun 22 '22 at 02:49
  • Vladimir's point is very nice. However, he sees Repository as a persistence concern, that's why he said it's not pure. In DDD, we should see Repository as a List/Array of Aggregates - for lookup purpose. In that perspective, it's perfectly fine that "An AccountFactory domain service ask AccountRepository (List/Array) for existing account of a given email address" (I use Factory to create entity, not Account's constructor - hence Account entity is still pure). Btw, I agree that we should definitely try all the ways possible to organize domain as pure as Vladimir suggests. – haotang Jun 22 '22 at 02:56
  • I am on the same boat: debating whether the repository interface should be in the application layer, or the domain layer. And @haotang's point of "domain services sometimes do need to query data" moved me toward putting them on the domain layer. M.Koch: when you said the domain layer should have no dependencies, I wonder whether you see having the domain repository interfaces in the domain layer is considered as dependencies? because to me, I don't see them as dependencies. – David Liang Jun 20 '23 at 05:29