Animated Architecture Patterns

System Map

One request and one event through a typical system: DNS, CDN, load balancer, gateway, services, cache, database, queue and search.

Acme Shoponline shop ▲ synchronous: the customer waits ▼ asynchronous: after the response miss Browsercustomer Inbox DNS Route 53 Load balancer NGINX · ALB CDN CloudFront API gateway Kong · API Gateway Kubernetes · ECS Catalog /products Orders /orders Cache Redis · ElastiCache Database PostgreSQL · RDS, Aurora GET /products/42 POST /orders POST /orders ×3 ✓ token ✓ rate limit HIT product:42 not queried Event streamKafka · SNS + SQS topic orders email lag 0 lag 1 search lag 0 lag 1 lag 2 lag 3 analytics lag 0 lag 1 OrderPlaced unread by search Email worker AWS Lambda Search indexer Kubernetes · ECS Analytics loader Kafka Connect · Firehose Email Postfix · Amazon SES Search OpenSearch · Elasticsearch Data lake Amazon S3 customer ✉ order 1001 ✓ order 1001 indexed ✓ event 1001 stored ✕ crashed 1 order behind 2 orders behind 3 orders behind order 1001 order 1002 order 1003 order 1004 outbox row outbox: sent 200 OK 201 Created 201 · 1002 201 · 1003 201 · 1004 ObservabilityPrometheus + Grafana · CloudWatch metrics · logs · traces from every box search lag 0 1 2 3 outbox relay

Step 1 · A page view. The browser resolves shop.example in DNS and loads images and scripts from the nearest CDN edge. Its API call GET /products/42 passes the load balancer and the API gateway, which checks the token and the rate limit, to the Catalog service, which finds product 42 in the cache (Redis) and answers without querying the database.

Step 2 · An order, one transaction. POST /orders takes the same path to the Orders service. One database transaction writes order 1001 and an OrderPlaced row in an outbox table, and the customer gets 201 Created as soon as it commits. Nothing else is called while the customer waits.

Step 3 · The event fans out. The outbox relay publishes OrderPlaced to the event stream (topic orders). Three consumer groups read it independently: the Email worker (a Lambda function) sends the confirmation, the Search indexer adds order 1001 to the search index and the analytics loader stores the event in the data lake on S3. All of it happens after the customer already has the response.

Step 4 · Running it. Every box sends metrics, logs and traces to observability (Prometheus and Grafana, or CloudWatch). When the Search indexer crashes, orders 1002–1004 still succeed and their events wait in the stream; the dashboard shows the search group's consumer lag climbing to 3, and search results stay behind until the indexer is back. That delay is the price of the asynchronous half: eventual consistency.

The problem

Most systems start as one service in front of one database, and for a while that is all they need. Then pressure arrives, one kind at a time: the same product page is read over and over, sending the confirmation email inside the checkout makes the customer wait on a mail service, the search box needs typo tolerance that SQL LIKE cannot give, and images travel from one data centre to customers on every continent. Each pressure has a well-known component that relieves it, and each component is a product with its own vocabulary: Kafka, Redis, an API gateway, a CDN.

Learning those products one at a time hides the questions that matter when you design: where does each one sit, what talks to it, and what changes in the rest of the system once it is there? This page is the map for the System Components and AWS Services pages of this catalog. It follows one page view and one order through an ordinary online shop, Acme Shop, so that every other page can be placed on it.

How it works

The shop has two halves, and the dotted line between them is the most useful thing on the map.

The synchronous half: the customer waits

Everything above the line happens while the customer waits for an answer, so every box there adds latency and every box there can fail the request.

  • DNS turns shop.example into addresses. In Amazon Route 53 an alias record can point a name, even the zone apex, straight at a CloudFront distribution or a load balancer, and Route 53 follows that resource when its addresses change.
  • The CDN serves images and scripts from an edge location near the customer. CloudFront routes each request to the edge location with the lowest latency and answers from its cache when it can; only misses travel on to the origin (CDN edge caching).
  • The load balancer spreads requests over healthy instances (load balancing). An Application Load Balancer works at layer 7, so it can route by path or host name; NGINX does the same job when you run it yourself.
  • The API gateway is the front door for the APIs. It checks the caller's token and applies rate limits before any service sees the request (API gateway). Amazon API Gateway can validate JWTs with a JWT authorizer on HTTP APIs, and it throttles with a token bucket, answering 429 Too Many Requests when the bucket is empty; Kong does the same work through plugins.
  • The services hold the business logic. Catalog serves /products and Orders serves /orders; both run as containers, scheduled by Kubernetes or Amazon ECS.
  • The cache keeps hot data in memory. In step 1 the Catalog service finds product 42 in Redis and answers without querying the database (cache-aside); the dashed line is the path a miss would take.
  • The database is the system of record: PostgreSQL, run by you or by Amazon RDS or Aurora.

The order: one transaction, then the answer

The order in step 2 is where the two halves meet. The Orders service has to store order 1001 and tell the rest of the shop about it. Doing that as two writes, first the database and then a message broker, loses one of them whenever the process dies in between. So it writes both in one database transaction, the order row and an OrderPlaced row in an outbox table, and answers 201 Created as soon as the transaction commits (transactional outbox). The confirmation email, the search index and the analytics copy are not on the request path at all: the customer has the answer before any of them starts.

The asynchronous half: after the response

  • The outbox relay reads committed outbox rows and publishes them. It can poll the table or follow the database's change log; Debezium's Outbox Event Router does the second for PostgreSQL (change data capture).
  • The event stream keeps OrderPlaced on the topic orders and lets every interested consumer read it at its own pace (publish-subscribe, event-driven architecture). With Kafka each consumer group keeps its own position in the log; with Amazon SNS fanning out to Amazon SQS each subscriber gets its own queue. Either way a slow or broken consumer holds back only itself.
  • Workers do the follow-up work: the Email worker (a Lambda function), the Search indexer (a container) and the analytics loader (Kafka Connect, or Amazon Data Firehose). Several copies of one worker can split a group's work between them as competing consumers.
  • Email sending goes through a mail service such as Amazon SES, or a mail server you run, such as Postfix.
  • Search (OpenSearch or Elasticsearch) answers the queries SQL handles badly: full text, typos, relevance ranking, facets. It holds a copy of data owned elsewhere, kept current by the indexer.
  • The data lake keeps every event in Amazon S3 for analytics.

Observability across both halves

Every box sends metrics, logs and traces to one place: Prometheus scraping metrics and Grafana charting them, or Amazon CloudWatch. It is how the failure in step 4 gets noticed at all. When the Search indexer crashes, nothing on the request path fails: orders 1002 to 1004 succeed, their events wait in the stream, and the symptom is the consumer lag of the search group climbing to 3 on the dashboard while search results fall behind the database. A trace that crosses the stream needs the trace context carried inside the event (distributed tracing), and logs from every box need one home (centralized logging).

Where it fits

This page is the map; the other pages zoom in. Each row is a role, with the products that usually fill it and the patterns of this catalog it implements or supports.

Role What it does in Acme Shop Open source AWS Patterns
DNS Turns shop.example into addresses Usually a managed service Amazon Route 53 Active-passive failover
CDN Serves images and scripts from the edge Usually a managed service Amazon CloudFront CDN edge caching
Load balancer Spreads requests over healthy instances NGINX Elastic Load Balancing (ALB) Load balancing, health endpoint monitoring
API gateway Checks tokens, limits rates, routes to services Kong Amazon API Gateway API gateway, rate limiting, gateway offloading, JWT validation
Services Business logic, run as containers Kubernetes Amazon ECS, Amazon EKS Microservices, autoscaling
Cache Keeps hot reads in memory Redis, Valkey Amazon ElastiCache Cache-aside
Database Stores orders; one transaction per order PostgreSQL Amazon RDS, Amazon Aurora Transactional outbox, read replicas
Outbox relay Publishes committed outbox rows Debezium — Change data capture
Event stream or queue Keeps events; each consumer reads at its own pace Apache Kafka Amazon MSK; Amazon SNS with Amazon SQS Publish-subscribe, event-driven architecture, queue-based load leveling
Workers Do follow-up work off the request path Kafka Connect, any container AWS Lambda, Amazon Data Firehose Competing consumers, web-queue-worker, idempotent consumer
Search Full-text queries over a copy of the data OpenSearch, Elasticsearch Amazon OpenSearch Service CQRS, materialized view
Email sending Delivers the confirmation Postfix Amazon SES —
Data lake Keeps every event for analytics — Amazon S3 Medallion architecture
Observability Metrics, logs, traces and alerts Prometheus, Grafana, OpenTelemetry Amazon CloudWatch Distributed tracing, centralized logging, telemetry pipeline

Component pages: Apache Kafka, RabbitMQ, Redis & Valkey, PostgreSQL, MongoDB, Apache Cassandra, Elasticsearch & OpenSearch, NGINX, Docker & Containers, Kubernetes, etcd, Prometheus & Grafana, HashiCorp Vault, Keycloak, Apache Flink, Amazon VPC, Amazon S3, Amazon SQS, Amazon SNS, Amazon EventBridge, Amazon Kinesis Data Streams, Amazon DynamoDB, Amazon RDS & Aurora, AWS Lambda, AWS Step Functions, Amazon ECS & Fargate, Amazon Route 53, AWS IAM, Amazon Cognito and Amazon CloudWatch so far.

Licences matter when you choose, and several changed recently (checked October 2026):

  • Redis 8 and later is offered under a choice of RSALv2, SSPLv1 or AGPLv3. Valkey, forked from Redis just before Redis moved to source-available licences and backed by the Linux Foundation, is BSD-licensed, and Amazon ElastiCache offers Valkey, Memcached and Redis OSS.
  • Elasticsearch added AGPLv3 in 2024 as a third option next to SSPL and the Elastic License 2.0. OpenSearch, the fork of Elasticsearch 7.10, is Apache 2.0 and a Linux Foundation project; Amazon OpenSearch Service runs OpenSearch and legacy Elasticsearch OSS up to 7.10.
  • Grafana moved to AGPLv3 in 2021. Kong Gateway's source is Apache 2.0, and NGINX uses a two-clause BSD licence.

When to use it

Start with one service and one database, and add a part when its pressure shows up in measurements, not in advance. A shop with a few hundred orders a day may need nothing from the asynchronous half: one service, PostgreSQL and a CDN go a long way.

Add When What it costs
A CDN Static files are a large share of the traffic, or customers are far from the servers Another cache to invalidate: ship files under versioned names
A cache Reads dominate and the same items are read again and again Stale reads, invalidation rules, and a database that must survive a cold cache
A load balancer and more instances One instance is no longer enough, or must not be a single point of failure Health checks, and state that has to live outside the instances
An API gateway Several services and clients share auth, rate limits and routing Another hop on every request, which must itself stay highly available
A queue Work can wait, or arrives in bursts At-least-once delivery, retries and a dead-letter queue to watch
A stream Several consumers need the same events, or need to replay them A cluster or service to run, lag to watch, and event schemas that become a contract
Search Queries outgrow SQL: full text, typos, ranking, facets A second copy of the data that lags behind, and reindexing

Trade-offs

  • Every box is another thing to run. Each needs patching, capacity planning, backups or replicas, alerts and a budget line, managed service or not.
  • Every hop is another way to fail. The synchronous half is only as available as the chain of boxes the customer waits on, so each call there needs a timeout, and retries need limits.
  • The asynchronous half lags. In step 4 search is 3 orders behind the database. That is eventual consistency: the interface has to say "we will email you", and a search that cannot find a brand-new order has to be acceptable.
  • Delivery is at least once. Brokers and Lambda event source mappings can deliver an event twice, so every consumer must be idempotent.
  • Failures go quiet. A crashed consumer breaks no request. Without consumer lag on a dashboard and an alert on it, the first report comes from a customer.

Implementation notes

  • Write the order and the outbox row in one transaction. With Debezium's default column names, the order in step 2 is:

    BEGIN;
    INSERT INTO orders (id, total) VALUES (1001, 59.90);
    INSERT INTO outbox (id, aggregatetype, aggregateid, type, payload)
    VALUES (gen_random_uuid(), 'order', '1001', 'OrderPlaced',
            '{"orderId": 1001, "total": 59.90}');
    COMMIT;
    

    The outbox id travels with the event as a header, which gives consumers a key for dropping duplicates. Debezium names the topic outbox.event. plus the aggregatetype value by default; set route.topic.replacement to publish to a topic such as orders instead.

  • Watch lag per consumer group. For Kafka, describe the group; the LAG column is LOG-END-OFFSET minus CURRENT-OFFSET for each partition:

    bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group search
    

    Amazon MSK publishes consumer-lag metrics such as MaxOffsetLag and EstimatedMaxTimeLag to CloudWatch or to Prometheus, and for SQS the equivalents are ApproximateNumberOfMessagesVisible and ApproximateAgeOfOldestMessage. Alert on lag that keeps growing, not on a single non-zero value.

  • Catch up within the retention. Waiting events are only safe for as long as the stream keeps them: 168 hours (7 days) by default for a Kafka 4.3 broker (log.retention.hours), and 4 days by default for an SQS queue, adjustable from 1 minute to 14 days. A Kafka consumer that restarts resumes from its group's last committed offset, and an SQS consumer finds its messages still waiting; either way it has to catch up before that window closes.

  • Give the cache a TTL and delete on write. A key that expires on its own bounds how stale a read can get, even when an invalidation is lost.

  • Throttle at the gateway and back off at the client. A 429 from the gateway is a signal for the client to slow down, not to retry at once.

  • Carry the trace context inside events. Put it in the message headers so the asynchronous half shows up in the same trace as the request that caused it; OpenTelemetry has conventions for Kafka, SQS and SNS.