A good default is to treat these patterns as solutions to specific bottlenecks, not milestones that every successful system eventually reaches.
Many systems serving millions of users never use event sourcing. Plenty of high-scale companies run without CQRS in most services. Conversely, some relatively small systems benefit from CQRS because of their domain complexity rather than their traffic.
Here are some practical rules of thumb.
| Pattern | Usually add when... | Don't add just because... |
|---|
| Database sharding | A single database can't meet storage, throughput, or latency requirements after indexing, caching, read replicas, and tuning | "We might have millions of users someday." |
| CQRS | Reads and writes have fundamentally different models, scaling needs, or teams | You heard it's a microservices best practice |
| Event sourcing | You need a complete audit trail, temporal reconstruction, or event replay as core business requirements | You want an event-driven architecture |
| Microservices | Independent deployment and team autonomy become limiting factors | The application is getting larger |
| Distributed messaging | Synchronous calls create coupling, reliability, or latency problems | Every service should communicate asynchronously |
My progression usually looks like this
Stage 1: Simple monolith
- One application
- One relational database
- Good schema design
- Proper indexes
- Background jobs
- Redis for caching if needed
This gets surprisingly far—often into the hundreds of thousands or even millions of users, depending on the workload.
Stage 2: Optimize before redesign
Before introducing new architecture:
- Add indexes
- Eliminate N+1 queries
- Cache expensive reads
- Use a CDN
- Add read replicas
- Profile the slow paths
- Batch operations
Many "scaling problems" disappear here.
Stage 3: Split by clear boundaries
If the codebase becomes difficult to evolve:
- Extract a search service
- Extract authentication
- Extract media processing
- Extract notifications
Notice these are natural boundaries, not arbitrary ones.
Stage 4: Introduce messaging
Use queues or event buses when you need:
- retries
- asynchronous work
- loose coupling
- long-running workflows
Examples:
- email
- image processing
- payment notifications
- analytics
- search indexing
Stage 5: CQRS (only where valuable)
CQRS shines when the read model differs dramatically from the write model.
Example:
Writes:
- Place order
- Cancel order
- Refund order
Reads:
- Customer dashboard
- Sales analytics
- Warehouse picking lists
- Executive reports
Trying to satisfy all of these with one normalized schema often becomes awkward.
Notice you don't need CQRS everywhere—just for the bounded context where it helps.
Stage 6: Sharding
This is usually much later than people expect.
Before sharding, try:
- larger database instances
- partitioning
- read replicas
- caching
- query optimization
- archival of old data
Sharding adds complexity:
- distributed transactions
- cross-shard queries
- rebalancing
- operational overhead
It's often the last major database scaling step.
Stage 7: Event sourcing
This is the most specialized pattern.
It's excellent when the event history is the business record.
Examples:
- financial ledgers
- trading
- inventory movement
- insurance
- workflow systems
- collaborative editing
It's usually unnecessary for:
- CRUD applications
- blogs
- e-commerce catalogs
- internal dashboards
- CMS systems
Many systems can still publish domain events without making events the source of truth.
A useful decision framework
Before adding a major architectural pattern, ask:
- What concrete problem are we solving?
- Can we measure that problem?
- Is there a simpler solution?
- What's the operational cost of this pattern?
- Can we introduce it incrementally?
- Could we remove it later if needed?
If you can't answer the first question with something measurable ("our primary database CPU is consistently above 80% under peak load," or "reporting queries are degrading transactional latency"), it's often a sign the change is premature.
One principle I've found consistently valuable
Optimize for the next order of magnitude, not the next three.
If you're serving 10,000 requests/day, design so reaching 100,000 is straightforward. When you approach 100,000, evolve toward handling 1 million. Building for 100 million requests/day from the outset often introduces complexity that slows development long before scale becomes the limiting factor.
The common trajectory of successful systems is often:
Simple architecture → optimize → measure → identify the actual bottleneck → apply the smallest architectural change that addresses it.
That approach keeps systems understandable while leaving room to evolve as real requirements emerge, rather than anticipated ones.