API Design
| Protocol | Data Format | Best For | Streaming |
|---|---|---|---|
| REST | JSON, XML | CRUD, public APIs, simple request-response | No |
| GraphQL | JSON (query language) | Complex UIs, multiple data sources, mobile apps | Subscriptions |
| gRPC | Protocol Buffers (binary) | Internal service-to-service, high-perf, real-time | Yes (native) |
REST
Resource-oriented, stateless, uses standard HTTP methods.
GET /orders # List orders
POST /orders # Create order
GET /orders/{id} # Get order detail
PATCH /orders/{id} # Update order
DELETE /orders/{id} # Delete order
GraphQL
Single endpoint, client specifies exactly what data it needs.
query {
order(id: "123") {
id, status, items { product { name } }
}
}
gRPC
Binary protocol, service contracts defined in .proto files, supports streaming.
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order);
rpc ListOrders (ListOrdersRequest) returns (stream Order);
}
Define the API contract before implementation. This forces clarity about what the system does and enables parallel front-end and back-end development. Use OpenAPI (REST), GraphQL SDL, or protobuf as your contract language.
Event-Driven Integration
Instead of one service calling another directly, services emit events and react to events.
┌──────────────┐
│ Order │
│ Service │
└──────┬───────┘
│ order.created
▼
┌──────────────┐
│ Kafka │
│ (Broker) │
└──────┬───────┘
│
├────────────────┬──────────────┐
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌────────────┐
│Inventory │ │Notification│ │ Analytics │
│ Service │ │ Service │ │ Service │
└──────────┘ └────────────┘ └────────────┘
order.created event fans out to multiple independent consumers.Pros: Loose coupling, many consumers, async.
Cons: Eventual consistency, debugging harder, schema evolution.
Use a schema registry (e.g., Confluent Schema Registry) to manage event schema evolution. Without it, producers and consumers silently drift apart, causing runtime failures that are hard to diagnose.
Microservices
When to Decompose
| Indicator | Description |
|---|---|
| Team friction | Teams can’t move without stepping on each other |
| Different scaling needs | Some features need more resources than others |
| Clear bounded contexts | Well-defined domain boundaries exist |
When NOT To
- Team is small (< 10 people)
- Domain is simple CRUD
- You haven’t identified clear service boundaries yet
Extract services only when the pain of the monolith exceeds the pain of distribution. A well-structured monolith with clear module boundaries is easier to maintain, deploy, and reason about than a distributed mess.
Common Pitfalls
| Pitfall | Symptom | Prevention |
|---|---|---|
| Distributed monolith | Tight coupling across services | Clear bounded contexts, async where possible |
| Network chaos | No circuit breakers, retries | Resilience patterns (timeouts, bulkheads) |
| Data inconsistency | Distributed transactions everywhere | Embrace eventual consistency, saga pattern |
| Over-splitting | Any feature touches 5+ services | Start coarse, split based on measured need |
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Auth │ │ Order │ │ Payment │
│ Service │◄──►│ Service │◄──►│ Service │
└──────────┘ └──────────┘ └──────────┘
│
┌─────┴─────┐
│ User │
│ Service │
└───────────┘