Retour au blog

Architecture Serverless : Comment Réduire les Coûts et Scaler Infiniment avec JavaScript

Salut HaWkers, le serverless n'est plus une tendance futuriste pour devenir l'architecture standard en 2025. Avec le marché SaaS dépassant 300 milliards de dollars et les dépenses cloud public atteignant 723.4 milliards de dollars, comprendre le serverless n'est plus optionnel.

JavaScript, avec sa nature event-driven, est parfaitement compatible avec des plateformes comme AWS Lambda, Google Cloud Functions et Azure Functions. Mais comment en profiter en pratique ?

Qu'est-ce que le Serverless et Pourquoi c'est Important

Serverless ne signifie pas "sans serveurs" - cela signifie que vous ne gérez pas les serveurs. L'infrastructure est complètement abstraite :

// Modèle Traditionnel - Vous gérez tout
const traditionalModel = {
  infrastructure: 'Vous provisionnez les serveurs',
  scaling: 'Vous configurez l\'auto-scaling',
  availability: 'Vous garantissez l\'uptime',
  costs: 'Vous payez 24/7, même sans trafic',
  maintenance: 'Patches, updates, sécurité = votre responsabilité'
};

// Modèle Serverless - Le Cloud gère tout
const serverlessModel = {
  infrastructure: 'Provisionné automatiquement',
  scaling: 'Scale automatiquement (0 à millions)',
  availability: 'SLA de 99.95%+ garanti',
  costs: 'Vous payez UNIQUEMENT par exécution',
  maintenance: 'Géré par le provider'
};

// Économie réelle
const costComparison = {
  traditional: {
    server: 'EC2 t3.medium = ~30€/mois',
    usage: 'Tourne 24/7',
    traffic: '10.000 requests/mois',
    costPerRequest: '30€ / 10.000 = 0,003€'
  },
  serverless: {
    lambda: '10.000 requests = 0,20€',
    freeExecutions: '1M exécutions/mois free tier',
    costPerRequest: '0,20€ / 10.000 = 0,00002€',
    savings: '99.3% d\'économie vs traditionnel'
  }
};

serverless scaling

AWS Lambda avec Node.js : Implémentation Pratique

Fonction Lambda Basique

// handler.js - AWS Lambda Function
export const handler = async (event) => {
  try {
    // Parse du body (si POST)
    const body = event.body ? JSON.parse(event.body) : {};

    // Logique métier
    const result = await processData(body);

    // Réponse HTTP
    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        success: true,
        data: result
      })
    };
  } catch (error) {
    console.error('Erreur:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({
        success: false,
        error: error.message
      })
    };
  }
};

async function processData(data) {
  // Votre logique ici
  return {
    processed: true,
    timestamp: new Date().toISOString()
  };
}

API REST Complète avec Lambda

// api/users.js - CRUD d'utilisateurs
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  DeleteCommand,
  ScanCommand
} from '@aws-sdk/lib-dynamodb';

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

const TABLE_NAME = process.env.USERS_TABLE;

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

  try {
    switch (httpMethod) {
      case 'GET':
        return await getUser(pathParameters?.id);
      case 'POST':
        return await createUser(JSON.parse(body));
      case 'PUT':
        return await updateUser(pathParameters?.id, JSON.parse(body));
      case 'DELETE':
        return await deleteUser(pathParameters?.id);
      default:
        return response(405, { error: 'Méthode non autorisée' });
    }
  } catch (error) {
    console.error(error);
    return response(500, { error: error.message });
  }
};

async function getUser(id) {
  if (!id) {
    // Lister tous les utilisateurs
    const result = await dynamo.send(
      new ScanCommand({ TableName: TABLE_NAME })
    );
    return response(200, result.Items);
  }

  // Récupérer un utilisateur spécifique
  const result = await dynamo.send(
    new GetCommand({
      TableName: TABLE_NAME,
      Key: { id }
    })
  );

  return response(200, result.Item);
}

async function createUser(data) {
  const user = {
    id: crypto.randomUUID(),
    ...data,
    createdAt: new Date().toISOString()
  };

  await dynamo.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: user
    })
  );

  return response(201, user);
}

async function updateUser(id, data) {
  const user = {
    id,
    ...data,
    updatedAt: new Date().toISOString()
  };

  await dynamo.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: user
    })
  );

  return response(200, user);
}

async function deleteUser(id) {
  await dynamo.send(
    new DeleteCommand({
      TableName: TABLE_NAME,
      Key: { id }
    })
  );

  return response(204, {});
}

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

Serverless Framework : Deploy Simplifié

# serverless.yml
service: my-api

provider:
  name: aws
  runtime: nodejs20.x
  region: eu-west-1
  environment:
    USERS_TABLE: ${self:service}-users-${self:provider.stage}
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.USERS_TABLE}"

functions:
  api:
    handler: api/users.handler
    events:
      - httpApi:
          path: /users
          method: GET
      - httpApi:
          path: /users/{id}
          method: GET
      - httpApi:
          path: /users
          method: POST
      - httpApi:
          path: /users/{id}
          method: PUT
      - httpApi:
          path: /users/{id}
          method: DELETE

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:provider.environment.USERS_TABLE}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
# Deploy complet avec une commande
serverless deploy

# Logs en temps réel
serverless logs -f api --tail

# Supprimer la stack complète
serverless remove

Cas d'Usage Parfaits pour le Serverless

1. APIs REST/GraphQL

// GraphQL avec Apollo Server sur Lambda
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler } from '@as-integrations/aws-lambda';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`;

const resolvers = {
  Query: {
    users: async () => {
      // Récupérer depuis DynamoDB
      return await getAllUsers();
    },
    user: async (_, { id }) => {
      return await getUserById(id);
    }
  },
  Mutation: {
    createUser: async (_, { name, email }) => {
      return await createNewUser({ name, email });
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers
});

export const handler = startServerAndCreateLambdaHandler(server);

2. Traitement de Fichiers

// Lambda triggered par upload S3
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';

const s3 = new S3Client({});

export const handler = async (event) => {
  // Event de S3 upload
  const bucket = event.Records[0].s3.bucket.name;
  const key = decodeURIComponent(event.Records[0].s3.object.key);

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

    const imageBuffer = await streamToBuffer(Body);

    // Redimensionner pour différentes tailles
    const sizes = [
      { name: 'thumbnail', width: 150 },
      { name: 'medium', width: 500 },
      { name: 'large', width: 1200 }
    ];

    await Promise.all(
      sizes.map(async ({ name, width }) => {
        const resized = await sharp(imageBuffer)
          .resize(width)
          .jpeg({ quality: 80 })
          .toBuffer();

        const newKey = key.replace(/\.\w+$/, `-${name}.jpg`);

        await s3.send(
          new PutObjectCommand({
            Bucket: bucket,
            Key: newKey,
            Body: resized,
            ContentType: 'image/jpeg'
          })
        );
      })
    );

    return { statusCode: 200, body: 'Images traitées avec succès' };
  } catch (error) {
    console.error(error);
    throw error;
  }
};

async function streamToBuffer(stream) {
  const chunks = [];
  for await (const chunk of stream) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

3. Scheduled Tasks (Cron Jobs)

// Lambda exécutée quotidiennement
export const handler = async (event) => {
  console.log('Exécution de la tâche de nettoyage quotidienne...');

  try {
    // Nettoyer les anciennes données
    await cleanupOldData();

    // Envoyer le rapport
    await sendDailyReport();

    // Backup des données importantes
    await backupCriticalData();

    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Tâches quotidiennes terminées' })
    };
  } catch (error) {
    console.error('Erreur tâche quotidienne:', error);
    await notifyAdmins(error);
    throw error;
  }
};

async function cleanupOldData() {
  // Supprimer les enregistrements de plus de 90 jours
  const ninetyDaysAgo = new Date();
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

  // Implémentation spécifique
}

async function sendDailyReport() {
  // Générer et envoyer le rapport par email
}

async function backupCriticalData() {
  // Backup vers S3 Glacier
}

async function notifyAdmins(error) {
  // SNS ou SES pour notification
}

Avantages et Inconvénients

✅ Avantages

  1. Coût : Payez uniquement ce que vous utilisez
  2. Scalabilité : Automatique et infinie
  3. Maintenance : Zéro overhead opérationnel
  4. Deploy : Extrêmement rapide
  5. Sécurité : Gérée par le provider

⚠️ Inconvénients

  1. Cold Start : ~100-500ms à la première exécution
  2. Limite de Temps : AWS Lambda = 15 min maximum
  3. Vendor Lock-in : Code couplé au provider
  4. Debugging : Plus complexe que le traditionnel
  5. Coûts en Haut Volume : Peut devenir cher

Quand Utiliser le Serverless

Utilisez le Serverless quand :

  • Trafic variable/imprévisible
  • Traitement batch/background
  • APIs avec bas/moyen trafic
  • Webhooks et intégrations
  • Prototypes et MVPs

Évitez le Serverless quand :

  • Traitement très long (>15 min)
  • Trafic très élevé et constant
  • Besoin de state en mémoire
  • Latence critique (<10ms)

Si vous voulez en savoir plus sur l'optimisation du code JavaScript pour les environnements serverless, lisez Performance en JavaScript : Techniques d'Optimisation Avancées où vous découvrirez comment écrire du code plus efficient.

C'est parti !

💻 Maîtrisez JavaScript Pour le Cloud

Le serverless est du JavaScript pur exécuté dans le cloud. Meilleur est votre JavaScript, plus vous profitez du serverless.

Si vous voulez construire une base solide en JavaScript :

Formes de paiement :

  • 9,90€ (paiement unique)

📖 Voir le Contenu Complet

Commentaires (0)

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

Ajouter des commentaires