Communication#

How the services build talk to each other and to the outside world: a recon pipeline calling an enrichment service, a collection agent reporting back to a controller, a defended estate emitting alerts. Two axes carry every choice: synchronous vs. asynchronous, and request/response vs. event. Get them wrong and the campaign’s plumbing fights the mission.

HTTP / REST#

The default for almost everything that talks over a network. REST is more convention than spec; the discipline is keeping the interface resource-oriented, the verbs idempotent where the methods say they should be, and the versioning strategy deliberate from day one.

  • The default for service-to-service and client-to-server.

  • Resource-oriented; verbs map to HTTP methods (GET, POST, PUT, PATCH, DELETE).

  • Idempotency: GET, PUT, DELETE should be idempotent; POST typically isn’t.

  • Status codes carry semantics: 2xx success, 3xx redirect, 4xx client error, 5xx server error.

  • Versioning: in the URL (/v1/...), in a header, or by media type.

REST done well: clear resource model, predictable semantics, leverages HTTP caches, easy to debug with curl.

gRPC#

The Google-originated RPC framework that won the internal-service space. Strongly typed, code-generated, and built on HTTP/2 so streaming is a first-class feature. The trade is browser ergonomics; gRPC-web or a REST gateway is needed for client-side calls.

  • Binary protocol over HTTP/2, with Protocol Buffers as the IDL.

  • Strongly typed, code-generated clients in many languages.

  • Streaming: server-streaming, client-streaming, bidirectional.

  • Best for: internal service-to-service traffic, low-latency RPC, streams.

  • Less convenient for browsers (use grpc-web or REST gateway).

GraphQL#

A query language and runtime where the client picks the shape of the response. Best for client-driven UIs that need flexible data shapes (mobile apps, B2B portals); the server-side cost is real (resolvers, query-cost limits, batching with DataLoader, and the loss of HTTP-cache simplicity).

  • A query language plus a runtime; the client picks the shape of the response.

  • Single endpoint; one network round-trip can fetch a tree of data.

  • Best for: client-driven UIs that need flexible data shapes (mobile, B2B portals).

  • Costs: server complexity (resolvers, batching with DataLoader, query cost limits), N+1 traps, harder caching than REST.

Async Messaging#

When you don’t need a synchronous reply, asynchronous messaging usually beats synchronous calls.

  • Queues (SQS, RabbitMQ), one consumer per message; load leveling between producers and consumers.

  • Streams / event logs (Kafka, Kinesis, Pulsar), replayable; many independent consumers.

  • Pub/Sub (NATS, MQTT, Cloud Pub/Sub), broadcast to subscribers.

Properties to think about per choice.

  • Delivery semantics: at-most-once, at-least-once, exactly-once (rare; usually achieved with idempotency).

  • Ordering: per partition / per key / global / none.

  • Retention: until consumed / time-based / forever.

  • Consumer model: competing consumers vs. independent consumer groups.

Request/Reply vs. Events#

  • Request/Reply, A asks B for an answer and waits. Tight coupling; A knows B exists and is up.

  • Event, A tells the world something happened. B may listen (or not). Loose coupling.

Heuristic: command-style (“do this”) tends to be sync; notification-style (“this happened”) tends to be async. When in doubt, prefer events; they scale further organizationally.

Idempotency#

In a network, retries happen. Timeouts trigger retries, retries arrive after the original request succeeded, and the system has to behave the same regardless of how many times an action is applied. Make handlers safe to call twice.

  • Carry an idempotency key on requests; the server stores result + key.

  • Build operations as state transitions: “set status = paid” not “increment paid_count”.

  • For events, dedupe on a stable event id.

Schemas and Contracts#

Producer/consumer schemas drift independently (producers add fields, consumers add new requirements) and the only place to catch the drift before production is in CI. The four mechanisms below are the standard ways.

  • Schema registry for events (Avro, Protobuf, JSON Schema).

  • Compatibility rules: backward, forward, full.

  • Consumer-driven contract tests to catch drift in CI.

  • Tolerant readers, ignore unknown fields, don’t fail on additions.

API Style Cheat Sheet#

A decision table for “what protocol fits this need.” Each row trades something different: REST for ubiquity, gRPC for internal performance, GraphQL for client-driven UIs, queues and streams for async decoupling, webhooks for third-party integration.

Need

Use

Notes

Public web API

REST + JSON

Easiest for partners; cacheable.

Browser-driven UI

GraphQL or REST

GraphQL when shapes vary widely.

Internal services

gRPC

Codegen, low latency, streams.

Real-time updates

WebSockets / SSE

SSE when one-way is enough; simpler.

Loose decoupling

Events on a log

Kafka-style replayable streams.

Batch processing

Queue + workers

SQS/RabbitMQ + autoscaling consumers.

Webhooks to third parties

HTTP POST + signature

Retries with exponential backoff; HMAC.