A new platform must serve a public partner API, three internal front-ends with different data needs, and high-volume service-to-service traffic. Choose the API styles and defend the choice.
Show the full answer Hide the answer
Resist "pick one"
These are three different problems with three different consumers. Standardising on one style optimises for architectural tidiness at the expense of every consumer.
Public partner API — REST with OpenAPI
Universally understood, works with every HTTP client, cacheable, and debuggable by people you cannot help. Third-party developers should not have to learn your preferences.
Design points: shallow resource hierarchies (two levels of nesting is usually enough — deep nesting
couples URLs to a structure that will change), correct method semantics (PATCH is not idempotent,
which surprises people, so partial updates need idempotency keys), cursor pagination rather than
offset, Problem Details (RFC 9457) error bodies with a stable type URI clients can branch on, and
idempotency keys on all creation endpoints.
Internal front-ends — GraphQL, or a BFG per client
Three clients with different data needs is the case GraphQL was designed for: each fetches exactly what it renders, in one request, without a REST endpoint per screen or per-client backends.
Non-negotiable prerequisites, not later optimisations: DataLoader-style batching (without it, 100 orders each resolving a customer is 101 queries and the service will not survive production), and persisted queries — which simultaneously bound query cost, shrink the payload to a hash, and restore GET-based CDN caching that GraphQL-over-POST loses.
If the three clients turn out to need similar data, a BFF per client is simpler and worth considering — GraphQL brings real operational complexity.
Service-to-service — gRPC
Binary Protobuf over HTTP/2: much smaller payloads, much faster parsing, generated clients in every language, and streaming with flow control.
The schema is the contract, and diffing it for breaking changes in CI is what makes the three Protobuf rules enforceable: never reuse a field number, never change a type, add rather than modify.
Use the standard status codes precisely — the UNAVAILABLE versus FAILED_PRECONDITION distinction is what lets generic retry middleware behave correctly without knowing the service.
Where events belong
Anything asynchronous or fan-out — an order placed, a payment settled — should be an event, not a call. Deciding this alongside the API styles prevents synchronous chains from forming by default.
What a strong answer adds
Naming the cost of three styles: three sets of tooling, documentation, authentication integration and observability. Justified here because the consumers are genuinely different; not justified for a smaller estate, where the answer is REST plus events.