2

I have several questions related to CQRS in eventually consistent systems with a need of resilient Command/Event processing.

  • Is it OK to have external/public synchronous Commands (triggered by API, e.g. RequestUserCreation) in conjunction with internal/private asynchronous commands (triggered by events, e.g. SendWelcomeEmail, AcquireUsername, etc)? Example: RequestUserCreation creates aggregate, which raises UserCreated, which triggers internal commands SendWelcomeEmail & AcquireUsername (using a messaging broker) and also SuggestFriends which raises FriendsSuggested, etc.
  • Is the so-called "Saga" just a pattern of chaining Events & Commands asynchronously, or is Saga an actual entity in the code? To me, any composite sequence of Events & Commands looks like Saga (for example, a sequence from the previous point looks like one). Am I getting it wrong?
  • I'm tempted to give those internal/private Commands a different name, say, Tasks, just to keep them fully separated from external/public Commands and emphasise on their asynchronous nature. Is this reasonable or am I introducing an unnecessary abstraction? I really feel like all Commands should be synchronous and external/public and don't want to polute them with internal processes.

Thanks in advance!

2
  • As you say you have several questions. You nicely distinguish between them in the bullet list. Can you split your question into three separate questions? That would make it easier to answer them. And in addition you would help future software engineers with the same question as one of yours. They would be guided to your question and the answers the question has collected. Separate questions are better found by google. Oct 31, 2022 at 1:53
  • I'm not so convinced this question needs to be split up. Sequences of events and sagas are close enough in concept that I'm interested to see if this question gets an answer. Sometimes questions can be so closely related that splitting them up is not beneficial. Oct 31, 2022 at 15:51

1 Answer 1

3

CQRS is a rather broad pattern, meaning that it can be implemented in any number of ways. It's really about separating your read and write models and designing each of those models in a way that optimizes and simplifies their usage separately.

For your specific questions, I don't think there is a definitive answer. However, there are pros and cons to each approach.

Internal vs. External Commands

For example, if you were to treat the internal and external commands the same, then it removes a dual path problem where you process "tasks" differently than you process "commands". If everything is a command or event, then those make sense to anybody else being introduced to the project. Introducing "tasks" increases the cognitive load of someone reading the code, and you need to determine if creating that separation is worth the additional complexity.

When deciding between different options of a solution, I would refer you to A Philosophy of Software Design by John Ousterhout. In that book, he describes 3 symptoms of software complexity, and these include "change amplification", "cognitive load", and "unknown unknowns". I see your suggestion to introduce "tasks" as potentially leading to change amplification and cognitive load. It leads to change amplification because anything you change to the way commands are processed will need those same changes to process tasks. It increases cognitive load because there is now a new concept that a developer must understand (i.e. "tasks").

With that said, I could easily argue the other way. Since there are inherent differences in the way "commands" and "tasks" behave (i.e. synchronous vs asynchronous in your example), then trying to fit them into the same bucket is difficult and misguided. This can also lead to additional complexity because we're trying to treat two different concepts as the same thing which also increases cognitive load.

My point is that you can make your solution work either way and there isn't one solution that is a silver bullet. Each decision has a tradeoff and what makes a good software engineer is understanding what those tradeoffs are, articulating those tradeoffs to stakeholders and other developers, and then making a decision to move forward.

Saga Pattern

Your question about the Saga pattern is a little more concrete. First off, you're right that the saga pattern is about performing a sequence of commands. However, there's a couple key points I think you're missing. Sagas are usually for executing commands across multiple aggregates (it's not clear if you're dealing with multiple aggregates in your example). Also, it's about executing those commands in a transactional manner — meaning that all the commands succeed, or if one command fails, then compensating commands are executed to "undo" the saga.

This can be done in a couple ways. In the book Building Event-Driven Microservices by Adam Bellemare, he talks about a couple ways to carry out a sequence of commands in a transactional manner. Adam covers this topic as it relates to microservices, but the same patterns can be used in a single service using CQRS. In that book, Adam talks about choreographed and orchestrated workflows.

A choreographed workflow is similar to what you describe. It's a sequence of commands that occur without a single entity inherently controlling it. Each command is processed independently — it only cares about what event triggers it and what events result from it. In case of an error, it works similarly. An error event could be raised and then compensating commands could result from that error event. In a choreographed workflow, there's likely no formal representation of a Saga in code.

On the other hand, an orchestrated workflow is a more formal representation of the sequences of commands/events that happen. In this pattern, an orchestrator keeps track of the current state of the workflow, listens to the message bus, and dispatches new commands when others finish. It controls the sequence and also handles the rollback when an error occurs. In this case, the orchestrator is the representation of your "saga".

In my experience, choreographed sagas are fine for simple use cases such as invoking one command as a result of an event. When you need additional use cases such as cross-aggregate validation, complicated rollback, time-based triggering, etc. then using an orchestrated saga makes more sense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.