Dans ce billet, nous allons voir ensemble comment implémenter un cas d'usage simple avec RuleShake.
Pour les impatients, voici le lien vers cet exemple : https://demo.ruleshake.com/discount
Besoin
Prenons un site e-commerce modeste vendant une douzaine d'articles (vraiment très modeste !) pour lequel nous voulons mettre en place un système de promotion.
Nous avons besoin de modéliser un référentiel d'articles ou pour chaque article définir son libellé, sa catégorie, son prix et une image.
Nous souhaitons mettre en plus 4 promotions :
- 0,03 € de remise pour chaque lot de 3 articles de la même catégorie
- 0,05 € de remise pour chaque pot de Nutella
- 0,10 € de remise pour chaque lot de 5 articles (quelque soit leurs catégories)
- 0,50 € de remise si le montant du panier (en soustrayant les autres remises) dépasse 10 €
Analyse
D'un point de vue RuleShake, on va pouvoir identifier deux éléments dans ce besoin :
- Un dataset représentant le référentiel d'articles qui sera défini dans RuleShake Referential
- Une collection permettant le calcul des promotions qui sera définie dans RuleShake Catalog puis publiée pour être exécutée dans RuleShake Runner
Implémentation
Référentiel de produits
Commençons donc par définir le dataset référentiel d'articles dans RuleShake Referential :
On voit ici que nous avons créé un dataset qui a pour code ARTICLES
et qui est composé de 4 propriétés :
LABEL
: le libellé de l'articleCATEGORY
: la catégorie de l'articleIMAGE
: lien vers une image représentant l'articlePRICE
: le prix de l'article
En plus de ces propriétés, chaque entrée (ou record) dans ce dataset devra renseigner un code unique. Pour cet exemple, nous allons renseigner le code barre (EAN13) de l'article :
Nous avons fini le paramétrage dans RuleShake Referential pour la création du référentiel des produits. Nous allons maintenant créer la collection qui permet le calcul des promotions.
Calcul de la promotion
Nous allons maintenant créer une collection dans RuleShake Catalog que nous allons appeler DISCOUNT
et dans laquelle on
va définir deux variables :
ARTICLE
: une variable d'entrée qui représente un article ajouté dans le panier. Cette variable est multiple, elle peut donc recevoir plusieurs valeurs. Elle est de typeRecord
, elle est donc liée à un dataset défini dans RuleShake Referential.DISCOUNTS
: une variable calculée de typeComposite
. Cela veut dire que cette variable n'a pas de valeur directe, mais contient des sous-variables.
Regardons maintenant la définition de la variable ARTICLE
:
Comme mentionné précédemment, les variables de type Record
sont forcément liées à un dataset défini dans RuleShake
Referential. Dans cet exemple, ARTICLE
est lié au dataset ARTICLES
que nous avons défini au début.
La variable ARTICLE
est définie comme multiple et pouvant avoir jusqu'à 10 000 valeurs.
Lors de l'appel de RuleShake Runner pour l'évaluation de la collection DISCOUNT
, pour chaque instance de la
variable ARTICLE
on donnera le code d'un record du dataset ARTICLES
, exemple :
{
"type": "record",
"reference": "ARTICLE[0]",
"value": "3017620425035"
}
Pour les variables de type Record
, RuleShake Runner va interroger RuleShake Referential pour résoudre ces variables et
construire une variable composite avec le même nom et ayant comme sous-variables les propriétés du dataset.
Dans la définition de la variable ARTICLE
, on visualise la liste des sous-variables qui seront résolues au runtime :
Ces sous-variables peuvent être utilisées dans les formules de calcul des autres variables calculées.
Maintenant, intéressons-nous à la variable DISCOUNTS
qui porte les promotions :
Nous avons ici modélisé les 4 promotions demandées sous forme de variables calculées de type Nombre
. Dans chacune de
ces variables, on va définir le calcul de la promotion concernée.
Prenons l'exemple de la promotion NUTELLA
:
Dans la définition de la valeur de cette variable, nous avons laissé le champ statique Valeur à vide et nous avons défini une formule de calcul. Cette formule est écrite dans le langage mvel.
Nous filtrerons les instances de la variable ARTICLE
pour ne garder que celles qui ont la sous-variable LABEL
valant
Nutella. Ensuite, si la liste résultante est vide, on retourne 0
sinon on multiplie la taille de cette liste par 0.05
.
Pour les autres variables promotions :
Promotion | Formule |
---|---|
SAME_CATEGORY | groups = MultipleHelper.groupBy(ARTICLE, "CATEGORY"); |
NB_ARTICLES | NumberHelper.floorDiv(MultipleHelper.count(ARTICLE), 5) * 0.1 |
TOTAL_AMOUNT | discount = DISCOUNTS.NB_ARTICLES.value + DISCOUNTS.SAME_CATEGORY.value + DISCOUNTS.NUTELLA.value; |
Utilisation
Maintenant que nous avons terminé le paramétrage de la collection DISCOUNT
, on peut commencer à l'évaluer avec des
variables représentants les articles.
Une API exposée par RuleShake Runner permet d'évaluer une collection en lui passant en entrée des inputs assignant des valeurs aux variables de nature Entrée
Exemple de requête :
POST ruleshake-runner/api/v1/evaluations
{
"requestTime": "2023-06-11T00:00",
"collectionCode": "DISCOUNT",
"inputs": [
{
"reference": "ARTICLE[0]",
"value": "3017620425035",
"type": "record"
},
{
"reference": "ARTICLE[1]",
"value": "3083680973939",
"type": "record"
},
{
"reference": "ARTICLE[2]",
"value": "3083680973939",
"type": "record"
},
{
"reference": "ARTICLE[3]",
"value": "3083681085860",
"type": "record"
},
{
"reference": "ARTICLE[4]",
"value": "3700009252567",
"type": "record"
}
]
}
En réponse, nous obtenons le résultat suivant (payload épuré des champs techniques pour améliorer la lecture) :
Cliquer pour développer
{
"reference": {
"code": "DISCOUNT",
"version": 1
},
"variables": [
{
"runtimeReference": "ARTICLE[0]",
"definitionReference": "ARTICLE",
"type": "RECORD",
"value": "3017620425035",
"datasetCode": "ARTICLES",
"subVariables": [
{
"runtimeReference": "ARTICLE[0]/IMAGE",
"definitionReference": "ARTICLE/IMAGE",
"type": "STRING",
"value": "https://images.openfoodfacts.org/images/products/301/762/042/5035/front_fr.427.400.jpg"
},
{
"runtimeReference": "ARTICLE[0]/PRICE",
"definitionReference": "ARTICLE/PRICE",
"type": "NUMBER",
"value": 3.49
},
{
"runtimeReference": "ARTICLE[0]/CATEGORY",
"definitionReference": "ARTICLE/CATEGORY",
"type": "STRING",
"value": "Sweet groceries"
},
{
"runtimeReference": "ARTICLE[0]/LABEL",
"definitionReference": "ARTICLE/LABEL",
"type": "STRING",
"value": "Nutella"
}
]
},
{
"runtimeReference": "ARTICLE[1]",
"definitionReference": "ARTICLE",
"type": "RECORD",
"value": "3083680973939",
"datasetCode": "ARTICLES",
"subVariables": [
{
"runtimeReference": "ARTICLE[1]/IMAGE",
"definitionReference": "ARTICLE/IMAGE",
"type": "STRING",
"value": "https://images.openfoodfacts.org/images/products/308/368/097/3939/front_fr.79.400.jpg"
},
{
"runtimeReference": "ARTICLE[1]/PRICE",
"definitionReference": "ARTICLE/PRICE",
"type": "NUMBER",
"value": 2.89
},
{
"runtimeReference": "ARTICLE[1]/CATEGORY",
"definitionReference": "ARTICLE/CATEGORY",
"type": "STRING",
"value": "Canned"
},
{
"runtimeReference": "ARTICLE[1]/LABEL",
"definitionReference": "ARTICLE/LABEL",
"type": "STRING",
"value": "Ratatouille Cassegrain"
}
]
},
{
"runtimeReference": "ARTICLE[2]",
"definitionReference": "ARTICLE",
"type": "RECORD",
"value": "3083680973939",
"datasetCode": "ARTICLES",
"subVariables": [
{
"runtimeReference": "ARTICLE[2]/IMAGE",
"definitionReference": "ARTICLE/IMAGE",
"type": "STRING",
"value": "https://images.openfoodfacts.org/images/products/308/368/097/3939/front_fr.79.400.jpg"
},
{
"runtimeReference": "ARTICLE[2]/PRICE",
"definitionReference": "ARTICLE/PRICE",
"type": "NUMBER",
"value": 2.89
},
{
"runtimeReference": "ARTICLE[2]/CATEGORY",
"definitionReference": "ARTICLE/CATEGORY",
"type": "STRING",
"value": "Canned"
},
{
"runtimeReference": "ARTICLE[2]/LABEL",
"definitionReference": "ARTICLE/LABEL",
"type": "STRING",
"value": "Ratatouille Cassegrain"
}
]
},
{
"runtimeReference": "ARTICLE[3]",
"definitionReference": "ARTICLE",
"type": "RECORD",
"value": "3083681085860",
"datasetCode": "ARTICLES",
"subVariables": [
{
"runtimeReference": "ARTICLE[3]/IMAGE",
"definitionReference": "ARTICLE/IMAGE",
"type": "STRING",
"value": "https://images.openfoodfacts.org/images/products/308/368/108/5860/front_fr.23.400.jpg"
},
{
"runtimeReference": "ARTICLE[3]/PRICE",
"definitionReference": "ARTICLE/PRICE",
"type": "NUMBER",
"value": 1.8
},
{
"runtimeReference": "ARTICLE[3]/CATEGORY",
"definitionReference": "ARTICLE/CATEGORY",
"type": "STRING",
"value": "Canned"
},
{
"runtimeReference": "ARTICLE[3]/LABEL",
"definitionReference": "ARTICLE/LABEL",
"type": "STRING",
"value": "Cooked chickpeas"
}
]
},
{
"runtimeReference": "ARTICLE[4]",
"definitionReference": "ARTICLE",
"type": "RECORD",
"value": "3700009252567",
"datasetCode": "ARTICLES",
"subVariables": [
{
"runtimeReference": "ARTICLE[4]/IMAGE",
"definitionReference": "ARTICLE/IMAGE",
"type": "STRING",
"value": "https://images.openfoodfacts.org/images/products/370/000/925/2567/front_fr.49.400.jpg"
},
{
"runtimeReference": "ARTICLE[4]/PRICE",
"definitionReference": "ARTICLE/PRICE",
"type": "NUMBER",
"value": 6.15
},
{
"runtimeReference": "ARTICLE[4]/CATEGORY",
"definitionReference": "ARTICLE/CATEGORY",
"type": "STRING",
"value": "Caterer"
},
{
"runtimeReference": "ARTICLE[4]/LABEL",
"definitionReference": "ARTICLE/LABEL",
"type": "STRING",
"value": "Salad Bowl Caesar Mix"
}
]
},
{
"runtimeReference": "DISCOUNTS",
"definitionReference": "DISCOUNTS",
"type": "COMPOSITE",
"value": [
{
"runtimeReference": "DISCOUNTS/NB_ARTICLES",
"definitionReference": "DISCOUNTS/NB_ARTICLES",
"type": "NUMBER",
"value": 0.1,
"properties": {
"DESC": "0,10€ discount for each set of 5 articles"
}
},
{
"runtimeReference": "DISCOUNTS/NUTELLA",
"definitionReference": "DISCOUNTS/NUTELLA",
"type": "NUMBER",
"value": 0.05,
"properties": {
"DESC": "0,05€ for each nutella jar"
}
},
{
"runtimeReference": "DISCOUNTS/TOTAL_AMOUNT",
"definitionReference": "DISCOUNTS/TOTAL_AMOUNT",
"type": "NUMBER",
"value": 0.5,
"properties": {
"DESC": "0,50€ discount if total amount (discounts deducted) is more than 10€"
}
},
{
"runtimeReference": "DISCOUNTS/SAME_CATEGORY",
"definitionReference": "DISCOUNTS/SAME_CATEGORY",
"type": "NUMBER",
"value": 0.03,
"properties": {
"DESC": "0,03€ discount for each set of 3 products of the same category"
}
}
]
}
]
}
Alors c'est sûr qu'une meilleure présentation que du JSON est possible ! (une IHM par exemple 😮, peut être même en Vue.js 😎). Si vous avez résisté au clic du lien au début de ce billet, vous avez maintenant mérité de le faire :