How to answer queries that need data from many services: compose at read time, or keep a CQRS view.
key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral
The query problem
Each service keeps its data private, so a query that spans services cannot use a JOIN.
API composition
An API composer calls every service that owns part of the answer, then merges the results.
When composition is not enough
Some queries need big in-memory joins, or a kind of database the owner does not use.
When composition hurts
API composition is fine for small, centred data, such as an order detail page. It struggles when a screen filters and sorts across services. Take a product list: clothing under $50, in stock.
The event-driven answer is a read model that handles the events of all the services and keeps a denormalized view ready for the query.
A product service can publish one whole document per product, keyed by id, on a compacted topic. The read model needs far less code than handling six partial events. If its data is damaged, or a new search need appears, consume the topic from the start with the same code, adding instances to go faster. There is no downtime and no special migration tool.
Compared with polling services. A read model that polls the services adds load, creates dependencies and widens the staleness window. Reacting to events avoids all three. The cost is that the view is eventually consistent, and it should hold little or no business logic, which stays with the owning service.
From Practical Event-Driven Microservices Architecture (ch. 4 and ch. 9).
CQRS
Command Query Responsibility Segregation: one model changes the data, a separate model answers queries.
A query-only service builds its own view from other services' events.
Choosing a view database
Let the queries choose the store. NoSQL fits many views because they only need simple updates by id.
If the view needs…
Use a…
Examples
Look up a JSON object by id
Document or key-value store
MongoDB, DynamoDB, Redis
Query JSON objects by fields
Document store
MongoDB, DynamoDB
Text search
Search engine
Elasticsearch
Graph queries (fraud)
Graph database
Neo4j
SQL reporting / BI
Relational database
PostgreSQL, MySQL
Keeping the view correct
Events can arrive twice and the view always trails the writes a little.
New views and a real example
Plan how to build a view that did not exist before.
Caching reads
Another way to cheapen queries is to cache. Newman gives three reasons.
Invalidation
Idea
Watch out
Time to live (TTL)
Entry is valid for a set time
Blunt: data can be stale for the whole TTL
Conditional GET (ETag)
Ask for the resource only if changed; 304 if not
Still a round trip, but saves rebuilding it
Notification
Origin sends an event when data changes
Needs a broker. Send heartbeats so silence is noticed
Write-through
Update cache and origin together
Easy only on the server side
Write-behind
Update cache first, origin later
Data loss risk; unclear source of truth. Rare
The golden rule. Do not cache in too many places. Two nested 1-minute TTL caches can serve data up to 2 minutes old while the client believes it is 1. The ideal number of caches is zero; add each one as a deliberate optimization.
From Building Microservices (ch. 13, Caching).
Recap
API composition: call the owners and join in memory. Simple, so try it first.
CQRS: maintain a read-only view from events when composition is too slow or awkward.
Pick the view database from the queries it must answer.
Make view updates idempotent and design the UI for replication lag.
When filters span services, build a read model from event streams (a compacted topic of documents) instead of composing calls.
Cache for speed, scale or robustness, pick an invalidation method (TTL, ETag, notification), and avoid stacking caches.
Quick check
If each of five services is 99.5% available, how available is findOrder() with API composition?Show answer
About 97.5%, because all five (composer plus four providers) must be up: 0.995^5 ≈ 0.975. Cached or partial answers help.
Sales caches album names with a 1-minute TTL, and Catalog caches them server-side with a 1-minute TTL. How stale can a name be in Sales?Show answer
Up to about 2 minutes, because Sales may refill its cache from a Catalog entry that is already nearly 1 minute old. Nested caches add up, so cache in as few places as possible.