Communication Selection
Priority: P1 (HIGH)
Choose the paradigm per hop from the constraint, not from familiarity. One flow may legitimately use three.
Paradigm Table
| Constraint observed | Paradigm | Cost accepted | | --- | --- | --- | | Resource CRUD for broad, unknown clients; HTTP caching wanted | REST | Over/under-fetching; N round trips for aggregates | | Internal service-to-service, low latency, streaming, schema-first | gRPC | Browser needs a proxy; L7 balancers need HTTP/2 awareness | | One client aggregates many sources with client-shaped responses | GraphQL | Query-cost limits and N+1 resolvers become your problem | | Bidirectional real-time (chat, collaboration, gaming) | WebSocket | Stateful connections; LB affinity; reconnect protocol | | Server push only (feeds, progress, notifications) | SSE | One direction; plain HTTP, auto-reconnect built in | | Cross-organization async callback | Webhook | Receiver retries, signature verification, idempotency required |
Sync vs Async per Flow
- Choose synchronous work when the caller needs its result to continue and the invariant requires it; bound it by the end-to-end deadline and define timeout, failure, and fallback behavior.
- Choose asynchronous work when the caller needs eventual completion rather than the result now; return a trackable operation ID or use a defined push contract.
- Set the number of synchronous dependencies from the invariant, dependency budgets, deadline, operational coupling, and failure semantics. A bounded chain of more than two hops can be valid; even one hop can be unacceptable if it breaks those constraints.
Service Discovery
- Start with DNS-based discovery of a load balancer; it is discovery enough for most systems.
- Move to a health-checked registry (server-side discovery) when instances churn faster than DNS TTLs propagate.
- Client-side discovery only when the client must pick the instance (cache affinity, zone-local routing); it couples every client to the registry.
DNS and Edge Routing
- DNS TTL is a failover lever: low TTL enables fast region switch, at the cost of resolver load.
- Geo-DNS routes users to the nearest region; it is routing, not failover - pair it with health checks.
- Do not use DNS for instance-level balancing; resolvers cache and ignore your weights.
Versioning
- Additive changes only on live contracts: new fields optional, old fields never repurposed.
- Breaking change = new version side by side, a deprecation window with usage telemetry, then removal.
- Internal gRPC/proto: reserve removed field numbers; never reuse them.
Anti-Patterns
- No one-paradigm-everywhere: edge REST plus internal gRPC plus async events is normal, not inconsistency.
- No WebSocket for server-push-only: SSE is cheaper and survives proxies better.
- No webhook without signature, retry policy, and idempotency key: all three, or it is not production.
- No GraphQL as a proxy for missing API design: schema sprawl without owners is the same mess with resolvers.
- No sync call to a service that only needs to know: notifications are events.
References
- Communication Selection Detail - per-paradigm failure modes, hop-by-hop guidance, migration notes