Cas Pratique - Plateforme E-Commerce, Pipeline d'Agrégation & Indexation

@joekakone

Joseph Konka @joekakone

03/03/2026 · 18 min

Concevez une application e-commerce complète avec MongoDB. Maîtrisez la modélisation avancée (embarquement vs référencement), le puissant pipeline d'agrégation ($match, $group, $unwind, $lookup) et l'optimisation par index.

Cas Pratique : Plateforme E-Commerce, Pipeline d'Agrégation et Indexation#

Savoir insérer et rechercher des documents simples constitue la base de MongoDB. Cependant, dans les projets d'ingénierie Big Data et d'analyse décisionnelle, la véritable valeur ajoutée réside dans la capacité à modéliser efficacement des relations complexes, à exécuter des analyses statistiques massives via le Pipeline d'Agrégation (Aggregation Pipeline) et à garantir des temps de réponse inférieurs à 10 millisecondes grâce à une indexation rigoureuse.

Dans ce projet pratique, nous allons concevoir le socle analytique d'une plateforme de commerce en ligne nommée « TechStore ».


1. Modélisation NoSQL : Arbitrage Embarquement vs Référencement#

La première question qui se pose à tout concepteur de base de données NoSQL est : « Dois-je imbriquer mes données dans un seul document (Embedding) ou créer des collections distinctes avec des identifiants partagés (Referencing) ? »

La règle d'or de la modélisation MongoDB#

« Les données qui sont consultées et modifiées ensemble doivent être stockées ensemble. »

flowchart TD
    subgraph Choix_Modelisation ["Stratégie de Modélisation"]
        A{Nature de la relation et fréquence d'accès}
        A -- 1:1 ou 1:Few stable et lu ensemble --> B["Embarquement (Embedding)<br/>Sous-documents ou Tableaux"]
        A -- 1:Many volumineux ou N:M évolutif --> C["Référencement (Referencing)<br/>Stockage de l'ObjectId d'une autre collection"]
    end

Notre modèle pour TechStore :#

  1. Collection clients : Entité indépendante contenant le profil, la localisation géographique et la date d'inscription.
  2. Collection produits : Catalogue centralisé avec référence, libellé, catégorie, prix unitaire catalogue et niveau de stock disponible.
  3. Collection commandes :
  4. Référence vers le client par son client_id (référencement).
  5. Embarquement des articles achetés : Les lignes de commande sont stockées directement sous forme d'un tableau de sous-documents dans la commande. Chaque article embarque non seulement l'identifiant du produit mais également le prix effectivement appliqué au moment de la vente (ce qui garantit l'intégrité de l'historique comptable même si le prix catalogue change ultérieurement).

2. Déploiement et peuplement du jeu de données#

Connectez-vous à votre instance mongosh dans le conteneur Docker :

docker exec -it analytics-mongo-1 mongosh -u admin -p Password123 --authenticationDatabase admin

Basculez sur la base de données du projet :

use techstore_db

Insérons un jeu de données représentatif comprenant des clients, des produits et un historique de commandes réparties sur plusieurs mois :

// 1. Peuplement de la collection 'clients'
db.clients.insertMany([
    {
        _id: ObjectId("65f400000000000000000001"),
        nom: "Alice Martin",
        email: "alice@example.com",
        ville: "Paris",
        segment: "Particulier",
        date_inscription: new Date("2025-01-15")
    },
    {
        _id: ObjectId("65f400000000000000000002"),
        nom: "Bob Dubois",
        email: "bob@techpro.fr",
        ville: "Lyon",
        segment: "Entreprise",
        date_inscription: new Date("2025-02-10")
    },
    {
        _id: ObjectId("65f400000000000000000003"),
        nom: "Claire Laurent",
        email: "claire@agence.com",
        ville: "Bordeaux",
        segment: "Entreprise",
        date_inscription: new Date("2025-03-01")
    }
])

// 2. Peuplement de la collection 'produits'
db.produits.insertMany([
    {
        _id: ObjectId("65f410000000000000000001"),
        ref: "LAP-01",
        nom: "Ordinateur Portable Pro",
        categorie: "Informatique",
        prix_catalogue: 1299.00,
        stock: 25
    },
    {
        _id: ObjectId("65f410000000000000000002"),
        ref: "ECR-01",
        nom: "Écran Incurvé 32 pouces",
        categorie: "Informatique",
        prix_catalogue: 399.00,
        stock: 40
    },
    {
        _id: ObjectId("65f410000000000000000003"),
        ref: "SIE-01",
        nom: "Chaise de Bureau Ergonomique",
        categorie: "Mobilier",
        prix_catalogue: 249.00,
        stock: 15
    },
    {
        _id: ObjectId("65f410000000000000000004"),
        ref: "CAS-01",
        nom: "Casque Réduction de Bruit",
        categorie: "Audio",
        prix_catalogue: 189.00,
        stock: 50
    }
])

// 3. Peuplement de la collection 'commandes' avec articles embarqués
db.commandes.insertMany([
    {
        numero: "CMD-2026-001",
        date_commande: new Date("2026-02-01"),
        client_id: ObjectId("65f400000000000000000001"), // Alice
        statut: "LIVREE",
        articles: [
            {
                produit_id: ObjectId("65f41000000000000001"),
                nom: "Ordinateur Portable Pro",
                categorie: "Informatique",
                quantite: 1,
                prix_unitaire: 1299.00
            },
            {
                produit_id: ObjectId("65f41000000000000004"),
                nom: "Casque Réduction de Bruit",
                categorie: "Audio",
                quantite: 1,
                prix_unitaire: 189.00
            }
        ],
        montant_total: 1488.00
    },
    {
        numero: "CMD-2026-002",
        date_commande: new Date("2026-02-05"),
        client_id: ObjectId("65f400000000000000000002"), // Bob
        statut: "LIVREE",
        articles: [
            {
                produit_id: ObjectId("65f41000000000000002"),
                nom: "Écran Incurvé 32 pouces",
                categorie: "Informatique",
                quantite: 3,
                prix_unitaire: 399.00
            },
            {
                produit_id: ObjectId("65f41000000000000003"),
                nom: "Chaise de Bureau Ergonomique",
                categorie: "Mobilier",
                quantite: 2,
                prix_unitaire: 249.00
            }
        ],
        montant_total: 1695.00
    },
    {
        numero: "CMD-2026-003",
        date_commande: new Date("2026-02-15"),
        client_id: ObjectId("65f400000000000000000001"), // Alice
        statut: "LIVREE",
        articles: [
            {
                produit_id: ObjectId("65f41000000000000003"),
                nom: "Chaise de Bureau Ergonomique",
                categorie: "Mobilier",
                quantite: 1,
                prix_unitaire: 249.00
            }
        ],
        montant_total: 249.00
    },
    {
        numero: "CMD-2026-004",
        date_commande: new Date("2026-03-01"),
        client_id: ObjectId("65f400000000000000000003"), // Claire
        statut: "EN_COURS",
        articles: [
            {
                produit_id: ObjectId("65f41000000000000001"),
                nom: "Ordinateur Portable Pro",
                categorie: "Informatique",
                quantite: 2,
                prix_unitaire: 1250.00
            }
        ],
        montant_total: 2500.00
    },
    {
        numero: "CMD-2026-005",
        date_commande: new Date("2026-03-02"),
        client_id: ObjectId("65f400000000000000000002"), // Bob
        statut: "ANNULEE",
        articles: [
            {
                produit_id: ObjectId("65f41000000000000004"),
                nom: "Casque Réduction de Bruit",
                categorie: "Audio",
                quantite: 2,
                prix_unitaire: 189.00
            }
        ],
        montant_total: 378.00
    }
])

3. Le Pipeline d'Agrégation (Aggregation Pipeline)#

L'Aggregation Pipeline est l'outil le plus puissant de MongoDB pour la Data Science et le Big Data. Il fonctionne comme une chaîne d'usinage industrielle où les documents traversent successivement plusieurs étapes (stages) de transformation :

flowchart LR
    A["Collection 'commandes'"] --> B["$match<br/>(Filtrer statut = 'LIVREE')"]
    B --> C["$unwind<br/>(Aplatir le tableau 'articles')"]
    C --> D["$group<br/>(Regrouper par catégorie)"]
    D --> E["$project<br/>(Formater et arrondir)"]
    E --> F["$sort<br/>(Trier par CA décroissant)"]
    F --> G["Résultat Analytique"]

Étape par étape : Les opérateurs d'étape (Stages)#

  1. $match : Filtre les documents d'entrée (analogue au WHERE en SQL). Il doit toujours être positionné le plus en amont possible pour éliminer un maximum de documents dès le début.
  2. $unwind : Déconstruit un tableau. Si un document de commande contient 3 articles, $unwind: "$articles" génère 3 copies du document, chacune portant un seul des articles.
  3. $group : Regroupe les documents selon une clé d'agrégation _id et applique des accumulateurs ($sum, $avg, $min, $max).
  4. $lookup : Effectue une jointure NoSQL externe gauche (Left Outer Join) avec une autre collection de la même base.
  5. $project : Remodèle les documents en ajoutant des champs calculés ou en renommant/supprimant des attributs.
  6. $sort : Trie le flux résultant (1 pour ascendant, -1 pour descendant).

Requête Analytique 1 : Chiffre d'affaires et panier moyen par statut#

Nous voulons calculer le montant total des ventes et la moyenne des commandes pour chaque statut :

db.commandes.aggregate([
    {
        $group: {
            _id: "$statut",
            chiffre_affaires: { $sum: "$montant_total" },
            nombre_commandes: { $sum: 1 },
            panier_moyen: { $avg: "$montant_total" }
        }
    },
    {
        $project: {
            statut: "$_id",
            chiffre_affaires: { $round: ["$chiffre_affaires", 2] },
            nombre_commandes: 1,
            panier_moyen: { $round: ["$panier_moyen", 2] },
            _id: 0
        }
    },
    { $sort: { chiffre_affaires: -1 } }
])

Sortie obtenue dans mongosh :

[
  {
    nombre_commandes: 3,
    statut: 'LIVREE',
    chiffre_affaires: 3432,
    panier_moyen: 1144
  },
  {
    nombre_commandes: 1,
    statut: 'EN_COURS',
    chiffre_affaires: 2500,
    panier_moyen: 2500
  },
  {
    nombre_commandes: 1,
    statut: 'ANNULEE',
    chiffre_affaires: 378,
    panier_moyen: 378
  }
]


Requête Analytique 2 : Ventes par catégorie avec $unwind#

Comment connaître le volume et la valeur des ventes par catégorie de produit, alors que chaque produit est enfoui dans un tableau articles au sein des commandes livrées ?

db.commandes.aggregate([
    // Étape 1 : Filtrer uniquement les commandes effectivement validées et livrées
    { $match: { statut: "LIVREE" } },

    // Étape 2 : Aplatir le tableau d'articles imbriqués
    { $unwind: "$articles" },

    // Étape 3 : Regrouper par catégorie d'article
    {
        $group: {
            _id: "$articles.categorie",
            quantite_vendue: { $sum: "$articles.quantite" },
            ca_categorie: {
                $sum: { $multiply: ["$articles.quantite", "$articles.prix_unitaire"] }
            }
        }
    },

    // Étape 4 : Formatage et tri par chiffre d'affaires décroissant
    {
        $project: {
            categorie: "$_id",
            quantite_vendue: 1,
            ca_categorie: { $round: ["$ca_categorie", 2] },
            _id: 0
        }
    },
    { $sort: { ca_categorie: -1 } }
])

Résultat :

[
  { quantite_vendue: 4, categorie: 'Informatique', ca_categorie: 2496 },
  { quantite_vendue: 3, categorie: 'Mobilier', ca_categorie: 747 },
  { quantite_vendue: 1, categorie: 'Audio', ca_categorie: 189 }
]


Requête Analytique 3 : Jointure NoSQL avec $lookup#

Pour enrichir une commande avec les données personnelles du client (nom, ville, segment) situées dans la collection clients :

db.commandes.aggregate([
    { $match: { statut: "LIVREE" } },
    {
        $lookup: {
            from: "clients",               // Collection cible
            localField: "client_id",       // Champ de la collection 'commandes'
            foreignField: "_id",           // Clé de correspondance dans 'clients'
            as: "info_client"              // Nom du nouveau tableau créé
        }
    },
    // Déplier le tableau 'info_client' (qui ne contient qu'un seul élément)
    { $unwind: "$info_client" },
    {
        $project: {
            numero: 1,
            montant_total: 1,
            client_nom: "$info_client.nom",
            client_ville: "$info_client.ville",
            client_segment: "$info_client.segment",
            _id: 0
        }
    }
])

4. Indexation et Optimisation des Performances#

Lorsque votre base de données contient des millions de documents, l'absence d'index oblige MongoDB à parcourir chaque document un à un sur le disque : c'est le COLLSCAN (Collection Scan).

Création d'index#

Pour rendre la recherche instantanée par un parcours d'arbre B-Tree en mémoire (IXSCAN) :

// 1. Index unique sur le numéro de commande
db.commandes.createIndex({ numero: 1 }, { unique: true })

// 2. Index composé sur le statut et la date de commande
db.commandes.createIndex({ statut: 1, date_commande: -1 })

// 3. Index de texte pour moteur de recherche plein texte
db.produits.createIndex({ nom: "text" })

Diagnostiquer avec explain("executionStats")#

La commande explain() permet d'analyser la stratégie choisie par l'optimiseur de requêtes :

db.commandes.find({
    statut: "LIVREE",
    date_commande: { $gte: new Date("2026-02-01") }
}).explain("executionStats")

Dans la section executionStats, observez les indicateurs clés : - stage: IXSCAN (l'index composé a été utilisé). - totalKeysExamined vs totalDocsExamined : Dans une requête optimale, le nombre de clés et de documents examinés doit être quasi-identique au nombre de documents retournés (nReturned).


5. Automatisation complète en Python (techstore_analytics.py)#

Voici le script complet qui connecte l'application analytique à MongoDB, exécute le pipeline et affiche un tableau de bord exécutif en console :

"""
Application décisionnelle et d'agrégation Big Data pour TechStore.
Auteur : Joseph Konka (AlgoJungle)
"""

from datetime import datetime
from pymongo import MongoClient
from pymongo.errors import PyMongoError


def executer_dashboard_analytique():
    uri = "mongodb://admin:Password123@localhost:27017/?authSource=admin"

    try:
        client = MongoClient(uri)
        db = client["techstore_db"]

        print("=" * 65)
        print("          📈 TECHSTORE - DASHBOARD ANALYTIQUE MONGODB")
        print("=" * 65)

        # 1. Pipeline d'agrégation : Performance par Catégorie
        pipeline_categories = [
            {"$match": {"statut": "LIVREE"}},
            {"$unwind": "$articles"},
            {
                "$group": {
                    "_id": "$articles.categorie",
                    "quantite_totale": {"$sum": "$articles.quantite"},
                    "chiffre_affaires": {
                        "$sum": {
                            "$multiply": [
                                "$articles.quantite",
                                "$articles.prix_unitaire",
                            ]
                        }
                    },
                }
            },
            {"$sort": {"chiffre_affaires": -1}},
        ]

        print("\n📦 VENTES PAR CATÉGORIE (Commandes Livrées) :")
        print(f"{'Catégorie':<18} | {'Unités vendues':>14} | {'Chiffre d Affaires':>18}")
        print("-" * 65)

        categories_stats = list(db.commandes.aggregate(pipeline_categories))
        ca_global = sum(cat["chiffre_affaires"] for cat in categories_stats)

        for cat in categories_stats:
            print(
                f"{cat['_id']:<18} | {cat['quantite_totale']:>14} | {cat['chiffre_affaires']:>16,.2f} €"
            )

        print("-" * 65)
        print(f"💰 Chiffre d'affaires total cumulé : {ca_global:,.2f} €")

        # 2. Pipeline d'agrégation : Top Clients (LTV - Lifetime Value)
        pipeline_clients = [
            {"$match": {"statut": "LIVREE"}},
            {
                "$group": {
                    "_id": "$client_id",
                    "total_depense": {"$sum": "$montant_total"},
                    "nb_commandes": {"$sum": 1},
                }
            },
            {
                "$lookup": {
                    "from": "clients",
                    "localField": "_id",
                    "foreignField": "_id",
                    "as": "client_info",
                }
            },
            {"$unwind": "$client_info"},
            {
                "$project": {
                    "nom": "$client_info.nom",
                    "ville": "$client_info.ville",
                    "segment": "$client_info.segment",
                    "total_depense": 1,
                    "nb_commandes": 1,
                    "_id": 0,
                }
            },
            {"$sort": {"total_depense": -1}},
        ]

        print("\n🏆 CLASSEMENT DES CLIENTS LES PLUS FIDÈLES (LTV) :")
        print(f"{'Client':<16} | {'Ville':<10} | {'Commandes':>9} | {'Total Dépensé':>14}")
        print("-" * 65)

        for client_ltv in db.commandes.aggregate(pipeline_clients):
            print(
                f"{client_ltv['nom']:<16} | {client_ltv['ville']:<10} | "
                f"{client_ltv['nb_commandes']:>9} | {client_ltv['total_depense']:>12,.2f} €"
            )

        print("=" * 65 + "\n")

    except PyMongoError as err:
        print(f"❌ Erreur lors de l'exécution des agrégations : {err}")
    finally:
        client.close()


if __name__ == "__main__":
    executer_dashboard_analytique()

Sortie console du tableau de bord :

=================================================================
          📈 TECHSTORE - DASHBOARD ANALYTIQUE MONGODB
=================================================================

📦 VENTES PAR CATÉGORIE (Commandes Livrées) :
Catégorie          | Unités vendues | Chiffre d Affaires
-----------------------------------------------------------------
Informatique       |              4 |         2,496.00 €
Mobilier           |              3 |           747.00 €
Audio              |              1 |           189.00 €
-----------------------------------------------------------------
💰 Chiffre d'affaires total cumulé : 3,432.00 €

🏆 CLASSEMENT DES CLIENTS LES PLUS FIDÈLES (LTV) :
Client           | Ville      | Commandes |  Total Dépensé
-----------------------------------------------------------------
Alice Martin     | Paris      |         2 |     1,737.00 €
Bob Dubois       | Lyon       |         1 |     1,695.00 €
=================================================================


6. Défis d'extension pour aller plus loin#

Pour approfondir votre expertise en Big Data avec MongoDB :

Défi 1 : Index TTL (Time-To-Live) pour paniers abandonnés#

  • Mission : Créez une collection paniers avec un index d'expiration automatique { date_creation: 1 }, { expireAfterSeconds: 86400 } pour purger automatiquement les paniers inactifs après 24 heures sans aucune tâche cron !

Défi 2 : Agrégation multi-facettes avec $facet#

  • Mission : Utilisez l'opérateur $facet pour générer en une seule passe réseau à la fois la répartition par catégorie et le panier moyen global.

Commentaires

Inscrivez-vous à une formation complète

Voir le catalogue