I am doing a project with DDD, for the first time. The project is related to car rental and there is something that really confuses me and I can't find an answer to it. I would be more than happy if someone could give me a detailed and clear answer.
I understand that in simple cases such as for example the manager wants to add/update a new/existing vehicle, he ultimately turns to the writing model that writes the data to the DB and in the case that he wants to see the existing vehicles, for example, he turns to the reading model, and so on for every read and write request.
My question is this and I will give a direct example from my project, I receive a request to rent a car with data such as: pick-up and drop-off locations, rental dates, driver's age, etc. and I need to return the available vehicles to the customer on those dates + price offers All of this, according to my understanding, should be calculated from several different aggregates, so I am making a domain service that will calculate the data, the question is where should I return the data to the client in this situation, after all, I will not save the calculation results in the DB just to make another request from the read model, so What do I do in situations where I need an immediate answer from the writing model and it does not need to go through the DB?
In such situations, are objects from the functions in the domain service returned directly to the client? And if so, which entities?
First and foremost, the manager do not turn to any type of model. The manager is a user and do not care about the implementation, they care about the business process, which do not involve technical terms such as "database", "model", "repository" etc.
Read model and write model are abstract terms. Depending on your the implementation of your domain, it may mean
Some architecture choices will influence whether you go with one of those. A reason to go with different tables/databases, can be when you want to use event sourcing or when your domains are very pure (No database/infrastructure related properties in your domain models or when the ORM impedance mismatch is particularly big) and different from your read models.
Another criteria for splitting your read and write models into multiple tables/databases is, when you expect them to scale differently, for example if the car rental company is so big or has so many visitors that you need many servers to serve the queries, while the actual reservations are little, then you can split them too. This allows easy scaling up for the read model and write model separately. Also a very high load on query side won't slow down your business when booking the actual rentals.
Shared tables you use when the domain is simpler and the differences are small enough or you are not very pragmatic/dogmatic about DDD and you don't need even sourcing.
In case of two different databases, you'd have the write model, when persisting the data, have send out messages that are used by the "read model" to create a read-only (or rather: query-able, because by default event sourced models can't be queried at all) model.
So far for the basics.
CQRS itself is more concerned about how the technical implementation of these is. In generally you do this with an in-process request/query framework, such as "MediatR" for .NET where you split the messages into "queries" and "command" where "command handler" contain your write logic or (less common) a read-only repository and an process manager (aka saga).
First it's unclear, why you need the drivers age for the query, as the data is kinda unrelated to the person wanting to rent it.
Anyways, you'd want to create a
AvailableCarsQueryor something like that as a "read model"/queryThe
AvailableCarsQuerydefines what's your query parameters and what is returned the expected return (IRequest<RentableCar[]>). Then implement the handlerHow the concrete query looks, depends on whether you have shared or separate databases/tables. If you find yourself that your write models are to hard to query, i.e. you need to do dozens of calls to get the data or you have to search in memory, consider splitting the persistence into different tables, which are more optimized for query performance.
Then call it
The write side would look similar, instead you'd have a
RentCarCommandinstead and (in general) no return value.Update to address some of the comments
Depends on the calculated data. If the data can be calculated at the time of the writing, you should write it into the read model every time the write model changes. Then you can simply query the read model w/o doing any calculations in your code. This is also a vital part of "optimizing read model for querying".
If it can't be calculated by the time of writing, then you can try to calculate it within the query (if simple enough), i.e. in the select clause of EF Core or have a service or the query handler calculate it, depending on whether or not the calculation logic is reusable in other places too.
Well yea, this kind of stuff sounds like a need for a service. I guess I'd fetch the read model like above with the necessary properties and enrich it within the service (i.e. calculate the prices and conditions there), then return it.