Serverless / FaaS Architecture
Serverless architecture runs application logic on managed compute that autoscales with demand — commonly Functions-as-a-Service (FaaS) plus managed backends (databases, queues, object storage). You write handlers; the cloud provider handles servers, patching, and elastic scale. You still design architecture: boundaries, state, idempotency, and cost.
"Serverless" does not mean "ops-free." It shifts ops toward observability, cost control, configuration, and distributed design discipline.
| Requirement | Serverless fit |
|---|---|
| Spiky / unpredictable traffic | Strong — scale to zero / out |
| Pay-per-use cost model desired | Strong for bursty workloads |
| Small event handlers / APIs | Strong |
| Long CPU-bound jobs (hours) | Weak — use containers/batch |
| Hard ultra-low latency every request | Weak — cold starts risk |
| Specialized hardware / GPUs always on | Often weak / specialized offerings |
- Why choose it: reduce idle cost, accelerate delivery of event-driven glue, let platform teams focus elsewhere.
- How: design small handlers, push state to managed stores, use queues for async, watch cold start and concurrency limits.
A cold start happens when a new execution environment initializes — loading runtime, code, and dependencies — before handling a request. Impact varies by language, package size, VPC networking, and provisioned concurrency settings.
| Mitigation | Tradeoff |
|---|---|
| Keep packages small | Discipline on deps |
| Provisioned concurrency | Pays for warm capacity |
| Prefer lighter runtimes | Language constraints |
| Avoid VPC unless needed | Networking security tradeoffs |
| Async where latency allows | UX eventual |
note
Cost typically tracks invocations, duration, memory, and downstream managed services. Bursty apps often save vs always-on clusters; high, steady, long-running workloads can cost more on FaaS than on containers.
- Model cost with expected RPS × duration × memory price + storage/queue costs.
- Watch chatty designs that multiply invocations.
- Set budgets and alerts — runaway recursion or retries can surprise.
- Max execution duration per invocation.
- Payload size limits on events/responses.
- Concurrency quotas and downstream DB connection storms.
- Local disk ephemeral and limited.
- Vendor-specific packaging and IAM models — portability costs.
Connection management
Use pooling proxies or serverless-friendly DB offerings; do not open a new DB connection naively per invoke at scale.
Idempotency
At-least-once invocation is common — design handlers safe to retry.
Observability
Structured logs, tracing, cold-start metrics, and DLQs for async paths.
| Pattern | Use |
|---|---|
| HTTP API + function | CRUD/BFF endpoints |
| Queue/stream consumer | Async processing |
| Scheduled function | Cron-style jobs |
| Fan-out via pub/sub | Event-driven integration |
| Orchestrator (step functions) | Multi-step workflows |
| 1 | export async function handler(event) {{ |
| 2 | const cmd = parse(event); |
| 3 | // idempotency key from event id |
| 4 | if (await alreadyProcessed(cmd.id)) return {{ ok: true }}; |
| 5 | await placeOrder(cmd); |
| 6 | await markProcessed(cmd.id); |
| 7 | return {{ ok: true }}; |
| 8 | }} |
| Choose | When |
|---|---|
| FaaS | Event glue, spiky APIs, minimal ops |
| Containers on orchestrator | Long processes, complex networking, steady load |
| Modular monolith on simple host | Early product, strong consistency, small team |
- Death by a thousand functions without module boundaries.
- Ignoring cold starts until launch day.
- Sync fan-out causing cascading latency and cost.
- Storing critical state only in local /tmp.
- No cost alerts on retries/DLQ storms.
| 1 | [ ] Workload shape fits (spiky/event/short) |
| 2 | [ ] Latency NFRs validated vs cold starts |
| 3 | [ ] Cost model spreadsheet signed off |
| 4 | [ ] Idempotency & retry policy defined |
| 5 | [ ] Concurrency vs DB capacity planned |
| 6 | [ ] Observability + alarms in place |
| 7 | [ ] Escape hatch to containers documented |
Keep domain/use cases pure; treat the function entrypoint as a driving adapter. This preserves testability and an escape hatch to containers later without rewriting business rules.
| 1 | import {{ placeOrder }} from "./application/place-order.js"; |
| 2 | import {{ pgOrders }} from "./adapters/pg-orders.js"; |
| 3 | import {{ stripePayments }} from "./adapters/stripe.js"; |
| 4 | |
| 5 | const uc = placeOrder({{ orders: pgOrders, payments: stripePayments }}); |
| 6 | export const handler = async (event) => uc(parse(event)); |
Serverless thrives with strong CI (packaging, integration tests against emulators/sandboxes), staged deploys, and infrastructure as code. Local fidelity is never perfect — invest in contract tests and ephemeral cloud environments for critical paths.
FaaS APIs differ across clouds. If portability is a Must NFR, isolate domain logic (hexagonal) and accept rewriting adapters — or prefer containers. If portability is only a Could, optimize for one platform's strengths and document lock-in in an ADR.
A media company moved image thumbnail generation to queue-triggered functions. API latency NFRs stayed on an always-on service; burst work scaled independently; cost dropped 60% vs a fleet of always-on workers. They kept domain code portable and later ran the same workers on containers for a on-prem customer — because hexagonal adapters made the swap possible.
| Concern | Practice |
|---|---|
| IAM least privilege | Per-function roles; no wildcards |
| Secrets | Managed secrets; never env in images/logs |
| Event injection | Validate envelopes; auth on HTTP triggers |
| Dependency risk | Scan packages; pin versions |
| Data exfil | Egress controls where required |
Serverless widens the attack surface via many entrypoints. Inventory triggers and apply the same threat modeling you would for microservices.
- Is the critical path tolerant of cold starts — measured, not hoped?
- Do we have idempotency keys on all mutating handlers?
- Can the database survive concurrency × connection behavior?
- Are costs alerted and modeled at 10× traffic?
- Is there an escape hatch ADR if FaaS limits bind us?
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.