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:

APIs are like restaurant menus - they tell you what you can order and how to ask for it!

311.2.1 The Sensor Squad Adventure: The Menu Problem

When the Sensor Squad’s pizza restaurant got popular, they had a problem. Customers kept asking for things in different ways:

  • “I want a large pepperoni!”
  • “Give me pizza, big one, with the red meat circles!”
  • “PIZZA NOW! MEAT!”

Sunny the Order Taker got confused and made mistakes. So they created a menu (that’s an API!):

PIZZA MENU v1: - Pizza size: Small, Medium, Large - Toppings: Pepperoni, Mushroom, Cheese - How to order: “I’d like a [SIZE] pizza with [TOPPING]”

Now everyone knew exactly how to ask! And when they added new pizzas, they made MENU v2 that still worked with the old way of ordering.

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

%% 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

Figure 311.1: Three API versioning approaches with different trade-offs
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

Figure 311.2: Service discovery flow: registration, discovery, and invocation

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

Figure 311.3: Server-side discovery: Load balancer abstracts service location from clients

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
NoteKey Takeaway

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: