Serverless architecture is a model in which you write and deploy code without managing or provisioning servers: the cloud provider takes complete care of the infrastructure, runs your code only when needed, and charges you only for actual execution time, usually in milliseconds. The name is misleading: of course there are servers, but you neither see nor administer them. Its purest expression is FaaS (Functions as a Service), where you deploy small functions that react to events. It matters because it lets you build systems that automatically scale from zero to thousands of executions, with zero cost when there is no traffic, and it drastically reduces the operational burden. But it is not a silver bullet: it has significant limitations (the cold start, maximum execution times, provider dependency) that you must know in order to decide when to apply it.
Contents
- What serverless is and what FaaS is.
- Event-driven architecture.
- The cold start problem.
- Advantages and limitations.
- Example of a serverless function.
- When to use serverless and when not to.
- Common mistakes and tips.
- Exercises and solutions.
- What serverless is and what FaaS is
"Serverless" is an umbrella that includes several types of managed services that scale on their own and are paid per use: serverless databases, message queues, object storage, etc. Its most representative component is FaaS (Function as a Service): you upload a function —a small, stateless unit of code— and the provider runs it in response to an event (an HTTP request, an uploaded file, a message in a queue, a timer). Examples: AWS Lambda, Azure Functions, Google Cloud Functions.
| Characteristic | Traditional server / VM | Container (K8s) | Serverless (FaaS) |
|---|---|---|---|
| You manage the OS | Yes | Partial | No |
| Scaling | Manual or configured | Configured | Automatic (even to zero) |
| Cost without traffic | You pay anyway | You pay anyway | Zero (or almost) |
| Deployment unit | Server/app | Container | Function |
| State | Can have | Can have | Stateless |
| Control | High | Medium | Low |
- Event-driven architecture
Serverless shines in event-driven architectures: functions are not "always on" waiting, but rather activate when a trigger fires. This fits naturally with asynchronous, decoupled flows.
graph LR
A[User uploads image] --> B[S3 Storage]
B -- ObjectCreated event --> C[Function: generate thumbnail]
C --> D[Save thumbnail to S3]
C --> E[Message queue]
E --> F[Function: notify user]Explanation of the diagram: the user uploads an image to storage. That action emits an event that triggers a function to generate a thumbnail. The function saves the result and publishes a message in a queue, which in turn triggers another function that notifies the user. Each piece is small, independent, and scales separately. There are no servers waiting: the code only runs when something happens.
- The cold start problem
When a function has not been used recently, the provider does not have an environment ready for it. On the first invocation it must: provision a container, load the runtime (Node, Python, Java…), and initialize your code. That initial delay is the cold start and can range from tens of milliseconds to several seconds. Subsequent invocations, while the environment remains "warm," are fast (warm start).
| Factor | Effect on the cold start |
|---|---|
| Language | Java/.NET usually take longer; Node/Python/Go start fast |
| Package size | The larger it is, the longer it takes to load |
| Allocated memory | More memory usually provides more CPU and faster startup |
| Dependencies and initialization | Connections and heavy libraries at startup penalize it |
Common mitigations: keep functions small, choose lightweight runtimes, use provisioned concurrency (pre-warmed environments paid for separately), and reuse connections outside the handler so they persist across warm invocations.
- Advantages and limitations
| Advantages | Limitations |
|---|---|
| Automatic scaling, even to zero | Cold start: latency on the first invocation |
| Pay for actual usage (no cost at rest) | Execution time limit (e.g., minutes, not hours) |
| Zero management of servers and patches | Stateless: you need external storage |
| Fast, granular deployments | Provider dependency (vendor lock-in) |
| Managed high availability | More complex debugging and monitoring |
| Ideal for irregular or spiky workloads | Costly if traffic is high and constant |
- Example of a serverless function
An AWS Lambda function in Node.js that responds to an HTTP request. The handler is the entry point that the provider invokes with the event (trigger data) and the context (execution metadata):
// index.js
// Reusable connection: declared OUTSIDE the handler to
// take advantage of "warm starts" and avoid reconnecting on every invocation.
const db = require("./db").connect();
exports.handler = async (event, context) => {
try {
// 'event' contains the trigger data (e.g. the HTTP request)
const userId = event.queryStringParameters?.id;
if (!userId) {
return { statusCode: 400, body: JSON.stringify({ error: "Missing id" }) };
}
const user = await db.getUser(userId);
// Response in the format the API Gateway expects
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user),
};
} catch (err) {
console.error("Error in the function:", err);
return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) };
}
};Explanation of the code:
- The line
const db = require("./db").connect();is outside the handler on purpose: the initialization code runs only once per environment and is reused across warm invocations, avoiding reconnecting each time. It is a key optimization in serverless. exports.handler = async (event, context) => {...}: the handler is the function the provider calls.eventcarries the trigger data;contextcarries metadata (remaining time, identifiers).- The input is validated and, if the
idis missing, it responds with HTTP code400(bad request). - The data is looked up and an object with
statusCode,headers, andbodyis returned. The provider (via API Gateway) translates that object into a real HTTP response. - The
catchblock logs the error and returns500so as not to expose internal details.
To define the HTTP trigger declaratively with the Serverless Framework:
service: users-api
provider:
name: aws
runtime: nodejs20.x
memorySize: 256 # MB of memory (affects CPU and cold start)
timeout: 10 # maximum execution seconds
functions:
getUser:
handler: index.handler # file.function
events:
- http:
path: /users
method: get # GET /users triggers the functionExplanation: runtime sets the execution environment; memorySize and timeout configure resources and the time limit; handler points to index.js and the handler function; the events block declares that a GET /users request invokes the function. The framework takes care of creating the Lambda, the API Gateway, and the permissions.
- When to use serverless and when not to
Good use cases:
- APIs and backends with irregular or unpredictable traffic (occasional spikes).
- Event processing: resizing images, processing uploaded files, reacting to messages in a queue.
- Scheduled tasks (cron) and one-off automations.
- Webhooks and lightweight integrations between systems.
- Prototypes and MVPs where you want to move fast without setting up infrastructure.
Bad use cases:
- Applications with high, constant, and predictable traffic: at that volume a reserved server or container is usually cheaper.
- Long processes (above the function's time limit) or with sustained intensive CPU/memory use.
- Workloads with critical, strict latency where the cold start would be unacceptable.
- Applications with persistent in-memory state (functions are ephemeral and stateless).
Common Mistakes and Tips
- Putting database connections inside the handler. Declare them outside to reuse them across warm starts, and watch out for connection pool exhaustion, a classic problem when many functions scale at once.
- Functions that are too large (monolithic functions). Keep each function focused on a single task; large packages worsen the cold start.
- Ignoring cost at scale. Serverless is cheap with little traffic, but with millions of constant invocations it can be more expensive than a container. Measure.
- Not designing for idempotency. Events can be delivered more than once; your functions must tolerate repeated executions without duplicate effects.
- Forgetting observability. Being distributed and ephemeral, without proper traces and metrics debugging is very difficult. Instrument from the start.
- Tip: embrace event-driven decoupling, but document the flow well: a tangle of functions without a diagram becomes unmanageable.
Exercises
- Explain why declaring the database connection outside the handler improves performance, and in what type of invocations it is noticeable.
- A company has an internal API with constant and high traffic 24 hours a day. Would you recommend serverless? Justify your answer.
- Describe a serverless event-driven flow to process invoices that are uploaded to storage: which triggers and functions would you chain together?
Solutions
- Because the code placed outside the handler runs only once when the environment is initialized and is reused while the environment remains warm, avoiding reopening the connection on each call. The improvement is noticeable in consecutive warm invocations (warm starts); on the first cold start initialization is still required.
- No, probably not. With constant, high traffic 24/7, the pay-per-invocation model tends to be more expensive than a reserved container or server, which also avoids the cold start entirely. Serverless performs better with irregular or spiky traffic.
- A possible flow: (1) the user uploads the invoice to object storage, which emits an
ObjectCreatedevent; (2) that event triggers a validation/extraction function that reads the PDF and obtains the data; (3) the function publishes a message in a queue; (4) that message triggers another recording function that saves the data to a database and, optionally, triggers a third notification function. Each step is independent, asynchronous, and scales separately.
Conclusion
You have understood the serverless model and its FaaS core, event-driven architecture, the cold start challenge, the balance of advantages and limitations, how a function is written, and above all when it is appropriate and when it is not. Serverless is a powerful tool for irregular workloads and event-driven flows, not a universal solution. In the next lesson we will gather cross-cutting best practices for building robust applications in the cloud with the cloud-native design patterns.
Application Architecture Course
Module 1: Fundamentals of Application Architecture
- What Is Application Architecture?
- The Role of the Software Architect
- Quality Attributes and Non-Functional Requirements
- Architectural Decisions and Trade-offs
- Architecture Documentation: Views and the C4 Model
Module 2: Design Principles and Tactics
- Coupling, Cohesion and Separation of Concerns
- SOLID Principles Applied to Architecture
- DRY, KISS, YAGNI and Other Design Principles
- Architectural Tactics for Quality Attributes
- Managing Technical Debt
Module 3: Architectural Styles and Patterns
- Monolithic Architecture
- Layered Architecture (N-Tier)
- Client-Server Architecture
- Hexagonal Architecture (Ports and Adapters)
- Clean and Onion Architecture
Module 4: Distributed Architectures and Microservices
- Introduction to Distributed Systems
- Microservices Architecture
- Service Decomposition and Bounded Contexts
- API Gateway, Service Discovery and Inter-Service Communication
- Resilience Patterns: Circuit Breaker, Retry and Bulkhead
- The CAP Theorem and Data Consistency
Module 5: Event-Driven Architectures and Messaging
- Fundamentals of Event-Driven Architecture
- Asynchronous Messaging: Queues and Brokers
- Event Patterns: Event Sourcing and CQRS
- Managing Distributed Transactions: The Saga Pattern
- Real-Time Data Streaming
Module 6: Domain-Driven Design (DDD)
- Core DDD Concepts
- Strategic Design: Bounded Contexts and Ubiquitous Language
- Tactical Design: Entities, Aggregates and Repositories
- Context Mapping
Module 7: Data and Persistence
- Persistence Strategies: SQL vs NoSQL
- Data Access Patterns: Repository, Unit of Work and DAO
- Database per Service and Distributed Data Management
- Caching and Invalidation Strategies
Module 8: Cloud Architecture and Deployment
- Cloud Computing Fundamentals (IaaS, PaaS, SaaS)
- Containers and Orchestration with Docker and Kubernetes
- Serverless Architecture
- Cloud-Native Design Patterns
- Infrastructure as Code (IaC)
Module 9: Quality, Security and Observability
- Scalability: Horizontal vs Vertical and Load Balancing
- High Availability and Fault Tolerance
- Security by Design and Authentication/Authorization
- Observability: Logging, Metrics and Tracing
- Performance and Load Testing
