--
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/c323c5f7-05c5-4ab8-9d70-90760d22e787n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/740333c6-65c4-4514-872d-978a948daa94n%40googlegroups.com.
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 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 ?
On 19 Jan 2021, at 08:25, - GasyTek <gas...@gmail.com> wrote:
Hi Jorge,
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/4741d2d0-797e-401d-9062-805c3710b9f5n%40googlegroups.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.)
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.)
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/CAPJBY44iCsi4YPR%3DVsbzj4bT8gwW2%2BhNVqhT9tCSpYS8yt0TpQ%40mail.gmail.com.
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.
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.
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.)
Out of interest, what are your actual business rules that determine whether a task is assignable to a given user?
So I'm curious about the implementation of your read model , is it a denormalized database ?
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.
--
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.