使用单一的命令和查询数据库,在清洁考古技术和 DDD 中填入复杂的查询
原标题:Where to imlement complex queries in Clean Architechture and DDD with a single database for commands and queries
I m trying to follow clean architecture principles, where I try to keep implementation details regarding persistence out of Application Core:
The repository interfaces are in Application Core project
The Application Core project does not reference any data access library (ORM)
The Infrastracture project references ORM and implements the repositories
(reference solution here)
However, I struggle with db queries like:
Give me open orders past due with customer details and total prices.
because such queries
contains some business logic (Order is considered Open when ....,. Past due is when....)
often must be optimized for performance (e.g dont fetch order items, but sum of the order items price).
I put OpenOrdersQuery dto in the Application Core layer, but where do I implement the handler for it?
If I implement it in Infrastructure project I m putting some business logic there
I can t implement it in Application Core, since there is no direct data access
问题回答
If I implement it in Infrastructure project I m putting some business logic there
Not really. Your domain model is indeed responsible for Business Logic (from the command side) and if this leaks into Application / Infrastructure layers then that s a smell that should be addressed.
However, you re now looking at the query side of CQRS. Ideally, you d have a database optimised for use-case handling (commands) and a database optimised for the read-side (query). But, in lieu of that, you can use the same database for write and reads. I often do this as first step and then migrate to a read-optimized warehouse when the performance demands it and/or I have the clarity of what queries might be executed to effectively design that read-side database.
Even when using a single database for commands and queries, I will not use ORM for query execution. I will query the database directly with SQL. This avoids the problem you ve encountered where your repository method (designed for commands) are potentially retrieving related data that is not relevant to the query being executed.
So, to summarise:
Command Pipeline
Client creates command
Client sends command to command module, which finds the handler to execute the command
Handler uses Domain Model (aka Command Model) to handle business logic for the command use case and may then add an entity using a repository interface.
The infrastructure repository implementation will add the entity to the unit of work in the ORM.
Query Pipeline
Client creates a Query
Client sends query to query module, which finds the handler to execute the query.
Handler creates SQL statement , opens a connection to database and queries directly. (It does not use your Domain Model for this).
Handler maps results to the required DTO for consumption by the client.
Yes, from one perspective, knowing what an "Open" order is could be considered business logic when there is not a specific boolean value "OrderIsOpen", but it is not command business logic (which should remain in the Domain Model).
At some stage, if you did set up a read database optimised for the queries that your client may execute, then during population of that database (following a command completion) you might consider using an "OrderIsOpen" field in the database to make queries more performant and remove the need for the query handler to know what other properties indicate that an Order is open.
contains some business logic (Order is considered Open when ....,. Past due is when....)
Yes, in this case you can argue that some business logic can leak into DAL/Infrastructure layer. One thing you can do is to make the query more generic/abstract, i.e. provide parameters to query which are expressed not in business terms related but DAL terms (i.e. something like list of statuses, date filters, etc.). I would argue that this approach pairs nicely combined with the specification pattern.
often must be optimized for performance (e.g dont fetch order items, but sum of the order items price).
This, I would argue, should not be a problem at all - you are free to create special entities/DTOs for specific cases and it s up to the Infrastructure/DAL/CQRS layer to map them (in general I m a big proponent of using different types for ORM entities and Domain/Business Logic level entities so you can decouple better storage and your Domain).