REST and RPC: The Lie of Location Transparency
For decades, developers have dreamed of making network calls as simple as local function calls. This dream has a name: location transparency. And it's a beautiful lie that has caused countless production outages.
Why This Matters
For decades, developers have dreamed of making network calls as simple as local function calls. Just call userService.getUser(id) and pretend the network doesn't exist. This dream has a name: location transparency. And it's a beautiful lie that has caused countless production outages.
Real-world relevance: Every microservices architecture at Netflix, Uber, or Amazon deals with this reality. Understanding why a network call is fundamentally different from a function call—and how modern frameworks like gRPC handle this—is essential for building resilient distributed systems.
Learning Objectives
- [ ] Understand why services provide encapsulation that databases don't
- [ ] Compare REST and SOAP philosophies—and why REST won
- [ ] Apply the six fundamental problems with RPC to design resilient service calls
- [ ] Evaluate when to use REST vs gRPC based on use case (public API vs internal services)
The Big Picture: Services as Encapsulated APIs
When you connect to a database, you can run arbitrary queries. SELECT * FROM users WHERE salary > 100000—if you have access, you can query anything.
Services are different. They expose a controlled API that enforces business logic:
Diagram Explanation: Databases allow arbitrary queries—powerful but dangerous. Services expose only what you explicitly allow, enforcing business rules and access control. This encapsulation is a key design goal of microservices.
Why Microservices?
The goal is independent deployability:
| Monolith | Microservices | |----------|---------------| | One deployment for everything | Each service deploys independently | | All teams coordinate releases | Teams release on their own schedule | | One schema version | Old and new service versions run simultaneously |
This means the encoding between services must support evolution—exactly what we've been studying!
The Service Chain: Clients All the Way Down
In the web model, there are clients and servers. But it's rarely that simple:
Diagram Explanation: Your web server is a server to browsers, but a client to backend services. Those services are clients to databases. In microservices, it's servers and clients all the way down.
Three Contexts for Web Services
| Context | Example | Network | |---------|---------|---------| | User-facing | Mobile app → API server | Public internet | | Internal services | Payment service → Inventory service | Same datacenter | | Cross-organization | Your backend → Stripe API | Public internet |
Each has different latency expectations, security requirements, and evolution constraints.
REST vs SOAP: The Philosophy War
Two approaches to web services emerged, almost diametrically opposed:
Diagram Explanation: REST embraces HTTP's design (resources, verbs, caching). SOAP tries to abstract HTTP away, adding layers of XML-based standards. REST's simplicity won for most modern APIs, especially public-facing ones.
Why REST Won
| Factor | REST | SOAP |
|--------|------|------|
| Debugging | curl https://api.example.com/users/123 | Need specialized tools |
| Documentation | OpenAPI/Swagger | WSDL (machine-readable, human-hostile) |
| Language support | Every language has HTTP | SOAP libraries vary in quality |
| Ecosystem | Caches, proxies, load balancers "just work" | Custom middleware needed |
REST isn't perfect, but it's good enough and universally supported.
The Six Problems with RPC
Here's where the book delivers a crucial insight. Remote Procedure Call (RPC) tries to make network calls look like local function calls. This "location transparency" sounds great but is fundamentally flawed:
Diagram Explanation: Local function calls are predictable, fast, and use shared memory. Network requests fail unpredictably, have variable latency, and require serialization. Pretending they're the same leads to brittle systems.
The Six Problems in Detail
| # | Problem | Local Call | Network Call | |---|---------|------------|--------------| | 1 | Predictability | Succeeds or fails based on inputs | Network can fail for reasons outside your control | | 2 | Outcomes | Returns result, throws exception, or hangs | Might timeout—you don't know if it worked | | 3 | Retries | Not needed | Retries can execute the action multiple times! | | 4 | Latency | Nanoseconds, consistent | Milliseconds to seconds, wildly variable | | 5 | Data passing | Pass pointers to memory | Must encode everything as bytes | | 6 | Types | Same language types | Cross-language type translation (remember JS's 2⁵³ problem?) |
The Timeout Problem
This deserves special attention. When a network call times out:
Diagram Explanation: The request succeeded, the order was created, but the response was lost. The client doesn't know if the request worked. Retrying might create a duplicate order. This is why idempotence is crucial for network APIs.
Modern RPC: Embracing the Network
Despite RPC's fundamental problems, it hasn't disappeared. Modern frameworks are more honest about being network calls:
Diagram Explanation: Modern RPC frameworks don't hide the network. They use futures for async operations, streams for continuous data, and have built-in handling for failures, timeouts, and retries.
gRPC Features
| Feature | Benefit | |---------|---------| | Streaming | Server can send multiple responses over time | | Futures/Promises | Async by default, compose multiple calls | | Binary encoding | Protocol Buffers = smaller, faster than JSON | | Code generation | Type-safe clients in any language | | HTTP/2 | Multiplexing, header compression |
When to Use What
| Use Case | Recommendation | Why | |----------|----------------|-----| | Public API | REST + JSON | Universal support, debuggable, no client install | | Internal microservices | gRPC | Performance, streaming, type safety | | Browser clients | REST or gRPC-Web | Browsers speak HTTP natively | | Mobile apps | gRPC or REST | gRPC is smaller on the wire |
API Evolution and Versioning
Remember: old and new service versions run simultaneously. How do you evolve APIs without breaking clients?
Diagram Explanation: Servers are typically updated first, then clients. This means servers need backward compatibility (accept old requests) and clients need forward compatibility (handle new response fields).
Compatibility Model for Services
| Direction | What It Means | Where Needed | |-----------|---------------|--------------| | Backward (on requests) | New server accepts old client requests | Always | | Forward (on responses) | Old client ignores new response fields | Always |
This is simpler than databases, where BOTH directions are needed on ALL data!
Versioning Strategies
| Strategy | Example | Trade-offs |
|----------|---------|------------|
| URL versioning | /api/v1/users, /api/v2/users | Clear, but multiple endpoints to maintain |
| Header versioning | Accept: application/vnd.api.v2+json | Cleaner URLs, harder to test in browser |
| API key versioning | Store client's version preference on server | Per-client control, complex management |
For public APIs, you often maintain multiple versions indefinitely—you can't force external clients to upgrade.
Real-World Analogy
Think of the difference between RPC and local calls like the difference between talking to someone in person vs making a phone call:
| Aspect | In-Person (Local) | Phone Call (Network) | |--------|-------------------|---------------------| | Reliability | They hear you or they don't | Call might drop mid-sentence | | Response | Immediate reaction | Might get "can you hear me?" silence | | Retries | "Sorry, say again?" works | Calling back might confuse them | | Latency | Instant | Variable delays, sometimes awkward | | Context | They can see your expressions | You have to describe everything | | Language | Shared understanding | Might need translation |
You wouldn't treat a phone call the same as an in-person conversation. Similarly, don't treat network calls like local function calls.
Practical Example: REST vs gRPC
"""
This example demonstrates REST vs gRPC from DDIA Chapter 4.
Key insight: gRPC makes the network explicit with streaming and futures.
"""
import requests
# ============== REST APPROACH ==============
def get_user_rest(user_id: int) -> dict:
"""
REST API call using requests library.
Simple and universal, but no type safety.
"""
try:
response = requests.get(
f"https://api.example.com/users/{user_id}",
timeout=5.0 # Explicit timeout!
)
response.raise_for_status()
return response.json()
except requests.Timeout:
# Network timeout - did the request work? Unknown!
print("Request timed out - status unknown")
raise
except requests.ConnectionError:
# Network failure - safe to retry
print("Connection failed - will retry")
raise
# Usage
user = get_user_rest(123)
# Output: {'id': 123, 'name': 'Alice', 'email': 'alice@example.com'}Key Takeaways
-
Services provide encapsulation that databases don't: A service exposes only predefined operations with business logic enforced. Databases allow arbitrary queries. This encapsulation enables independent team ownership and deployment.
-
REST embraces HTTP; SOAP abstracts it away: REST uses URLs for resources, HTTP verbs for actions, and JSON for data. Its simplicity and universal tooling support made it the winner for most public APIs.
-
RPC's location transparency is a dangerous lie: Network calls fail unpredictably, have variable latency, can timeout without response, and require serialization. Never treat them like local function calls.
-
Modern RPC frameworks are honest about the network: gRPC, Finagle, and Rest.li use futures for async, streams for continuous data, and have explicit timeout/retry handling. They don't hide the network—they embrace it.
-
Service APIs need long-term compatibility: For public APIs, you may maintain multiple versions indefinitely. Servers update first, then clients. Design for backward compatibility on requests, forward compatibility on responses.
Common Pitfalls
| ❌ Misconception | ✅ Reality | |------------------|-----------| | "Network calls are like local function calls" | Network calls can fail, timeout, duplicate, and have variable latency. They're fundamentally different. | | "If the call times out, it failed" | A timeout means unknown—the request might have succeeded, response lost. Retrying can cause duplicates. | | "REST is slow; use RPC for performance" | gRPC's binary encoding is faster, but REST with JSON is often fast enough. Choose based on ecosystem and use case. | | "SOAP is dead" | It's still used in large enterprises, banking, and government systems with established WS-* infrastructure. | | "Our microservices can evolve freely" | External clients can't be forced to upgrade. Public APIs often maintain multiple versions indefinitely. | | "gRPC works everywhere" | Browser support requires gRPC-Web proxy. For public APIs, REST's universal support often wins. |
What's Next?
We've covered two modes of dataflow: databases (messages to your future self) and services (request-response between processes). The final mode is asynchronous message passing—where a broker sits between sender and receiver.
Next section: Message-Passing Dataflow explores:
- Message brokers (Kafka, RabbitMQ)
- Decoupling producers and consumers
- The actor model for distributed systems
This completes our tour of how encoded data flows through systems!