Course outline

15.8

Serverless Architectures

A mobile to-do API, a global blog with emails and thumbnails, microservices, and offloading downloads to the edge.

28 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Build a serverless mobile back end step by step: a REST API over HTTPS with API Gateway, Lambda and DynamoDB, sign-in with a Cognito user pool, per-user S3 folders through an identity pool, DAX for hot reads and API Gateway caching.
  • Build a global serverless website: static files from S3 through CloudFront with OAC, a public REST API, DynamoDB global tables, a welcome email through DynamoDB Streams, Lambda and SES, and thumbnails from S3 events.
  • Explain synchronous and asynchronous patterns between microservices, service discovery, and the pros and cons of microservices, and show which problems serverless solves.
  • Offload heavy static downloads from an EC2 Auto Scaling group by putting CloudFront in front, without changing the application.
  • Choose across the whole chapter: Lambda vs Fargate vs EC2, DynamoDB vs RDS, API Gateway vs ALB, Step Functions vs chaining with SQS, and check a design against the Well-Architected serverless questions.

#Why whole architectures

Lessons 15.1 to 15.7 taught one service at a time. Real systems, and most exam questions, combine five or six of them: a requirement list arrives (“mostly read”, “users upload files”, “global readers”, “send a welcome email”), and you must pick the piece that meets each line. This lesson walks through four classic designs and builds each one up a requirement at a time, then closes the chapter with a chooser and a checklist.

Read every requirement as a keyword that points to a service:

Requirement in the questionPiece to addTaught in
REST API over HTTPS, no serversAPI Gateway + Lambda15.6, 15.1
Database that scales to millions of requestsDynamoDB15.4
Managed, serverless sign-inCognito user pool15.7
Users access AWS resources directly (their own S3 folder)Cognito identity pool + IAM policy variables15.7
Mostly reads, high read throughput on DynamoDBDAX15.5
Repeated identical API responsesAPI Gateway caching15.6
Static files for a global audienceS3 + CloudFront with OAC11.1
Data close to users in several RegionsDynamoDB global tables (or Aurora Global Database)15.5, 7.3
“When X is saved, do Y”DynamoDB Streams or S3 events → Lambda15.5, 10.7
Absorb bursts, retry failuresSQS (with a dead-letter queue)13.1, 13.2
One event, many consumersSNS fan-out to SQS13.4
Multi-step workflow with waits and retriesStep Functions15.7

#Case 1: the MyTodoList mobile app

A to-do app for the students of a Dhaka university. The requirements, in our own words:

  1. A REST API over HTTPS.
  2. A serverless architecture: no instances to patch or scale.
  3. Users read and write files in their own folder in S3, directly.
  4. Users sign in through a managed, serverless authentication service.
  5. Users write to-dos but mostly read them.
  6. The database must scale and give high read throughput.

#The REST layer

The core is the most common serverless shape of all: client → API Gateway → Lambda → DynamoDB.

  • API Gateway gives the HTTPS endpoint, validates requests, throttles, and verifies authentication before anything else runs.
  • Lambda holds the business logic. It runs only when a request arrives and is billed per request and per millisecond.
  • DynamoDB stores the to-dos with userId as the partition key, so “all of Riya’s to-dos” is one cheap Query, never a Scan.

For sign-in the app talks to a Cognito user pool. It checks the password (and MFA if enabled), and returns JWT tokens: an ID token and an access token, valid for one hour by default, and a refresh token (30 days by default). The app sends the token in the Authorization header. A Cognito authorizer on the API Gateway method checks the token’s signature and expiry; an expired or forged token gets 401 and Lambda is never invoked or billed. The function reads the user’s ID from the verified token (the sub claim), never from the request body.

#Giving each user their own S3 folder

Requirement 3 says users interact with S3 directly, not through Lambda. Pushing every photo through Lambda would cost compute time and hit the payload limits of API Gateway (10 MB) and Lambda (6 MB for a synchronous invoke).

The classic wrong answer is to put an IAM user’s access keys inside the app. Anyone can unpack a mobile app and pull out the keys, and those keys open every user’s folder forever.

The right answer is a Cognito identity pool:

  1. The app gives its user pool token to the identity pool.
  2. The identity pool calls STS and returns temporary AWS credentials (about one hour) for its authenticated IAM role.
  3. That role’s policy uses a policy variable so each user reaches only their own prefix:
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::todo-files-example/users/${cognito-identity.amazonaws.com:sub}/*"
}

IAM substitutes the caller’s identity ID at request time. One role serves every user, yet a request for another student’s folder gets 403 AccessDenied. The same pattern works for other services: with the condition key dynamodb:LeadingKeys, each user can query only the DynamoDB items whose partition key is their own ID (lesson 15.7).

#High read throughput: DAX

Users mostly read, and during exam week thousands of them open the same lists again and again. Put DAX (DynamoDB Accelerator) between Lambda and the table:

  • It is an in-memory, write-through cache built for DynamoDB, with the same API: only the client in the code changes.
  • Cache hits come back in microseconds; the table’s read capacity barely moves during the spike.
  • The item cache TTL is 5 minutes by default.
  • DAX serves eventually consistent reads. A strongly consistent read passes through to the table.
  • A DAX cluster lives in your VPC, so the Lambda function must be attached to that VPC (lesson 15.3).

#Caching at API Gateway

For responses that are the same for many callers, cache one layer earlier. A REST API stage can keep responses for a TTL (default 300 seconds, from 0 to 3,600). A cache hit skips Lambda and DynamoDB completely. HTTP APIs have no cache; if you need it, choose a REST API or put CloudFront in front.

Two traps:

  • The cache key is the resource path plus the parameters you choose. If a per-user response is cached without the Authorization header (or the user ID) in the key, one user gets another user’s list.
  • Writes do not clear the cache. After a POST, a cached GET stays stale until the TTL runs out. Use a short TTL for changing data, or let clients send Cache-Control: max-age=0 (they need execute-api:InvalidateCache).

MyTodoList: a serverless mobile back end, one piece at a time

A to-do app for the students of a Dhaka university. Users sign in, mostly read their lists, sometimes write, and keep files in their own S3 folder. Watch the REST path, an expired token, the per-user S3 folder, DAX and the API Gateway cache, including the ways each one can go wrong.

Scenario

Cognito proves who you are; API Gateway checks it before any code runs.

Mobile app
Cognitouser + id pools
API GatewayREST · HTTPS
Lambdatodo API
DynamoDBtodos
sign in
Step 1 of 6
1

Requirements: a REST API over HTTPS, no servers, a managed sign-in service. Riya opens the app and signs in with her email and password. The app talks to a Cognito user pool, not to our code: we never store passwords ourselves.

#What the mobile case teaches

  • A serverless REST API: HTTPS, API Gateway, Lambda, DynamoDB.
  • Cognito handles authentication; API Gateway checks it before any code runs.
  • Cognito identity pools give app users temporary, tightly scoped credentials to call AWS directly (S3 here; DynamoDB or Lambda work the same way).
  • Two caching layers: DAX for the database reads, API Gateway caching for whole responses.

#Case 2: the MyBlog.com website

A Bangladeshi travel blog read in Dhaka, London and Toronto. The requirements:

  1. The website must scale globally.
  2. Posts are rarely written, often read.
  3. Part of the site is purely static files; the rest is a dynamic REST API.
  4. Cache wherever possible.
  5. Every new subscriber gets a welcome email.
  6. Every photo uploaded gets a thumbnail.

#Static content, globally and securely

The HTML, CSS, JavaScript and images go into an S3 bucket, and CloudFront serves them from edge locations near each reader. The first reader near an edge causes one fetch from S3; everyone after is served from the edge cache. That is exactly the “rarely written, often read” shape a CDN is built for.

To keep the bucket private, use Origin Access Control (OAC):

  • CloudFront signs its requests to S3 with SigV4.
  • The bucket policy allows s3:GetObject only for the principal cloudfront.amazonaws.com with a condition AWS:SourceArn equal to this distribution.
  • Block Public Access stays on. A direct S3 URL gets 403, so nobody can bypass CloudFront’s geo restrictions, WAF rules or caching.

Trap: OAC works with the bucket’s REST endpoint. If CloudFront points at the S3 static website endpoint, that is a custom origin, OAC cannot be used, and the bucket must be public (lessons 10.3 and 11.1). OAC replaced the older origin access identity (OAI).

#A public REST API

Comments and like counts change, so they come from API Gateway → Lambda → DynamoDB (with DAX if reads get hot). Anyone may read posts, so this API is public: no Cognito needed. Protect a public API with throttling, usage limits and AWS WAF instead (lesson 21.6).

#DynamoDB global tables

Readers are on three continents. Make the table a global table: a replica in each chosen Region, every replica accepts reads and writes, and changes replicate asynchronously, usually within a second. Deploy the API stack in each Region and route readers with Route 53 latency routing and health checks (lessons 8.3, 8.4).

  • Replicas are eventually consistent across Regions by default. Two writes to the same item in two Regions in the same moment are settled by last writer wins, so one edit silently disappears. Send all writes for an item to one home Region if that matters.
  • Side note: since 2025, global tables also offer multi-Region strong consistency (three Regions, or two replicas plus a witness), for zero data loss at the cost of higher write latency. The exam’s default answer for “global, low-latency reads and writes” is still plain global tables.
  • The relational alternative is Aurora Global Database: one writer Region, read-only secondary Regions, replication typically under a second, and promotion of a secondary in a disaster (lesson 7.3).

MyBlog.com: static files at the edge, a public API, data in two Regions

A Bangladeshi travel blog read in Dhaka, London and Toronto. Posts are written rarely and read constantly. Watch static pages served from CloudFront, a reader who tries to skip CloudFront, a public API read, a write replicated by a global table, and a Region outage.

Scenario

The first reader near an edge fills the cache; everyone after is served locally.

ap-southeast-1
eu-central-1
Readers
CloudFront
S3static files
API GatewayREST
Lambda
Global table
Replica
GET /index.html
Step 1 of 3
1

The HTML, CSS, JavaScript and photos sit in a private S3 bucket. A reader in London opens the home page; DNS sends her to the nearest CloudFront edge location, not to Singapore.

#The welcome email flow

The sign-up function only saves the subscriber’s row and answers at once. The email is sent by a separate flow:

  1. DynamoDB Streams is enabled on the Users table. Each new item appears as an INSERT record, kept for 24 hours, in order per item.
  2. An event source mapping polls the stream and invokes the Welcome function with a batch of records. Lambda does the polling; you pay only for invocations.
  3. The function’s execution role allows ses:SendEmail, so it calls Amazon SES through the SDK. SES is the serverless way to send email: no mail server to run (lesson 25.2).

Why not send the email inside the sign-up function? Because then a slow or failing mail service slows or fails sign-up. With the stream, sign-up is fast and the email retries on its own.

The failure to know: for streams, Lambda retries a failing batch until the records expire by default, and the shard is blocked behind it. Set a maximum retry count and record age, turn on bisect batch on function error, and send failures to an on-failure destination (SQS, SNS or S3). A new AWS account also starts in the SES sandbox (separately in each Region): it can send only to verified addresses, at most 200 messages per 24 hours and 1 message per second, until you request production access.

#The thumbnail flow

The author uploads photos either through CloudFront to the bucket (OAC also signs uploads) or with S3 Transfer Acceleration; either way the bytes enter the AWS network at a nearby edge (lesson 10.6).

An S3 event notification (s3:ObjectCreated:*) invokes the Thumbnail function asynchronously. It reads the photo, writes a small version to a separate thumbnails bucket, and CloudFront serves both. A failed async event is retried twice, then goes to a DLQ or on-failure destination.

S3 can also send its events to SQS or SNS instead (the optional step on this diagram):

  • S3 → SQS → Lambda buffers a burst (500 photos after a trip), retries each failed photo with a visibility timeout, and parks poison messages in a dead-letter queue. S3 event destinations must be standard queues and topics, not FIFO.
  • S3 → SNS → several SQS queues fans one upload out to many consumers (thumbnail, virus scan, search index).
  • S3 → EventBridge gives content filtering and many targets (lesson 19.5).

Never write the output into the bucket (or prefix) that triggers the function: each thumbnail would trigger another thumbnail. Lambda’s recursive loop detection stops a chain after about 16 invocations for S3, SQS, SNS and EventBridge custom buses, but use a separate bucket or a prefix filter anyway.

MyBlog.com events: a welcome email and a thumbnail, with nobody polling

Top row: a new subscriber is saved, the table’s stream triggers a function, and SES sends the welcome email. Bottom row: a photo upload triggers a thumbnail function, directly or through an SQS buffer. Then see a failing email function, a burst of uploads and a function that triggers itself.

Scenario

The sign-up code only saves a row; the stream does the rest.

Reader
API Gateway
Sign-up
Users
Stream24 h
Welcome
SES
CloudFrontOAC
Uploads
Thumbnail
Thumbnails
POST /subscribePutItem
Step 1 of 4
1

Nusrat subscribes to the blog. The sign-up function saves her row in the Users table and answers at once. It does not send the email itself, so a slow mail service can never slow down sign-up.

#The website in one picture

  • Static content: S3 + CloudFront, the bucket private with OAC.
  • Dynamic content: a public serverless REST API (API Gateway, Lambda), with no Cognito because nobody signs in to read.
  • Global data: DynamoDB global tables (Aurora Global Database for relational data).
  • Events: DynamoDB Streams → Lambda (role with SES permission) → SES for the welcome email; S3 events → Lambda (directly, or via SQS or SNS) for thumbnails.

#Case 3: microservices

A Dhaka online shop wants to break its monolith into microservices: many services that talk to each other through APIs, each built in whatever shape suits it, so that each team gets a leaner development lifecycle: deploy, scale and fix its own service on its own schedule.

A typical environment looks like this, each service with its own DNS name in Route 53:

ServiceFront doorComputeData
catalog.example.comALBECS containersRDS
orders.example.comAPI Gateway (HTTPS)LambdaDynamoDB
search.example.comALBEC2 Auto Scaling groupElastiCache

The freedom is the point: nothing forces two services to use the same compute or the same database.

#Synchronous and asynchronous patterns

SynchronousAsynchronous

The caller sends a request and waits for the answer.

  • API Gateway or a load balancer (ALB, NLB) in front of each service; REST or gRPC calls.
  • Simple to reason about; the caller gets the answer at once.
  • Latencies add up along the chain, and every service in the chain must be up at the same moment. One slow service ties up all its callers (a cascading failure).

The caller hands off a message and moves on.

  • SQS queues, SNS topics (fan-out), Kinesis streams, EventBridge, and Lambda triggers such as S3 events.
  • Producers and consumers scale and fail independently; messages wait in the queue when a consumer is down.
  • The result is eventually consistent: the app shows “processing” for a while, and you must handle retries, duplicates and dead letters.

Rule of thumb: use a synchronous call only when the caller needs the answer right now (the price of an item at checkout). Everything else (shipping, invoices, loyalty points, emails) should be an event. When a synchronous call is unavoidable, protect it with short timeouts, retries with backoff, a circuit breaker and a cached fallback.

#Finding each other: service discovery

Services must find each other without hard-coded IP addresses, because containers and functions come and go:

  • Route 53 names for each service’s front door (alias records to ALBs or API Gateway custom domains), as in the table above.
  • AWS Cloud Map: a registry of service instances, queried through DNS or the API; ECS registers tasks in it automatically.
  • ECS Service Connect: a name like http://catalog inside the ECS namespace, with a managed proxy that adds retries and metrics (lesson 14.2).
  • Side note: Amazon VPC Lattice connects services across VPCs and accounts with built-in auth; it rarely appears in the exam.

Microservices: synchronous calls couple failures, queues absorb them

A Dhaka online shop splits its monolith. Orders runs on API Gateway and Lambda, Catalog on ECS behind an ALB with RDS, Shipping is a Lambda fed by a queue. Each service has its own DNS name. Watch routing, a synchronous call, what happens when Catalog is down, and the asynchronous path when Shipping is down.

Scenario

DNS names point to each service’s front door; each team picks its own stack.

Route 53
Users
API Gatewayorders
Orders
SNSorder-placed
SQS
ALBcatalog
CatalogECS
RDS
Shipping
catalog.?ALB
Step 1 of 3
1

The app asks Route 53 for catalog.example.com and gets the ALB’s address (an alias record). orders.example.com points to the API Gateway custom domain in the same way.

#The cost of microservices, and what serverless fixes

Microservices are not free. The usual challenges:

  • Repeated overhead for each new service: a pipeline, monitoring, a load balancer, a cluster, security setup.
  • Poor server density: many small services each keep their own half-idle instances.
  • Many versions of many services running at the same time, which makes testing and rollbacks complex.
  • More client code: every client must integrate with many separate endpoints.

Serverless patterns remove part of that pain:

  • API Gateway and Lambda scale automatically and you pay per use, so a small service costs nearly nothing when idle and nobody tunes server density.
  • You can clone an API and reproduce whole environments from a template (AWS Serverless Application Model, CloudFormation, CDK).
  • API Gateway can generate client SDKs from the API’s OpenAPI (Swagger) definition, so clients do not hand-write integration code.

#Case 4: offloading software updates

A Bangladeshi accounting-software company serves update files from EC2 instances in an Auto Scaling group across three AZs, behind a load balancer, reading the files from EFS. Most of the year this is quiet. On release day thousands of installations download the same 300 MB file at once: the ASG scales out, network transfer from EC2 explodes, and the bill follows. The constraint: do not change the application, but cut cost and CPU.

The answer is to put CloudFront in front of the existing load balancer:

  • No change to the architecture: the ALB becomes CloudFront’s origin, and the download hostname points to the distribution.
  • Update files are static: once a version is published it never changes, which is the perfect content to cache at the edge.
  • The EC2 instances are not serverless, but CloudFront is, and it scales for us. Each file is fetched from the origin roughly once per edge (or once per regional edge cache), not once per customer.
  • The ASG barely scales, so you save EC2 hours, EFS throughput and origin bandwidth, and availability improves because the edge absorbs the spike.

Two edge cases to design for:

  • Same name, new content: if latest.zip is replaced, edges keep the old file for up to the TTL (24 hours by default). Put the version in the file name (update-7.2.1.zip) so each release is a new cache key; for emergencies, create an invalidation (the first 1,000 paths a month are free).
  • Bypassing CloudFront: allow only the CloudFront managed prefix list in the ALB’s security group, or use a CloudFront VPC origin so the ALB can be internal (lesson 11.2).

Why not Global Accelerator? It speeds up the network path to your instances but does not cache: every byte would still come from EC2. Why not move the files to S3? That would work and be cheaper still, but it changes the application, which the question forbids.

Software updates: put CloudFront in front, change nothing else

A Bangladeshi accounting-software company serves its update files from EC2 instances in an Auto Scaling group behind an ALB, reading them from EFS. On release day thousands download the same 300 MB file. Compare the day without and with CloudFront, then two edge cases.

Scenario

Every download is served by an instance; the group scales out and the bill follows.

ASG
Customers
ALB
EC2
EC2
EFSupdate files
GET update-7.2.zipGETGET
Step 1 of 3
1

Version 7.2 is announced at 10 am. Every customer’s software requests update-7.2.zip from the ALB at the same time, and the two instances stream it from EFS.

#Choosing across the chapter

Every case above made the same few decisions. Here they are side by side.

#Compute: Lambda, Fargate or EC2

LambdaECS / EKS on FargateEC2 (or ECS / EKS on EC2)
UnitA function per eventA container task or podAn instance
Max run time15 minutesUnlimitedUnlimited
SizeUp to 10,240 MB memory, up to 6 vCPUUp to 16 vCPU, 120 GBAny instance type, GPUs
Scale to zeroYes, by itselfOnly if you set desired count to 0No
BillingPer request + GB-secondsPer second for configured vCPU and memoryPer second of instance, with Reserved Instances, Savings Plans, Spot
Best forShort, spiky, event-driven workLong-running services and jobs, containers, no serversSteady 24/7 load, GPUs, special OS or host access

#Data: DynamoDB or RDS / Aurora

DynamoDBRDS / Aurora
ModelKey-value and document; access patterns designed into keys and indexesRelational; joins and ad-hoc SQL
ScaleMillions of requests per second, on-demand modeVertical, plus read replicas; Aurora Serverless v2 scales capacity
Connections from LambdaHTTPS API, no connection pool neededConnection storms: use RDS Proxy (lesson 15.3)
GlobalGlobal tables, active-activeAurora Global Database, one writer Region
Pick it whenKnown access patterns, huge or spiky scale, serverless stackRelationships, transactions across many tables, reporting, existing SQL code

Chapter 16 compares all database families (16.1).

#Front door: API Gateway or ALB

API GatewayApplication Load Balancer
Built forAPIs: keys, usage plans, quotas, request validation, transformation, caching (REST), WebSocketHTTP routing to EC2, containers, IPs or Lambda
AuthIAM, Cognito, Lambda authorizer, resource policiesCognito or OIDC authentication on listener rules
Timeout29 s integration timeout by default (Regional and private REST APIs can raise it)Idle timeout up to 4,000 s
PricePer request (plus cache hours)Per hour plus capacity units
Pick it whenSpiky or low-volume APIs, API management features, Lambda back endsSteady high volume, long requests, container and instance fleets

#Workflow: Step Functions or SQS between functions

Step FunctionsSQS between Lambda functions
ShapeMany ordered steps, branches (Choice, Parallel, Map)One step per queue, each independent
WaitingWait states, callbacks with a task token for days (Standard: up to one year)Messages wait up to 14 days in the queue
ErrorsRetry and Catch per state, visible historyVisibility timeout retries, dead-letter queue
PriceStandard: per state transition; Express: per request and durationPer request, very cheap
Pick it whenOrchestration, human approval, audit trailBuffering and decoupling a single step, very high volume

Which serverless piece fits?

Nine real-life cases from the whole chapter. Read the story and the keywords, choose between the services that compete for it, then read why.

1. Choose a case(Right first time: 0/9)

Compute

A Dhaka e-commerce shop needs a small thumbnail for every product photo sellers upload. Each one takes about 2 seconds. Some hours bring 3,000 uploads, the night brings none.

Keywords:event-drivenseconds per jobidle at night

2. Your answer

#A Well-Architected serverless checklist

AWS publishes a Serverless Applications Lens for the Well-Architected Framework (chapter 26, lesson 26.1). The short version, pillar by pillar:

PillarAsk yourself
Operational excellenceIs the whole stack a template (AWS Serverless Application Model, CloudFormation, CDK)? Do we deploy with versions and aliases, and gradual traffic shifting? Are logs, metrics and traces (CloudWatch, X-Ray) on?
SecurityDoes each function have its own least-privilege execution role? Are APIs authenticated (Cognito, IAM, Lambda authorizer) or protected by WAF when public? Are secrets in Secrets Manager or Parameter Store, not in code? Are buckets private behind OAC?
ReliabilityDoes every async path have retries, a DLQ or on-failure destination, and idempotent consumers? Are bursts buffered with queues? Is reserved concurrency set so one function cannot starve the rest? Are timeouts shorter at each hop than at the caller?
Performance efficiencyIs memory tuned (it also sets CPU)? Are cold starts handled where they matter (provisioned concurrency, SnapStart)? Are hot reads cached (CloudFront, API Gateway cache, DAX)?
Cost optimizationIs Lambda on arm64 (Graviton) where possible? Is DynamoDB in the right capacity mode? Do steady 24/7 workloads belong on containers or EC2 instead? Are logs given a retention period?
SustainabilityDoes the system scale to zero when idle? Are we moving fewer bytes (caching, compression, filtering events at the source)?

#What goes wrong

SymptomLikely causeFix
Mobile users can read other users’ S3 filesShared IAM user keys in the app, or a role without a policy variableIdentity pool with ${cognito-identity.amazonaws.com:sub} in the resource
Users see each other’s to-do listsAPI Gateway cache key does not include the userAdd the Authorization header to the cache key, or do not cache per-user responses
Lambda times out talking to DAXFunction not in the DAX cluster’s VPC, or security group blocks the DAX port (8111, or 9111 with encryption)Attach the function to the VPC subnets, open the DAX port from the function’s security group
CloudFront returns 403 for every objectBucket policy does not allow this distribution’s ARN, or the origin is the website endpointFix the OAC bucket policy; use the REST endpoint
Welcome emails stopped, new subscribers stuckStream batch failing (SES sandbox, bug) and retried until it expiresMax retries and record age, bisect on error, on-failure destination; SES production access
Bill spikes after deploying the thumbnail functionOutput written into the triggering bucket: recursive loopSeparate output bucket or a prefix filter
Checkout fails whenever one internal service is downLong synchronous call chainMake non-urgent calls asynchronous (SNS, SQS); timeouts, circuit breaker
Customers get an old update fileSame file name reused, edge cache TTL not expiredVersioned file names; invalidation for emergencies
Comment edits vanish in a multi-Region blogConcurrent writes to the same item in two Regions, last writer winsWrite each item from one home Region

#Serverless architectures in system design

  • Start from the requirements list and map each line to a piece, as in the table at the top. Most exam answers are exactly that mapping.
  • Push work out of the request path: return to the user fast, and let streams, S3 events and queues do the slow parts.
  • Cache at every layer that can: CloudFront for static and cacheable responses, API Gateway caching for repeated API answers, DAX for hot DynamoDB items.
  • Let users talk to AWS directly when it is safe (S3 uploads with identity pool credentials or presigned URLs) instead of streaming bytes through Lambda.
  • Use managed services for undifferentiated work: Cognito for sign-in, SES for email, Step Functions for workflows.
  • Know when serverless stops paying: steady high volume, runs over 15 minutes, GPUs, or heavy relational reporting point to containers, EC2 and RDS.

#Try it yourself

Pieces of the MyBlog.com design, in ap-southeast-1, account 111122223333. Outputs are shortened.

# 1. Scaffold and deploy a serverless API (API Gateway + Lambda + DynamoDB) from a template
sam init --name blog-api-example --runtime python3.13 --app-template hello-world
sam build && sam deploy --guided
#   Outputs: ApiUrl = https://a1b2c3d4e5.execute-api.ap-southeast-1.amazonaws.com/Prod/

# 2. Turn on DynamoDB Streams on the Users table (new images are enough for a welcome email)
aws dynamodb update-table --table-name blog-users-example \
  --stream-specification StreamEnabled=true,StreamViewType=NEW_IMAGE
#   "LatestStreamArn": "arn:aws:dynamodb:ap-southeast-1:111122223333:table/blog-users-example/stream/2026-10-28T09:12:44.123"

# 3. Connect the stream to the Welcome function, with safe failure handling
aws lambda create-event-source-mapping --function-name welcome-email-example \
  --event-source-arn arn:aws:dynamodb:ap-southeast-1:111122223333:table/blog-users-example/stream/2026-10-28T09:12:44.123 \
  --starting-position LATEST --batch-size 10 \
  --maximum-retry-attempts 3 --maximum-record-age-in-seconds 3600 --bisect-batch-on-function-error \
  --destination-config '{"OnFailure":{"Destination":"arn:aws:sqs:ap-southeast-1:111122223333:welcome-failed-example"}}'
#   "State": "Creating", "MaximumRetryAttempts": 3, "BisectBatchOnFunctionError": true

# 4. Send a test email through SES (in the sandbox, both addresses must be verified)
aws sesv2 send-email --from-email-address hello@blog.example \
  --destination ToAddresses=nusrat@reader.example \
  --content 'Simple={Subject={Data=Welcome},Body={Text={Data=Thanks for subscribing}}}'
#   "MessageId": "0100018f-example-0000"

# 5. Trigger thumbnails only for uploads/ so the output in thumbs/ can never loop back
aws s3api put-bucket-notification-configuration --bucket blog-photos-example \
  --notification-configuration '{"LambdaFunctionConfigurations":[{
     "LambdaFunctionArn":"arn:aws:lambda:ap-southeast-1:111122223333:function:thumbnail-example",
     "Events":["s3:ObjectCreated:*"],
     "Filter":{"Key":{"FilterRules":[{"Name":"prefix","Value":"uploads/"}]}}}]}'

# 6. Turn a table into a global table by adding a replica in Frankfurt
aws dynamodb update-table --table-name blog-comments-example \
  --replica-updates '[{"Create":{"RegionName":"eu-central-1"}}]'
#   "Replicas": [{"RegionName": "eu-central-1", "ReplicaStatus": "CREATING"}]

# 7. A mobile user gets temporary credentials from an identity pool
aws cognito-identity get-id --identity-pool-id ap-southeast-1:11111111-2222-3333-4444-555555555555 \
  --logins cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_Example=eyJraWQiOi...
#   "IdentityId": "ap-southeast-1:9f8e7d6c-1234-5678-9abc-def012345678"
aws cognito-identity get-credentials-for-identity \
  --identity-id ap-southeast-1:9f8e7d6c-1234-5678-9abc-def012345678 \
  --logins cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_Example=eyJraWQiOi...
#   "Credentials": { "AccessKeyId": "ASIAEXAMPLE", "Expiration": "2026-10-28T10:15:00+06:00", ... }

# 8. Software updates: invalidate a reused file name at every edge
aws cloudfront create-invalidation --distribution-id E2EXAMPLE1234 --paths "/latest.zip"
#   "Status": "InProgress"

#Summary

Key takeaways

  • Mobile back end (MyTodoList): HTTPS REST with API Gateway → Lambda → DynamoDB; a Cognito user pool signs users in and API Gateway verifies the JWT before Lambda runs; a Cognito identity pool gives temporary credentials limited by ${cognito-identity.amazonaws.com:sub} to each user’s own S3 folder, never keys in the app; DAX for high read throughput; API Gateway caching (REST APIs, default 300 s) for repeated responses, with the user in the cache key.
  • Global website (MyBlog.com): static files in a private S3 bucket behind CloudFront with OAC (bucket policy for one distribution, REST endpoint only); a public REST API needs no Cognito; DynamoDB global tables (or Aurora Global Database) for global data, last writer wins on conflicts.
  • Events: DynamoDB Streams → Lambda → SES for the welcome email (the function’s IAM role allows SES); S3 events → Lambda, or via SQS / SNS, for thumbnails. Separate output buckets; caps on stream retries; DLQs.
  • Microservices: each service its own shape and its own DNS name; synchronous (API Gateway, load balancers) couples availability, asynchronous (SQS, SNS, Kinesis, Lambda triggers) absorbs failures; service discovery with Route 53, Cloud Map, Service Connect. Serverless eases the overhead: auto scaling, pay per use, cloned environments, generated client SDKs.
  • Software updates offloading: put CloudFront in front of the existing ALB: no application change, static files cached at the edge, the ASG barely scales, large savings. Not Global Accelerator (no cache).
  • Choosing: Lambda for short event-driven work, Fargate for long-running containers, EC2 for GPUs and steady load; DynamoDB for known key access at scale, RDS/Aurora for joins; API Gateway for API features and spiky traffic, ALB for steady volume and long requests; Step Functions for orchestration, SQS for buffering one step.