← All cloud projects
LIVE us-east-1 2026
CLOUD PROJECT · 2026

Event-driven order pipeline

A public demo you can actually use: submit an order and it runs a real pipeline — API Gateway, Lambda, DynamoDB Streams, SQS, a dead-letter queue and an EventBridge Pipe. It is also the project where the course's own architecture turned out to be broken, because a Pipe sharing a queue with a worker silently eats the worker's messages.

API GatewayLambdaDynamoDB StreamsSQSEventBridge PipesSNSCloudWatch

Try it → Submit an order and it runs the real pipeline. Orders are deleted automatically after 24 hours.

The pipeline

AWS CloudAPI GatewaythrottledLambdaCreateOrderDynamoDBorders · TTLLambdaProcessOrderSNS topicfan-outSQSorder-processingLambdaOrderWorkerSQSdead-letter queueCloudWatchalarmSQSpipe-sourceEventBridgePipeSQShigh-value-ordersBrowserdemo pageEmailPOSTPENDINGStreamsPROCESSINGpublishrawrawpollCOMPLETED · FAILED3 receivesvisible ≥ 1ALARMpollamount > 10000

Ingest (top): browser → API Gateway → CreateOrder → DynamoDB as PENDING. DynamoDB Streams → ProcessOrder (PROCESSING) → SNS fan-out, raw delivery, one queue per consumer. Left: worker queue → OrderWorker (COMPLETED or FAILED); three failed receives → dead-letter queue → CloudWatch alarm → email. Right: Pipe queue → EventBridge Pipe, amount over 10,000 → high-value-orders queue.

requestasync / eventfailure / failover Hover, tap or Tab to a component to see what it does and why. Swipe sideways for the whole diagram.

Three things to try on the demo: a normal order completes; an amount over 10,000 is rejected as a business failure and routed as high-value; and the word FAIL in the notes triggers a technical failure that retries three times and lands in the dead-letter queue.

The architecture in the course does not work

The exercise this came from puts the EventBridge Pipe’s source and the worker Lambda’s event-source mapping on the same SQS queue. Both are pollers. A standard queue delivers each message to exactly one consumer — and a Pipe deletes the messages its filter rejects rather than returning them.

So the Pipe doesn’t occasionally win a race. It eats normal orders and drops them on the floor.

First run, three test orders:

TestAmountExpectedActual
Normal1,200COMPLETEDstuck PROCESSING — worker never saw it
High-value20,000FAILED + routedrouted, but DynamoDB never reached FAILED
Forced failure1,500retry ×3 → DLQstuck PROCESSING, DLQ empty, alarm never fired

All three queues read zero. The messages were gone, and nothing reported an error.

Isolating it

I stopped the Pipe and changed nothing else, then resubmitted the same orders:

normal order   -> COMPLETED    completedAt 01:59:08.879Z
forced failure -> PROCESSING, retried 3x, DLQ=1 at t≈70s
                  alarm OK -> ALARM at t≈125s, email delivered

One variable changed, both failures resolved. That’s the difference between a diagnosis and a guess.

The fix

The Pipe and the worker are two independent consumers of the same event. They shouldn’t compete for one queue — they should each get their own copy. ProcessOrderFunction now publishes to SNS, and both queues subscribe.

Both subscriptions use RawMessageDelivery=true. Without it, SNS wraps the payload in an envelope and both the worker’s JSON.parse(record.body) and the Pipe’s $.body.amount filter break.

Afterwards, all three cases behave correctly in the same run — the high-value order is marked FAILED by the worker and routed by the Pipe, from one submission. Under the original topology only one of those could ever happen.

What the DLQ is for

The dead-letter queue is the cheapest failure signal in an event-driven system. One alarm on ApproximateNumberOfMessagesVisible >= 1 covers every failure mode downstream of the queue.

It also forces a distinction that’s easy to get backwards:

Business failureTechnical failure
Exampleamount over the limitDynamoDB timeout, malformed JSON
Handlingconsume the message, mark FAILEDthrow — let SQS retry
Retried?No — retrying can’t change the answerYes, 3×
Reaches the DLQ?NoYes

Get it backwards and you either retry forever or lose errors silently.

Running it in public

A public, unauthenticated endpoint that writes to DynamoDB is a cost-amplification target, so it’s guarded:

It costs about $0.10/month because everything in it is pay-per-request and idle genuinely costs nothing.

Other defects in the source material