Retour au blog

Architecture Serverless en 2025 : Pourquoi Votre Prochaine API Devrait Etre Serverless

Salut HaWkers, vous maintenez encore des serveurs 24h/24 en attente de requetes ? Vous payez pour une capacite que vous n'utilisez pas 90% du temps ? Vous passez des nuits a configurer l'auto-scaling ?

Serverless a change la donne. En 2025, la question n'est plus "dois-je utiliser serverless ?", mais plutot "pourquoi ne l'utilise-je pas encore ?". Je vais vous montrer exactement comment ca fonctionne, quand l'utiliser, et du code reel de production.

Qu'est-ce que Serverless (Vraiment) ?

Serverless NE signifie PAS "sans serveurs". Cela signifie que vous ne gerez pas de serveurs.

Modele traditionnel :

  • Vous provisionnez EC2/VPS
  • Installez Node.js, PM2, nginx
  • Configurez load balancer, auto-scaling
  • Surveillez l'utilisation CPU/RAM
  • Payez 24h/24, meme sans trafic

Modele serverless :

  • Vous ecrivez une fonction
  • Deploy avec une commande
  • La plateforme gere tout
  • Scale automatiquement (0 a des millions de requetes)
  • Payez uniquement par execution reelle

Principales Plateformes en 2025

1. AWS Lambda (Le Geant)

  • Plus grand ecosysteme (integration avec 200+ services AWS)
  • Supporte Node.js, Python, Go, Java, .NET, Rust
  • Cold start : ~100-200ms (beaucoup ameliore)
  • Tarification : 0,20$ par 1M requetes + temps de calcul

2. Vercel Functions (Le Dev-Friendly)

  • Deploy integre avec Git
  • Reseau edge global
  • Parfait pour Next.js/React
  • Cold start : ~50ms
  • Tarification : 100k invocations gratuites, puis 0,40$/1M

3. Cloudflare Workers (Le Plus Rapide)

  • Edge computing (tourne dans 300+ villes)
  • Cold start : ~0ms (toujours "chaud")
  • V8 isolates (pas de containers)
  • Tarification : 100k/jour gratuit, puis 0,50$/1M

4. Netlify Functions (Le Simple)

  • Integration parfaite avec JAMstack
  • Deploy automatique
  • Bon pour les startups
  • Tarification : 125k/mois gratuit

Votre Premiere Function : Hello World Reel

AWS Lambda + API Gateway :

// handler.js
exports.handler = async (event) => {
  const { name = 'World' } = event.queryStringParameters || {};

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify({
      message: `Hello, ${name}!`,
      timestamp: new Date().toISOString(),
      requestId: event.requestContext.requestId
    })
  };
};

Deploy avec Serverless Framework :

# serverless.yml
service: hello-world-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1

functions:
  hello:
    handler: handler.handler
    events:
      - httpApi:
          path: /hello
          method: get

# Deploy avec une commande
# serverless deploy

Resultat : API fonctionnant a https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello?name=Jeff

Serverless Computing

Cas Reel : API d'E-commerce

Construisons une API complete de produits avec CRUD, validation et cache.

// api/products/index.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event) => {
  const { httpMethod, pathParameters, body } = event;

  try {
    switch (httpMethod) {
      case 'GET':
        return pathParameters?.id
          ? await getProduct(pathParameters.id)
          : await listProducts();

      case 'POST':
        return await createProduct(JSON.parse(body));

      case 'PUT':
        return await updateProduct(pathParameters.id, JSON.parse(body));

      case 'DELETE':
        return await deleteProduct(pathParameters.id);

      default:
        return response(405, { error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('Error:', error);
    return response(500, { error: error.message });
  }
};

// Helper : Reponse standardisee
function response(statusCode, data) {
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify(data)
  };
}

// GET /products
async function listProducts() {
  const command = new ScanCommand({
    TableName: process.env.PRODUCTS_TABLE
  });

  const result = await docClient.send(command);

  return response(200, {
    products: result.Items,
    count: result.Count
  });
}

// GET /products/:id
async function getProduct(id) {
  const { GetCommand } = await import('@aws-sdk/lib-dynamodb');

  const command = new GetCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id }
  });

  const result = await docClient.send(command);

  if (!result.Item) {
    return response(404, { error: 'Product not found' });
  }

  return response(200, result.Item);
}

// POST /products
async function createProduct(data) {
  const { PutCommand } = await import('@aws-sdk/lib-dynamodb');

  // Validation
  const errors = validateProduct(data);
  if (errors.length > 0) {
    return response(400, { errors });
  }

  const product = {
    id: crypto.randomUUID(),
    ...data,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };

  const command = new PutCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Item: product
  });

  await docClient.send(command);

  return response(201, product);
}

// PUT /products/:id
async function updateProduct(id, data) {
  const { UpdateCommand } = await import('@aws-sdk/lib-dynamodb');

  const errors = validateProduct(data, true);
  if (errors.length > 0) {
    return response(400, { errors });
  }

  // Construire l'expression d'update dynamiquement
  const updateExpressions = [];
  const expressionAttributeNames = {};
  const expressionAttributeValues = {};

  Object.keys(data).forEach((key, index) => {
    updateExpressions.push(`#field${index} = :value${index}`);
    expressionAttributeNames[`#field${index}`] = key;
    expressionAttributeValues[`:value${index}`] = data[key];
  });

  updateExpressions.push('#updatedAt = :updatedAt');
  expressionAttributeNames['#updatedAt'] = 'updatedAt';
  expressionAttributeValues[':updatedAt'] = new Date().toISOString();

  const command = new UpdateCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id },
    UpdateExpression: `SET ${updateExpressions.join(', ')}`,
    ExpressionAttributeNames: expressionAttributeNames,
    ExpressionAttributeValues: expressionAttributeValues,
    ReturnValues: 'ALL_NEW'
  });

  const result = await docClient.send(command);

  return response(200, result.Attributes);
}

// DELETE /products/:id
async function deleteProduct(id) {
  const { DeleteCommand } = await import('@aws-sdk/lib-dynamodb');

  const command = new DeleteCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id }
  });

  await docClient.send(command);

  return response(204, null);
}

// Validation de produit
function validateProduct(data, isUpdate = false) {
  const errors = [];

  if (!isUpdate && !data.name) {
    errors.push('Name is required');
  }

  if (data.name && data.name.length < 3) {
    errors.push('Name must be at least 3 characters');
  }

  if (!isUpdate && data.price === undefined) {
    errors.push('Price is required');
  }

  if (data.price !== undefined && (data.price < 0 || isNaN(data.price))) {
    errors.push('Price must be a positive number');
  }

  return errors;
}

Optimisations Essentielles

1. Reduire les Cold Starts :

// ❌ Mauvais : Import dans la fonction
export const handler = async () => {
  const AWS = require('aws-sdk'); // Import a chaque execution
  const db = new AWS.DynamoDB.DocumentClient();
  // ...
};

// ✅ Bon : Import en haut (reutilise)
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async () => {
  // docClient deja instancie
};

// ✅ Encore mieux : Lazy loading
let docClient;

function getDocClient() {
  if (!docClient) {
    const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
    const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
    docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
  }
  return docClient;
}

2. Provisioned Concurrency (Pour APIs critiques) :

# serverless.yml
functions:
  api:
    handler: handler.handler
    provisionedConcurrency: 2 # Toujours 2 instances "chaudes"
    reservedConcurrency: 100 # Limite la concurrence max

3. Caching Intelligent :

// Cache en memoire (persiste entre invocations de la meme instance)
let cachedProducts = null;
let cacheExpiry = 0;

async function getProductsCached() {
  const now = Date.now();

  if (cachedProducts && now < cacheExpiry) {
    console.log('Cache hit!');
    return cachedProducts;
  }

  console.log('Cache miss, fetching...');
  const products = await fetchProductsFromDB();

  cachedProducts = products;
  cacheExpiry = now + (5 * 60 * 1000); // Cache 5 minutes

  return products;
}

4. Bundle Size Optimise :

// ❌ Mauvais : Import complet
const _ = require('lodash');

// ✅ Bon : Import specifique
const debounce = require('lodash/debounce');

// ✅ Encore mieux : Tree-shaking avec ES modules
import { debounce } from 'lodash-es';

Integrations Puissantes

1. S3 Triggers (Traiter les uploads) :

// Redimensionner images automatiquement
export const handler = async (event) => {
  const s3 = new S3Client({});
  const sharp = require('sharp');

  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;

    // Telecharger l'image
    const { Body } = await s3.send(new GetObjectCommand({
      Bucket: bucket,
      Key: key
    }));

    const buffer = await streamToBuffer(Body);

    // Redimensionner
    const resized = await sharp(buffer)
      .resize(800, 600, { fit: 'inside' })
      .jpeg({ quality: 80 })
      .toBuffer();

    // Upload de la version redimensionnee
    await s3.send(new PutObjectCommand({
      Bucket: bucket,
      Key: `thumbnails/${key}`,
      Body: resized,
      ContentType: 'image/jpeg'
    }));

    console.log(`✓ Resized ${key}`);
  }
};

2. EventBridge Scheduled (Cron jobs) :

functions:
  sendDailyReport:
    handler: handlers/reports.daily
    events:
      - schedule:
          rate: cron(0 9 * * ? *) # Tous les jours a 9h UTC
          enabled: true
// handlers/reports.js
export const daily = async () => {
  const users = await fetchActiveUsers();
  const report = generateReport(users);

  await sendEmail({
    to: 'admin@example.com',
    subject: 'Daily Report',
    body: report
  });

  console.log(`✓ Sent report to ${users.length} users`);
};

3. SQS Queues (Traitement asynchrone) :

// Producer : Envoie messages a la file
export const createOrder = async (event) => {
  const sqs = new SQSClient({});
  const order = JSON.parse(event.body);

  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.ORDERS_QUEUE_URL,
    MessageBody: JSON.stringify(order)
  }));

  return response(202, { message: 'Order queued for processing' });
};

// Consumer : Traite messages de la file
export const processOrder = async (event) => {
  for (const record of event.Records) {
    const order = JSON.parse(record.body);

    try {
      await processPayment(order);
      await sendConfirmationEmail(order);
      await updateInventory(order);

      console.log(`✓ Order ${order.id} processed`);
    } catch (error) {
      console.error(`✗ Order ${order.id} failed:`, error);
      // Message retourne dans la file (retry automatique)
      throw error;
    }
  }
};

Couts Reels : Comparaison

Scenario : API avec 1M requetes/mois, 200ms en moyenne

Solution Cout/mois Details
EC2 t3.small (24/7) 17$ + 5$ Load Balancer = 22$
AWS Lambda 0,40$ 0,20$ req + 0,20$ compute
Vercel Functions 0,40$ Apres 100k gratuit
Cloudflare Workers 5$ Plan paye (5$/mois)

Avantage serverless : Economise 80-90% sur trafic faible/moyen.

Quand serverless devient cher :

  • Trafic constant eleve (>10M req/mois)
  • Fonctions longue duree (>15min)

Quand NE PAS Utiliser Serverless

Evitez pour :

  1. WebSockets persistants (utilisez EC2/ECS)
  2. Traitement lourd (ML training, video encoding)
  3. Latence critique (<10ms consistant)
  4. Etat persistant en memoire (caches grands)

Alternative hybride :

// API serverless + Worker traditionnel
// Lambda pour endpoints
export const api = async (event) => {
  // Traite requete legere
  // Pour taches lourdes, envoie a la file
  await sqs.sendMessage({
    queueUrl: WORKER_QUEUE,
    body: JSON.stringify({ task: 'heavy-compute' })
  });
};

// EC2 worker traite la file
// Tourne 24/7, optimise pour taches lourdes

L'Avenir du Serverless

Tendances 2025 :

  • Edge computing (Cloudflare Workers, Vercel Edge)
  • Serverless containers (AWS Fargate, Cloud Run)
  • Streaming responses (donnees progressives)
  • IA integree (embeddings, summarization)

Pour mieux comprendre les patterns asynchrones essentiels en serverless, consultez Decouvrir la Puissance d'Async/Await en JavaScript.

C'est parti !

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires