Chapter 7: Advanced CQRS - Separating Databases for Writes and Reads
Of course. Now that we understand Domain Events as a communication mechanism, we will apply it to take the final and most powerful step in the CQRS pattern: physical database separation.
The Scenario 📝
- System: Our e-commerce application has grown successfully, with extremely high traffic.
- Problem: The "lightweight" CQRS pattern (using a single database) is starting to encounter issues:
- Load Contention: The volume of read queries (even though optimized) is still too large, affecting the performance of critical write commands.
- Schema Contention: The read side needs denormalized tables and indexes to run fastest, but adding them to the write database (which is designed to be normalized) will slow down write operations.
The Solution: Physical Database Separation ✅
The Logic: We will separate the system into two distinct data stores.
- Write Database: This remains our production PostgreSQL. Its schema is highly normalized, optimized for writes, and ensures integrity. It only serves Commands.
- Read Database: A completely new database, designed solely for reading. Its schema is radically denormalized. It could be another PostgreSQL instance, or a different technology altogether like Elasticsearch (for search) or Redis (for caching).
The Synchronization Mechanism: This is where Domain Events shine.
- A
Command(e.g.,MarkOrderAsPaid) is executed, changing theOrderAggregate in the Write Database. - The
OrderAggregate raises aDomain Event, for example,OrderPaidEvent. - An
Event Handler(running in the background) listens for this event. - This handler reads the data from the event and then updates or creates a corresponding record in the Read Database. For example, it might update a denormalized
OrderSummariestable.
- A
Analysis of the Result ✨
- Maximum Performance:
- Reads: Read queries are now incredibly fast because they query a "flat" schema designed specifically for them.
- Writes: The write database is completely freed from the burden of read queries, allowing it to process transactions most efficiently.
- Independent Scalability: You can scale these two systems independently. If the application has more readers than writers, you just need to increase the resources for the Read Database without affecting the Write Database.
- Technological Flexibility: You can choose the best technology for each job. PostgreSQL for writes to ensure ACID compliance, and Elasticsearch for reads to get powerful search capabilities.
The Trade-off
- Eventual Consistency: This is the biggest trade-off. There will be a delay (usually very small, a few milliseconds) between the time data is written and when it appears in the Read Database.
- Complexity: This architecture is significantly more complex to build and maintain (requires a message queue, background workers, etc.).
Conclusion of the Series
Our journey has taken us from structuring a single class to designing a complex, distributed system.
- DDD provides you with the tools to model complex business logic cleanly and maintainably on the Write side.
- CQRS is the pattern that liberates the Read side, allowing it to achieve almost limitless performance and scalability.
These are advanced architectural patterns. Start with the basic DDD patterns, apply "lightweight" CQRS when needed, and only move to physical database separation when the performance and scale requirements of your system truly demand it. Thank you for following along!