Core lesson
Event-Driven Order Processing
Evolve synchronous checkout into durable order acceptance, event routing, buffered consumers, coordinated fulfillment, and retry-safe processing.
After this, you will understand
Learn to separate five jobs that are often blurred together: accepting business state, routing facts, buffering work, coordinating steps, and notifying subscribers.
Article guideprerequisites, mental models, and concepts
Article overview
Three useful mental models
Store the order first, publish an OrderCreated fact, route it with EventBridge, buffer independent workers with SQS, coordinate ordered fulfillment with Step Functions, and make every side effect retry-safe.
A synchronous dependency chain blocks checkout, a database write and event publish drift apart, one queue is mistaken for fanout, or retries repeat payments and inventory changes.
Create a durable acceptance boundary, publish reliable business events, give each consumer its own delivery shape, and design duplicates, poison messages, backpressure, and compensation explicitly.
Think before reading
Why does an at-least-once message consumer need idempotency?
Because the same message can be delivered again after a timeout or failure, and repeating the handler must not repeat the business effect.
Connected learning
These lessons add useful context to the current core lesson.Concepts Covered
- Synchronous versus asynchronous order work
- Durable order acceptance
- The database-write/event-publish gap
- EventBridge event buses and rules
- SQS buffering, visibility timeout, and backpressure
- Lambda polling and partial batch failure handling
- At-least-once delivery and idempotent consumers
- Dead-letter queues and replay
- Step Functions orchestration and compensation
- SNS notification fanout
- AWS Solutions Architect Associate exam (SAA-C03) messaging recognition patterns
1. Situation
The cart API from the previous study now accepts checkout requests.
After an order is accepted, the business wants to:
- reserve inventory;
- authorize payment;
- arrange fulfillment;
- email or text the customer;
- update analytics; and
- notify fraud and operations systems.
Only one outcome must complete before the checkout API responds:
The order must be durably accepted, or the client must receive a clear failure.
Analytics can be late. Email can be retried. A temporary shipping outage should not prevent new orders from being accepted. Payment and inventory need more careful coordination because one may succeed while the other fails.
This creates a new architecture problem:
synchronous acceptance -> asynchronous fulfillment and side effects
The services overlap at the edges, but they do not have the same central job.
2. Naive Design
Naive option 1: keep the entire order inside one request
flowchart LR Client["Client"] --> API["Checkout API"] API --> Inventory["Inventory"] --> Payment["Payment"] --> Shipping["Shipping"] --> Email["Email"] --> Analytics["Analytics"]
The code is easy to trace when everything works. In production, every dependency becomes part of checkout availability and latency.
If analytics pauses for thirty seconds, the customer waits. If email fails, the system must decide whether to reject an otherwise valid order. If the client retries after a timeout, the entire chain may run again.
Naive option 2: publish an event before saving the order
Consumers receive OrderCreated, but the order database write then fails. They try to process an order that does not exist.
Naive option 3: save the order, then publish separately
1. database write succeeds
2. process crashes
3. event publish never happens
The customer sees an accepted order, but fulfillment never learns about it. This is the dual-write gap: two systems changed by two separate operations cannot be assumed to succeed atomically.
Naive option 4: use one SQS queue for every subscriber
Payment, analytics, and email workers all poll the same queue. Each message is normally processed by one competing consumer, so the services divide the messages instead of each receiving a copy.
A work queue is not automatically pub/sub fanout.
3. What Breaks
Follow four failure stories.
Failure 1: shipping is unavailable
In the synchronous chain, checkout fails even though the company could safely accept the order and arrange shipping later.
The architecture needs a durable asynchronous boundary so one consumer's outage does not stop order intake.
Failure 2: a payment response is lost
The payment provider authorizes the charge, but the worker times out before recording success. The message returns and is processed again.
At-least-once delivery needs an idempotency key such as the order ID or payment operation ID. Retrying transport must not mean charging twice.
Failure 3: one malformed message fails every batch
A Lambda polls ten SQS messages. Nine succeed and one malformed message fails. If the integration treats the whole batch as failed, all ten become visible again and repeat work.
The architecture needs partial batch failure handling and a dead-letter path for poison messages.
Failure 4: the queue keeps growing quietly
SQS preserves the work while a downstream service slows down. That prevents immediate loss, but it also hides a widening business delay unless the team monitors the age of the oldest message.
A queue is a buffer, not a cure. The architecture needs backpressure-aware concurrency and operational alarms.
4. AWS Architecture
Build the system by communication job.
Step 1: create a durable order-acceptance boundary
The checkout API validates the request and durably stores the order before reporting success.
flowchart LR Client["Client"] --> API["Checkout API"] API --> Store["Order store<br/>durable acceptance"] Store -->|"Order accepted"| API --> Client
The system must also preserve the intent to publish OrderCreated. Common solutions include:
- a transactional outbox, where the order and an outbox record are written in one database transaction and a relay publishes the event; or
- a database change stream that reliably exposes the committed change to a publisher.
flowchart LR API["Order service"] -->|"One supported transaction"| State["Order + publish intent"] State --> Relay["Outbox or change-stream relay"] Relay --> Event["OrderCreated event"]
This complexity exists to prevent the gap where business state commits but the event disappears. Review the Outbox Pattern when this boundary needs deeper implementation detail.
For the rest of the lesson, assume OrderCreated represents a committed business fact, not a hopeful command sent before storage succeeds.
Step 2: use EventBridge to route the business fact
Publish the event to an EventBridge event bus. A rule matches fields such as event source and detail type, then sends the event to a target.
flowchart LR Event["OrderCreated"] --> Bus["EventBridge event bus<br/>routing desk"] Bus -->|"Fulfillment rule"| Workflow["Fulfillment target"] Bus -->|"Analytics rule"| Analytics["Analytics target"] Bus -->|"Notification rule"| Notify["Notification target"]
EventBridge is useful when producers should not know every consumer and routing depends on event content.
Configure target retry policy and an EventBridge dead-letter queue when undeliverable target events must be retained after delivery attempts are exhausted.
That DLQ answers:
Could EventBridge deliver the event to its configured target?
It is different from a consumer queue's DLQ, which answers whether a delivered work item repeatedly failed during processing.
Step 3: put SQS in front of independent workers that need buffering
Give each independent consumer its own queue when it needs durable backlog, controlled processing speed, or isolation from outages.
flowchart LR Bus["EventBridge"] -->|"Analytics rule"| AnalyticsQ["Analytics SQS queue"] Bus -->|"Fraud rule"| FraudQ["Fraud SQS queue"] AnalyticsQ --> AnalyticsWorker["Analytics worker"] FraudQ --> FraudWorker["Fraud worker"]
Each queue receives the events for its consumer. Workers for the same queue compete to process its messages; separate queues let independent services each receive and retain their own copy.
SQS also creates backpressure. A downstream service can process at its safe rate while new messages wait durably in the queue.
Step 4: process SQS messages with retry safety
Lambda uses an event source mapping to poll SQS and invoke the function with a batch of messages.
flowchart LR Queue["SQS queue<br/>durable inbox"] -->|"Lambda polls a batch"| Worker["Lambda consumer"] Worker -->|"Success: message removed"| Done["Business effect recorded"] Worker -.->|"Failure: visible again later"| Queue Queue -->|"Too many failed receives"| DLQ["Consumer DLQ"]
When a message is received, SQS hides it for the visibility timeout. If processing succeeds, it is deleted. If processing does not complete successfully, it can become visible and be delivered again.
Therefore:
- set the visibility timeout to cover expected processing and retry behavior;
- make the consumer idempotent;
- use partial batch responses so successful records are not retried merely because another record failed; and
- send repeatedly failing messages to a DLQ with enough context for diagnosis and replay.
The DLQ is an isolation area, not automatic resolution. Someone or something must inspect, fix, and redrive the messages deliberately.
Step 5: use Step Functions when the business process has ordered state
Fulfillment is different from an independent analytics update. It has explicit sequence and decisions:
flowchart LR Start["OrderCreated"] --> Reserve["Reserve inventory"] Reserve -->|"Reserved"| Pay["Authorize payment"] Reserve -->|"Unavailable"| Reject["Reject or backorder"] Pay -->|"Paid"| Ship["Request fulfillment"] Pay -->|"Failed"| Release["Release inventory"] Ship --> Confirm["Confirm order"]
Use Step Functions when the workflow benefits from visible state, ordered steps, branching, timeouts, retries, Catch paths, and execution history.
The Release inventory branch is a compensating action. It does not reverse time; it performs a new business operation that attempts to offset an earlier successful step.
Standard Workflows fit durable, auditable, potentially long-running coordination. Express Workflows fit high-volume, short-duration execution when their different history and delivery semantics match. Even with workflow guarantees, external side effects and explicitly configured retries should use idempotency.
Step 6: use SNS for notification fanout
After the order is confirmed, publish a notification to an SNS topic when several supported subscribers should receive the same announcement.
flowchart LR Confirmed["OrderConfirmed"] --> Topic["SNS topic<br/>announcement system"] Topic --> Email["Email channel"] Topic --> SMS["SMS channel"] Topic --> Queue["Subscriber SQS queue"]
SNS pushes copies to subscribers. SQS stores work for polling consumers. Subscribing SQS queues to SNS combines fanout with durable consumer-specific buffering.
Do not add SNS when EventBridge rules already provide the required application routing. Use it when its push notification or topic fanout model is the actual requirement.
Completed architecture
flowchart LR Client["Client"] --> API["Order API"] API --> State["Durable order<br/>+ publish intent"] State --> Relay["Reliable event relay"] Relay --> Bus["EventBridge<br/>content-based routing"] Bus --> Workflow["Step Functions<br/>ordered fulfillment"] Bus --> AnalyticsQ["SQS<br/>analytics buffer"] AnalyticsQ --> Analytics["Lambda<br/>idempotent consumer"] Bus --> FraudQ["SQS<br/>fraud buffer"] FraudQ --> Fraud["Fraud consumer"] Workflow --> Topic["SNS<br/>notification fanout"] Topic --> Channels["Email, SMS, or subscriber queues"]
This is not a requirement to use every service in every workflow. It is a map of distinct communication needs.
5. Request Or Data Flow
Learn three lifecycles.
Lifecycle 1: order acceptance
- The client sends a checkout request with an idempotency key.
- The API validates the request and records the order durably.
- The same durable boundary records the intent to publish
OrderCreated. - The API returns an accepted order ID.
- A relay publishes the committed fact to EventBridge.
The client no longer waits for analytics, email, or the entire fulfillment workflow.
Lifecycle 2: independent queued consumer
- An EventBridge rule sends
OrderCreatedto the analytics SQS queue. - Lambda polls a batch and processes each message.
- The handler records an idempotency marker or performs an idempotent update.
- Successful messages are removed.
- Failed messages become visible for retry; repeated failures move to the consumer DLQ.
Lifecycle 3: coordinated fulfillment failure
- Step Functions reserves inventory.
- Payment authorization fails after its configured retry policy.
- A
Catchpath records the failure and requests inventory release. - The order state becomes failed or requires review.
- Operations can inspect execution history and business records.
The workflow makes the failure path visible. It does not make compensation infallible; compensating actions also need monitoring and retry safety.
6. Security Controls
Limit who can publish and consume
Give producers permission to publish only the expected events to the intended event bus. Give EventBridge the target permissions it needs. Give consumers access only to their queues, data stores, APIs, and secrets.
Use resource policies carefully for cross-account event buses, SNS topics, and SQS queues.
Keep sensitive data out of broad events
Events should normally contain identifiers and the safe context consumers need. Do not broadcast card details, secrets, authentication tokens, or unnecessary personal data through every target.
A useful event might say:
{
"detail-type": "OrderCreated",
"detail": {
"orderId": "order-8472",
"customerId": "customer-19"
}
}
Consumers retrieve protected details through authorized systems when necessary.
Encrypt and audit deliberately
Use service encryption and customer-managed KMS keys when key-control requirements justify them. Ensure key policies and IAM permissions allow the publishing and consuming services that need access.
Use CloudTrail for control-plane changes and CloudWatch logs for application and workflow behavior. Redact sensitive payloads before logging.
7. Resilience Controls
The order is not safely accepted until its durable state and publish intent survive the request. An event bus cannot repair an order that was acknowledged only in memory.
Assume messages and events can be delivered more than once. Build idempotent consumers for payments, inventory, notifications, and analytics. Use stable operation identifiers and conditional state transitions.
For SQS consumers:
- configure visibility timeout around processing behavior;
- use a redrive policy and consumer DLQ;
- enable partial batch failure reporting where appropriate; and
- monitor queue age, not only queue depth.
For EventBridge targets, configure retry and an EventBridge DLQ when delivery failures must be retained. Know which DLQ you are investigating.
For Step Functions, define Retry only for errors that are safe to retry, use Catch for explicit failure paths, and monitor failed or timed-out executions. Compensation should be represented as business state, not assumed to be a perfect rollback.
8. Performance Controls
SQS absorbs bursts but increases asynchronous completion time when arrival rate exceeds processing rate. That is controlled degradation, not free throughput.
Set Lambda concurrency so consumers drain the queue without overwhelming payment providers, databases, or partner APIs. The correct target is sustainable downstream throughput, not the fastest possible empty queue.
Batching reduces polling and invocation overhead but increases the amount of work affected by one invocation. Partial batch responses reduce unnecessary replay of successful records.
Choose queue type from ordering requirements:
- Standard queues favor high throughput and at-least-once delivery with best-effort ordering.
- FIFO queues provide FIFO ordering and deduplication features within their documented model, with different throughput and design constraints.
Use Step Functions Standard for durable, auditable coordination and Express for suitable short, high-volume workflows. Choose from execution requirements, not because one name sounds faster.
9. Cost Controls
Event-driven architecture replaces idle servers with several usage meters:
- EventBridge events and target invocations;
- SQS API requests and payload transfer;
- Lambda invocations, duration, and logs;
- Step Functions state transitions or Express execution resources;
- SNS deliveries;
- KMS, data transfer, and downstream service usage.
Filter events before invoking expensive consumers. Give EventBridge rules precise patterns and avoid accidental loops where a target emits an event that matches the same rule repeatedly.
Keep event payloads small. Store large documents in S3 or another suitable data store and send references plus integrity and identity metadata.
Batch SQS messages where it improves efficiency, but account for failure isolation and processing time. Retain logs long enough for operations without storing every payload indefinitely.
Do not introduce EventBridge, SNS, SQS, and Step Functions into the same branch unless each has a distinct requirement. Managed services reduce operations, not architectural cost or cognitive load.
10. Exam Variants
| If the question says... | Think... | Why |
|---|---|---|
| "Route events from many producers by content" | EventBridge | Rules match event fields and send events to targets |
| "Buffer work while a consumer is unavailable" | SQS | The queue retains backlog for polling workers |
| "Only one worker should process each work item" | SQS with competing consumers | A queue distributes work rather than broadcasting it to every worker |
| "Several subscribers need the same notification" | SNS topic, often with subscriber SQS queues | SNS fans out; SQS can add durable per-subscriber buffering |
| "Coordinate ordered steps, retries, branches, and audit history" | Step Functions | A state machine makes workflow control explicit |
| "A message failed repeatedly and needs isolation" | SQS DLQ | Poison messages leave the main consumer path for investigation |
| "EventBridge cannot deliver to its target" | EventBridge target retry and DLQ | This is a routing-delivery failure, not a consumer-processing failure |
| "Prevent duplicate payment after retry" | Idempotent consumer and stable operation ID | At-least-once delivery can repeat a message |
| "Preserve ordering for a message group" | SQS FIFO | FIFO is an ordering signal when its constraints fit |
| "Database commit must not lose event publication" | Transactional outbox or reliable change stream | It closes the dual-write gap |
11. Common Traps
| Trap | Better reasoning |
|---|---|
| "Every post-order action belongs in the checkout request." | Acknowledge after durable acceptance; move independently retryable work off the client path. |
| "Write the order, then publish, and assume both happen." | Preserve publish intent atomically with business state or derive it from a reliable change stream. |
| "EventBridge is just a durable work queue." | EventBridge routes events; SQS provides consumer-controlled backlog and polling. |
| "One SQS queue fans each message to every service." | Consumers on one queue compete; give independent subscribers separate queues or use fanout. |
| "SNS and SQS are interchangeable." | SNS pushes copies to subscribers; SQS stores work for consumers to poll. |
| "A DLQ fixes bad messages." | A DLQ isolates failures; diagnosis, correction, and redrive still require an owner. |
| "Exactly-once workflow means payment needs no idempotency." | Retries and external side effects still require business-level duplicate protection. |
| "A growing queue means the system is resilient." | The work is retained, but increasing message age means the business is falling behind. |
| "Retry the entire Lambda batch when one record fails." | Use partial batch reporting when supported so successful records do not repeat unnecessarily. |
| "Compensation rolls the distributed system backward." | Compensation is a new business action that can also fail and need retries. |
Final Mental Model: One-Minute Review
| Term | Exact job | Remember it as |
|---|---|---|
| Durable order state and publish intent | Ensure an accepted order cannot disappear between storage and event publication | The signed intake receipt |
| EventBridge | Match business events and route them to selected targets | The mailroom sorting desk |
| SQS | Retain backlog and let workers process at a controlled rate | A durable inbox |
| Lambda consumer | Poll and process queued work | The inbox worker |
| Visibility timeout | Temporarily hide a received message while processing is attempted | The checked-out work slip |
| DLQ | Isolate repeatedly undeliverable or unprocessable work for investigation | The exception tray |
| Step Functions | Coordinate ordered steps, branches, retries, and failure paths | The workflow supervisor |
| SNS | Push one notification to multiple subscribers | The announcement system |
| Idempotency | Make repeated delivery produce one intended business effect | The duplicate stamp checker |
flowchart LR API["Order API"] --> State["Durable order<br/>+ publish intent"] State -->|"Reliable relay publishes"| Bus["EventBridge<br/>route the fact"] Bus --> Queue["SQS<br/>buffer independent work"] Queue --> Worker["Lambda<br/>idempotent consumer"] Queue -->|"Repeated failure"| DLQ["DLQ<br/>exception isolation"] Bus --> Flow["Step Functions<br/>coordinate fulfillment"] Flow --> Topic["SNS<br/>fan out notifications"]
The central distinction is route versus buffer versus coordinate versus broadcast. EventBridge chooses destinations, SQS holds work, Lambda performs work, Step Functions manages workflow state, and SNS copies notifications to subscribers. Idempotency makes the whole design safe under repeated delivery.
12. Related Topics
Use Amazon EventBridge for event buses, patterns, rules, targets, retries, and archives. Use Amazon SQS for queue types, visibility, redrive, and polling. Review AWS Step Functions for Standard and Express workflows, and Amazon SNS for topic fanout and notification endpoints.
Continue to Secure Partner File Ingest On S3 to apply the same buffering, duplicate-handling, and trust-boundary thinking to external file arrivals.
Official AWS references:
Finished reading?
Your reading history is saved in this browser so you can continue later.
Recommended Next
Secure Partner File Ingest On S3AWS Architecture Scenarios17 min readThis applies the foundation mental models to a real architecture decision instead of a service inventory.
Optional exploration
These links add context, but they do not replace the recommended next lesson.
Related Patterns
Reusable architecture moves built from these ideas.
More Links
Additional references connected to this page.
Arcflow Plus is coming — review drills, research breakdowns, more AI. Get one email at launch.