Serverless Computing & Microservices

Chapter 6
Event-Driven Architecture and Messaging

Advance Diploma in Cloud Computing
Serverless Computing & Microservices   •   Advance Diploma

Event-Driven Architecture and Messaging

Introduction and Context

Event-driven architecture (EDA) is an approach in which software components communicate by producing and consuming events. An event is a record that something significant has happened, such as OrderPlaced, PaymentAuthorized, or ShipmentDispatched. Unlike a traditional request–response interaction, the producer of an event does not necessarily know which components will consume it, when they will consume it, or how many consumers exist.

EDA is central to serverless computing and microservices because it supports loose coupling, elastic scaling, asynchronous processing, and independent deployment. Cloud platforms provide managed queues, publish–subscribe brokers, event buses, and streaming services, allowing organisations to build distributed systems without operating every infrastructure component themselves.

However, asynchronous systems introduce difficult engineering problems. Messages may be delayed, duplicated, reordered, lost, or delivered to a consumer that subsequently fails. Consequently, robust event-driven design requires explicit decisions about delivery semantics, idempotency, retry policies, ordering, observability, data ownership, and consistency.

Learning Objectives

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

  • distinguish queues, publish–subscribe systems, event buses, and event streams;
  • design producers, consumers, topics, subscriptions, partitions, and consumer groups;
  • explain at-most-once, at-least-once, and effectively-once processing;
  • implement retries, idempotent consumers, acknowledgements, visibility timeouts, and dead-letter handling;
  • compare event sourcing and conventional state persistence;
  • explain CQRS, choreography, orchestration, and saga-based transactions;
  • select appropriate messaging patterns for serverless and microservice systems;
  • evaluate reliability, scalability, security, governance, and operational trade-offs.

1. Foundations of Event-Driven Systems

1.1 Events, commands, and messages

A message is data transferred between software components. A command requests an action: ReserveInventory. An event reports a completed or observed fact: InventoryReserved. A command normally has one intended handler, whereas an event may have zero, one, or many subscribers.

A useful event envelope contains:

FieldPurpose
Event identifierGlobally identifies this occurrence
Event type and versionDescribes its meaning and schema
Occurrence timeIndicates when the event happened
ProducerIdentifies the originating service
Correlation identifierLinks events belonging to one business operation
Causation identifierIdentifies the message that caused this event
PayloadBusiness data describing the occurrence
Trace contextSupports distributed tracing

Events should describe business facts rather than implementation instructions. CustomerEmailChanged is generally more stable and reusable than UpdateCustomerRow.

1.2 Core components

An event-driven system usually contains:

  1. Producer: creates a message.
  2. Broker or transport: accepts, stores, routes, and delivers messages.
  3. Consumer: processes a message.
  4. Queue: holds messages for competing consumers.
  5. Topic or event bus: distributes messages to subscriptions according to routing rules.
  6. Stream: retains an ordered sequence of records for replay and analysis.
  7. Dead-letter queue (DLQ): stores messages that cannot be processed successfully.

The broker is not merely a faster database. Its guarantees, retention model, ordering rules, acknowledgement protocol, and failure behaviour must be understood before it is selected.

1.3 Synchronous and asynchronous communication

In synchronous communication, a caller waits for a response. This is appropriate when an immediate result is necessary, but it creates temporal coupling: both services must be available at the same time. Asynchronous messaging removes this requirement. A producer can submit work and continue, while a consumer processes it later.

Asynchrony improves resilience and elasticity, but introduces eventual consistency. A read immediately after a successful command may temporarily show old data. User interfaces should therefore communicate states such as Payment pending rather than implying that all downstream work has completed.

2. Messaging Models

2.1 Queues and competing consumers

A queue generally delivers each message to one consumer in a consumer group. Multiple workers can read from the same queue, increasing throughput. A message is usually hidden or reserved after delivery and removed only after successful acknowledgement.

Queues are suitable for image processing, invoice generation, email delivery, and background jobs. They provide work distribution rather than broadcast. If three independent services must each receive an event, a single queue is usually insufficient; separate subscriptions are required.

A queue's approximate processing capacity can be considered as:

C = n × r

where C is capacity in messages per second, n is the number of concurrently active consumers, and r is the sustainable processing rate of one consumer in messages per second. This is an engineering approximation. It assumes consumers are similarly capable, the broker is not the bottleneck, and sufficient work is available. Network, database, rate limits, and cold-start effects may reduce actual capacity.

2.2 Publish–subscribe

In publish–subscribe (pub/sub), a producer publishes to a topic and multiple subscriptions receive independent copies. Subscribers may be services, functions, analytics pipelines, or notification systems. Pub/sub reduces producer knowledge of consumers and supports independent evolution.

A subscription commonly has its own retry policy, filtering rule, acknowledgement state, and dead-letter destination. Filtering should be based on stable attributes such as event type, tenant, or region. A consumer should not need to receive every event merely to discard most of them.

2.3 Event buses and routing

An event bus routes events between producers and targets using rules. A rule may match event type, source, account, tenant, or payload attributes. Event buses are useful for integrating bounded contexts and cloud services. They often support fan-out, filtering, cross-account delivery, and managed targets.

An event bus differs conceptually from a stream. A bus focuses on routing and delivery to targets; a stream focuses on durable ordered retention and replay.

2.4 Event streams

A stream is an append-oriented sequence of records. Records are commonly assigned to partitions. Ordering is typically guaranteed only within a partition, not across the entire stream. A partition key, such as accountId or orderId, should be chosen when all events for an entity must retain relative order.

Consumer groups allow several instances of one application to divide partitions among themselves. Different consumer groups can independently read the same retained stream. This supports replay, historical analysis, and construction of new projections.

Stream retention allows a consumer to reset its position and replay records. Replay is powerful but requires deterministic, idempotent processing and compatibility between historical events and current code.

2.5 Comparison of messaging models

ModelPrimary purposeTypical delivery relationshipRetention emphasis
QueueWork distributionOne worker in a groupUntil acknowledgement or expiry
Pub/sub topicBroadcastOne copy per subscriptionOften short to moderate
Event busRouting and integrationRule-selected targetsUsually delivery-oriented
Event streamOrdered history and replayConsumer groups read recordsExplicit long-term retention

3. Delivery Semantics and Reliability

3.1 At-most-once

At-most-once delivery attempts delivery once and does not retry after a failure. A message may be lost, but duplicates are avoided by the transport. This can be acceptable for telemetry where occasional loss is tolerable. It is inappropriate for payments or inventory unless another mechanism guarantees correctness.

3.2 At-least-once

At-least-once delivery retries until acknowledgement or expiry. A consumer may receive the same message more than once. This is the common practical model for cloud queues and event subscriptions because it prioritises avoiding loss.

Consumers must therefore be idempotent. An operation is idempotent when applying it repeatedly has the same intended effect as applying it once. A consumer may store the event identifier in a durable inbox table before or as part of applying the business change. The database operation and duplicate check should be designed atomically where possible.

3.3 Exactly-once claims

Exactly-once delivery is difficult across independent systems. A broker may provide exactly-once processing within a restricted transaction boundary, but external effects such as sending an email, charging a card, or calling another API can still be repeated. In practice, systems usually implement effectively-once business outcomes using at-least-once delivery, idempotency keys, transactional writes, and reconciliation.

3.4 Acknowledgement and visibility timeout

A consumer acknowledges a message after it has completed the required durable work. Acknowledging before the database commit risks loss. Acknowledging only after all work is complete may cause redelivery if processing exceeds the visibility timeout.

If the visibility timeout is too short, active work may be delivered to another consumer. If it is too long, recovery from a crashed consumer is delayed. Long-running jobs may extend the lease, or they may be decomposed into smaller tasks.

3.5 Retry and backoff

Retries are useful for transient failures such as temporary database unavailability. They are harmful for permanent failures such as invalid schema or a rejected business rule. A common policy uses bounded exponential backoff with jitter:

tk = min(tmax, t0 × 2k) + J

Here, t_k is the delay before retry k, t_0 is the initial delay, t_max is the maximum delay, and J is a random jitter value within an agreed range. The formula assumes retries are numbered from k = 0 and that the service has a finite retry budget. Jitter prevents many consumers from retrying simultaneously after a shared outage.

Retry decisions should classify errors as:

  • transient and retryable;
  • permanent and non-retryable;
  • unknown and subject to cautious bounded retry.

3.6 Dead-letter handling

A dead-letter queue stores messages that exceed retry limits, expire, or fail validation. DLQs are not disposal bins. They require ownership, alerting, retention policies, access control, and a replay procedure.

A safe replay process should:

  1. inspect the message and failure reason;
  2. determine whether the defect is in data, code, configuration, or a downstream dependency;
  3. correct the cause;
  4. test replay in a controlled environment;
  5. replay with rate limits and monitoring;
  6. prevent duplicate side effects through idempotency;
  7. record the outcome.

Poison messages should not be endlessly retried. Schema validation, maximum payload sizes, and quarantine workflows reduce operational risk.

3.7 Ordering, duplication, and causality

Ordering is often narrower than expected. A queue may preserve order only for a single message group; a stream may preserve order only within a partition; parallel consumers may complete work in a different order from delivery. If ordering is essential, define the ordering scope and use an entity key, sequence number, or version check.

Correlation identifiers connect related messages, while causation identifiers show why a message was produced. Together they support tracing, audit, and diagnosis of distributed workflows.

4. Event Sourcing and CQRS

4.1 Event sourcing

Event sourcing stores state changes as an append-only sequence of domain events rather than storing only the latest state. Current state is obtained by replaying events, often into a projection.

For example, an account might have:

AccountOpened → MoneyDeposited → MoneyWithdrawn.

The balance is derived from these events. Event sourcing provides an audit trail, temporal reconstruction, and the ability to create new projections. It also requires event versioning, retention management, careful privacy treatment, and robust replay performance.

An event is a historical fact and should not be silently edited. Corrections are normally represented by compensating events. Snapshots can reduce replay cost by storing state after a known event position; the snapshot remains an optimisation, not the authoritative history.

4.2 CQRS

Command Query Responsibility Segregation (CQRS) separates the model used to change data from the model used to read data. Commands validate intent and enforce business rules. Queries use read-optimised projections, possibly in a different database.

CQRS does not require event sourcing, and event sourcing does not automatically require CQRS. They are frequently combined because an event log can feed multiple read projections.

The principal trade-off is complexity. Separate models improve scalability and domain clarity, but introduce projection lag, rebuilding procedures, schema evolution, and operational overhead.

4.3 Projection consistency

If an event is committed at time t₀ and a projection reflects it at time t₁, projection lag is:

L = t₁ − t₀

L is measured in time units and is meaningful only when both timestamps use a consistent clock or a broker offset is used instead. Monitoring lag helps identify overloaded consumers and informs user-interface expectations.

5. Distributed Transactions: Sagas

A distributed transaction cannot normally rely on one ACID transaction spanning independent services. A saga divides a business transaction into local transactions. Each local transaction commits independently and publishes an event or command that advances the saga.

Every step should have a compensating action where meaningful. Compensation is not a perfect rollback: an email cannot always be unsent, and a financial transfer may require a new corrective transaction.

5.1 Choreography

In choreography, services react to one another's events without a central coordinator. For example:

OrderPlaced → InventoryReserved → PaymentAuthorized → OrderConfirmed.

Advantages include autonomy and low central coordination. Disadvantages include implicit control flow, difficult global visibility, and the risk of cyclic or overly complex event dependencies.

5.2 Orchestration

In orchestration, a saga coordinator sends commands to services and records progress. The coordinator can implement timeouts, retries, compensation, and explicit state transitions. This improves visibility but introduces a central component that must be designed for availability and must not contain excessive business logic.

5.3 Saga design rules

A robust saga should define:

  • the state machine and valid transitions;
  • correlation and idempotency identifiers;
  • timeout and cancellation behaviour;
  • compensation for each completed step;
  • handling of partial success;
  • operator intervention procedures;
  • audit records and customer-visible status.

6. Applied Case: Illustrative Cloud Commerce Platform

Background

Consider a fictional online retailer hosted with serverless functions. Its order service, inventory service, payment service, notification service, and analytics service are independently deployed.

Problem

The original design performs a synchronous chain of calls. During promotions, payment latency causes request timeouts. Some orders are charged even when the order response reports failure. Inventory and payment states occasionally disagree.

Analysis

The system has temporal coupling, insufficient idempotency, and no explicit recovery model. A single request attempts to complete several independent business transactions. Retrying the HTTP request can repeat the charge.

Approach

The platform adopts an event-driven saga:

  1. The order service commits OrderPlaced with a unique order identifier.
  2. An event bus routes the event to inventory and payment subscriptions.
  3. Inventory emits InventoryReserved or InventoryRejected.
  4. Payment processes an idempotency key derived from the order identifier and emits PaymentAuthorized or PaymentFailed.
  5. An order-process coordinator records events and issues compensation when necessary.
  6. If payment fails after reservation, the inventory service processes ReleaseInventory.
  7. Notification and analytics consume independent subscriptions.
  8. Failed messages are retried with bounded backoff and moved to DLQs for investigation.

Each consumer stores processed event identifiers. The payment provider receives an idempotency key so that a retry does not create a second charge. The order read model displays Pending, Confirmed, Rejected, or Requires review.

Outcome

The platform absorbs bursts through queues, scales consumers independently, and avoids presenting a false immediate success. Partial failures become visible states rather than hidden inconsistencies. The design does not eliminate failure; it makes failure recoverable and observable.

Learning points

The case demonstrates that messaging is not simply a transport replacement. Correctness depends on durable state transitions, idempotency, compensation, clear ownership, and operational controls.

7. Implementation and Operational Best Practices

Message and schema design

Use stable event names, explicit versions, documented schemas, and backward-compatible evolution. Prefer additive changes. Consumers should tolerate unknown fields and should not assume that event order is globally total. Avoid placing secrets or unnecessary personal data in events.

Reliability engineering

Use durable storage before acknowledging work. Set retry limits, visibility timeouts, and retention periods deliberately. Apply circuit breakers to failing downstream services. Use rate limits and backpressure so that consumers do not overwhelm databases or external APIs.

A simple queueing relationship is often expressed through Little's Law:

N = λ × W

N is the average number of items in the system, λ is the long-run arrival rate, and W is the average time an item spends in the system. The relationship assumes a stable system over the measurement interval and consistent units, such as messages and messages per second, producing seconds. It helps estimate how backlog grows when arrival rate persistently exceeds processing capacity.

Observability

Monitor queue depth, age of the oldest message, processing latency, retry count, DLQ volume, consumer error rate, stream lag, and throughput. Propagate trace and correlation identifiers. Logs should record outcomes and identifiers without exposing credentials or sensitive payloads.

Security and governance

Apply least-privilege permissions separately to publishing, consuming, replaying, and administering messages. Encrypt data in transit and at rest. Define retention and deletion policies, especially for personal or regulated data. Event immutability can conflict with privacy obligations; designs may need tokenisation, encryption-key destruction, minimised payloads, or a controlled erasure strategy.

Audit administrative actions such as replay, redrive, schema changes, and access to DLQs. Multi-tenant systems should enforce tenant isolation in routing, authorisation, encryption, and observability.

Testing

Test duplicate delivery, out-of-order events, delayed consumers, broker unavailability, malformed payloads, downstream timeouts, replay, and partial saga completion. Contract tests verify that producers and consumers agree on schemas. Property-based and fault-injection testing can reveal assumptions that ordinary happy-path tests miss.

8. Contemporary Developments and Critical Evaluation

Managed serverless event services increasingly provide integrated routing, filtering, workflow execution, and observability. Stream processing and change-data-capture systems allow database changes to feed analytics and projections. However, these capabilities can produce hidden coupling and cloud-provider dependence.

AI services are often integrated through asynchronous jobs because model inference, document processing, and embedding generation may be slow or expensive. Such systems require explicit handling of model version, prompt or input provenance, confidence, human review, and potentially sensitive data. An event saying that an AI classification completed should identify the model version and evaluation status; it should not be treated as inherently correct.

The central architectural judgement is not whether a system can be made asynchronous, but whether asynchronous complexity creates sufficient value. A small operation may be clearer with a synchronous transaction. EDA is most valuable where independent scaling, integration, resilience, replay, or temporal decoupling justify the additional operational discipline.

Chapter Summary

Event-driven architecture uses messages and events to decouple producers from consumers. Queues distribute work, pub/sub broadcasts events, event buses route messages, and streams retain ordered records for replay. At-least-once delivery is common and requires idempotent consumers, durable acknowledgement, bounded retries, and dead-letter workflows. Ordering is usually limited to a partition or message group.

Event sourcing represents state as an event history, while CQRS separates command and query models. Sagas coordinate multi-service business processes through local transactions and compensation. Choreography distributes control through events; orchestration makes control explicit through a coordinator. Reliable implementations also require schema governance, observability, security, privacy controls, testing, and operational procedures for replay and recovery.

Key Terms

Event: A record that a significant fact has occurred.

Command: A message requesting an action.

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

Queue: A work-distribution mechanism generally consumed by one worker in a group.

Publish–subscribe: A model in which multiple subscriptions receive an event independently.

Event bus: A routing service that directs events to selected targets.

Event stream: A retained, ordered sequence of records supporting consumer positions and replay.

Consumer group: A set of consumers sharing responsibility for reading work.

Acknowledgement: A confirmation that processing has completed sufficiently for removal or checkpointing.

Idempotency: The property that repeated application produces the same intended outcome as one application.

At-least-once delivery: Delivery that may duplicate messages but aims to avoid loss.

Dead-letter queue: A destination for messages that cannot be successfully processed.

Event sourcing: Persisting domain changes as an append-only sequence of events.

CQRS: Separating models and responsibilities for commands and queries.

Projection: A read-oriented representation derived from events.

Saga: A distributed transaction composed of local transactions and compensations.

Choreography: Saga coordination through decentralised event reactions.

Orchestration: Saga coordination by an explicit coordinator.

Eventual consistency: A model in which replicas or projections converge after a delay.

Backpressure: Controlling incoming work when processing capacity is constrained.

Review Questions

Short Answer Questions

  1. What is the difference between an event and a command?
  2. Why do at-least-once systems require idempotent consumers?
  3. What is the purpose of a dead-letter queue?
  4. How does a consumer group operate in an event stream?
  5. Distinguish event sourcing from CQRS.

Long Answer Questions

  1. Compare queues, pub/sub systems, event buses, and event streams, including suitable applications for each.
  2. Explain acknowledgement, visibility timeout, retry, backoff, and dead-letter handling in a reliable serverless consumer.
  3. Discuss the advantages and limitations of event sourcing and CQRS when used together.
  4. Compare choreography and orchestration for implementing a saga.
  5. Explain how security, privacy, schema evolution, and observability affect event-driven architecture.

Analytical / Application Questions

  1. Design a messaging architecture for a document-processing service that receives large bursts, occasionally encounters malformed files, and must not process a document twice.
  2. An order workflow reserves inventory successfully, but payment repeatedly fails. Specify the saga state, compensation, retry behaviour, customer-visible status, and operational alerts.
  3. A stream consumer's backlog grows continuously while arrival rate remains stable. Use capacity reasoning and Little's Law to diagnose possible causes and propose corrective actions.

Multiple Choice Questions with Answers

Question 1

Which messaging model is primarily intended to distribute work among competing workers?

A. Queue

B. Broadcast topic with independent subscriptions

C. Event stream replay

D. Event schema registry

Correct Answer: A. Queue

Question 2

What is the main reason an at-least-once consumer must be idempotent?

A. Messages are always delivered in global order

B. The same message may be delivered more than once

C. The broker never stores messages

D. Consumers cannot acknowledge messages

Correct Answer: B. The same message may be delivered more than once

Question 3

Which action is normally safest before acknowledging a message?

A. Log only the message type

B. Start an untracked external request

C. Complete the required durable business change

D. Delete the source database record

Correct Answer: C. Complete the required durable business change

Question 4

What is the principal purpose of a dead-letter queue?

A. To permanently hide all failed messages

B. To store messages requiring investigation after processing failure

C. To guarantee global ordering

D. To replace schema validation

Correct Answer: B. To store messages requiring investigation after processing failure

Question 5

In a partitioned event stream, ordering is typically guaranteed at which scope?

A. Across every topic in the organisation

B. Across all consumer groups

C. Within an individual partition

D. Only after all consumers stop

Correct Answer: C. Within an individual partition

Question 6

Which statement about event sourcing is correct?

A. It stores only the latest row state

B. It represents changes as an append-oriented event history

C. It eliminates the need for schema evolution

D. It guarantees that external side effects occur once

Correct Answer: B. It represents changes as an append-oriented event history

Question 7

What distinguishes orchestration from choreography in a saga?

A. Orchestration uses an explicit coordinator

B. Orchestration forbids compensation

C. Choreography requires a single central controller

D. Choreography cannot use events

Correct Answer: A. Orchestration uses an explicit coordinator

Question 8

Given N = λ × W, what is N when the arrival rate is 20 messages per second and average time in the system is 3 seconds?

A. 6 messages

B. 17 messages

C. 23 messages

D. 60 messages

Correct Answer: D. 60 messages

Question 9

Which retry strategy helps prevent many consumers from retrying simultaneously?

A. Unlimited immediate retries

B. Fixed zero-second delay

C. Bounded exponential backoff with jitter

D. Acknowledging before processing

Correct Answer: C. Bounded exponential backoff with jitter

Question 10

What is a major trade-off introduced by CQRS?

A. It removes the need for read models

B. It can improve read scalability but introduces projection lag and operational complexity

C. It guarantees synchronous consistency everywhere

D. It prevents independent deployment

Correct Answer: B. It can improve read scalability but introduces projection lag and operational complexity

Serverless Computing & Microservices
Chapter 6 of 15