8.0 KiB
CQRS does NOT change service boundaries or any other implementation or approach. You will be doing CQRS inside of a service boundary. If you have a business component, CQRS will happen entirely within a BC. History
Why did it come about? Multi-user collaborative systems. Users end up seeing stale data. The problem comes when the users start using their stale data to make decisions about how to change data. In a collaborative environment, we might end up overwriting each other's changes. Collaboration is one situation that traditional architectures didn't handle really well. The assumption was optimistic concurrency. A big focus of CQRS is the data itself and it's accuracy, along with performance. How quickly can we keep the users up-to-date? Collaborative Data
How much data in an average system is collaborative? Not usually too much. On an Amazon page for a book you might have:
-
Inventory (multiple users update stock numbers by buying books, shipments increasing stock)
-
Ratings (multiple users submitting ratings) In a traditional layered system, you might cache data to query against and let updates/adds/deletes go all the way through to the DB. This is kinda of CQRS. But, in the end, you don't solve the contention at the database when two updates come at once or very close proximity.
-
For a lot of data, it just fits much better to think of a single updater. Think of the price for a book, or it's title. You most likely will have one product manager updating those kinds of data one at a time. Whatever the simple thing is to do, do that! CQRS Theory
Be up-front about data staleness
It's important to be up-front about the staleness of your data. A bank might say "your balance is accurate as of 10 minutes ago". Many systems are not up-front like that with how stale data is. Keep queries simple
UI -> persistent view model, query only between them.
For each view in the UI, have a view/table in the DB (select * from my table where id = @id)
KEEP IT SIMPLE Be careful of data duplication. Another signal of service boundaries being off.
Duplication => logical structure that has a great deal of overlap with another logical structure
Replication => moving data from one physical place to another physical place. Moving data, same data in two different places, but part of the same logical structure.
Data duplication can be ok, but only with super, super stable business concepts. If you find one, you should be suspect of it. Deployment and Security -
Deploy persistent view model DB to the web tier (only SELECT is permitted)
-
- Don't have to go through firewall - faster
- Don't use the term DB, use the word cache
- Document DB is a good choice because you usually don't need any relationships
- If you find yourself adding a cache on top of persistent view model, you're probably making it too complex
-
Role based security
-
- Roles probably map to service boundaries, so the data for a particular role probably comes from a different table
- This will simplify role-based security
-
Just as secure as in-memory caches, if not more so
Use for preliminary validation
-
When submitting commands, we need to perform validation
-
Because the persistent view model is close, it's fast for validation
-
Uniqueness: we can do a quick check in the PVM to check if a username is already taken. This wouldn't work in the rare case of a race condition: different users signing up at the same time with the same username.
-
Related entity existence: address validation, existence of street name
-
-> results in few commands being rejected As engineers we are often looking for the single solution that will solve all of our problems. That never happens. In solving scalability issues, we can usually follow the 80-20 rule: a solution that solves 80% of the problem. Commands
Validation and business rules are often used interchangeably. What we mean with validation is: -
is the input potentially good?
-
structured correctly?
-
ranges, lengths, etc? Business rules:
-
should we do this?
-
based on current system state When you have if statements in your business logic that is operating on data in your database, you can still end up with data that is invalid. For example, if someone adds a book to their shopping cart and then 2 minutes later that product is marked to no longer be sold, then the item is still in the shopping cart. Even though the business rule said we can't add products we don't sell anymore to shopping carts, the shopping cart addition action passed before the flag was set. Business rules are foiled by race conditions. Should we do what the user asked?
We need the system to remain consistent, so we need to define transaction boundaries. Sometimes these transaction boundaries are hard to find. For a grid of orders that allows in-place editing, do we have a transaction for each field edited, for each row edited or for the entire edited data set as a whole? We are not capturing user-intent very well. In a traditional UI, the checkbox doesn't capture what the user wants to do. When a user checks three boxes to pick three seats next to each other at a movie theater, we are not capturing the fact that all the user wants is three seats next to each other, maybe in a certain general section. Solution: look at user intent you want to capture and model that directly. Instead of allowing a user to select the exact seats in the exact section, we gather the intent of the user to have 3 seats in a certain section and we let the system pick the best fit for the request. This then also allows the request to be asynchronous. We can tell the user the seats they got and allow them to cancel if they don't like the choice. CQRS is more of an analysis tool that can reveal this type of thing instead of just a magic tool that will solve problems. There is no longer a need to show an actual status of seating. So, the query model is simply a static image of the stadium and it's sections. The command model is then the revised version of capturing user intent, letting the system find the seats and then notifying the user about it. Do we need to preserve this idea of first-come, first serve? When it comes to collaborative systems online, the great thing is that no one knows who was first in line. What is a good command?
You want to be able to structure your commands to be able to do something like: -
"Thank you. Your confirmation email is on its way."
-
Just fake it in the UI (amazon shopping cart example) You need to think about how to design away the technical contention. When you're in a collaborative environment, you will see more command-centric types of concepts that capture user intent. When not in a collaborative setting, you will see more "update entity x in such a way" level and you know you're doing CRUD. When in that situation, stick with synchronous, UI to DB setups and scale out the DB. How to use CQRS
CQRS is too much complexity to apply to an ENTIRE system. It should be applied to only the components that NEED it. CRUD operations outside of a collaborative environment simply don't need the complexity that CQRS requires and, in fact, that complexity will harm the system. In a collaborative domain, you expect high contention. This might change your data model and persistence model. The types of systems were are creating today do not play well with abstractions. So, even though we are so good at abstracting as we're designing code these days, it fails when we come to try to apply it to enterprise software. We need, rather, to find the simplist solution that solves the actual problem. We shouldn't try, for example, to build a document database on top of a relational database. We shouldn't try to build a relational database in a document DB, etc. Start with data-level modeling!! CQRS is a set of questions you ask that drive the design rather than be a set of answers itself. It's an analysis methodology more than a tool to find a solution.