Lesson 22-Serverless Architecture Fundamentals

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/users to /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:

  1. 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);
    });
}
  1. 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;
}
  1. 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:

  1. 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
  1. 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>
  );
}
  1. 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

Share your love