Hello! Welcome back to our final module, System Design & Production Readiness.
In our last lesson, we took the first and most critical step in system design: decomposing a complex system into microservices using Domain-Driven Design. We saw how identifying bounded contexts—like Trip Management, Driver Management, and Payment for a ride-sharing app—gives us a set of cohesive, loosely coupled service candidates.
Now that we have our services, the next logical question an interviewer will ask is, "How do they talk to each other?" This brings us to today's topic.
Today, you will learn to design API contracts and choose appropriate communication patterns for key inter-service interactions. This is where we define the "seams" between our services, specifying not just what they do, but how they interact in a reliable, scalable, and evolvable way. This skill is fundamental to demonstrating senior-level architectural thinking.
1. The API as a Contract
An API (Application Programming Interface) is more than just code; it's a formal contract between a service and its consumers. This contract defines the available operations, the structure of requests and responses, and the expected behavior.
In a microservices architecture, we typically deal with two distinct types of APIs:
- Public APIs: These are exposed to external clients like web browsers or mobile apps. They must prioritize broad compatibility, ease of use, and strong security.
- Backend APIs: These are used for internal, service-to-service communication. Here, raw performance, efficiency, and low latency are often more critical than universal compatibility.
API design - Azure Architecture Center
The Microsoft Azure Architecture Center provides a concise overview of this distinction and the key factors to consider when designing APIs in a microservices context.
Please read the introduction and the 'Considerations' section. Focus on the distinction between public and back-end APIs and the trade-offs mentioned, such as REST vs. RPC, efficiency, and serialization formats.
The article mentions several communication styles, but for a synchronous, request-response model, three technologies dominate the conversation in system design interviews: REST, GraphQL, and gRPC. Let's dive into each one.
2. Choosing Your Communication Protocol
When designing inter-service communication, you're not just picking a technology; you're making a series of trade-offs. The choice of protocol impacts performance, developer experience, and how your system can evolve.
The following video provides an excellent, interview-focused overview of REST, GraphQL, and gRPC. It explains what they are, when to use them, and what to watch out for.
API Design in System Design Interviews w/ Meta Staff Engineer
This video from 'Hello Interview' is presented by a former Meta staff engineer and is tailored specifically for system design interviews. It will serve as our foundation for understanding the three main synchronous API protocols.
Watch the following segments: Introduction to Protocols (02:34 - 03:59): Get an overview of REST, GraphQL, and RPC and their primary use cases. Deep Dive on REST (03:59 - 12:13): This is critical. Pay close attention to resources (plural nouns), HTTP verbs, idempotency, path vs. query parameters, and response codes. This is your default choice for many APIs. Introduction to GraphQL (12:13 - 15:43): Understand the problem GraphQL solves (over/under-fetching) and how its query language works. Introduction to RPC/gRPC (17:20 - 19:36): Focus on why gRPC is so fast and why it's a great choice for internal service-to-service communication.
Let's consolidate and expand on what you've just seen.
2.1. REST: The Universal Standard
REST is the de facto standard for public-facing APIs. Its principles are built on the foundation of the web itself: HTTP.
- Style: Architectural Style (not a strict protocol).
- Transport: HTTP/1.1 (typically).
- Payload: JSON (human-readable text).
- Contract: Defined using OpenAPI (formerly Swagger).
Key Principles:
- Resource-Oriented: Everything is a resource, identified by a URL (e.g.,
/drivers,/trips/{tripId}). Resources should be nouns, not verbs. - Standard Verbs: Use HTTP methods to express actions:
GET: Retrieve a resource. Idempotent.POST: Create a new resource. Not idempotent.PUT: Replace an existing resource. Idempotent.PATCH: Partially update a resource. Not typically idempotent.DELETE: Delete a resource. Idempotent.
- Statelessness: Each request from a client contains all the information needed to process it.
When to use REST:
- Public-facing APIs: Its ubiquity means any developer or client can easily consume it.
- Simple CRUD-oriented services: When you're primarily creating, reading, updating, and deleting resources.
- When you need to support browser clients directly.
2.2. gRPC: High-Performance Internal Communication
gRPC is a modern framework for Remote Procedure Calls (RPC) developed by Google. It's designed for speed and efficiency.

- Style: Remote Procedure Call (RPC). You call methods on a remote service as if they were local.
- Transport: HTTP/2, enabling multiplexing and header compression.
- Payload: Protocol Buffers (Protobuf), a compact, efficient binary format.
- Contract: Defined in
.protofiles, which are strongly typed and can be used to auto-generate client and server code in multiple languages.
When to use gRPC:
- Internal service-to-service communication: This is its primary sweet spot. The performance gains are significant in a microservices environment.
- Low-latency, high-throughput systems: Financial trading, real-time analytics, etc.
- Polyglot environments: When services are written in different languages, the
.protocontract ensures they can communicate seamlessly.
2.3. GraphQL: Flexible and Client-Driven
GraphQL is a query language for APIs, developed by Facebook to address the needs of complex UIs and mobile applications.
- Style: Query Language.
- Transport: HTTP/1.1 (typically).
- Payload: JSON.
- Contract: Defined using a GraphQL Schema Definition Language (SDL).
Key Differentiator: Instead of multiple endpoints that return fixed data structures (like REST), GraphQL exposes a single endpoint (e.g., /graphql). The client sends a query specifying exactly the data fields it needs, and the server returns a JSON object with precisely that data—no more, no less.
When to use GraphQL:
- Mobile applications: Helps minimize network usage by fetching only necessary data.
- Complex UIs with multiple components: A single GraphQL query can aggregate data from multiple backend sources, replacing many REST calls.
- When your API consumers evolve rapidly: Frontend teams can change their data requirements without needing backend API changes.
Test your understanding!
You are designing a system with three services: Product Catalog, Shopping Cart, and Order Service. A mobile client needs to display a user's shopping cart, which includes product names, images, and prices.
Which communication pattern is most appropriate for the interaction between the mobile client and the backend? Which pattern is best for the internal communication when the Shopping Cart service needs product details from the Product Catalog service? Justify your choices.
Show answer
-
Mobile Client to Backend: GraphQL is an excellent choice here. The mobile client can send a single query to fetch all the necessary data for the shopping cart view (cart items, product details, prices). This avoids the "N+1" problem of fetching the cart and then making separate REST calls for each product's details. It also saves bandwidth, which is critical on mobile networks. REST could also work, but would likely require a dedicated
Backend-for-Frontend (BFF)service to aggregate the data efficiently. -
Internal (
Shopping CarttoProduct Catalog): gRPC is the ideal choice for this internal, service-to-service communication. TheShopping Cartservice needs to fetch product information from theProduct Catalog. Using gRPC provides a low-latency, high-performance connection. The strongly-typed contract defined in a.protofile ensures reliability and makes it easy to maintain the API between the two teams developing these services. REST could be used, but you'd be sacrificing performance for no real gain in this internal context.
3. API Design Best Practices and Pitfalls
Defining a good API contract goes beyond just choosing a protocol. It's about creating an interface that is clear, robust, and can evolve gracefully. This is where you can really demonstrate seniority in an interview.
Top 10 Rest API Design Pitfalls by Victor Rentea @ Spring I/O 2025
Let's now watch some clips from a fantastic talk by Victor Rentea on common API design pitfalls. This will give you a deeper, more practical understanding of what makes a 'production-ready' API.
Please watch these specific segments, as they cover crucial design considerations: Backward Compatibility & Versioning (01:17 - 07:41): Understand what a 'breaking change' is and why you must avoid it. Pay attention to the discussion around API versioning (V1, V2). DTOs vs. Domain Models (23:06 - 29:00): This is a critical point. He explains why you should never expose your internal domain/JPA entities directly in your API and the importance of using Data Transfer Objects (DTOs). Action-Oriented APIs (38:32 - 46:32): This is an advanced concept. Understand the limitations of pure CRUD and the value of creating action-oriented endpoints (e.g., POST /orders/{id}/cancel) that model business intent. Error Handling (48:27 - 50:20): Learn the best practice of returning all validation errors at once, rather than one by one.
Key Design Principles to Remember:
- Separate Contract from Implementation (DTOs): Always use Data Transfer Objects (DTOs) for your API requests and responses. Never expose your internal JPA entities or domain models. This allows you to evolve your internal logic and database schema without breaking the public API contract.
- Plan for Evolution (Versioning): Breaking changes are inevitable. Plan for them by versioning your API (e.g.,
/api/v1/users,/api/v2/users). This allows you to introduce new versions while giving clients time to migrate from older ones. - Handle Partial Data (Pagination): For any endpoint that can return a large list of items (
GET /orders), you must implement pagination. The two main strategies are offset-based (?page=2&size=25) and cursor-based (?cursor=...&limit=25). Cursor-based is more robust for data sets that change frequently. - Design for User Intent (Beyond CRUD): Don't just think in terms of
Create,Read,Update,Delete. Model specific business actions. Instead of a genericPUT /orders/{id}that does everything, consider more explicit endpoints likePOST /orders/{id}/submit,POST /orders/{id}/cancel, orPUT /orders/{id}/shipping-address. This makes your API more meaningful and your server-side logic cleaner. - Be Explicit about Authentication: For endpoints that require a user to be logged in (e.g., creating a tweet), the user's identity should come from a secure token in the request header (like a JWT), not from a
userIdfield in the request body. This prevents one user from performing actions on behalf of another.
4. The Hybrid Approach: Best of Both Worlds
In many modern systems, you don't have to choose just one protocol. A highly effective and common pattern is to use different protocols for different purposes.
The API Gateway Pattern:
- External/Public API: Use REST or GraphQL at the edge. An API Gateway receives requests from clients (browsers, mobile apps).
- Internal/Backend API: Use gRPC for communication between the API Gateway and the internal microservices, and for communication between the microservices themselves.

This hybrid approach gives you the best of both worlds: the universal accessibility of REST for external clients and the high performance of gRPC for your internal network.
REST or gRPC? A Guide to Efficient API Design
This article provides a great summary comparing REST and gRPC and explicitly describes the hybrid gateway pattern. It's a good way to solidify your understanding of when to use which.
Read the following sections: Choosing Your Champion: This gives clear use cases for when REST or gRPC is the better choice. Best of Both Worlds with REST and gRPC: Focus on the 'Gateway Pattern: REST Outside, gRPC Inside'. Making the Right Choice: A Decision Framework: Review the decision matrix. This is a great mental model for interviews.
5. Conclusion and Interview Preparation
Today, you've learned how to bridge the services you identified in the last lesson by defining their API contracts and choosing the right communication patterns.
Key Takeaways:
- APIs are Contracts: Distinguish between public-facing APIs (favoring REST/GraphQL for compatibility) and internal APIs (favoring gRPC for performance).
- Know Your Protocols: Understand the core trade-offs between REST (universal, text-based), gRPC (performant, binary, RPC), and GraphQL (flexible, client-driven).
- Embrace the Hybrid Model: The pattern of using REST/GraphQL at the API Gateway and gRPC for internal services is a powerful and common solution in modern architectures.
- Design for Evolution: Use DTOs, versioning, and action-oriented endpoints to create robust and maintainable APIs.
Senior Engineer Interview Question
"Continuing with the ride-sharing platform we discussed, let's focus on the Trip Service, Driver Service, and Payment Service.
- Design the REST API contract for creating a new trip. Specify the endpoint, HTTP method, a sample request body, and expected success/error responses.
- When a trip is completed, the
Trip Serviceneeds to tell thePayment Serviceto process the payment. Which communication protocol would you choose for this interaction and why? - The mobile app needs a screen showing a user's trip history, including the route map, driver's name, and final fare for each trip. This data comes from the
Trip Service,Driver Service, andPayment Service. How would you design the API to efficiently provide this data to the mobile app?"
Show detailed answer
"This is a great question that touches on several key design trade-offs. Here's how I would approach it.
1. REST API for Creating a Trip
I would choose REST for this public-facing API because it will be called by our mobile clients, and REST's ubiquity makes it easy to work with.
-
Endpoint:
POST /v1/trips -
Method:
POSTis appropriate because we are creating a new resource, and this operation is not idempotent. Sending the same request twice should create two separate trip requests. -
Sample Request Body (DTO):
{ "pickup_location": { "latitude": 34.0522, "longitude": -118.2437 }, "destination_location": { "latitude": 34.0112, "longitude": -118.4927 }, "vehicle_type": "STANDARD" }Note that the
rider_idis not in the body. It would be extracted from the authenticated user's JWT in theAuthorizationheader. -
Responses:
- Success (202 Accepted): Creating a trip involves finding a driver, which is an asynchronous process. So, I would immediately return a
202 Acceptedstatus with aLocationheader pointing to the newly created trip resource (e.g.,Location: /v1/trips/trip-uuid-123). The response body could contain the initial state of the trip. The client can then poll this resource or listen on a WebSocket for updates (e.g.,DRIVER_ASSIGNED). - Client Error (400 Bad Request): If the request is malformed (e.g., missing location), I'd return a
400with a detailed error body listing all validation failures. - Authentication Error (401 Unauthorized): If the JWT is missing or invalid.
- Success (202 Accepted): Creating a trip involves finding a driver, which is an asynchronous process. So, I would immediately return a
2. Internal Communication: Trip Service to Payment Service
For this internal, service-to-service communication, I would choose gRPC.
- Why gRPC?
- Performance: This is a high-volume internal call. gRPC's use of HTTP/2 and binary Protocol Buffers offers significantly lower latency and smaller payload sizes than a JSON/HTTP-based REST call.
- Strongly-Typed Contract: The interaction can be rigidly defined in a
.protofile (e.g., aChargeUserForTripmethod). This generates client and server code, reducing boilerplate and catching integration errors at compile time, which is crucial for reliability between two critical services. - Efficiency: In a microservices environment with potentially thousands of these calls per minute, the efficiency gains from gRPC are substantial.
The Trip Service would use a gRPC client to call a method like paymentService.chargeUserForTrip({ tripId: "...", amount: "..." }).
3. API for Trip History Screen
For this requirement, I would strongly advocate for GraphQL.
- Why GraphQL?
- Avoids Over-fetching and Under-fetching: The trip history screen needs a specific combination of data from three different services (
Trip,Driver,Payment). A traditional REST approach would require either:
a) The mobile app making three separate calls (one to each service's REST endpoint), which is inefficient and slow.
b) A dedicated BFF (Backend-for-Frontend) REST endpoint likeGET /v1/trip-history-detailsthat aggregates the data. This works, but you end up creating many such custom endpoints. - Client-driven Flexibility: GraphQL provides a single endpoint where the mobile client can specify exactly the fields it needs:
query GetTripHistory { tripHistory { id routeMapUrl finalFare { amount currency } driver { name } } } - Single Round Trip: The client gets all the data it needs in a single network request, which is ideal for mobile performance. The GraphQL server would be responsible for internally calling the
Trip,Driver, andPaymentservices (likely via gRPC) to resolve this data. This approach puts the power in the hands of the client developers, allowing them to evolve the UI without requiring new backend deployments."
- Avoids Over-fetching and Under-fetching: The trip history screen needs a specific combination of data from three different services (
Fantastic work today. We've defined how our services will communicate. The next piece of the puzzle is how they manage their data. In our next lesson, we will select and justify data management strategies (e.g., CQRS, event sourcing) for different services within the system, which will lead us into a discussion of eventual consistency and distributed transactions.
Can't find a good explanation? Sign up and we'll make it for you
Sign up