4

Aggregate-roots are there to control the state-changes - what is allowed currently and what's not. If the state-transition is allowed, go on. If not, you throw an exception explaining the reason why it was not allowed.

But what is if a state-change doesn't happen because it's already in the requested state?

For example, if you have a Approve method on your aggregate-root and by the time it is called the state is already approved?

  • Should an exception be thrown a la "XYZ is already approved"?
  • Or should it be silently ignored?
  • Or should the state-change be "signaled" again (event-sourcing, next paragraph)?

In my case I use event sourcing, so an event is being emitted if a state-change occured. Having events in my event-stream with no real state-change doesn't feel "clean" to me because I'd like to be confident that the events actually were produced due to state-changing actions.

Is there a rule of thumb?

EDIT:
In the described case approving an approved element would not really hurt. So tend to this way (thank you @Eben Roux, @guillaume31).

But let's add a bit more spice to it (the actual question behind the question):

Assume:

  • message-bus
  • async command/event handling
  • a process-manager

What if a process-manager (aka saga) issues a command (async) and want to know if a command succeeded? I think it would reduce the mental load/error-sources if the process-manager would not have to care about this implementation detail.

I see 3 ways of handling that:

  • have a "Command with id ABC succeeded/failed" message sent on bus
    process-manager waits for that message instead of the event
  • make command execution sync
    if process-manager encounters no exception, everything's fine, move on
  • introduce a new event ApprovalDeclined { WasAlreadyApproved = true }
    additionally the process-manager waits for this event - the declination is part of the aggregate history, maybe an advantage, maybe never needed...

I know: "it depends"
But can you think of any other (more elegant/easier/different) solution? What's your favorite "process-manager-compatible" way of dealing with this issue?

David Rettenbacher
  • 5,088
  • 2
  • 36
  • 45

3 Answers3

2

I don't think that there is going to be a rule of thumb for this.

Message idempotence is, of course, a good thing so simply ignoring the message/state-change is probably the way to go.

I would not signal it again as there is no effect.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
1

I'd say it depends on your domain. If you want to warn the user that they're approving an already-approved thing, there has to be some feedback from the aggregate. It can be an exception as in Deactivate() here, or a return value relayed by the application service / command handler (note this might not be 100% CQRS compliant).

If the fact that approval has already been made isn't important to the domain task, you can just ignore the action or execute it again regardless.

From a UI perspective, you will most likely not allow to reapprove something anyway, so the cases where this happens will be marginal : concurrent approvals by 2 users, scripts that do "brute force" approval, etc.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
0

It seems - to me - you're confusing application and infrastructure level concerns.

The application level is concerned with expected domain specific behavior. What would be a business reason to Approve twice? Why would that even happen in the first place? Maybe it's just a UI level concurrency thing, but then, why are multiple users approving the same thing at the exact same time? Maybe there's an issue at the business level they haven't solved yet. Understanding the why first is essential. The solution often isn't in the software itself.

The infrastructure level is concerned with things such as guaranteed message delivery (e.g. at least once or exactly once). When the infrastructure can't live up to that promise how does that impact the business (if any)? How does it impact your process? What is your process about anyway? Again, it's essential to reason about these things from a domain specific point of view. At the end of the day you're doing either risk or operational cost mitigation.

In this particular case, ask yourself why and how your process got where it was before firing off the approve command (a bit strange for a process to approve, it's usually done by humans). How come it did not observe the previous approve (remember, the what-ever-aggregate was already approved)? You could introduce a timeout (as a message to its (the process) future self) but it seems kind of strange to do it in this case (I don't know nearly enough to tell).

Don't take this the wrong way, but I think you either need to dig deeper into the domain specific side of things or share more of the specifics.

Yves Reynhout
  • 2,982
  • 17
  • 23
  • I read back and forth your answer but I think it hits not the point I try to figure out - but it's still geatly appreciated! The (evolved) question is not about message delivery - it's more about: how does the process-manager know it's command succeeded - in a general way? Apart from the given case - which was just an (maybe bad) example - I still try wrap my head around what to do when I already concluded I need to implement a process-manager (without an ESB)... – David Rettenbacher Nov 04 '14 at 22:59
  • ...This affects the "rule" by which the aggregates deal with this no-state-change - exception or no-event-emitting-passthrough. Either: No aggregate-state-change => exception => CommandFailed system-event on bus with command-id in it. "Failed" though the aggregate is in the right desired state? Seems wrong. Or: No aggregate-state-change => command "passes" without emitted event-message => CommandSucceeded system-event on bus. This would make the most sense to me though now the process-manager listens for system-events, not the "normal" events... I just search for some guidance in this matter. – David Rettenbacher Nov 04 '14 at 23:16