Back to blog
Integration Engineeringintermediate

Request/Response — REST & Synchronous Integration

Master synchronous integration with REST: HTTP semantics, resource design, status codes, versioning, idempotency, resilience patterns, OpenAPI contracts, and knowing exactly when synchronous is the right choice.

SystemForgeApril 18, 202610 min read
RESTHTTPRequest/ResponseAPI DesignSynchronousIntegration Patterns
Share:š•

Request/Response is the oldest and most intuitive integration pattern: one system asks a question, another system answers it. In the modern web this means REST over HTTP — but doing it well involves far more than hitting an endpoint. This lesson covers the full depth of synchronous integration: the protocol semantics that make it reliable, the design decisions that make it maintainable, and the resilience techniques that make it survive real-world conditions.


What Request/Response Actually Means

Request/Response is a synchronous, two-way communication pattern:

Client                          Server
  │                               │
  │──── HTTP Request ────────────►│
  │                               │  (client blocks here)
  │◄─── HTTP Response ────────────│
  │                               │

The defining characteristic is temporal coupling: the client must wait while the server processes the request. Neither side can proceed independently — the client is blocked until the response arrives or the connection times out.

This coupling is a deliberate trade-off. You accept it because you genuinely need the answer before you can continue.

When Synchronous Request/Response Is the Right Choice

Use it when:

  • The caller cannot proceed without the result (e.g., validate a payment before confirming an order)
  • Latency is critical and the operation completes in milliseconds to low seconds
  • Immediate feedback is required (user-facing operations, real-time queries)
  • The operation is stateless and idempotent — the same request can safely be retried

Do not use it when:

  • The operation takes more than a few seconds (use async + callback instead)
  • The caller can continue without the result (use fire-and-forget messaging)
  • High availability is more important than immediacy (a downed server blocks all callers)
  • You need to fan out to multiple receivers (pub/sub scales better)

HTTP as an Integration Protocol

REST (Representational State Transfer) is an architectural style, not a protocol. It uses HTTP as its transfer protocol and treats everything as a resource addressable by a URL.

The HTTP Verbs and Their Contracts

Understanding HTTP verb semantics is not optional — it determines how caches, proxies, load balancers, and retry logic behave:

| Verb | Meaning | Safe? | Idempotent? | Has Body? | |------|---------|-------|-------------|-----------| | GET | Retrieve a resource | Yes | Yes | No | | POST | Create a resource or trigger an action | No | No | Yes | | PUT | Replace a resource entirely | No | Yes | Yes | | PATCH | Partially update a resource | No | No* | Yes | | DELETE | Remove a resource | No | Yes | Optional | | HEAD | Retrieve headers only (no body) | Yes | Yes | No |

Safe means the operation has no side effects — a GET should never modify state.
Idempotent means calling it N times has the same effect as calling it once — critical for safe retries.

HTTP Status Codes You Must Use Correctly

Misused status codes break client error handling and caching:

2xx — Success
  200 OK             Standard success with body
  201 Created        Resource was created; include Location header
  202 Accepted       Request accepted; processing is async
  204 No Content     Success with no response body (DELETE, PUT)

3xx — Redirection
  301 Moved Permanently   Resource has a new permanent URL
  304 Not Modified        Cached version is still valid (ETags)

4xx — Client Errors (caller's fault — do not retry without changing the request)
  400 Bad Request         Malformed request syntax or invalid data
  401 Unauthorized        Not authenticated
  403 Forbidden           Authenticated but not authorised
  404 Not Found           Resource does not exist
  409 Conflict            State conflict (e.g., duplicate resource)
  410 Gone                Resource permanently deleted
  422 Unprocessable Entity Validation failed (well-formed but semantically invalid)
  429 Too Many Requests   Rate limit exceeded; includes Retry-After header

5xx — Server Errors (server's fault — may be worth retrying)
  500 Internal Server Error  Unexpected server failure
  502 Bad Gateway            Upstream service returned invalid response
  503 Service Unavailable    Server overloaded or down; includes Retry-After
  504 Gateway Timeout        Upstream service timed out

Rule: 4xx means the client must change something before retrying. 5xx (and 429) means the client can retry the same request after waiting.


RESTful Resource Design

Resource Naming

Resources are nouns, not verbs. URLs identify things, not actions.

Good:
  GET    /orders              — list orders
  GET    /orders/1234         — get order 1234
  POST   /orders              — create an order
  PUT    /orders/1234         — replace order 1234
  PATCH  /orders/1234         — partially update order 1234
  DELETE /orders/1234         — delete order 1234

Bad:
  GET    /getOrder?id=1234
  POST   /createOrder
  POST   /updateOrder/1234
  POST   /deleteOrder/1234

Nested Resources

Express relationships through URL hierarchy — but only one level deep:

GET  /orders/1234/items          — items belonging to order 1234
GET  /orders/1234/items/5        — item 5 of order 1234
POST /orders/1234/items          — add item to order 1234

Avoid deep nesting:
  /customers/99/orders/1234/items/5/reviews  ← too deep, use /items/5/reviews

Request and Response Bodies

Use JSON as the default content type. Always include Content-Type: application/json on requests with a body.

Response envelope — keep it flat:

JSON
// Good — data at the root
{
  "orderId": "ORD-1234",
  "status": "confirmed",
  "totalAmount": 299.99
}

// Avoid unnecessary wrapping
{
  "data": {
    "result": {
      "order": { ... }
    }
  }
}

Error response — always structured:

JSON
{
  "type": "https://errors.systemforge.io/validation-error",
  "title": "Validation Failed",
  "status": 422,
  "detail": "The 'quantity' field must be a positive integer.",
  "instance": "/orders/1234/items",
  "errors": [
    { "field": "quantity", "code": "must_be_positive", "message": "Must be greater than 0" }
  ]
}

This follows RFC 7807 Problem Details — a standard error format that clients can parse consistently.


Idempotency in REST

Idempotency is what makes retries safe. Without it, a network timeout can cause duplicate operations.

Natural idempotency: GET, PUT, and DELETE are idempotent by definition.

Making POST idempotent: Use an idempotency key — a client-generated unique ID included in the request header:

HTTP
POST /orders HTTP/1.1
Content-Type: application/json
Idempotency-Key: client-uuid-7f3a9b2c

{
  "customerId": "CUST-99",
  "items": [...]
}

The server stores the idempotency key and the result of the first successful request. If the same key arrives again (a retry), the server returns the cached result without re-executing the operation.

Storage: Idempotency keys should be stored with a TTL (typically 24 hours). After expiry, re-processing is acceptable since it is no longer a retry.


API Versioning

APIs change. Consumers break when they change unexpectedly. Versioning prevents this.

Versioning Strategies

URI path versioning (most common, most visible):

/api/v1/orders
/api/v2/orders

Header versioning (clean URLs, less discoverable):

HTTP
GET /api/orders HTTP/1.1
Accept: application/vnd.systemforge.v2+json

Query parameter versioning (avoid — pollutes query strings, harder to cache):

/api/orders?version=2

Semantic Versioning Rules

  • Non-breaking changes (new optional fields, new endpoints): no version bump required
  • Breaking changes (removed fields, changed field types, changed behaviour): increment major version
  • Support the previous major version for a minimum agreed deprecation period (typically 6–12 months)

What Constitutes a Breaking Change

Breaking:
  - Removing a field from the response
  - Changing a field's type (string → integer)
  - Renaming a field
  - Changing an endpoint URL
  - Making an optional field required
  - Changing HTTP method for an operation
  - Removing an enum value

Non-breaking:
  - Adding a new optional field to the response
  - Adding a new optional request parameter
  - Adding a new endpoint
  - Adding a new enum value

OpenAPI Contracts

Define your API as an OpenAPI (Swagger) specification before writing a line of implementation code. The spec is the contract between you and your consumers.

YAML
openapi: "3.1.0"
info:
  title: Orders API
  version: "2.0"
paths:
  /orders:
    post:
      summary: Create an order
      operationId: createOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
      responses:
        "201":
          description: Order created
          headers:
            Location:
              schema:
                type: string
              description: URL of the created order
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "422":
          description: Validation error
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemDetails"

Benefits of contract-first design:

  • Consumers can generate client SDKs from the spec before the server is built
  • Mock servers can be generated from the spec for parallel development
  • The spec can be validated in CI to catch breaking changes before deployment

Resilience Patterns for REST Clients

Synchronous clients must be resilient. Networks are unreliable. Servers fail. Build resilience in:

Timeouts

Always set two timeouts:

  • Connection timeout — how long to wait for the TCP handshake to complete (typically 2–5 seconds)
  • Read timeout — how long to wait for the server to return the full response (typically 10–30 seconds, depending on the operation)

Never use infinite timeouts. They cause threads to block indefinitely and cascade into service-wide unavailability.

Retry with Exponential Backoff

Retry 5xx and 429 responses — but not 4xx (those require request changes):

Attempt 1 → 503 Service Unavailable
  Wait: 1s + jitter
Attempt 2 → 503 Service Unavailable
  Wait: 2s + jitter
Attempt 3 → 503 Service Unavailable
  Wait: 4s + jitter
Attempt 4 → 200 OK āœ“

Jitter (a random offset) prevents all retrying clients from hitting the recovering server at the same instant (the thundering herd problem).

Only retry idempotent operations. Never retry a POST without an idempotency key — you risk creating duplicates.

Circuit Breaker

A circuit breaker monitors call failure rates. When failures exceed a threshold, it opens — subsequent calls fail immediately without hitting the server, giving it time to recover.

CLOSED → calls pass through, failures counted
  (failure rate > threshold)
    ↓
OPEN → calls fail immediately, no server contact
  (after timeout)
    ↓
HALF-OPEN → one test call allowed through
  (success) → CLOSED
  (failure) → OPEN

Libraries: Polly (.NET), Resilience4j (Java), resilience (Python).

Bulkhead

Isolate resource pools so that failures in one integration do not starve resources for others. Use separate HTTP client instances (with separate connection pools and thread limits) for different downstream services.


Caching

HTTP caching reduces latency and load on servers. Use it aggressively for read-heavy APIs.

Cache-Control Headers

HTTP
Cache-Control: public, max-age=300        — cache for 5 minutes
Cache-Control: private, max-age=60        — user-specific, 1 minute
Cache-Control: no-cache                   — revalidate before using cached copy
Cache-Control: no-store                   — never cache (sensitive data)

ETags (Conditional Requests)

HTTP
// First request
GET /orders/1234
Response: ETag: "abc123"

// Subsequent request
GET /orders/1234
If-None-Match: "abc123"
Response: 304 Not Modified  ← no body transferred

ETags allow clients to check whether a resource has changed without downloading the full response. Essential for polling patterns.


Pagination

Never return unbounded collections. Implement pagination from day one:

Cursor-based pagination (preferred for real-time data):

JSON
{
  "items": [...],
  "nextCursor": "eyJpZCI6MTAwfQ==",
  "hasMore": true
}

Offset-based pagination (simpler, works for stable datasets):

JSON
{
  "items": [...],
  "page": 2,
  "pageSize": 20,
  "totalCount": 487
}

Cursor-based is preferred for feeds and event streams because it handles concurrent inserts correctly — a new record inserted at the top does not shift items across pages.


REST vs. Other Synchronous Options

| Option | When to prefer it | |--------|-------------------| | REST/HTTP | External APIs, public interfaces, browser clients, wide ecosystem compatibility | | gRPC | Internal service-to-service calls where performance matters; strongly typed contracts; streaming | | GraphQL | Multiple clients with different data shape needs; reduces over-fetching | | SOAP | Legacy systems, financial/healthcare environments requiring WS-Security or ACID transactions |


Lesson Summary

  • Request/Response is the right pattern when the caller genuinely cannot continue without the result and the operation completes quickly.
  • HTTP verb semantics (safe, idempotent) determine how retries, caches, and proxies behave — use them correctly.
  • Status codes communicate who is responsible for a failure: 4xx = caller must fix the request; 5xx = server failure, may retry.
  • Idempotency keys make POST safe to retry — store the key and cached result server-side with a TTL.
  • Always version APIs, define breaking change policy, and use OpenAPI specs as the authoritative contract.
  • Resilience is the caller's responsibility: set timeouts, retry with exponential backoff and jitter, implement circuit breakers.

Next: Event-Driven Architecture

API Design Principles Knowledge Check

5 questions Ā· Test what you just learned Ā· Instant explanations

Enjoyed this article?

Explore the Integration Engineering learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.