2

I know there are many posts on this question but none of them gives a clear answer or solution (atleast for my specific case)

Some of them are

In CQRS/ES, can a command create another command?

https://medium.com/workleap/why-command-should-not-call-command-in-cqrs-5da046a9fed1

However the general advice in all the posts is that we should not call one command inside another command.

Let me explain my specific case here now

  1. I have two type of entities Document and Form.
  2. A document can have multiple forms
  3. There is a CreateDocument command. Document can be created without any child forms
  4. There is a CreateForm command. Form can be created for any existing Document
  5. So both Document and Form can be created separately.
  6. While creating the Document if Form is received then the forms should also be created and that is where the issue comes

Now I have separate commands for both document and form creation but forms can also be created as a part of document creation.

So the problem comes when I receive forms as part of document creation.

In this case both document and form should be created but there commands are separately written.

Possbile solutions

  1. Should I duplicate the whole code of form creation inside CreateDocument command?
  2. Should I call the CreateForm command inside CreateDocument command (This is nowhere recomended)?
  3. Should I trigger an event after the document is created and that event handler will handle the creation of forms if any?

Problems with above solutions

Solution 1: This is the cleanest and most correct in the sense that it will allow me to do all the operations under one transaction which is required for my case. But the downside is that I will have to duplicate a lot of code, I am not sure if it is the right practice.

Solution 2 - This is the easiest but it is not recomended anywhere. Also, it lacks the posibility of doing everything in one transaction, so it is possible that document is saved but forms saving fails.

Solution 3 - This is similar to solution 2 but is better approach as I have read but this again lacks the transaction. Also, the events are captured using INotificationHandler<T> types, so this is also not a command, so I am not sure if I should again call the command inside the event handler to create forms?

Your suggestions please

3 Answers 3

3

You can solve every problem in computer science with another level of indirection... except the problem of too many levels of indirection!

-- attributed to various people, including D.J. Wheeler

The basic principle of a command is that it is supposed to be a whole item of work at the highest conceptual level offered by the application.

Typically a command would be something in an application that the user themselves would trigger directly (although this might be complicated by applications that are written to assume automatic scheduling, or subsystems which are expected to be composed and triggered indirectly).

In the simplest applications the implementation of a command may be trivial, but in many realistic applications a command might be responsible for a very complicated state transformation, including those that might be found to fail part way through (requiring reversals, compensating actions, or supervisory alerts), or which the user may have the option to undo.

The main problems with composition of commands are threefold.

Firstly, when commands can call sub-commands recursively (each level of which might have complicated effects in their own right) the visibility of what the command actually does is often grossly obscured - defeating both the ability to correctly devise and verify the command at the outset, and for the command to be a simple, linear statement of what it does for investigation and maintenance purposes later.

Secondly, command-query separation requires the high-level operations to do just one or the other, but it often isn't possible to avoid queries which also cause incidental writes internally (e.g. for logging), or commands which also cause incidental reads internally (e.g. for checking or control purposes).

The point of CQS is that the mixture of reads and writes occurs only at the highest level possible where the overall flows, and transition from reading to writing, are clearest and most easily orchestrated. A single command might well read as well as write to achieve its aim, but it does so at its top level.

But if commands call sub-commands, then these flows which are top-level in each command, become buried again within a much larger command, muddying the relevant kind of separation.

Thirdly, when the functionality of multiple commands does need to be composed, the most desirable kind of composition is often not sequential, but a more complicated re-weaving of activity.

The most obvious example is when it would seem desirable to iterate the same command with ten slightly different inputs. You don't hit the database ten times by composing a single command ten times in series. You don't redraw the screen ten times in series. You hit the database once, writing ten lots of changes, then redraw the screen once. The desired behaviour of a command for ten cases is not a sequential composition of the behaviour of the whole end-to-end command for one case.

Concluding remarks

I've realised in the process of writing this answer that the word "command" is really being used in a number of subtly different contexts and capacities. For example, applications as different as say user-driven document editors, and say internal APIs (which only a developer will ever see), could each have the concept of "commands". But I think the principles will apply the same in each.

If there are commands which share significant functionality (and therefore create the temptation to call one another), then in some cases simple copy-and-paste might be more appropriate. But if commonality is too frequent (or too subtle and very important to keep strictly aligned) to tolerate this, then moving common parts into methods which are not themselves high-level commands might well be the solution.

These common parts are then designed to be composable elements called within commands, rather than whole commands in their own right, and don't have to be useful in their own right or perform all the other things that would go into the commands.

7
  • How would this apply to the extreme case of applications which only offer a single, (but highly complex) unit of work? Would this suggest that CQRS is not a good design paradigm for applications for this class? Commented Jul 13 at 14:53
  • @user1937198, I think it would depend on how the application is complex. I mean if it does only one thing, then by definition there can't logically be a separation amongst its own repertory of queries and commands. But the innards, such as a database layer, may still be organised into finer-grained elements with queries and commands. Also, in my experience, the more complicated the processing is, the more you need to inspect inputs and intermediaries, or do dry runs and inspect outputs without committing them. (1/2)
    – Steve
    Commented Jul 13 at 16:46
  • So it might be unreasonable to design a highly complicated application to have only one button, even if in the normal case that one button is essentially all what the whole thing exists for. I think CQS is jargon for a very general design principle about the orderliness of reads and writes, a recurrent motif that appears everywhere and at all scales in data processing, like a fractal pattern, so I'm strained to think of any example from my own experience where it can be said not to apply at all. (2/2)
    – Steve
    Commented Jul 13 at 16:50
  • An example of a highly complicated application with 1 button is a home insurance quote generation tool. This might have a single button to request a quote for an address, and then a hugely complicated backend to apply a ton of logic with data gathered from other sources to generate a quote. Similar examples for example appear in various other forms of price search engines such as transport. Other commands would might exist to populate local versions of certain datasets, however the vast majority of the logic would be preformed on demand in the single request. Commented Jul 13 at 17:22
  • Now an alternative way to view these applications might be that they are applications with 0 commands, and one very complex query that also accesses external stores. Commented Jul 13 at 17:25
2

Overall I agree with Steve's answer but it didn't quite get to the final station on the train line of thought, in my opinion.

To summarize their point, although I'd recommend just reading their answer first: CQRS exists to provide an early segregation into read-only or write-only territory, and to define those distinct operations as straightforward operations taken by a user (or arguable an automated worker who is considered to be external to this codebase).

The above is all correct, but it doesn't fully address the underlying issue of code reusability. The key point missing in the previous answer here (IMO) is the explicit confirmation that the need for reusability does not invalidate your need for CQRS. You need to solve it a different way that's compatible with CQRS, but it doesn't invalidate it (nor does CQRS invalidate the need for code reusability).

When you have multiple commands that use the same subset of logic, you have to acknowledge that you no longer have a one-to-one mapping of command handlers vs business logic. Up until now, based on your question, you've been working in the assumption that each command represent a unique set of instructions that relate only to that command, i.e.:

diagram

Your question reveals that this is no longer the case.

Should we call a command inside another command in CQRS?

Your title proposes doing something like this:

diagram

But this isn't defining the handlers and the business logic as two separate concepts. The "Bar" concept isn't a command, it's a reusable operation which should be available as a command on its own, or as a subset of the implementation logic for different commands.

This is better represented by an abstraction, i.e.:

diagram

Note that the existence of the "Bar logic" box is optional. I added it to indicate that if there is some Bar-specific logic, that this logic should still belong to the Bar command.

But the shared logic should be abstracted elsewhere, i.e. an external class that gets injected (or otherwise provided) to your handlers, so they can outsource part of their work to that reusable component.

In effect, this has nothing to do with CQRS. We could've completely ignored that the initial two blue boxes were command handlers. This advice is just Abstraction 101 that applies to any kind of reusable logic between separate classes: abstract the reusable bit into a component of its own.

Don't make that reusable component a command. It's not. Commands should describe specific actions, not the underlying logic with which they may be implemented.

9
  • 1
    This is a good supplement. One of the things I found when drafting my answer, was the difficulty of being specific about what a "command" actually is (as distinct from any old method), in a way that would be useful to anyone who didn't already have an expert understanding. Every time I considered a different phrasing, I find myself making claims that are only true in an implied context, and are therefore liable to be taken too generally, or which just beg an expert understanding (or massive further explanation) of other concepts in turn. There's definitely more about it than meets the eye.
    – Steve
    Commented Jul 15 at 13:20
  • @Steve: I'm an analogy man, and I describe it as well-labeled buttons on a machine. Some of them might activate a single thing inside the machine, some might activate a complex orchestration, but the buttons are a boundary between the external operator and the machine's internals, at a stop gap for a user not needing more detailed knowledge than what the buttons describe.
    – Flater
    Commented Jul 15 at 23:41
  • It's good but this falls down when you're talking about commands in internal layers, modules, or subsystems, because these things are not externally visible to an operator, only to the developer. And now I need to explain all those things, too. Although the average person is familiar with using everyday machines with buttons, they are not familiar with the principles of actually designing those machines and their control interfaces or internal mechanical linkages (and what not to do), so the use of the analogy conveys nothing extra to the learner who has no machine-design background already.
    – Steve
    Commented Jul 16 at 6:14
  • @Steve: Internal/external are not absolutes, they're relative values. If there is an internal layer boundary, then there are things internal and things external to that boundary. The point of an analogy is not to explain it to physical machine designers, I don't know where you're getting that from. I think you're not seeing the wood for the trees in that analogy.
    – Flater
    Commented Jul 16 at 6:37
  • 1
    @wongx: That is wholly contextual and can't be answered universally. The name should reflect whatever the role/purpose of that logic it. "Service" is a filler word when you can't find a better noun to describe the object. It is marginally better than "Manager". If you can come up with a more appropriate name (calculator, validator, mapper, client, worker, repository, factory, builder, ...) then use that name over "service".
    – Flater
    Commented Oct 1 at 22:35
1

No you shouldn't. I guess the tricky bit is explaining why, because I think there are multiple overlapping ideas at play here.

Lets take the easy reason first though. As outlined in your second link, if you have objects on the same 'layer' which can call each other, then you risk circular loops. This is especially true if you are passing messages around queues.

Now let's talk about CQRS, here I think there is an overlap with Event Sourcing (ES). In ES you have a lot of problems with events raising other events and what events get written and need to be replayed etc. It's a nightmare. But I think I might be right in saying "ES is (long?) dead in 2024"? So we don't need to think about it ever again

Now if we take a plain CQRS where we are just splitting our DB reads and writes it seems like this problem is going to solve itself when you code it out. You wont repeat the underlying saveForm(Form f) method with actual SQL, but its going to be in the same repository or at least datalayer as SaveDocument(Document d) so you can have a private method and used it in both, there's no need for a separate command to avoid the duplication of code.

If you have a more general event driven architecture though, you might still want to put a clear separation between the events and models. In this case i would have Document simply reference its Form's IDs and have AddDocument(), AddForm() with no overlap in persistence code. (Add is better than Create here, because you can create documents in memory, you are adding them to your persistence layer)

Now if I want to save a document with some forms I call all the methods

var doc = new Document
var f1 = new Form
doc.FormIds.Add(f1.Id)
commandQueue.Add(new AddFormCommand(f1)) //save the form
commandQueue.Add(new AddDocumentCommand(doc)) //save the doc with attached ids

The key difference between this approach and having the AddDocumentCommand generate AddFormCommands is that your orchestation of the events is done on a single layer above the commands it references.

If you do manage to create an infinite loop, or other undesired effect in the orchestration, it won't be because of some hidden side effect of a command, you can look at this layer and see the orchestration logic in full.

This might not be code, it could be routing rules for your MQs or a state engine. The key is its in one place and obeys a direction of referencing

12
  • Event sourcing is long dead, eh? I haven't found a use case for it yet, so I have no practical knowledge, but there was a lot of buzz about this a while ago. I guess I always felt it was a niche solution rather than a generalized solution. Commented Jun 12 at 18:37
  • It seemed like there was a point in time when it was "the new thing" and everyone wanted to put it in stuff. Kinda hoping to get a million comments from people who are still using it and want to say how to make it work.
    – Ewan
    Commented Jun 12 at 19:51
  • @Ewan I think the way I asked question made it look like it is just about entering one record in document and one in form. Unfortunately it is not that simple, it is much more than that. There is a whole lot of things happening in each command when the document or form is added. I tried to ask the question in the simplest way just to explain the scenario without bombarding the question with too many business details. In reality both the commands have many dependent tables filling logic as well as other business logic, so we definitely need two separate commands. Commented Jun 13 at 5:54
  • Considering these other details, now what do you think should be the approach for me? As I explained I do need two separate commands for adding document and adding form Commented Jun 13 at 5:56
  • 2
    you can see in my example there are two commands
    – Ewan
    Commented Jun 13 at 8:21

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.