Que fait Règles en Cascade sur la JVM ?
Dépendance (Maven) : com.rulescascade:rules-cascade-core (Java 17+, aucune autre dépendance runtime ; package com.rulescascade). Gradle : implementation("com.rulescascade:rules-cascade-core:<version>"). Les bundles sont générés par rcas compile --all dans build/rules/. N'évaluez jamais le YAML au moment de l'exécution et ne copiez jamais une règle dans le code.
Charger une fois, évaluer par requête
import com.rulescascade.*;
// Un bean / singleton, créé au démarrage. Une exception ici doit arrêter l'application.
RuleSet rules = RuleSet.fromBundle((Map<String, Object>) Json.parse(Files.readString(Path.of("build/rules/<ruleset-id>.bundle.json"))));
List<String> missing = rules.missingOperators(); // opérateurs personnalisés nécessaires aux règles
if (!missing.isEmpty()) throw new IllegalStateException("opérateurs non enregistrés : " + missing);
Lorsque le service parse du JSON avec Jackson, activez DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS pour que les montants arrivent exactement dans le moteur.
Préférez rules.enforce(request) (depuis 1.0.0-alpha.7) : il retourne le résultat autorisé ou lève com.rulescascade.RuleViolationException, dont problem() est la réponse 422, identique dans tous les runtimes Rule Cascade. Un seul handler répond pour chaque endpoint :
@RestControllerAdvice
class RuleErrors {
@ExceptionHandler(RuleViolationException.class)
ResponseEntity<Map<String, Object>> denied(RuleViolationException e) {
return ResponseEntity.status(e.status()).contentType(MediaType.APPLICATION_PROBLEM_JSON).body(e.problem());
}
}
Pour une annotation sur les méthodes de contrôleur, copiez @EnforceRules et RuleEnforcementAspect depuis l'exemple Spring Boot (https://rulescascade.com/reference/examples/backend-spring-boot/) ; il nécessite spring-boot-starter-aspectj (spring-boot-starter-aop avant Spring Boot 4). Chaque endpoint qui modifie l'état suit quatre étapes :
- Évaluer côté serveur :
rules.evaluate(EvaluationRequest.builder(entity, operation).data(data).original(original).actor(...).build(), Channel.SERVER). L'acteur provient du contexte de sécurité, jamais d'un en-tête contrôlé par l'appelant. - Refuser en cas de refus : quand
!result.allowed(), levez uneRuleViolationException(result)et mappez-la une fois, dans un@RestControllerAdvice/ mapper d'exception, vers 422ProblemDetailavectype: urn:rule-cascade:rule-violationet le résultat (result.toMap()) comme propriété. - Persister, après avoir appliqué les effets
compute(effect.type()estvalue) : la valeur du serveur est stockée. - Exécuter
result.commands()après le commit (@TransactionalEventListener(phase = AFTER_COMMIT)ou une outbox), une fois paridempotencyKey.
Placement : Spring un @Configuration avec un RuleSet @Bean et un service par entité qui construit la requête ; Quarkus/Micronaut un producteur @Singleton. Kotlin et Scala appellent la même API. RuleSetHolder recharge les bundles à intervalles réguliers ou selon un planning cron et conserve les dernières bonnes règles.
Remplacer une validation existante
rcas analyze --module <module> liste les annotations Bean Validation, les validateurs Spring et @PreAuthorize, les règles Drools .drl, les vérifications manuelles, les limites nommées (MAX_QUANTITY = 10), les comparaisons avec des nombres et les branches switch sur un statut. Un candidat avec drift signifie que le même nom a une autre valeur dans le front-end : un seul ruleset doit trancher pour les deux. Gardez @NotNull/@Size sur le DTO si vous le souhaitez ; déplacez les décisions métier dans les règles. Ajoutez un test de parité (JUnit @ParameterizedTest sur les mêmes entrées pour l'ancienne vérification et le moteur), puis supprimez l'ancienne vérification.
Tests
Affirmez decision et les codes de résultat, pas les textes des messages. Les tests dorés dans le ruleset (rcas check) testent les règles ; la CI exécute rcas check et rcas compile --all avant mvn verify / gradle test.
Référence : https://rulescascade.com/usage/java/ et https://rulescascade.com/playbooks/backend-api/