Short answer
You don't do cross-service joins or reach into another service's database — that recreates the monolith's coupling without its transaction guarantees.
Explanation
Either compose at the edge (call each service and join in a BFF, accepting latency) or, for hot read paths, build a denormalized read model/materialized view populated asynchronously from the services' events, accepting eventual consistency. Choose ownership deliberately: the data lives with the service that's the source of truth, and everyone else gets a replicated, read-optimized projection.
Common mistakes
- Joining across services' databases directly.
- Not considering API composition, CQRS or a read model.