entity-frameworkdependency-injectiondomain-driven-designapplication-layerbusiness-layer

Should An Application Service Be Injected Into A Domain Service


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

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.


Solution

  • "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.