CQRS - Read model business logic duplication

892 views
Skip to first unread message

- GasyTek

unread,
Jan 18, 2021, 10:00:28 AM1/18/21
to DDD/CQRS
Hi,

When implementing CQRS, there are cases where business logic needs to be used at both sides (read and write). Take the below example.

At the WRITE side :

class Task 
  ...
  + IsAssignableToMe(string currentUserName)
  ...
  
At the READ side

class TaskReadModel
  ...
  + IsAssignableToMe { get; set; }
  ...

The value of property "IsAssignableToMe" depends on the current connected user so it can't be stored in database and must be computed for every query on the read side.
The "IsAssignableToMe" method of the write side already contains all the necessary logic to compute the value.

We can duplicate the logic from the write side to the read side and it will work but I feel umconfortable with this approach.
Furthermore, this example uses a really simplistic business logic but there are cases where a more complex logic and more classes are involved and duplicating everything seems not the right choice.

How do you deal with that kind of scenario ? Any advices ?

Thanks

Riana

Tomas P.

unread,
Jan 18, 2021, 11:41:20 AM1/18/21
to ddd...@googlegroups.com
Hi Riana,

without knowing more about your architecture my suggestion might be off, but let's try :)

What you described seems like a "permissions" or "authorization" to me. 
So I'd maybe extract the code to some auth / permission package / class and then reuse it in both write and read classes.


Best
Tomas

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/4ad2758e-ec52-4faf-8365-cb4bdcbbf1acn%40googlegroups.com.

- GasyTek

unread,
Jan 18, 2021, 1:47:52 PM1/18/21
to DDD/CQRS
Hi Tomas, 

Thanks for your advice and for trying ! :)

Your advice make sense but let me give you more context. 
The "IsAssignedToMe" logic is already implemented on the  "Task" aggregate on the write side as it depends also on other datas hold by the task (like its status, creation date etc.)

//
// The Task Aggregate, member of the Domain Model on the Write side.
//
class Task 
{

public bool IsAssignableToMe(string currentUserName) 
{
return this.isAssigned 
&& this.assignedToUser == currentUserName 
&& this.Status == Status.InProgress
&& this.CreationDate >= DateTime.Now.AddDays(-5) 
... more tests
;
}

}

For this "simple" case, I (may be) can extract this code and find a way to create a shared package on which the Write Domain objects and the Read objects will depend.
But there are some more complex cases, where the business logic is far more complex  with more code , and externalizing it from the Domain Model seems weird for me as it defeats the goal of a domain model.

Generally speaking, I have the impression that we can't totally avoid business logic duplication on the write and the read side but I'm not totally sure and I would like feedbacks of other practitioners.

What is your thoughts with these additional informations ? 

Thanks

Riana

Jorge Olivé

unread,
Jan 18, 2021, 1:47:52 PM1/18/21
to ddd...@googlegroups.com
Hello,

A very interesting question indeed :) . At first glance It feels like requirements such as this kind of invalidate the whole CQRS principle. Anyway, just a few ideas I can think of:

- Move the IsAssignable logic to an application service / domain service if all the relevant task domain state to make such a decision is shared between the Task and TaskReadModel objects. Be careful, as  you're moving away behaviour from your domain object hence moving to anemic modelling territory.

- Load the Task object in the read side and project to your model, but one can argue that this makes storing the Read model useless. Could be an option if you have multiple use cases for this TaskReadModel where only a few need to map the IsAssignable boolean.

- Expose this particular function through a dedicated use case and move out the IsAssignable flag from the readmodel. Here you would expect the client to perform the orchestration. Obviously his new endpoint/service would need to recover the domain model, but you're probably doing this already in the write side. Might or not be an option depending on your requirements but seems the one that is more consistent with the architectural style.

Regards,
Jorge







Rickard Öberg

unread,
Jan 18, 2021, 9:03:12 PM1/18/21
to ddd...@googlegroups.com
Hi!

The write model can use the read model, so I put all of these things in the read model, and let the write model use it for decision making.

In my architecture the write model ONLY does writes. So the name “is*” to me is a hint that it’s a read, i.e. belongs in the read model.

/Rickard

Sent from my iPad

On 18 Jan 2021, at 23:00, - GasyTek <gas...@gmail.com> wrote:



- GasyTek

unread,
Jan 19, 2021, 3:24:58 AM1/19/21
to DDD/CQRS
Hi Jorge,

Thanks for taking time to answer this !
You give me some interesting solutions here, I have to try to see the one that can saisfy my needs.
I therefore retain that there is no "standard" solution to this kind of situation, the answer is (as often) "it depends" :)
Just by curiosity, have you already encountered such a situation or similar issue during your cqrs journey ?

Riana

- GasyTek

unread,
Jan 19, 2021, 3:50:08 AM1/19/21
to DDD/CQRS
Hi Rickard,

------------------------------
The write model can use the read model, so I put all of these things in the read model, and let the write model use it for decision making.
In my architecture the write model ONLY does writes. So the name “is*” to me is a hint that it’s a read, i.e. belongs in the read model.
------------------------------  

Thanks for your answer.
Your solution has the corrolary of having to implement a  more complex read model (instead of just a flat view).
In this situation, the read model potentially has to have some properties that it needs to implement the "is*" logics but that it will not need to display to user.
That may be a pragmatic approach to the problem, but I have the impression that it will tightly couple the read and the write side and the read side can't be anymore "eventually consistent" as the write side should make decisions according to consistent datas.

Riana

Peter Hageus

unread,
Jan 19, 2021, 3:53:12 AM1/19/21
to ddd...@googlegroups.com
Note that the read model does not have to be a table in a relational database, it could just as well be an in-memory projection loaded on demand (helps if it’s built from a single stream/aggregateroot).

When I have this kind of duplicate functionaly I usually create a service that takes a parameter object that can easily be constructed both from the write model and from the ui layer (or wherever it’s required.)

/Peter

- GasyTek

unread,
Jan 19, 2021, 4:23:19 AM1/19/21
to DDD/CQRS
Hi Peter , 

Thanks for your answer.
The service where you put the logic will be impletemented at which side ? Write, read or an external package as mentionned on previous conversations ?

Riana

Peter Hageus

unread,
Jan 19, 2021, 4:47:48 AM1/19/21
to ddd...@googlegroups.com
On the write side usually, since I try to keep down the number of dependencies there. Most of the time it’s not so much a service as just a static method/function.

/Peter

Rickard Öberg

unread,
Jan 19, 2021, 5:20:28 AM1/19/21
to ddd...@googlegroups.com
Hi,

The read model is what is needed to use a projection of the event stream. Some of this is for user display, some is not. All of it is the read model, or read models plural. Semantically it's all the same.

On Tue, Jan 19, 2021 at 4:50 PM - GasyTek <gas...@gmail.com> wrote:
Thanks for your answer.
Your solution has the corrolary of having to implement a  more complex read model (instead of just a flat view).
In this situation, the read model potentially has to have some properties that it needs to implement the "is*" logics but that it will not need to display to user.
That may be a pragmatic approach to the problem, but I have the impression that it will tightly couple the read and the write side and the read side can't be anymore "eventually consistent" as the write side should make decisions according to consistent datas.

The write side gets coupled to read side, because in order to write you have to be able to read. That's ok. 

As for issues around eventual consistency, if you need consistent writes then you would typically use optimistic writes anyway (load snapshot with version X, apply command to get events, write new events only if aggregate is still at X, otherwise discard and retry), and you can definitely do this with the read model as well (i.e. read an attribute which says what version of aggregate has been used to create the database read model projection).

/Rickard 

- GasyTek

unread,
Jan 20, 2021, 5:07:34 AM1/20/21
to DDD/CQRS
Thank you Rickard for the clarifications !

------------
The read model is what is needed to use a projection of the event stream. Some of this is for user display, some is not. All of it is the read model, or read models plural. Semantically it's all the same.  
------------

Just for precision, actually, we do not use event sourcing. The write side is a 3nf relational database that store the Domain Model and the read side is a denormalized database.

From my comprehension of ES, we build the aggregates in-memory by replaying events so I can distinguish two "read models" here :
  • When the write side have to make a decision, it relies on the aggregate state which is the projection of events in memory. This is probably one of the "read model" you mentionned
  • The actual read model, at least from my point of view is the denormalized tables that we query only to display to clients.
Our issue appears on the 2nd type of "read model", when we have to retrieve data and transform it according to a business rules and logic that are already implemented on the aggregates on the write side.

Any more thoughts ?

Riana

Rickard Öberg

unread,
Jan 20, 2021, 5:23:17 AM1/20/21
to ddd...@googlegroups.com
Hey!

On Wed, Jan 20, 2021 at 6:07 PM - GasyTek <gas...@gmail.com> wrote:
------------
The read model is what is needed to use a projection of the event stream. Some of this is for user display, some is not. All of it is the read model, or read models plural. Semantically it's all the same.  
------------

Just for precision, actually, we do not use event sourcing. The write side is a 3nf relational database that store the Domain Model and the read side is a denormalized database.

From my comprehension of ES, we build the aggregates in-memory by replaying events so I can distinguish two "read models" here :
  • When the write side have to make a decision, it relies on the aggregate state which is the projection of events in memory. This is probably one of the "read model" you mentionned
One way to create the aggregate state is by replaying events. Another, which we use, is to simply load the state from the read model database. As long as you include version numbers you can get consistent writes, if needed, using optimistic locking techniques.
 
  • The actual read model, at least from my point of view is the denormalized tables that we query only to display to clients.

Right, so what I would say is that this model can be used for many things, one of which is display to clients. If you want to use this to implement the logic you described, that works too.
 
Our issue appears on the 2nd type of "read model", when we have to retrieve data and transform it according to a business rules and logic that are already implemented on the aggregates on the write side.

Any more thoughts ?

Not really, same as before. Any logic that has to do with reading state is not on the write side. It is on the read side, and used by the write side. Write models write, read models read. If you need to read state to make a decision, such as isAssignableToMe, that is in the read model.

All of this is the same regardless of whether you use event sourcing or not. As long as you use CQRS, these principles apply, as far as I know.

cheers, Rickard

Harrison Brown

unread,
Jan 20, 2021, 11:59:24 AM1/20/21
to ddd...@googlegroups.com
I’d use the Specification pattern here. I’d create an IsAssignableToUserSpecification class and use it in both read and wrote sides. As for which side you define it on, I’m not sure it’s terribly important — you could extract it out somewhere that makes it clear that it’s shared. Personally, I’d put it in the write side domain mode as I normal find most business logic lives there and I’d like to keep it all over there, but it depends on your system. As others have said, the write side can use the read side so if you have the majority your complexity in read then you could put it there. The most important thing, in my view, is to avoid duplicating that business logic (ie, “what defines something as being assignable to me”) and the specification pattern does that. 

Watch this talk: https://verraes.net/2013/06/unbreakable-domain-models/

Harrison

Sent from my iPhone

On 19 Jan 2021, at 08:25, - GasyTek <gas...@gmail.com> wrote:

Hi Jorge,

Harrison Brown

unread,
Jan 20, 2021, 11:59:39 AM1/20/21
to ddd...@googlegroups.com

Any logic that has to do with reading state is not on the write side. It is on the read side, and used by the write side. Write models write, read models read. If you need to read state to make a decision, such as isAssignableToMe, that is in the read model.

For what it’s worth, in many of our CQRS apps we don’t apply the same heuristic about only reading state from the read model. (@Rickard, I’d be interested to hear you expand on that statement a bit).

We quite often have cases where our write model is complex and does lots of checking of state to make decisions (mainly around (1) whether a user command is valid, (2) what updates to actually do in response to a user command, and (3) how to automatically respond to events). For these, we load up aggregates in the write model which look at their own state to make decisions. We don’t encumber the read model with any of this logic because our read model is used to answer queries and business logic about whether a command is valid or what to do in response to an event is irrelevant in that model.

I actually prefer talking about CQRS as having a “command model” and a “query model” (which, I think, is actually closer to the original idea than “write side” and “read side”) because it allows me to be more pragmatic. Got a piece of state or a business concept which is only relevant when interpreting a user command? That lives in the command model. Got a piece of state or business concept which is only relevant when displaying a particular view to a particular user group? That goes in the query model.

We do sometimes allow the command model to query the query model, but I actually find that to introduce unhelpful coupling most of the time. I want my command model and query model to be two entirely standalone thing as it’s then much easier to understand, test, deploy and refactor. In practice, that means we do sometimes project domain events into a representation (e.g., rows in a SQL database) in the command model so the command model can ask it questions because those questions are only relevant when interpreting a user command (so we don’t want to encumber the query model).

However, I’m not 100% strict about that; if I’m having to jump through hoops to keep that separate, or introducing significant duplication of business logic, then I’m happy to query the query model from the command model, but I tend to do so by introducing an interface into the command model which is implemented in my infrastructure layer to go and read from the query model (which might be running on a different database or a different server, hence putting that stuff in the infrastructure layer) to avoid coupling the two models together. The use of interfaces there introduces a clean boundary which (a) is easier to reason about (you don’t have to think about the query model when working on the command model, you just have to understand the meaning of the interface), (b) allows me to mock those interfaces in tests so each model can be standalone, and (c) gives me options in deployment such as introducing caching in the command model’s implementation if, for example, the command model is happy to accept query model responses up to 1 hour stale.

The second diagram on this page matches how I think about CQRS: https://martinfowler.com/bliki/CQRS.html

However, CQRS is not an architecture. You don’t have to have entirely separate standalone models with different data stores, etc. It could be as simple as having a different set of interfaces to a single service/bounded context/app; one set for writing (MakeCusomerPreferredCommand, ChangeCustomerTier, UpdateCustomerDeatils) and one set for reading (GetCustomer, GetCustomersInArrers, GetGoldTierCustomers). That is the essence of the pattern. All of those could be backed by a single model if your domain isn’t complex enough to warrant more exotic architecture. CQRS allows us to do interesting architectures (event sourcing, separated read and write databases, eventual consistency, projections) but it’s not a requirement — you can get the benefits of the pattern with a plain old boring active record system.

(As Rickard says, this applies to both ES and traditional architectures. CQRS doesn’t require ES.)

Rickard Öberg

unread,
Jan 21, 2021, 12:23:51 AM1/21/21
to ddd...@googlegroups.com
Hi Harrison!

First off, excellent reply! Like where you're going with this.

On Thu, Jan 21, 2021 at 12:59 AM Harrison Brown <harr...@surupartners.com> wrote:

Any logic that has to do with reading state is not on the write side. It is on the read side, and used by the write side. Write models write, read models read. If you need to read state to make a decision, such as isAssignableToMe, that is in the read model.

For what it’s worth, in many of our CQRS apps we don’t apply the same heuristic about only reading state from the read model. (@Rickard, I’d be interested to hear you expand on that statement a bit).

In part this is because the write model, or as you called it (and which is probably better!) the command model, I view as being essentially a function: (command,metadata,state)->(list of events). So the only thing the command model can do is given a command, the metadata about the context of the invocation, and state, produce a sequence of zero or more events.

In our system it is not as clean as this yet, but this is where I think I want to get the design to eventually. Currently we have a REST API layer reading command and creating metadata (timestamp, user id, correlation id, etc.), and then delegate to what we call the use case context layer (see DCI architecture for details). The use case context takes the command, and performs any kind of pre-aggregate validation that requires use of external state, like reading a database or service. Once that external validation has passed, then the command+metadata+snapshot is sent to an aggregate model, which does aggregate internal validation based on snapshot, and creates zero or more events as output. So if this is the command model, and only this, then any things related to examining the current state of the system would be related to a query model, of some kind. If you want to keep the query model name only for what is exposed externally, then yes, that becomes different. There are various patterns for minimizing the dependency between this validation code and the query model, apply as needed.

So where I would want to go is essentially for the command model to be a single function: (command,metadata,state)->(list of events). In Java terms (what we use) it would be something like: BiFunction<CommandMetadata,MyAggregateState,CompletionStage<List<Event>>. CommandMetadata comes from API layer and holds both command and metadata, SomeAggregateState is setup by the use case context and holds both snapshot state plus anything needed for validation purposes, and the function returns a CompletionStage to allow for exceptions and asynchronous handling, and a List of Event instances produced.

This function internally could at first be one that handles all commands for all aggregates, and on invocation selects which aggregate type it is (based on command type), invokes repository for loading snapshot (from events or read model), and internally then delegates to the final aggregate. It can be composed and delegated internally as needed basically, but externally the command model is just a function.

If state then is the composite of aggregate instance snapshot plus query model (direct or abstracted to minimize surface), then the aggregate on its own should have enough information to make the correct validation of whether the command is processable or not. This should make it easier to develop and test in isolation, and allows for quite some flexibility in how to produce the state needed for processing, done by the use case context between the API layer and the command model. Makes sense so far?

We quite often have cases where our write model is complex and does lots of checking of state to make decisions (mainly around (1) whether a user command is valid, (2) what updates to actually do in response to a user command, and (3) how to automatically respond to events). For these, we load up aggregates in the write model which look at their own state to make decisions. We don’t encumber the read model with any of this logic because our read model is used to answer queries and business logic about whether a command is valid or what to do in response to an event is irrelevant in that model.


Right, so in the above model it would be up to the context (the C in DCI) to compose the (db) state and services needed when a command comes in from the API layer, pass this into the aggregate along with command, which then can do the right thing. The context is the glue, and some of what it provides is not available for external consumption in the query model.
 

I actually prefer talking about CQRS as having a “command model” and a “query model” (which, I think, is actually closer to the original idea than “write side” and “read side”) because it allows me to be more pragmatic. Got a piece of state or a business concept which is only relevant when interpreting a user command? That lives in the command model. Got a piece of state or business concept which is only relevant when displaying a particular view to a particular user group? That goes in the query model.


Agree, those terms are better.
 

We do sometimes allow the command model to query the query model, but I actually find that to introduce unhelpful coupling most of the time. I want my command model and query model to be two entirely standalone thing as it’s then much easier to understand, test, deploy and refactor. In practice, that means we do sometimes project domain events into a representation (e.g., rows in a SQL database) in the command model so the command model can ask it questions because those questions are only relevant when interpreting a user command (so we don’t want to encumber the query model).


Yup, if you add the context as an architectural tool to do the composing of state needed by the aggregate to validate and react to the command, we're on the same page. The context COULD be using the read model for this, but might hide it behind an adapter or other pattern to avoid direct coupling between read and write model.
 

However, I’m not 100% strict about that; if I’m having to jump through hoops to keep that separate, or introducing significant duplication of business logic, then I’m happy to query the query model from the command model, but I tend to do so by introducing an interface into the command model which is implemented in my infrastructure layer to go and read from the query model (which might be running on a different database or a different server, hence putting that stuff in the infrastructure layer) to avoid coupling the two models together. The use of interfaces there introduces a clean boundary which (a) is easier to reason about (you don’t have to think about the query model when working on the command model, you just have to understand the meaning of the interface), (b) allows me to mock those interfaces in tests so each model can be standalone, and (c) gives me options in deployment such as introducing caching in the command model’s implementation if, for example, the command model is happy to accept query model responses up to 1 hour stale.

The second diagram on this page matches how I think about CQRS: https://martinfowler.com/bliki/CQRS.html

However, CQRS is not an architecture. You don’t have to have entirely separate standalone models with different data stores, etc. It could be as simple as having a different set of interfaces to a single service/bounded context/app; one set for writing (MakeCusomerPreferredCommand, ChangeCustomerTier, UpdateCustomerDeatils) and one set for reading (GetCustomer, GetCustomersInArrers, GetGoldTierCustomers). That is the essence of the pattern. All of those could be backed by a single model if your domain isn’t complex enough to warrant more exotic architecture. CQRS allows us to do interesting architectures (event sourcing, separated read and write databases, eventual consistency, projections) but it’s not a requirement — you can get the benefits of the pattern with a plain old boring active record system.

(As Rickard says, this applies to both ES and traditional architectures. CQRS doesn’t require ES.)


Agree with all of the above.

cheers, Rickard

Harrison Brown

unread,
Jan 21, 2021, 5:01:09 AM1/21/21
to ddd...@googlegroups.com

Ah OK, I didn't know you were using DCI. That's a very different architecture to what I tend to use (hexagonal) so we'll have been using different terminology. So, for those reading, I expect the key things are the concepts, not the specifics of how Rickard or I implement commands, the exact boundaries between read and write, etc.

Personally, I would argue that having the command model use data from the query model (whether behind an interface or not) makes the whole thing more like code organisation within a system component rather than an architecture of a system component because you can't really think about the command model or query model particularly in isolation. In my team, I can put one or two developers to build the command model and they can be fully productive and deploy their code without knowing what anyone else is doing; I can then put a different developer to build the query model and they only need to know what events the command model is emitting (that's the contract, and the query model is downstream from the command model) and will build up their own data store for query use cases. In that sense, the command model and the query model can be entirely autonomous components; they can be deployed onto different servers; they can change entirely independently. However, to reiterate: CQRS at that level (with entirely autonomous components/microservices/whatever) is only rarely warranted. It's still useful to apply CQRS to even just your API layer (so your web page or mobile app or whatever reads from one set of API endpoints and sends commands to another).

To come back to your original question, Riana: You said, "duplicating everything seems not the right choice". To be honest, if you actually have a very different query and command model, each backed by their own separate data store, I would be tempted to just accept the duplication. You could use the specification pattern and share that class between both sides (and have infrastructure code in each model query their own data stores and then present that specification class with a common representation as a value object — the comamnd model might create that value object by loading up aggregates, the query model might create it by directly querying one or two flat database tables) but at least you're not duplicating the business concept. To be even more pragmatic, I don't really think it's all that bad to have entirely separate implementations of IsAssignableToUserSpecification(Task) in the query and command models — yes, you'll have to remember to update both if the meaning of "is assignable to user X" ever changes in your business, but in my experience business concepts are fairly stable. Eric Evans suggests 'following the contours of the domain' which I feel this does. In this way, you've removed conceptual duplication by embodying a business concept into a explicit specification name even if you have duplicate code, but I often make that choice because fighting to remove code duplication often just introduces unhelpful coupling. Having to change two specification classes rather than one is annoying, but it probably won't happen often if the business concept is stable. (If the business concept isn't stable — i.e., the the rules about whether a task can be assigned to a given user are complex and regularly changing — then any duplication might be difficult to deal with so the suggestion above doesn't work.)

Another option to consider is whether your command model really needs to enforce this. What's the real business impact a task getting assigned when it "shouldn't have"? Assuming your query model and command models aren't massively out of sync (i.e., your eventual consistency isn't measured in days) you can use your query model to hide the button or whatever in the user interface and then have the command model accept whatever it's given, assuming the business impact isn't awful. My team quite often get stuck on this sort of thing because they assume that all requirements from the business are absolute, but actually when I speak to the business and say "I can prevent 95% of cases for 1 day's work or 100% of cases for 1 week's work" they usually opt for the cheaper, simpler option because the business impact isn't that great, especially if you can detect the 'failure' and automatically recover from it. Greg Young gives a good example of this: the business might say "users can't have duplicate email addresses" so a naive developer might think they have to enforce this in the command model with 100% enforcing full set validation. Actually, if you rethink the scenario a bit you realise that if your query model is only a few seconds behind your command model then you can have your registration form user interface (or web tier, or API layer, or whatever is between the user and the command model) do some pre-validation to check an email isn't already taken and not send the command. In the tiny fraction of cases where two people try to sign up with the same email address in the exact same moment then you can just detect that happening (e.g., with a process manager) in an eventually consistent fashion, issue a command to the command model to remove one of the duplicates and then email that user saying "Sorry, you tried to register with a duplicate email. Click here to try again." That approach can save a lot of complexity in the system with almost zero business impact. Hopefully you can see the analogy to your situation, Riana: Assuming your query model isn't massively out of sync with your command model, you might get away with just implementing IsAssignableToUserSpecification in the query model and then use your query model to (a) not show the button in the user interface if it's not assignable, and/or (b) have your user interface run a pre-flight validation and not attempt to assign send the command if the query model says it's not assignable. You'd only need (b) to catch cases where the task was assignable when the user loaded the page but became unassignable before they clicked the button. We often don't even both with (b) as the business impact is often low, and I prefer to spend time writing code that detects and recovers from failures rather than attempts to 100% enforce rules all the time — catching 95% and recovering from the 5% that slip through tends to be cheaper and introduces much less complexity into a system. I'd actually advocate doing that before I would resort to calling the query model from the command model because, as you rightly point out, doing so “tightly couple the read and the write side and the read side can't be anymore "eventually consistent" as the write side should make decisions according to consistent data”. I also want to avoid coupling the query and command models, so I try to avoid calling the query model from the command model. Having to resort to locking, etc. is a code smell, I'd say — by the time you're doing that you've probably lost the benefits of CQRS (separate, autonomous conceptual models) and are applying an architectural pattern that's just for the sake of it. The key thing is to figure out whether you actually need full consistency. If you do, maybe CQRS isn't the right architecture for you? Does it really solve a problem you have? If you do need consistency but you also want to use CQRS, you can apply CQRS without separated, eventually consistent data stores (as I said in my last message).

As you said, Riana, "there is no "standard" solution to this kind of situation, the answer is (as often) it depends". Out of interest, what are your actual business rules that determine whether a task is assignable to a given user?

Harrison

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.

- GasyTek

unread,
Jan 21, 2021, 3:00:26 PM1/21/21
to DDD/CQRS
Hey Rickard, thanks for your answer !

One way to create the aggregate state is by replaying events. Another, which we use, is to simply load the state from the read model database. As long as you include version numbers you can get consistent writes, if needed, using optimistic locking techniques.

Right, so what I would say is that this model can be used for many things, one of which is display to clients. If you want to use this to implement the logic you described, that works too.
 

So I'm curious about the implementation of your read model , is it a denormalized database ? 

Because if it is, I think it should be a pain to load the state of a write model from it (which is often a complex domain model that uses for instance inheritance if you use a OO language), and if it is not, then  the optimization of the read model that should be fast and scalable is lost ..

Riana

- GasyTek

unread,
Jan 21, 2021, 3:06:28 PM1/21/21
to DDD/CQRS
Hi Harrison, and thanks for participating :)

I’d use the Specification pattern here. I’d create an IsAssignableToUserSpecification class and use it in both read and wrote sides. As for which side you define it on, I’m not sure it’s terribly important — you could extract it out somewhere that makes it clear that it’s shared. Personally, I’d put it in the write side domain mode as I normal find most business logic lives there and I’d like to keep it all over there, but it depends on your system. As others have said, the write side can use the read side so if you have the majority your complexity in read then you could put it there. The most important thing, in my view, is to avoid duplicating that business logic (ie, “what defines something as being assignable to me”) and the specification pattern does that. 

Using specificatino pattern is indeed one possible solution, may be even better than using a shared service .
I'll definitely consider this option :)

Thanks !

Riana

 

- GasyTek

unread,
Jan 21, 2021, 3:45:46 PM1/21/21
to DDD/CQRS

Hi Harrison,

Le jeudi 21 janvier 2021 à 11:01:09 UTC+1, Harrison Brown a écrit :

Ah OK, I didn't know you were using DCI. That's a very different architecture to what I tend to use (hexagonal) so we'll have been using different terminology. So, for those reading, I expect the key things are the concepts, not the specifics of how Rickard or I implement commands, the exact boundaries between read and write, etc.

Personally, I would argue that having the command model use data from the query model (whether behind an interface or not) makes the whole thing more like code organisation within a system component rather than an architecture of a system component because you can't really think about the command model or query model particularly in isolation. In my team, I can put one or two developers to build the command model and they can be fully productive and deploy their code without knowing what anyone else is doing; I can then put a different developer to build the query model and they only need to know what events the command model is emitting (that's the contract, and the query model is downstream from the command model) and will build up their own data store for query use cases. In that sense, the command model and the query model can be entirely autonomous components; they can be deployed onto different servers; they can change entirely independently. However, to reiterate: CQRS at that level (with entirely autonomous components/microservices/whatever) is only rarely warranted. It's still useful to apply CQRS to even just your API layer (so your web page or mobile app or whatever reads from one set of API endpoints and sends commands to another).

I do agree 100% with this approach and comprehension of CQRS.
 

To come back to your original question, Riana: You said, "duplicating everything seems not the right choice". To be honest, if you actually have a very different query and command model, each backed by their own separate data store, I would be tempted to just accept the duplication. You could use the specification pattern and share that class between both sides (and have infrastructure code in each model query their own data stores and then present that specification class with a common representation as a value object — the comamnd model might create that value object by loading up aggregates, the query model might create it by directly querying one or two flat database tables) but at least you're not duplicating the business concept. To be even more pragmatic, I don't really think it's all that bad to have entirely separate implementations of IsAssignableToUserSpecification(Task) in the query and command models — yes, you'll have to remember to update both if the meaning of "is assignable to user X" ever changes in your business, but in my experience business concepts are fairly stable. Eric Evans suggests 'following the contours of the domain' which I feel this does. In this way, you've removed conceptual duplication by embodying a business concept into a explicit specification name even if you have duplicate code, but I often make that choice because fighting to remove code duplication often just introduces unhelpful coupling. Having to change two specification classes rather than one is annoying, but it probably won't happen often if the business concept is stable. (If the business concept isn't stable — i.e., the the rules about whether a task can be assigned to a given user are complex and regularly changing — then any duplication might be difficult to deal with so the suggestion above doesn't work.)

Our architecture is pretty simple actually :
  1. We do not use Event sourcing
  2. We have a command model which is a rich domain model persisted in a database by using an ORM
  3. We have a query model which is tailored to be displayed to the client. It is also persisted into the same database so there are no physical separation of command and query
  4. The command and query models are synchronized in-memory and transactionally, so no issue with eventual consistency.
We would just like to use two different models for command and query for performance and also, to be able to evolve one model independently of the other.
But then , I stumbled upon this issue with business logic duplication.
My command model needs this "IsAssignableToMe" function because it is used to make decision on some actions at the command side.

Out of interest, what are your actual business rules that determine whether a task is assignable to a given user?

It depends mainly on the current connected user and the status of the task.
We use the state pattern actually to implement the different status associated to a task (one status = one state) , so if we decide to "externalize" (in a specification ) the business logic of "IsAssignableToMe", we'll have to deal with this implementation of the state pattern also and may be rethink it .

 Riana

Rickard Öberg

unread,
Jan 21, 2021, 10:40:57 PM1/21/21
to ddd...@googlegroups.com
Hi!

On Fri, Jan 22, 2021 at 4:00 AM - GasyTek <gas...@gmail.com> wrote:
So I'm curious about the implementation of your read model , is it a denormalized database ? 

We are using the Neo4j graph database, so denormalized doesn't really apply. It is fast to load for query model and for aggregate snapshots. We also only typically load a fraction of the state for the aggregate snapshot, only that which is really needed for validation checks. We don't use inheritance, but we do use composition, almost like aspect oriented programming. It becomes pretty much trivial to use with a graph database that can easily compose various concerns onto the same node in the database.

In short, I have many things to consider, but performance and scalability have so far not been an issue at all with this setup.

/Rickard

- GasyTek

unread,
Jan 22, 2021, 1:44:11 PM1/22/21
to DDD/CQRS
Hi Rickard,

Ok, I better understand your architecture and the choices you made now.

Thanks a lot for all these informations !

Riana

Fabian Schmied

unread,
Jan 23, 2021, 4:37:10 AM1/23/21
to DDD/CQRS
Hi everyone,

As a lurker on this thread so far, I just want to say thank you everyone for sharing insights on your architectures. @Rickard, @Harrison, @Riana, and everyone else - it's extremely valuable to read about how other people understand and implement CQRS.

@Riana This is how our team is dealing with the duplication problem you're describing.

We, too, have a "write" and a (primary) "read" model, although I agree with Harrison that "command" and "query" model would have been a much better choice of terms, so I'll use those. We use event sourcing, but I think that's not very relevant to how we deal with the question of business rule duplication. In our case, the command model is the innermost part of our architecture, it cannot (statically) depend on types defined by the query model. In fact, the query model depends on contract types defined by the command model, i.e., domain events etc. However, we can use dependency inversion: the command model can define contractual interfaces that can be implemented by (or by using) the query model. In addition, the aggregates within the command model themselves have state (in our case, state that is hydrated from events in an event stream in an event store). When the command model handles a use case, it will use the state from one or more of these aggregates, but it can also use those interfaces it defines if it needs to query data (e.g., data spanning or aggregating many aggregate instances).

As long as a command model use cases needs only data from a single aggregate, that use case will be 100% consistent. We model our aggregates in such a way that the important command model invariants can be guarded in this way. However, there are lots of use cases that don't _need_ to be 100% consistent, i.e., very rare race conditions or inconsistencies are acceptable. (For example, duplicate checks don't need to be 100% consistent in our domain.) For those, it's okay to read data from multiple aggregates or from the query interfaces.

So, now about the duplication aspect. We have the following common scenarios in our code base:
* Often, the command model implements a business rule and "publishes" the result (in our case, this means issuing the result as a domain event) using terms from the ubiquitous language. The query model can just pick up the result. No duplication.
*  Sometimes, the query model implements a "query business rule" that the command model also needs to use. The command model defines a query interface and can thus execute the query. No duplication, but the command model must accept that the result may be "old" (not as in days, but perhaps as in seconds) .
* We also have rules whose results cannot be "published" and don't need to be "queried"; e.g., some sort of calculation or policy decision that needs to be done ad hoc, both on command model state and on state retrieved from the query model. This matches your scenario, Riana, I think. In this case, the command model can define a domain service that neither reads the data from the command model state nor from the query model state, but gets the input passed in (in the form of command model contract types). Then both command model aggregates and the query model can use that service and fill in their own input data. I think this is really the specification pattern approach suggested by Harrison, and we probably should've called those "specifications" rather than "domain services". The main point is that those specifications belong to the command model in our architecture, but they can be reused by the query model.
* And finally, we sometimes just duplicate business rules. especially at the UI layer. For example, our UI validation sometimes mimics what the command model also validates, usually to improve usability in some way. This has the drawback of possible inconsistency between UI and command model, but we need to balance this with cost/technical difficulties.

The specification-like aspect only works when it's okay to have some coupling between the command and the query model. I agree with Harrison that there will be situations where you want to keep a query model more independent from the command model's code base, but in our case, we have a primary query model that's implemented by the same team in the same code base, modified for the same user stories, etc., so that coupling is okay for us.

Best regards,
Fabian

Harrison Brown

unread,
Jan 23, 2021, 6:10:13 AM1/23/21
to DDD/CQRS

I think this is really the specification pattern approach suggested by Harrison

Yes, this is exactly what we’re doing. We also used to domain services for this sort of thing but we found they attracted code and became messy ‘God’ classes — you can imagine TaskValidationService becoming bloated and inscruitible. Individual specification classes encourage/force better naming and become part of the ubiquitous language, eg. TaskIsAssignableToUserSpecification(Task $t, User $u) : bool. (This is the same reason we prefer the command pattern, such as PromoteCustomerToGoldTier in our application layer rather than application services, such as CustomerService::promoteToGold)

In this case I’d try and find a better name than ‘assignable to’ which better articulates the underlying business concept, if you can. If not, you might be able to compose the specification from existing business stable concepts with AND and OR statements, eg:

class TaskIsAssignableToUserSpecification {
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function isSatisfiedBy(Task $task) : bool
    {
        $composed_specification = new AndSpecification(
            new TaskIsAssignedToSpecification($this->user),
            new TaskIsIncompleteSpecification(),
            new TaskIsOlderThanSpecification(Date::now()->minus('5 days'))
        );

        return $composed_specification->isSatisfiedBy($task);
    }
}

We use these specification classes as the point at which we do the dependency inversion Fabian described so clearly. To do so, we pass in dependencies the specification needs to make a decision — eg. new TaskHasFewerCommentsThan(5, $comment_service) — and we can then decide whether the dependency we pass in (the comment service, in that example) is something we have in the command model already or whether we need to define an interface in the command model and implement in a different way, which could be by querying the query model, could be consistent or not, etc.

- GasyTek

unread,
Jan 24, 2021, 12:14:54 PM1/24/21
to DDD/CQRS
Hi Fabian, and thanks for sharing your experience !

I understand your solution to the issue of sharing business logic, I think I'm gonna take this way by using specifications as Harison proposed.

You said that your command model sometimes question your query model in some situations. 
I can't see any cases where the command model have to use the query model to make decision.
For me, the command model normally has every piece of data it needs because it is the "reference", the query model is just a projection of it ..

Riana

Fabian Schmied

unread,
Jan 24, 2021, 2:36:12 PM1/24/21
to ddd...@googlegroups.com
Hi Riana,

> I can't see any cases where the command model have to use the query
> model to make decision.
> For me, the command model normally has every piece of data itneeds
> because it is the "reference", the query model is just a projection of it ..

Well, first, there is a technical reason. Our command model uses an event store (without projections support) as its persistence backend. Even though the command store has all the data, as you say, it's not stored and accessible in a way that would support all kinds of queries. For example, even though there may be a "TaskAdded" event for every task added, the command store's persistence backend cannot answer questions such as "how many tasks have been added in the last 24 hours?" For this, we need the query model. (This will be different for you as you have a relational persistence backend.)

And second, even if it were possible to query the command model's backing data store, we might choose not to do this because the command model is optimized for the "read aggregate data/write aggregate data" use case. Performing queries might require indexes, reorganization of data, or something else not in line with what the command model is optimized for.

As I say, "might". In the end, it's always tradeoffs and a question of (functional and nonfunctional) requirements.

Best regards,
Fabian


--
You received this message because you are subscribed to a topic in the Google Groups "DDD/CQRS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dddcqrs/d7aiKi6ha6g/unsubscribe.
To unsubscribe from this group and all its topics, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/f3557561-581c-44e4-a04f-ede7cc61b6a0n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages