Reference implementation of three microservices patterns on AWS: Outbox, Idempotency, and Saga. Companion artifact to the FactualMinds blog post Microservices Design Patterns on AWS (2026).
A working order-fulfillment system across four Lambda-based microservices (Order, Payment, Inventory, Shipping), wired together with DynamoDB Streams, EventBridge, and Step Functions. Deploy it, place an order, watch the saga run, and break a step to see compensation fire.
The point of the repo is to show how the three patterns connect. Each of them shipped many times in isolation — the value here is seeing them work together in one stack.
The order service writes the order entity and the event record in a single
TransactWriteItems call. A DynamoDB stream filtered for pk prefix
OUTBOX# invokes the publisher Lambda, which forwards to EventBridge and
starts the saga. Trade-off: no separate outbox table, so the orders table
holds two row-types, but the transactional write stays trivial. Look at
services/order_service/handler.py.
Every saga task uses @idempotent_function with a DynamoDB persistence
layer and a JMESPath of idempotency_key. The saga derives a deterministic
key per step from the order ID, so the same order replayed end-to-end never
double-charges, double-reserves, or double-ships. Trade-off: Powertools
adds a ~12 MB layer to your function bundle and one DynamoDB lookup per
invocation. Worth it. Look at services/payment_service/handler.py.
A standard workflow orchestrates payment → inventory → shipping with
Retry (full-jitter exponential backoff) and Catch blocks that route
to compensation tasks. Compensation runs in reverse order: a shipping
failure releases inventory then refunds payment; an inventory failure
just refunds payment. Trade-off: orchestration centralises state but
introduces a single coordinator (acceptable in 2026 — Step Functions
Standard is durable and auditable). Look at
state_machines/order_saga.asl.json.
See architecture.md for the data-flow diagram and
the rationale behind each AWS service choice.
- AWS account with permission to create Lambda, DynamoDB, EventBridge, Step Functions, IAM roles, and CloudWatch log groups
- AWS SAM CLI ≥ 1.130
- Python 3.12 (only needed for
sam buildto package the functions) jq(forbin/start-saga.sh)- The Powertools layer ARN for your region. The default in
template.yamlis the us-east-1 / Python 3.12 / x86_64 layer; substitute your own at deploy time with--parameter-overrides PowertoolsLayerArn=.... The current ARNs are at https://docs.powertools.aws.dev/lambda/python/latest/.
make build
make deploy # first run is interactive; subsequent runs reuse samconfig.toml
make start-saga # POSTs a sample order; prints the Step Functions execution ARN
make tail-logs FN=OrderService
make destroy # tear it all downOpen the printed Step Functions execution ARN in the AWS Console to see
the saga visualisation. Force a failure by editing services/inventory_service/handler.py
to raise RuntimeError("simulated stock-out") inside _reserve_stock,
redeploy, and run make start-saga again — you'll see the RefundPayment
compensation branch fire.
This is a pattern reference, not a production stack. Specifically:
- Not multi-region. Active-passive replication, route 53 failover, and cross-region DynamoDB global tables are out of scope. See the blog's Bulkhead and multi-region sections.
- Not multi-account. A real org would split prod / staging / dev across AWS accounts and use VPC Lattice for cross-account service connectivity.
- No production auth. The API Gateway is open to the internet — add Cognito, an IAM authoriser, or API keys before exposing this anywhere.
- No chaos / failure-injection tests. The saga is correct on paper; validate it under real failure with FIS or Gremlin before betting a business on it.
- No DLQ on the EventBridge bus. Attach SQS DLQs to each rule before production traffic.
- Mocked side-effects. Payment, inventory, and shipping write to DynamoDB instead of calling Stripe / a WMS / a carrier API. Replace the inner functions; keep the wrapping idempotency decorators.
For each gap above, the corresponding pattern in the FactualMinds blog post explains the production-grade fix.
| Component | Version |
|---|---|
| AWS SAM CLI | 1.130.0 |
| Python | 3.12.4 |
aws-lambda-powertools[tracer] |
≥ 3.0 |
boto3 |
≥ 1.34 |
| Powertools Lambda layer | AWSLambdaPowertoolsPythonV3-python312-x86_64:6 (us-east-1 default) |
| Region tested | us-east-1 |
Last updated: 2026-05-02