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 :#
- Collection
clients: Entité indépendante contenant le profil, la localisation géographique et la date d'inscription. - Collection
produits: Catalogue centralisé avec référence, libellé, catégorie, prix unitaire catalogue et niveau de stock disponible. - Collection
commandes: - Référence vers le client par son
client_id(référencement). - 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 :
Basculez sur la base de données du projet :
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)#
$match: Filtre les documents d'entrée (analogue auWHEREen SQL). Il doit toujours être positionné le plus en amont possible pour éliminer un maximum de documents dès le début.$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.$group: Regroupe les documents selon une clé d'agrégation_idet applique des accumulateurs ($sum,$avg,$min,$max).$lookup: Effectue une jointure NoSQL externe gauche (Left Outer Join) avec une autre collection de la même base.$project: Remodèle les documents en ajoutant des champs calculés ou en renommant/supprimant des attributs.$sort: Trie le flux résultant (1pour ascendant,-1pour 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
paniersavec 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
$facetpour générer en une seule passe réseau à la fois la répartition par catégorie et le panier moyen global.
Commentaires