'Difference between command handlers and aggregate methods in functional DDD

Following a functional programming paradigm, I have a CQRS architecture with event sourcing as the main persistence mechanism.

Currently my aggregates consist of

  • a set of command handlers (behavior)
  • a reducer to fold events to the current aggregate state (data)

A command handler does

  1. fetch the event stream for a given aggregateId
  2. folds the current aggregate state
  3. applies some business logic based on the current state
  4. persist any events created in step 3

Example of a command handler

type CommandHandler = (
  state: AggregateState,
  command: Command
) => E.Either<Err.Err, DomainEvent[] | void>;

Basically steps 1, 2 and 4 are abstracted away in a generic function:

// pseudo-code
const wrapCommandHanler = (handler: CommandHandler) => {
  return wrapped = (command: Command) => {
    const events = fetchEvents();
    const state = applyReducer(events);
    const newEvents = handler(state, command);
    persistEvents(newEvents);
  }
}

So my command handlers are quite lean and focused and only contain the business logic.

I read about DDD, but the given examples were following a OOP paradigm. In these examples the command handler would call an aggregate method where the aggregate is a class that contains state and domain logic.

But in my case the aggregate state and behavior is separated and my command handlers ARE the aggregate behavior. So my command handlers contain the domain logic.

My question(s): Is this approach "correct" / valid DDD or am I shooting myself in the foot with this? If not, what is the main purpose of separating an aggregate function and a command handler?



Solution 1:[1]

You'll probably want to review Jérémie Chassaing's recent work on Decider

My question(s): Is this approach "correct" / valid DDD or am I shooting myself in the foot with this?

It's fine - there's no particular reason that you need your functional design to align with "Java best practices" circa 2003.

If not, what is the main purpose of separating an aggregate function and a command handler?

Primarily to create a clear boundary between the abstractions of the problem domain (ex: "Cargo Shipping") and the "plumbing" - the application logic that knows about I/O, messaging, databases and transactions, HTTP, and so on.

Among other things, that means you can take the aggregate "module" (so to speak) and move it to other contexts, without disturbing the relationships of the different domain functions.

That said, there's nothing magic going on - you could refactor your "functional" design and create a slightly different design the gives you similar benefits to what you get from "aggregates".

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 VoiceOfUnreason