Serverless Application Architecture
Single Function Architecture
Core Concept:
Single function architecture involves a single Serverless function handling a complete business logic unit, typically corresponding to a simple API endpoint or event handler.
Characteristics:
- Simple and intuitive, easy to understand and maintain
- Suitable for tasks with a single responsibility
- Independent deployment and scaling
Use Cases:
- Simple CRUD operations
- Data transformation tasks
- Independent event handlers
Example:
// Single function handling user creation
exports.handler = async (event) => {
const { name, email } = JSON.parse(event.body);
// Validate input
if (!name || !email) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing required fields' })
};
}
// Save to database
const userId = await saveUserToDatabase({ name, email });
return {
statusCode: 201,
body: JSON.stringify({ userId, name, email })
};
};
Pros and Cons:
- ✅ Simple and easy to use
- ✅ Independent deployment
- ❌ Limited scalability for functionality
- ❌ Difficult to maintain for complex business logic
Multi-Function Collaboration Architecture
Core Concept:
Multi-function collaboration architecture involves multiple specialized functions working together to complete complex business processes, with each function responsible for a specific subtask.
Characteristics:
- Decoupled functions with single responsibilities
- Independent deployment and scaling
- Supports complex business workflows
Use Cases:
- Complex business processes
- Tasks requiring parallel processing
- Functions maintained by different teams
Example:
User Registration Process:
1. validateUser - Validate user input
2. createUser - Create user record
3. sendWelcomeEmail - Send welcome email
4. notifyAdmin - Notify administrator
Implementation:
// 1. Validation function
exports.validateUser = async (event) => {
// Validation logic
};
// 2. User creation function
exports.createUser = async (event) => {
// User creation logic
};
// 3. Email sending function
exports.sendWelcomeEmail = async (event) => {
// Email sending logic
};
// 4. Admin notification function
exports.notifyAdmin = async (event) => {
// Notification logic
};
Orchestration Methods:
- Direct Invocation: Functions call each other directly (not recommended, loses Serverless benefits)
- Event-Driven: Triggered via message queues/SNS
- Step Functions: Orchestrated using AWS Step Functions
Serverless and Microservices
Core Concept:
Serverless architecture is naturally suited for microservices, where each microservice can be one or more Serverless functions communicating via events or APIs.
Characteristics:
- Independent deployment and scaling
- Technology stack agnostic
- Automatic scaling capabilities
Microservice Example:
E-commerce System Microservices:
1. Product Service - Product management
2. Order Service - Order management
3. Payment Service - Payment processing
4. Notification Service - Notification handling
Implementation:
# serverless.yml example
service: order-service
provider:
name: aws
runtime: nodejs14.x
functions:
createOrder:
handler: handler.createOrder
events:
- http:
path: orders
method: post
getOrder:
handler: handler.getOrder
events:
- http:
path: orders/{id}
method: get
resources:
Resources:
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Orders
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Inter-Service Communication:
- Synchronous: Via API Gateway calls
- Asynchronous: Via SNS/SQS or EventBridge
Advantages:
- Independent scaling for each service
- Fault isolation
- Technology heterogeneity (different services can use different languages/frameworks)
Serverless Design Patterns
Event-Driven Pattern
Core Concept:
A loosely coupled architecture based on events, where components communicate through events rather than direct calls.
Characteristics:
- Decouples producers and consumers
- Asynchronous processing
- Highly scalable
Implementation:
// Event producer
exports.handler = async (event) => {
// Process business logic
await sendEvent('order.created', { orderId: '123' });
};
// Event consumer
exports.orderCreatedHandler = async (event) => {
// Handle order creation event
};
Event Source Examples:
- S3 object uploads
- DynamoDB changes
- SNS notifications
- Scheduled tasks
Advantages:
- Loose coupling
- High scalability
- Strong fault tolerance
Proxy Pattern (API Gateway)
Core Concept:
Using API Gateway as a unified entry point for frontend and backend services, handling routing, authentication, rate limiting, and more.
Characteristics:
- Unified entry point
- Centralized management
- Security control
Implementation:
# serverless.yml API Gateway configuration
functions:
userHandler:
handler: handler.userHandler
events:
- http:
path: users/{id}
method: get
authorizer: aws_iam
orderHandler:
handler: handler.orderHandler
events:
- http:
path: orders
method: post
cors: true
Advanced Features:
- Path Rewriting: Map
/api/usersto/users - Request Validation: Use JSON Schema to validate request bodies
- Response Transformation: Modify response formats
Advantages:
- Simplifies frontend integration
- Centralized security control
- Unified monitoring
Fan-Out and Fan-In Pattern
Fan-Out Pattern:
Distributes a single event to multiple consumers for processing.
Implementation:
// Publish event to SNS topic
exports.handler = async (event) => {
await sns.publish({
TopicArn: 'arn:aws:sns:us-east-1:123456789012:MyTopic',
Message: JSON.stringify(event)
}).promise();
};
Fan-In Pattern:
Aggregates data from multiple sources into a single processing workflow.
Implementation:
// Consume data from multiple SQS queues
exports.handler = async (event) => {
// Process data from different queues
for (const record of event.Records) {
// Processing logic
}
};
Use Cases:
- Data processing pipelines
- Event broadcasting
- Data aggregation
Serverless and Frontend Architecture
BFF (Backend for Frontend)
Core Concept:
A backend service tailored for specific frontend applications (mobile, web, etc.), addressing the unique needs of different clients.
Characteristics:
- Client-specific APIs
- Reduces adaptation layers between frontend and backend
- Optimizes performance and user experience
Implementation:
Web App → Web BFF → Core Microservices
Mobile App → Mobile BFF → Core Microservices
Example:
// Web BFF - Optimized data retrieval for web
exports.handler = async (event) => {
// Aggregate data from multiple microservices
const [user, orders] = await Promise.all([
getUserData(event.pathParameters.userId),
getOrdersData(event.pathParameters.userId)
]);
// Customize response format for web
return {
user,
orders,
recommendations: getRecommendations(user.preferences)
};
};
Advantages:
- Reduces network requests
- Optimizes data formats
- Client-specific optimizations
Frontend State and Serverless Data Synchronization
Core Challenge:
Maintaining consistency between frontend state and Serverless backend data, especially in offline scenarios.
Solutions:
- Optimistic Updates:
// Update UI first, then send request
function updateItem(item) {
// 1. Update UI
setItem({ ...item, status: 'updated' });
// 2. Send request
api.updateItem(item.id, { status: 'updated' })
.catch(() => {
// 3. Rollback on failure
setItem(item);
});
}
- State Persistence:
// Persist state using IndexedDB or LocalStorage
function saveState(state) {
localStorage.setItem('appState', JSON.stringify(state));
}
function loadState() {
const state = localStorage.getItem('appState');
return state ? JSON.parse(state) : initialState;
}
- Conflict Resolution:
// Last-write-wins strategy
function resolveConflict(local, remote) {
return { ...local, ...remote, updatedAt: new Date() };
}
Advanced Patterns:
- State Synchronization Service: Dedicated Serverless function for state synchronization
- Change Data Capture (CDC): Monitor database changes and sync to frontend
Serverless Frontend Routing
Core Concept:
Implementing frontend routing in Serverless architecture, particularly for single-page applications (SPAs).
Implementation:
- API Gateway Routing:
# serverless.yml
functions:
homePage:
handler: handler.homePage
events:
- http:
path: /
method: get
aboutPage:
handler: handler.aboutPage
events:
- http:
path: /about
method: get
# SPA routing fallback
catchAll:
handler: handler.catchAll
events:
- http:
path: /{proxy+}
method: any
- Frontend Routing Configuration:
// React Router configuration
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
{/* SPA routing */}
<Route path="*" component={NotFoundPage} />
</Switch>
</Router>
);
}
- Hybrid Rendering:
// Server-side rendering + client-side hydration
exports.handler = async (event) => {
const { path } = event.pathParameters;
// Return pre-rendered HTML based on path
if (path === '/') {
return {
statusCode: 200,
body: renderToString(<HomePage />),
headers: { 'Content-Type': 'text/html' }
};
}
// Return SPA for other paths
return {
statusCode: 200,
body: renderToString(<App />),
headers: { 'Content-Type': 'text/html' }
};
};
Performance Optimizations:
- Route Prefetching: Preload resources for likely visited routes
- Code Splitting: Split code bundles by route
- Server-Side Caching: Cache responses for frequently accessed routes



