Should An Application Service Be Injected Into A Domain Service

486 views Asked by At

I am working on a WinForms application using Entity Framework 6 with the following layers:

  • Presentation
  • Application
  • Domain
  • Infrastructure

When a user clicks the save button from the UI, it calls an application service in the Application layer and passes in the request. The application service then calls a domain service with the request. The domain service calls on an several entities within a domain model to perform validations on data used in the request.

One or more validations in the domain model require information from a repository to determine if data in the request received from the Presentation Layer conforms to certain business rules.

I am considering two options to address this.

  1. Have the Application Service retrieve the information needed from the repositories for validation and pass those values into the Domain Service which will call on the domain model and entities to validate the incoming request for rules and values. Then let the Application Service save the request when the Domain Service has finished its validations, which will result in returning control back to the Application Service which was synchronously waiting for completion of validations. If I do this, then the domain layer will have no direct or indirect (injected) reference to the repositories. Unit testing of the Domain Service will be easier if I do this because nothing is injected into it to perform validations. Everything it needs is already passed in. The drawback is that some business knowledge is put into the Application Service because now it needs to know which repository information to retrieve for validations of a request.

  2. When calling the domain service for validation of the request, inject an instance of the Application Service into it. The Domain Service can then get information from the repository using the injected Application Service, whose service contract is defined in the Domain Layer. Once all the information is available it is passed as needed to various entities to validate rules and values. Once validation is completed, the Domain Service saves the request using the injected Application Service. When the Domain Service is done and exits, it returns the status of the save operation to the Application Service which has been waiting for validation to complete. The outer waiting application service can then return the results of the save to the UI. One concern I have here is that when unit testing the Domain Service I will have to mock the injected Application Service.

Which option or other course of action would work out better? Thanks in advance.

2

There are 2 answers

1
plalx On BEST ANSWER

"Should An Application Service Be Injected Into A Domain Service"

No, never!

Resolving the data from the application service and passing it to the domain service is usually fine, but if you think that domain logic is leaking then you can apply the Interface Segregation Principle (ISP) and define an interface in the domain based on what contract is required to query for the "wanted data". Implement that interface on your repository or any other object that can fulfill the task and inject it into your domain service.

E.g. (pseudo-code)

//domain

public interface WantedDataProvider {
    public WantedData findWantedData(...) {}
}

public class SomeDomainService {
    WantedDataProvider wantedDataProvider;
}

//infrastructure

public class SomeRepository implements WantedDataProvider {
    public WantedData findWantedData(...) {
        //implementation
    }
}

EDIT:

I have a request aggregate root with an employee name. One rule is the employee must be full time and not a contractor

If the information to perform the validation already exists on an aggregate, you may also use this AR as a factory for other ARs. Assuming an employee holds it's contract type...

Employee employee = employeeRepository.findById(employeeId);
Request request = employee.submitRequest(requestDetails); //throws if not full time
requestRepository.add(request);

Note that the invariant can only be made eventually consistent here, unless you change your aggregate boundaries, but it's the same with the other solutions.

2
Alexey Zimarev On

When a significant process or transformation in the domain is not a natural responsibility of an Entity or Value Object, add an operation to the model as a standalone interface declared as a Service. Define the interface in terms of the language of the model and make sure the operation name is part of the Ubiquitous Language. Make the Service stateless - Evans, the Blue Book

Don't lean to heavily toward modelling a domain concept as a Service. Do so only if the circumstances fit. If we aren't careful,, we might start to treat Services as our modelling "silver bullet". Using Services overzealously will usually result in the negative consequences of creating an Anemnic Domain Model, where all the domain logic resides in Services. - Vernon, the Red Book

What I am trying to tell by extensive citing, is that you seems to see Domain Services as something you MUST have where you absolutely don't have to do it. Your application services can happily use repositories to fetch the aggregate and then call the aggregate root method to perform necessary operations. Your aggregate root is responsible to protect its own invariants by executing necessary validations inside it and throw validation exception if anything should go wrong.

In case you absolutely must use a domain service, if you are doing it right, meaning you implement onion architecture, where inner layers do not know about outer layers, your domain service won't be able to know about the application service. However, you can, if you absolutely have to, send a delegate into the domain service to do something that the application service desires. However, this scenario is too complex to be true. Seeing that you only require the validation, please read the cited parts of the blue and red books and make the right decision. Again, there is no mandatory domain service in DDD, this is one of common misconceptions.