Search code examples
javadesign-patternsarchitecturedependency-injectioncommand-pattern

Implementing the Commands of CQRS in a Web environment with dependency INjection


I'm currently experimenting with some architectural patterns, one of them being the implementation of CQRS, especially the "Command" part of the pattern.

Basically I have commands like

public class SavePersonCommand {

   @Inject
   private IPersonRepository repository;

   Person person;

   public SavePersonCommand(Person personToSave){
      this.person = personToSave;
   }

   public void execute(){
      ...
      repository.save(personToSave);
      ...
   }
}

For simplicity I don't specify any interfaces/abstract classes the command might implement. The point is, this is the standard implementation of a Command, you pass all the necessary information to the constructor and then you have a parameterless method (i.e. execute()) which executes the business logic of your command.

The issue though is IPersonRepository. IPersonRepository contains the logic for persisting the entity to some storage and thus belongs to the data layer of my application. Btw, the simplified structure of my app is like this

ApiLayer -> Core <- DAL

where ApiLayer depend on Core and DAL depends on Core. Meaning Core does not have dependencies on the specific DAL nor does it obviously have on the Api layer. So the distribution of the classes would be..

ApiLayer
  PersonApi
Core
  IPersonRepository
  PersonCommand
  Person
DAL
  PersonRepository -> IPersonRepository

Now, in the ApiLayer, where I want would normally get an instance of the SavePersonCommand.

@Path("/api/v1/person")
public class PersonApi {

   @POST
   public void savePerson(Person person) {
      SavePersonCommand personCommand = new SavePersonCommand(person);
      personCommand.execute();
   }

}

The issue here is however, how do I get the IPersonRepository injected into the command. I wouldn't want to get it injected in the ApiLayer like

@Path("/api/v1/person")
public class PersonApi {

   @Inject
   private IPersonRespository personRepo;

   @POST
   public void savePerson(Person person) {
      // obviously modify the interface of SavePersonCommand
      SavePersonCommand personCommand = new SavePersonCommand(personRepo, person);
      ...
   }
}

..this is kinda ugly..

I have some thoughts but would like to hear how you implement this normally.


Solution

  • Based on Steven's hints the simplest approach to solve my issue is the following.

    Core Layer

    Here I have the following

    public class SavePersonCommandHandler implements ICommandHandler<SavePersonCommand> {
    
        @Inject
        IPersonRepository personRepository;
    
        @Override
        public void handle(SavePersonCommand command) {
            Person person = command.getPerson();
            personRepository.save(person); 
        }
    }
    

    The SavePersonCommand is simply a data object that transfers the data from my Api layer to the Core layer.

    public class SavePersonCommand {
        private Person person;
    
        public SavePersonCommand(Person person) {
           this.person = person;
        }
    
        public Person getPerson() {
           return this.person;
        }
    }
    

    I could even only pass the person's properties as native data types rather than passing the entire Person object. Thus, I could remove the dependency to the datatype which I might want to only use in my core layer.

    Api Layer

    The api layer then gets an instance of the required command handler.

    @Path("/api/v1/person")
    public class PersonApi {
    
       @Inject
       ICommandHandler<SavePersonCommand> commandHandler;
    
       @POST
       public void savePerson(Person person) {
          commandHandler.handle(new SavePersonCommand(person));
       }
    
    }
    

    Note that normally you also add a Bus in between that "sends" a command down the pipe which then gets intercepted by the correct CommandHandler. Didn't want to complicate as for the sake of my question this is the answer I found.

    These articles were quite helpful: