Conditional `create` commands within a CQRS architecture

752 Views Asked by At

I will simplify my problem:

My LightsState API can receive 2 type of inputs: lightOn {lightId: ##} and lightOff {lightId: ##}. (AMQP input but irelevant here)

These inputs translate well into 2 Commands: TurnLightOnCmd and TurnLightOffCmd.

These commands will create 2 Events: LightTurnedOnEvent and LightTurnedOffEvent.

These events will be applied to the Light Aggregate and the persisted projection will be the state of the light.

All good until here.

But because there is no input: create light, i can not make a CreateLightCmd from that. I can only invoke a CreateLightCmd when I receive a lightOn input with a NEW lightId to create a Light Aggregate and then also apply TurnLightOnCmd on it.

I am not sure how to handle this and also follow good CQRS practices. Is it ok to call the Query side from Command side to check if light exists by id and then invoke CreateLightCmd first if needed? Or should I make a db query from within the Command side and keep the Command and Query sides decoupled? Or are there any other solutions to this?

Thanks

2

There are 2 best solutions below

1
On BEST ANSWER

Is it ok to call the Query side from Command side to check if light exists by id and then invoke CreateLightCmd first if needed?

Not really - that introduces race conditions, the consequences of which may not make you happy.

Review: DDD+CQRS+ES is very similar architecturally to DDD alone. The basic notion was that we persist information into our storage appliance (aka "the database"). The model runs in a process that loads the current state from the database, uses the command to compute a new state, and then stores that new state in the database.

The same pattern holds when we are doing event sourcing - we read the history from the database we use for writes, compute new events, and append those events to the history.

But: creation patterns are weird.

When we try to query for the history of an identifier that we haven't seen before, then we are going to get a null, or a None, or a history with no events in it, or something like that.

The surprise is: that's fine.

For your use case, you wouldn't necessarily want a CreateLightCmd -- instead, you want to produce a new LightCreatedEvent when you get one of the other commands that should implicitly create the light.

In pseudo code:

TurnLightOn (cmd) {
    history = getHistory(cmd.lightId)
    if (history.isEmpty) {
        history.append(LightCreatedEvent.from(cmd))
    }
    history.append(LightTurnedOnEvent.from(cmd))
    save(cmd.lightId, history)
}
0
On

Take a look at Udi Dahan's seminal post, "Don't create aggregate roots" [0]. The key points are:

  • Don’t Create Aggregate Roots
  • Always Get An Entity

What that means is that when you issue a command, you should always have an aggregate with an actual aggregate root entity initialized to it's default state. In your case, the default state is "light off". You execute the command on this light and now it is in state "light on". Now you save it to the database and it is created. Unless your domain cares about LightCreatedEvent, you don't need to model it.

[0] http://udidahan.com/2009/06/29/dont-create-aggregate-roots/