Microservices Architecture

Chapter 5
Service Communication and API Design

Bachelors Program in Full Stack Development
Microservices Architecture   •   Bachelor

Service Communication and API Design

Introduction

Microservices are useful only when independently deployable services can communicate reliably, securely, and intelligibly. Communication design therefore includes more than choosing REST or gRPC. It requires decisions about interaction style, data contracts, failure handling, discovery, gateways, compatibility, observability, and governance.

A service interaction has at least four participants: a caller, a provider, a communication protocol, and a contract. The contract specifies what may be requested, what is returned, which errors are possible, and how the interface changes over time. Poorly designed communication creates temporal coupling, cascading failures, duplicated business rules, and difficult deployments.

Learning Objectives

By the end of this chapter, students should be able to:

  1. Distinguish synchronous request–response communication from asynchronous messaging and events.
  2. Select REST, RPC, gRPC, queues, or event streams for appropriate problems.
  3. Design API contracts with explicit schemas, errors, idempotency, and compatibility rules.
  4. Explain versioning, schema evolution, service discovery, gateways, and contract testing.
  5. Design failure handling using timeouts, retries, bounded backoff, circuit breakers, and compensation.
  6. Analyse a realistic distributed workflow involving ordering, correlation, and partial failure.

1. Communication Models

Synchronous request–response

In synchronous communication, a caller waits for a response. HTTP REST and gRPC commonly use this model. It is appropriate when the caller needs an immediate result, such as validating a customer address or retrieving a product price.

Its principal cost is temporal coupling: the caller and provider must both be available within the request deadline. Latency accumulates across a call chain. If a request makes sequential calls with latencies L₁, L₂, and L₃, the network portion of the critical path is approximately:

Ltotal ≈ L₁ + L₂ + L₃

This is an approximation because queueing, serialization, retries, and processing overhead may add further delay.

Asynchronous messaging

In asynchronous communication, a producer sends a message to a queue or broker and does not wait for the consumer to finish. The broker can buffer work, enabling the producer and consumer to operate at different rates. A queue generally distributes a task to one consumer, whereas a topic or event stream can deliver a published event to multiple subscribers.

Asynchrony improves resilience and throughput but introduces eventual consistency, duplicate delivery, delayed failure detection, and ordering questions. Consumers must commonly be idempotent: processing the same message more than once should not create an incorrect result.

Commands and events

A command requests an action: ReserveInventory. It has an intended recipient and may be rejected. An event records a fact: InventoryReserved. It is normally addressed to interested subscribers and should describe what happened rather than what another service must do.

The distinction prevents hidden coupling. A service should not publish an event named UpdatePaymentService; it should publish a domain fact such as PaymentAuthorized.

Comparison of synchronous and asynchronous service communication Synchronous Caller Provider request / response Asynchronous Producer Broker Consumer buffered delivery; later processing
Comparison of synchronous and asynchronous service communication
Figure: Comparison of synchronous and asynchronous service communication

Figure: Synchronous communication requires a response path; asynchronous communication separates production from consumption through a broker.

2. REST API Design

REST uses resources, representations, and a uniform interface over HTTP. A well-designed endpoint uses nouns rather than remote-procedure verbs:

OperationExampleTypical meaning
GET/orders/42Retrieve a representation
POST/ordersCreate a subordinate resource or request processing
PUT/orders/42Replace a resource representation
PATCH/orders/42Partially modify a resource
DELETE/orders/42Request removal

HTTP safety and idempotency must be distinguished. A safe method is intended only for retrieval and does not request a state change; GET is safe. DELETE is not safe, because it requests a state change, but it is defined as idempotent: repeating the same deletion has the same intended final state, even if later responses differ, such as 204 No Content followed by 404 Not Found.

POST is generally non-idempotent. If a client may retry a create request, the API can accept an idempotency key and store the result associated with that key. The key must be scoped appropriately and retained long enough to cover the retry window.

Status codes should carry stable meaning. Common examples include 200 OK, 201 Created, 202 Accepted, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 429 Too Many Requests, and 503 Service Unavailable. Error bodies should be machine-readable and include a stable error code, human-readable detail, correlation identifier, and—where useful—field-level validation errors.

3. RPC and gRPC

RPC models communication as calling a remote operation. It is often convenient for internal service-to-service calls with explicit procedures and typed schemas. gRPC commonly uses Protocol Buffers, generated client/server code, HTTP/2 transport, deadlines, metadata, and streaming.

RPC can hide distributed-system realities. A remote call is not equivalent to a local function: it may time out, partially execute, return stale data, or fail after the server has committed a change. Every RPC should therefore define deadlines, retry eligibility, error semantics, and idempotency.

REST is often advantageous for broad external interoperability and resource-oriented APIs. gRPC is often advantageous for strongly typed internal APIs, low overhead, and streaming. The choice should follow consumers, operational constraints, and governance—not fashion.

4. API Contracts and Schemas

An API contract specifies:

  • endpoint or procedure names and input parameters;
  • data types, required fields, constraints, and defaults;
  • successful and failure responses;
  • authentication and authorization requirements;
  • timeout, retry, and rate-limit expectations;
  • ordering, delivery, and idempotency semantics;
  • ownership and deprecation policy.

OpenAPI can describe HTTP APIs; Protocol Buffers can define gRPC contracts; AsyncAPI can document message channels. Documentation is not a substitute for executable validation. Providers should validate responses against schemas, and consumers should test assumptions about fields and errors.

Schema evolution

A backward-compatible change allows existing consumers to continue working. Usually safe changes include adding an optional response field, adding a new endpoint, or adding a new event type. Risky changes include renaming or removing fields, changing units, narrowing accepted values, changing a field from optional to required, or changing the meaning of an existing value.

The compatibility direction matters. A reader should generally tolerate unknown fields, while a writer should avoid requiring fields that older readers cannot understand. Protobuf field numbers, once published, should not be reused after deletion. Events should be self-describing enough to support replay and should include an explicit schema version where governance requires it.

A useful contract rule is: additive first, migration second, removal last. Providers can introduce a new field, migrate consumers, observe usage, announce deprecation, and remove the old field only after evidence that no supported consumer depends on it.

Versioning

Common strategies are URI versioning (/v2/orders), header or media-type versioning, and parallel procedures or topics. Versioning should represent a meaningful contract boundary, not every minor change. A major version may be necessary when semantics or required data change incompatibly. Version numbers alone do not solve migration; ownership, deadlines, telemetry, and a removal policy are also required.

5. Reliability of Interactions

Timeouts and deadlines

A timeout bounds how long a caller waits. A deadline is an absolute time by which the operation must finish. Deadlines should propagate downstream so that a service does not continue expensive work after the original request has expired.

Retries and capped backoff

Retries are appropriate for transient failures such as connection resets or temporary overload, but not for validation errors or authorization failures. Retrying a non-idempotent operation can duplicate effects unless an idempotency mechanism exists.

Let b be the base delay in milliseconds, n the zero-based retry number, C the maximum total delay in milliseconds, and Jₙ a random jitter value. A correctly capped exponential backoff is:

dₙ = min(C, b × 2ⁿ + Jₙ)

where 0 ≤ Jn ≤ jmax and b, C, and jₘₐₓ are expressed in the same time unit. This formula caps the complete delay, not merely the exponential component. For b = 100 ms, C = 1,000 ms, jₘₐₓ = 50 ms, and n = 3, if J₃ = 40 ms, then d₃ = min(1,000, 800 + 40) = 840 ms. If n = 4 and J₄ = 50 ms, d₄ = min(1,000, 1,600 + 50) = 1,000 ms.

Jitter reduces synchronized retry bursts. A production policy must also limit the retry count, respect server Retry-After guidance, and use a retry budget so that recovery attempts do not amplify overload.

Circuit breakers and bulkheads

A circuit breaker can move from closed to open after a failure threshold, reject calls during a cool-down period, and then permit limited half-open probes. Thresholds must be based on measured error rates and latency, not arbitrary assumptions. Bulkheads isolate resources, such as separate connection pools for critical and non-critical dependencies.

Delivery semantics

At-most-once delivery may lose messages but avoids redelivery. At-least-once delivery may duplicate messages and requires idempotent consumers. Exactly-once effects are difficult across independent systems; they usually require transactional coordination or carefully designed deduplication and are not implied merely by a broker feature.

6. Messaging, Events, Ordering, and Correlation

A message should include a unique message identifier, event type, schema version, creation time, producer, correlation identifier, and causation identifier where appropriate. A correlation ID connects all operations belonging to one business request. A causation ID identifies the message or command that caused the current message.

Ordering is usually guaranteed only within a defined scope, such as a partition or aggregate key. If order matters for an order, publish messages using the order identifier as the partition key and reject or defer an event whose sequence number is unexpectedly old. Consumers should handle duplicates and late arrival.

Outbox patterns solve a common atomicity problem: a service updates its database and publishes an event. The service writes the business change and an outbox record in one local transaction; a relay later publishes the outbox record. Consumers record processed message IDs or use an equivalent deduplication strategy. This provides reliable publication, but not instantaneous global atomicity.

7. Service Discovery and API Gateways

Service discovery maps a logical service name to reachable instances. Client-side discovery makes the caller select an instance; server-side discovery delegates selection to a load balancer or platform. Health checks must distinguish process liveness from readiness to serve traffic. Discovery data can be stale, so clients need connection timeouts and failure handling.

An API gateway can terminate TLS, authenticate requests, apply rate limits, route traffic, transform protocols, and provide a controlled external boundary. It should not become a business-logic monolith. Backend-for-Frontend services may provide consumer-specific aggregation, but excessive aggregation can recreate tight coupling and long latency chains.

8. Consumer-Driven Contract Testing

Consumer-driven contract testing begins with a consumer expressing the interactions it requires, including representative requests, responses, fields, status codes, and errors. The provider verifies those contracts in its build pipeline. This differs from end-to-end testing: it tests the interface boundary without requiring the entire production topology.

A strong workflow is:

  1. Consumer writes a contract for a meaningful interaction.
  2. The contract is published to a broker or repository.
  3. Provider verification runs against the provider implementation.
  4. Results are recorded by provider version and consumer version.
  5. Deployment is permitted only when supported contracts pass.
  6. Deprecated interactions are tracked until all consumers migrate.

Contracts should test behavior rather than implementation details. They should include unknown-field tolerance, error behavior, authentication assumptions, and event schemas where relevant. Contract tests do not replace security tests, performance tests, or domain-level integration tests.

9. Illustrative Case: Retail Checkout

Background and problem

A retail platform contains Order, Inventory, Payment, and Notification services. A customer submits an order. Inventory reservation may succeed while payment may fail, and network retries may duplicate commands.

Analysis

A single synchronous chain—Order calls Inventory, then Payment, then Notification—creates availability and latency coupling. It also creates an ordering problem: an InventoryReserved event may arrive after a payment failure has already caused cancellation. The design must correlate messages, define state transitions, and compensate successful steps.

Approach

The Order service creates an order with status PENDING and a unique order ID. It publishes a ReserveInventory command containing the order ID, correlation ID, and idempotency key. Inventory emits either InventoryReserved or InventoryReservationRejected. On reservation, Order requests payment authorization. Payment emits PaymentAuthorized or PaymentFailed.

If payment succeeds, Order marks the order confirmed and emits OrderConfirmed. If payment fails after inventory has succeeded, Order marks the order CANCELLATION_PENDING and publishes ReleaseInventory. Inventory emits InventoryReleased, after which Order marks the order cancelled. If the release fails temporarily, the command is retried with bounded backoff; if it remains unresolved, an operational reconciliation queue is created. The customer-facing response can be 202 Accepted while the workflow completes asynchronously.

Every message includes orderId, messageId, correlationId, causationId, and a sequence or state version. Consumers reject duplicate state transitions and ignore stale events. For example, a late InventoryReserved event after cancellation must not reopen the order; it should trigger a compensating ReleaseInventory command if a reservation exists.

Outcome and learning points

The workflow accepts eventual consistency but makes it visible through order status. The outbox pattern prevents committed reservations or payments from being silently disconnected from published events. Idempotency keys prevent duplicate reservation and payment effects. Compensation is not rollback: it is a new business action that may fail and therefore requires monitoring, retry, and reconciliation.

10. Governance, Security, and Contemporary Practice

Authentication establishes identity; authorization determines permitted actions. OAuth 2.0 and OpenID Connect are common for delegated identity, while mutual TLS can authenticate service workloads. Tokens should have suitable audiences, lifetimes, and scopes. Sensitive data should be minimized, encrypted in transit, protected in logs, and retained according to legal and organizational policy.

API governance should define ownership, naming, schema review, deprecation, incident response, and service-level objectives. Observability should correlate logs, metrics, and traces using trace and correlation identifiers. Useful measures include latency percentiles, error rate, saturation, retry volume, queue age, consumer lag, and dead-letter counts.

Contemporary platforms increasingly use service meshes for traffic policy, identity, retries, and telemetry. A mesh can standardize infrastructure concerns but cannot decide business idempotency or compensation semantics. AI-assisted API generation and schema transformation can accelerate development, but generated contracts require human review for security, privacy, semantic compatibility, and failure behavior.

Challenges and Limitations

Distributed communication cannot eliminate partial failure. More services increase operational overhead, contract surfaces, and debugging complexity. Asynchronous designs improve decoupling but make user experience, consistency, and support processes harder to reason about. Gateways and meshes can centralize policy but become high-impact dependencies. Contract testing detects interface incompatibility, but it cannot prove that a service implements the correct business meaning. These limitations require architectural simplicity, explicit ownership, testing, and operational discipline.

Best-Practice Checklist

  • Choose synchronous calls only when an immediate result is needed.
  • Use explicit deadlines and propagate them downstream.
  • Retry only transient, retryable operations; make effects idempotent.
  • Cap the complete backoff delay and define jitter bounds and units.
  • Treat DELETE as idempotent, not safe.
  • Give every message stable identifiers and correlation metadata.
  • Design for duplicates, reordering, late events, and partial failure.
  • Prefer additive schema evolution and measured deprecation.
  • Keep gateways focused on cross-cutting boundary concerns.
  • Automate consumer-driven contract verification.
  • Monitor queues, retries, compensation, and dead-letter traffic.

Chapter Summary

Service communication is an architectural decision involving latency, coupling, failure, consistency, and governance. REST provides resource-oriented HTTP interfaces; RPC and gRPC provide typed procedure-oriented communication; messaging and events separate producers from consumers but require idempotency, correlation, ordering policies, and reconciliation. Contracts define data and behavioral expectations, while schema evolution and versioning protect independently deployed consumers. Timeouts, correctly capped jittered backoff, circuit breakers, bulkheads, outbox publication, service discovery, gateways, and consumer-driven contract tests address common distributed-system risks. The retail case demonstrates that compensation, not assumed rollback, is required when one successful step must be reversed after a later failure.

Key Terms

API contract: A documented agreement about operations, data, errors, and behavior.

Asynchronous communication: Interaction in which the sender does not wait for processing to finish.

Backward compatibility: Ability of existing consumers to continue working after a provider change.

Circuit breaker: A control that temporarily rejects calls to an unhealthy dependency.

Command: A message requesting an action.

Compensation: A new action that counteracts a previously completed business action.

Correlation ID: An identifier linking related operations across services.

Event: A record of a fact that has occurred.

Idempotency: Property whereby repetition has the same intended final effect.

Message broker: Infrastructure that routes, stores, and delivers messages.

Outbox pattern: Transactional recording of business change and pending event publication.

RPC: Remote procedure call.

Schema evolution: Controlled change to the structure and meaning of exchanged data.

Service discovery: Mapping of logical service identities to reachable instances.

Synchronous communication: Request–response interaction in which the caller waits.

Timeout: A limit on waiting or execution duration.

Versioning: Managing distinct contract revisions over time.

Consumer-driven contract test: A provider verification test based on consumer expectations.

Review Questions

Short Answer Questions

  1. Why does synchronous communication create temporal coupling?
  2. Distinguish an HTTP safe method from an idempotent method.
  3. What information should a correlation ID provide?
  4. Why are unknown response fields usually tolerated by consumers?
  5. What problem does the outbox pattern address?

Long Answer Questions

  1. Compare REST, gRPC, and asynchronous messaging for internal service communication.
  2. Explain backward-compatible schema evolution and a safe deprecation process.
  3. Describe timeout, retry, circuit-breaker, and bulkhead responsibilities.
  4. Explain service discovery and the appropriate responsibilities of an API gateway.
  5. Describe consumer-driven contract testing and its limitations.

Analytical / Application Questions

  1. Design a checkout workflow in which inventory succeeds but payment fails. Specify ordering, identifiers, compensation, and final states.
  2. A service uses b = 200 ms, C = 1,500 ms, jₘₐₓ = 100 ms, and retry n = 3. Calculate the delay when J₃ = 80 ms using the capped-total formula, and explain why the cap is not exceeded.
  3. An API changes a previously optional field to required and removes an old error code. Analyse compatibility risks and propose a migration plan.

Multiple Choice Questions with Answers

Question 1

Which statement correctly describes HTTP DELETE?

A. It is safe and non-idempotent.

B. It is unsafe but idempotent.

C. It is safe and idempotent.

D. It is neither safe nor idempotent.

Correct Answer: B. It is unsafe but idempotent.

Question 2

Using dₙ = min(C, b × 2ⁿ + Jₙ), what is d₃ when b = 100 ms, C = 1,000 ms, and J₃ = 40 ms?

A. 800 ms

B. 840 ms

C. 1,000 ms

D. 1,040 ms

Correct Answer: B. 840 ms

Question 3

Which message is an event rather than a command?

A. ReserveInventory

B. AuthorizePayment

C. ReleaseInventory

D. InventoryReserved

Correct Answer: D. InventoryReserved

Question 4

What is the primary purpose of an idempotency key?

A. To encrypt a request

B. To prevent repeated submission from creating duplicate effects

C. To discover service instances

D. To increase message ordering guarantees

Correct Answer: B. To prevent repeated submission from creating duplicate effects

Question 5

Which change is generally backward compatible for an existing response consumer?

A. Removing a response field

B. Renaming a response field

C. Adding an optional response field

D. Changing a field's unit from dollars to cents without notice

Correct Answer: C. Adding an optional response field

Question 6

What does the outbox pattern make reliable?

A. Global rollback across all services

B. Atomic recording of a local business change and pending event publication

C. Exactly-once delivery across arbitrary brokers

D. Elimination of network failures

Correct Answer: B. Atomic recording of a local business change and pending event publication

Question 7

What is the main purpose of a correlation ID?

A. To identify a database column

B. To link related operations across service boundaries

C. To replace authentication credentials

D. To specify a schema field number

Correct Answer: B. To link related operations across service boundaries

Question 8

Which responsibility is most appropriate for an API gateway?

A. Owning every domain rule

B. Applying boundary authentication and rate limiting

C. Replacing all provider databases

D. Guaranteeing business-level exactly-once effects

Correct Answer: B. Applying boundary authentication and rate limiting

Question 9

At-least-once delivery requires consumers to be prepared for which condition?

A. Duplicate messages

B. No messages ever being delayed

C. Guaranteed global ordering

D. Automatic transaction rollback

Correct Answer: A. Duplicate messages

Question 10

In the retail case, what should happen if inventory is reserved but payment fails?

A. The order should immediately be confirmed.

B. The reservation should be ignored permanently.

C. A compensating release-inventory action should be issued.

D. The payment service should directly modify inventory storage.

Correct Answer: C. A compensating release-inventory action should be issued.

Microservices Architecture
Chapter 5 of 14