Architecture
Microservices Architecture: Communication, Resilience and Distributed Consistency
Key patterns – sync/async, Service Discovery, circuit breaker, Saga.
Microservices architecture splits a system into small, independent services, each serving a clear business domain (bounded context). Each service runs in its own process, can be deployed and changed independently, and communicates with the rest through defined interfaces. The approach enables teams to work in parallel, scale individual components and choose suitable technologies per service. This article reviews key patterns: communication, service discovery, resilience and consistency.
When microservices?: suitable when multiple teams, complex domains, or different scalability requirements per component. Not suitable for every system – a well-organized monolith is better than immature microservices. Gradual migration (strangler fig) reduces risk.
Synchronous communication: REST or gRPC – direct calls between services. Advantages: simple, predictable. Disadvantages: time coupling (if service B is slow, A waits), chained calls (A→B→C) create latency and bottlenecks. Limit depth, define timeouts and retries, and consider API Gateway to unify entry points.
Asynchronous communication: message queues (RabbitMQ, SQS) or streaming (Kafka). The service publishes an event; consumers receive and process. Advantages: decoupled, handles load, recovery (messages are stored). Disadvantages: eventual consistency, need for idempotency (duplicate processing), and complexity in debugging and tracing. Suitable for flows that do not require immediate response.
Service Discovery: services need to find each other without hardcoding addresses. Client-side discovery: the client asks a registry (Consul, etcd) and gets a list of instances. Server-side: load balancer or proxy (e.g. Kubernetes Service) routes traffic. In K8s, Service DNS provides built-in discovery.
Resilience: failure of one service should not bring down the whole chain. Circuit breaker: after N failures – "opens" and does not call B until timeout; then "half-open" – one attempt. Retry with exponential backoff and defined timeout. Fallback: default response or cache when the service is unavailable. Bulkhead: isolate resources (separate thread pools) so failure in one service does not consume all threads.
Distributed consistency: cross-service transactions do not run in a single ACID. Approaches: Saga – each service performs a local step; on failure – compensating transactions or compensation events. Event sourcing – state is derived from events; services publish events and update local state. Accept eventual consistency and plan idempotency and ordering when needed.
Contract testing: verifying that interfaces between services match – not only at runtime but as part of CI. Consumer-driven contracts (Pact) or schema tests (OpenAPI) prevent silent breakage when a service changes its API.
Observability: logs, metrics and tracing are distributed – need correlation (request ID, trace ID) and a central system. So you can trace a request end-to-end and debug in a distributed system.
API Gateway and BFF: central Gateway (Kong, AWS API Gateway) handles auth, rate limiting and routing. BFF (Backend for Frontend) – a service that aggregates calls to multiple microservices and adapts the response to client needs – reduces round-trips and simplifies the frontend.
In summary: microservices require planning communication (sync vs async), service discovery, resilience (circuit breaker, retry, fallback) and consistency (Saga, event sourcing, eventual consistency). Contract testing and observability support stability over time.