1. Bertrand Goetzmann
  2. storego

Wiki

Clone wiki

storego / Home

Captures d'écran de StoreGo

Bienvenue

Bienvenue sur le wiki du projet StoreGo !

StoreGo est un projet d'application web développée avec le framework Grails destiné à démontrer les fonctions d'agrégation de la base de données NoSQL MongoDB, mais aussi à en afficher les résultats grâce à différentes librairies JavaScript de visualisation de graphes.

Vous trouverez dans le répertoire data de ce projet le fichier transactions.zip, contenant des fichiers XML décrivant des transactions de vente issues d'un système d'encaissement. Nous décrivons par la suite comment créer la base de données storego qui servira de source de données pour l'application web.
Voici la liste des composants utilisés dans ce projet avec leurs versions :

Démarrage du serveur MongoDB

Le serveur de base de données MongoDB peut être démarré avec une commande de la forme :

mongod --dbpath ./data/db/ -vvvvvvvv --rest

L'option --rest permet ici d'avoir un accès de type REST en lecture seule sur les données.

Création de la base de données storego

La base de données storego est créée à partir de l'application Grails StoreGo, lors de l'importation des données. Mais avant de démarrer l'application Grails, il est nécessaire d'indiquer dans le fichier Config.groovy où se trouve le répertoire contenant les fichiers XML des transactions de vente. Pour cela, modifier la valeur de l'entrée storego.integration.dir en lui affectant le chemin répertoire contenant les fichiers XML dézippés depuis l'archive transactions.zip.

Assurez-vous d'avoir démarré le serveur MongoDB, puis démarrez l'application Grails StoreGo.

Dans la page d'accueil, l'application vous invite à importer les données de manière à créer la base de données storego en cliquant sur le lien importation ; déclenchez l'importation : celle-ci va concrètement créer la base de données storego, et une collection nommée transactions comprenant des documents. Chaque document de cette collection représente une transaction de vente. A la fin de l'importation des données, vous devriez voir un message indiquer ceci :

785 transactions importées depuis 894 fichier(s).

C'est le contrôleur Grails IntegrationController qui a la charge de lire et d'analyser les fichiers XML et de construire la collection MongoDB transactions.

Interface REST

Par défaut, MongoDB comprend une simple interface REST, et il suffit d'utiliser certaines conventions dans l'URL pour obtenir les données voulues au format JSON. Par exemple, pour récupérer les données de la collection transactions limitée à 10 documents, on utilisera l'URL suivante :

http:localhost:28017/storego/transactions/?limit=10

Notez que le nom de la collection apparaît dans l'URL. L'application web StoreGo propose un tel accès au travers du lien REST dans la colonne de droite, sous le titre "mongodb transactions".

Accès à la base de données avec GMongo

L'application StoreGo utilise le pilote GMongo pour accéder à la base de données storego ; créé par Paulo Poiati, ce pilote s'appuie sur le pilote Java de MongoDB, en tirant partie du langage Groovy (MongoDB dispose en effet de nombreux clients d'accès pour différents langages de programmation).

Dans l'application StoreGo, on trouve le service Grails DatabaseService dont le but est de fournir un accès à la base de données storego en proposant la méthode db(). Grâce à Grails, ce service peut facilement être injecté dans n'importe quel contrôleur de l'application web.

Format JSON d'une transaction

Dans sa forme JSON, une transaction de vente de la collection transactions se présente ainsi :

{ "_id" : { "$oid" : "4f22a0052b453b1b61253fa5" },
  "articles" : [ { "code" : "3033710026364",
        "famille" : "0001",
        "libelle" : "47G EMINC.VEAU MAG",
        "rayon" : "001",
        "sous_famille" : "00085"
      },
      { "code" : "3036811356002",
        "famille" : "0001",
        "libelle" : "1L POTAG.MALIN LIE",
        "rayon" : "001",
        "sous_famille" : "00085"
      },
      { "code" : "5410228171065",
        "famille" : "0002",
        "libelle" : "LEFFE BLD BASKET",
        "rayon" : "001",
        "sous_famille" : "00240"
      },
      { "code" : "3590941000155",
        "famille" : "0008",
        "libelle" : "BQ150G MACHE PX RD",
        "rayon" : "001",
        "sous_famille" : "00640"
      }
    ],
  "caisse" : "001",
  "client" : {  },
  "date" : { "$date" : 1304200800731 },
  "ilot" : "001",
  "montant" : 9.80,
  "nombre-article" : 4,
  "ticket" : "000002"
}

Dans les données d'une transaction de vente on retrouve donc :

  • la liste des articles vendus (clé articles),
  • sur quel poste d'encaissement (caisse) a eu lieue la vente (clé caisse),
  • si un client a été identifié lors de la vente (clé client),
  • la date et l'heure de la vente (clé date),
  • à quel groupe de caisses la caisse fait partie (clé ilot),
  • le montant total de la transaction (clé montant),
  • le nombre d'articles vendus (clé nombre-article),
  • le numéro de ticket (clé ticket).

D'autre part, un article est décrit par :

  • un code article (clé code),
  • un libellé (clé libelle), et des informations relatives à la structure marchandise d'appartenance de l'article, celle-ci étant hiérarchique :
  • chaque article appartient à une sous-famille d'articles (clé sous_famille),
  • qui appartient à son tour à une famille d'articles (clé famille),
  • et cette dernière appartient à un rayon (clé rayon).

La collection transactions

Ce qui suit présente quelques opérations réalisables sur la collection transactions. Celle-ci peut aussi être listée au travers d'une grille jQuery EasyUI.

count

Le nombre total de documents présents dans la collection est renvoyé par cette expression dans le contrôleur Grails GMongoController de l'application :

databaseService.db().transactions.count()

databaseService est une référence vers le service Grails DatabaseService automatiquement initialisée par Grails.

findOne

Supposons que vous vouliez retrouver une transaction de vente pour laquelle un client a été identifié, et que vous connaissiez le code de ce client. Dans un document représentant une transaction de vente pour laquelle un client a été identifié (au travers d'une carte de fidélité par exemple), la clé client est un document comprenant les clés code (code client) et nom (son nom). Le code ci-dessous permet ainsi de retrouver une transaction du client de code '1012' :

def clientCode = '1012'
def trx = databaseService.db().transactions.findOne('client.code': clientCode)

Une fois le document de la transaction récupérée, vous pouvez bien entendu obtenir les données de cette transaction en utilisant l'opérateur /point/. Pour accéder le nom du client, on utilisera :

trx.client.nom

C'est ce qu'utilise l'action findOne du contrôleur GMongoController qui peut être déclenchée depuis l'application web.

find

Voici un exemple permettant de retrouver les transactions de vente contenant deux articles particuliers, dont les codes figurent dans la liste items :

def items = ['3780000000000', '0000000002006']		
def results = databaseService.db().transactions.find('articles.code': ['$in': items])
		
def transactions = results.collect { it.ticket }

Ici on récupère dans la variable transactions, les numéros des tickets qui contiennent les articles de code 3780000000000 et 0000000002006. Cet exemple peut être exécuté via l'action find du contrôleur Grails GMongoController, avec le lien find de l'application web. Pour plus d'informations sur les requêtes de MongoDB, vous pouvez accéder à la page Advanced Queries.

distinct

Il peut être utile de connaître la liste ou le nombre distinct de caisses sur lesquelles ont eu lieu des ventes, grâce à l'appel distinct('caisse') sur la collections transactions.

def caisses = databaseService.db().transactions.distinct('caisse')

L'action distinct du contrôleur Grails GMongoController renvoie également des informations sur les ilôts (groupes de caisses), les clients, et le nombre distinct d'articles vendus. Vous pouvez déclencher cette action avec le lien distinct de l'application web (colonne de droite).

Fonctions d'agrégation

Regroupement selon une clé

Voyons maintenant comment utiliser l'opération de regroupement afin de déterminer le nombre de transactions de vente réalisées sur chaque caisse ; pour cela nous allons réaliser un regroupement selon la clé caisse, et compter le nombre de transactions associées à chacune d'elle. Dans le code Groovy ci-dessous :

def result = databaseService.db().transactions.group([caisse: true], [:], [sum: 0],
	"function(doc, prev) { prev.sum += 1 }")

l'appel à la méthode group prend les arguments décrits ci-dessous :

  • [caisse: true] : on regroupe selon la clé caisse ;
  • [:] : on n'utilise pas de filtre ;
  • [sum: 0] : document initial créé pour chaque nouvelle valeur de clé rencontrée ;
  • function(doc, prev) { prev.sum += 1 } : c'est la fonction d'agrégation utilisée.

Cette fonction JavaScript reçoit deux arguments : le document en cours (doc) (non utilisé ici), et le document d'agrégation (prev). Après exécution, on obtient au final un document donnant pour chaque caisse, le nombre de transactions qui lui sont associées. La valeur chaîne de caractères de l'objet result est, dans le cadre de l'application web :

 [ { "caisse" : "001" , "sum" : 70.0} , { "caisse" : "002" , "sum" : 136.0} , { "caisse" : "004" , "sum" : 63.0} , { "caisse" : "006" , "sum" : 210.0} , { "caisse" : "009" , "sum" : 213.0} , { "caisse" : "042" , "sum" : 93.0}]

Dans l'application StoreGo, l'action group du contrôleur GMongoController prépare ensuite les données à transmettre à la vue Grails group.gsp :

def data = JSON.parse(result.toString()).sort { it.caisse }
[title: 'Nombre de transactions par caisse', result: result, categories: data.collect { it.caisse } as JSON, data: data.collect { it.sum }]

A partir des données transmises, et grâce à la librairie Highcharts JS, la vue construit un histogramme présentant le Nombre de transactions par caisse :

Nombre de transactions par caisse


Cliquez sur le lien group 1 de l'application web (colonne de droite), pour voir cet histogramme qui s'affiche avec une animation.

Le lien group 2 de l'application web conduit à une nouvelle page présentant un graphe (un treemap circulaire) utilisant la librairie JavaScript Protovis, destiné à présenter la répartition des transactions par groupe de caisse.

Répartition des transactions par groupe de caisse

Que se soit pour un ilôt ou une caisse, l'aire du cercle associé est d'autant plus importante que le nombre de transactions correspondant est élevé. Nous vous invitons à examiner le code source de l'action group2 du contrôleur GMongoController.

Regroupement selon une clé calculée

Cette fois, nous allons à nouveau utiliser la méthode group, pour calculer le nombre de transactions de vente réalisées par jour, et donc regrouper par jour. Bien qu'un document transaction possède une clé date, il s'agit en fait d'une date et d'une heure particulières ; nous pouvons utiliser le paramètre keyf pour transmettre une fonction JavaScript qui servira à définir une nouvelle clé, day, pour chaque document, et par laquelle le regroupement se fera :

def keyf = """\
function(doc) {
    var dateKey = new Date(doc.date.getFullYear(), doc.date.getMonth(), doc.date.getDate()).getTime()
    return {day: dateKey};
}
"""
def command = ['$keyf': keyf, cond: [:], initial: [count: 0], $reduce: "function(doc, out) { out.count += 1 }"]		
def result = databaseService.db().transactions.group(command)

Notez que les autres paramètres de la méthode group, transmis sous la forme d'une map Groovy, sont similaires à ceux dont nous avons déjà parlés plus haut.
Le code présenté ci-dessus est celui de l'action keyf du contrôleur Grails GMongoController. A partir des données agrégées, le code de cette action se poursuit pour transmettre les données nécessaires (au format JSON) à la construction du graphe Highcharts JS :

def data = result.collect { [it.day.toLong(), it.count] }
[title: 'Nombre de transactions par jour', result: result, data: data as JSON]

Ce graphe représente le nombre de transactions par jour.

Nombre de transactions par jour



L'application StoreGo propose un autre exemple d'utilisation du paramètre keyf qui permet de représenter la répartition des transactions par tranche de montant :

Répartition des transactions par tranche de montant

Ce graphe découpe les transactions en fonction de leurs montants, selon un découpage en quatre tranches ; par ailleurs, l'utilisateur de l'application web peut agir sur ce découpage en sélectionnant deux valeurs de manière interactive, sur une glissière.
Le code source correspondant est celui de l'action keyf2 du contrôleur GMongoController.

Map/reduce

MongoDB permet d'effectuer des opérations de type map/reduce, et dans StoreGo, cela va nous permettre de produire un nouveau rapport : celui des articles les plus vendus.

Meilleures ventes

Dans l'appel de la méthode mapReduce sur la collection transactions, ci-après (code de l'action mapReduce du contrôleur GMongoController), le premier paramètre fournit la fonction map, et le second la fonction d'agrégation reduce :

def result = databaseService.db().transactions.mapReduce(
    """
    function map() {
    	for (var i in this.articles)
        	emit(this.articles[i].code, {label: this.articles[i].libelle, count: 1})
    }
    """,
    """
    function reduce(key, values) {
        var count = 0
		for (var i in values)
            count += values[i].count
        return {label: values[0].label, count: count}
    }
    """,
    "mrresult",
    [:] // No Query
)

La fonction map est ici appelée pour chaque document de la collection, et parcourt la liste des articles de la transaction en émettant un document - que nous appellerons D - pour chaque article ; ce document, qui comprend les clés label (ayant pour valeur le libellé de l'article) et count (valant 1), est associé à la valeur du code de l'article en cours qui servira de clé de regroupement pour la fonction reduce.

Quant à la fonction reduce, elle est appelée pour chaque valeur distincte de code article dans le paramètre key avec, dans le paramètre valeurs, l'ensemble des documents D associés au code article. Le code de la fonction reduce peut ainsi sommer toutes les valeurs trouvées dans la clé count, et produire un nouveau document contenant un libellé d'article et le nombre de fois que cet article apparaît dans les transactions de vente.

Le troisième paramètre de l'appel mapReduce est mrresult : c'est le nom de la collection MongoDB dans laquelle le résultat de l'appel sera stocké. Dans le code de l'action mapReduce, cette collection est ensuite parcourue pour obtenir une nouvelle liste de données destinée à l'affiche du rapport dans une vue GSP.

def mrresult = databaseService.db().mrresult.find().sort('value.count': -1).limit(10)

def categories = []; def data = []
mrresult.each { item ->
    categories << item.value.label
    data << item.value.count.toInteger() 
} 

[title: 'Meilleures ventes', categories: categories as JSON, result: mrresult, data: data]

Notez de quelle manière, dans la première de code du bloc ci-dessus, on parvient à se limiter aux dix meilleures vente, avec un tri décroissant sur le nombre d'articles.

Le graphe produit par l'action Grails mapReduce utilise à nouveau la librairie Highcharts JS.

Updated