%% fig-alt: "API versioning strategies comparison showing URL versioning, header versioning, and query parameter versioning with their trade-offs for IoT service evolution"
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D', 'fontSize': '14px'}}}%%
graph LR
subgraph url["URL Versioning"]
U1["/api/v1/devices"]
U2["/api/v2/devices"]
end
subgraph header["Header Versioning"]
H1["GET /devices<br/>Accept: application/vnd.iot.v1+json"]
H2["GET /devices<br/>Accept: application/vnd.iot.v2+json"]
end
subgraph query["Query Parameter"]
Q1["/devices?version=1"]
Q2["/devices?version=2"]
end
style U1 fill:#16A085,stroke:#2C3E50,color:#fff
style U2 fill:#16A085,stroke:#2C3E50,color:#fff
style H1 fill:#E67E22,stroke:#2C3E50,color:#fff
style H2 fill:#E67E22,stroke:#2C3E50,color:#fff
style Q1 fill:#7F8C8D,stroke:#2C3E50,color:#fff
style Q2 fill:#7F8C8D,stroke:#2C3E50,color:#fff
311 SOA API Design and Service Discovery
311.1 Learning Objectives
By the end of this chapter, you will be able to:
- Design Resilient APIs: Implement versioning strategies, rate limiting, and backward compatibility for IoT service interfaces
- Implement Service Discovery: Configure dynamic service registration and discovery for scalable IoT deployments
- Choose Discovery Patterns: Select between client-side and server-side discovery based on deployment requirements
311.2 Prerequisites
Before diving into this chapter, you should be familiar with:
- SOA and Microservices Fundamentals: Understanding SOA vs microservices and service decomposition strategies
- Cloud Computing for IoT: Understanding cloud service models provides context for where services run
- MQTT Fundamentals: MQTT is commonly used alongside REST APIs in IoT systems
APIs are like restaurant menus - they tell you what you can order and how to ask for it!
311.2.2 Key Words for Kids
| Word | What It Means |
|---|---|
| API | A menu that tells computers how to ask for things |
| Version | Like “Menu v1” and “Menu v2” - newer versions with more options |
| Service Discovery | Finding which kitchen is open and ready to cook |
311.3 API Design and Versioning
APIs are the contracts between services. Poor API design creates tight coupling and painful migrations.
311.3.1 RESTful API Design for IoT
Resource-Oriented Design:
# Good: Resource-oriented
GET /devices # List devices
GET /devices/{id} # Get specific device
POST /devices # Create device
PUT /devices/{id} # Update device
DELETE /devices/{id} # Delete device
GET /devices/{id}/telemetry # Get device telemetry
POST /devices/{id}/commands # Send command to device
# Bad: RPC-style
POST /getDevices
POST /createDevice
POST /updateDevice
POST /deleteDevice
POST /getDeviceTelemetry
POST /sendDeviceCommand
IoT-Specific Considerations:
| Concern | REST Best Practice | IoT Adaptation |
|---|---|---|
| Large payloads | Pagination | Streaming for time-series |
| Real-time updates | Polling | WebSocket/SSE subscriptions |
| Batch operations | Multiple calls | Bulk endpoints |
| Binary data | Base64 encoding | Protobuf/CBOR |
311.3.2 API Versioning Strategies
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL Path | /v1/devices |
Clear, cacheable, easy routing | URL pollution, version in every link |
| Header | Accept: application/vnd.iot.v1+json |
Clean URLs | Hidden, harder to test |
| Query Param | /devices?version=1 |
Easy to add | Can break caching, looks hacky |
Recommendation for IoT: URL path versioning (/v1/, /v2/) is most practical. IoT devices often have limited HTTP customization and need simple, predictable endpoints.
311.3.3 Backward Compatibility Rules
Safe Changes (Non-Breaking): - Adding new optional fields - Adding new endpoints - Adding new optional query parameters - Making required fields optional
Breaking Changes (Require New Version): - Removing fields - Renaming fields - Changing field types - Changing URL structure - Making optional fields required
311.4 Service Discovery
In dynamic environments, services need to find each other without hardcoded addresses.
311.4.1 The Service Discovery Problem
%% fig-alt: "Service discovery problem showing client needing to find service instances that scale dynamically with IP addresses changing as instances are added or removed"
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D', 'fontSize': '14px'}}}%%
sequenceDiagram
participant C as Client
participant LB as Load Balancer
participant R as Service Registry
participant S1 as Instance 1
participant S2 as Instance 2
participant S3 as Instance 3
Note over S1,S3: Services register on startup
S1->>R: Register (ip: 10.0.1.10)
S2->>R: Register (ip: 10.0.1.11)
S3->>R: Register (ip: 10.0.1.12)
Note over C,R: Client discovers services
C->>R: Where is Telemetry Service?
R->>C: [10.0.1.10, 10.0.1.11, 10.0.1.12]
Note over C,S3: Client calls service
C->>S2: GET /telemetry (load balanced)
S2->>C: Response
311.4.2 Discovery Patterns
1. Client-Side Discovery
The client queries the registry and chooses an instance:
# Client-side discovery example
import consul
def get_telemetry_service():
c = consul.Consul()
services = c.catalog.service('telemetry-service')[1]
# Client chooses instance (round-robin, random, etc.)
instance = random.choice(services)
return f"http://{instance['Address']}:{instance['ServicePort']}"2. Server-Side Discovery
A load balancer handles discovery:
%% fig-alt: "Server-side service discovery pattern showing client calling load balancer which queries registry and routes to healthy service instance abstracting discovery from client"
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D', 'fontSize': '14px'}}}%%
graph LR
C[Client] --> LB[Load Balancer<br/>nginx/HAProxy]
LB --> R[Service Registry<br/>Consul/etcd]
LB --> S1[Instance 1]
LB --> S2[Instance 2]
LB --> S3[Instance 3]
R -.->|Health updates| LB
style C fill:#2C3E50,stroke:#16A085,color:#fff
style LB fill:#E67E22,stroke:#2C3E50,color:#fff
style R fill:#7F8C8D,stroke:#2C3E50,color:#fff
style S1 fill:#16A085,stroke:#2C3E50,color:#fff
style S2 fill:#16A085,stroke:#2C3E50,color:#fff
style S3 fill:#16A085,stroke:#2C3E50,color:#fff
311.4.3 Service Registry Options
| Registry | Protocol | Strengths | IoT Use Case |
|---|---|---|---|
| Consul | HTTP/DNS | Health checks, KV store | Multi-datacenter IoT |
| etcd | gRPC | Strong consistency | Kubernetes-native |
| Eureka | HTTP | Netflix ecosystem | Spring Boot IoT |
| Kubernetes DNS | DNS | Built into K8s | Container-native |
311.5 Summary
This chapter covered API design and service discovery for IoT platforms:
- RESTful API Design: Use resource-oriented design with consistent naming and HTTP methods
- API Versioning: URL path versioning (
/v1/,/v2/) is most practical for IoT with limited HTTP customization - Backward Compatibility: Add fields freely, remove fields only in new major versions
- Service Discovery: Client-side (Consul) for multi-region, server-side (K8s DNS) for single-cluster
In one sentence: Well-designed APIs with clear versioning strategies and dynamic service discovery enable IoT platforms to evolve without breaking deployed devices.
Remember this rule: Adding new optional fields is always backward compatible; removing or renaming fields requires a new API version.
311.6 What’s Next?
Continue learning about service architectures:
- SOA Resilience Patterns: Build fault-tolerant systems with circuit breakers, bulkheads, and retry mechanisms
- SOA Container Orchestration: Deploy and manage containerized services with Docker and Kubernetes
- SOA and Microservices Overview: Return to the main chapter for a comprehensive view