KDW
KDW

Reputation: 525

Does DDD violate some OOP principles?

Perhaps I might be confused or misunderstanding some basic principles. When applying DDD in an Axon based event sourcing project, you define a number of aggregates each of them having several command handler methods verifying if a state change of the aggregate is valid and a corresponding collection of event handler methods applyling each requested state change.

EDIT START: So an aggregate could look like

@Aggregate
public class Customer {

    @AggregateIdentifier
    private CustomerId customerId;
    
    private Name name;
    

    @CommandHandler
    private Customer(CreateCustomerCommand command) {
        AggregateLifecycle.apply(new CustomerCreatedEvent(
                command.getCustomerId(), command.getName());
    }
    
    @EventSourcingHandler
    private void handleEvent(CustomerCreatedEvent event) {
        this.customerId = event.getCustomerId();
        this.name = event.getName();
    }
    
}

EDIT END

So my first question is: Am I correct to conclude the aggregate does not implement any state changing methods directly (typical public methods altering the properties of the aggregate instance) but all state changing methods are defined in a separate domain service interacting with Axon's command gateway?

EDIT START: In other words, should an aggregate define setters responsible for sending a command to the framework which will result in the corresponding command handler and event handler being called by adding the code below to the aggregate?

public void setName(String name){
    commandGateway.send(new UpdateNameCommand(this.customerId, name));
}

@CommandHandler
private void handleCommand(UpdateNameCommand command) {
    AggregateLifecycle.apply(new NameUpdatedEvent(command.getCustomerId(), command.getName());
}
        
@EventSourcingHandler
private void handleEvent(NameUpdatedEvent event) {
   this.name = event.getName();
}

This seems to violate some recommendations since a reference to the gateway is needed from within the aggregate.

Or do you typically define such methods in a separate service class which will then send the command to the framework.

@Service
public class CustomerService {
    
    @Autowired
    private CommandGateway gateway;
    
    public void createCustomer(String name) {
        CreateCustomerCommand command = new CreateCustomerCommand(name);
        gateway.send(command);      
    }
    
    public void changeName(CustomerId customerId, String name) {
        UpdateNameCommand command = new UpdateNameCommand (customerId, name);
        gateway.send(command);
    }
    
}

This seems the correct approach to me. This seems to make (at least to my opinion) the aggregate's behavior not directly accessible for the outer world (all command and event handler methods can be made private) like a more "traditional" objects which are the entry-point for requesting state changes...

EDIT END

And secondly: Isn't this in contradiction with OOP principles of each class defining its (public) interface methods to interact with? In other words, doesn't this approach make an object a more or less dump object in which direct interaction is impossible?

Thanks for your feedback, Kurt

Upvotes: 0

Views: 328

Answers (1)

Allard
Allard

Reputation: 2890

So my first question is: Am I correct to conclude the aggregate does not implement any state changing methods directly (typical public methods altering the properties of the aggregate instance) but all state changing methods are defined in a separate domain service interacting with Axon's command gateway?

Absolutely not! The aggregate itself is responsible for any state changes.

Probably, the misconception is around the Command Handler (method) within the aggregate, when using event sourcing. In that case, the Command Handler (method) should not directly apply changes. Instead, it should apply an Event, which then invokes an Event Sourcing Handler (method) within that same aggregate instance to apply the state changes.

Whether using Event Sourcing or not, the aggregate should expose actions (e.g. Command Handlers) only, and make its decisions based on those actions. Preferably, the Aggregate (of a Command Handler) doesn't expose any state outside of the Aggregate boundaries.

And secondly: Isn't this in contradiction with OOP principles of each class defining its (public) interface methods to interact with? In other words, doesn't this approach make an object a more or less dump object in which direct interaction is impossible?

That would have been the case if the aggregate relied on external components to manage its state. But it doesn't.

additional reactions after question edit

So my first question is: Am I correct to conclude the aggregate does not implement any state-changing methods directly (typical public methods altering the properties of the aggregate instance) but all state changing methods are defined in a separate domain service interacting with Axon's command gateway?

I think it's exactly the opposite. The first aggregate in the question is an example of an aggregate that has all state changing operations inside of itself. Exposing setters to "change state" is a very bad idea. It forces the logic to decide on when to change this value outside of the Aggregate. That, in my opinion, is a "violation" of OOP.

Aggregates, in a DDD and/or CQRS context should no be instructed to change state. Instead, they should receive the actual business intent to react to. That is what a Command should reflect: the business intent. As a result, the Aggregate may change some attributes, but only to ensure that any commands happening after that behave in such a way that it reflects the things that have happened before. With event sourced aggregates, that has an extra intermediate step: applying an event. That needs to be done to ensure that sourcing an aggregate from past decisions yields the exact same state. Also note that these events are not "state change decisions", but "business decisions". The state changes are a result of that.

Final comments

The service class shown in the end would be the typical interaction. A component sending commands, not directly interacting with the Aggregate instance. However, the "UpdateNameCommand" as a comparison with the "setName" in the previous example put me off, since Commands should not be "C(R)UD" operations, but actual business operations. At may be the case that UpdateName is such a business operation.

Upvotes: 3

Related Questions