Rule Cascade auf der JVM
Abhängigkeit (Maven): com.rulescascade:rules-cascade-core (Java 17+, keine weitere Laufzeitabhängigkeit;
Paket com.rulescascade). Gradle: implementation("com.rulescascade:rules-cascade-core:<version>").
Bundles stammen aus rcas compile --all, abgelegt in build/rules/. Werte das YAML niemals zur Laufzeit aus und kopiere niemals eine Regel in den Code.
Einmal laden, pro Anfrage auswerten
import com.rulescascade.*;
// Ein Bean / Singleton, das beim Start erstellt wird. Eine Ausnahme hier muss die Anwendung stoppen.
RuleSet rules = RuleSet.fromBundle((Map<String, Object>) Json.parse(Files.readString(Path.of("build/rules/<ruleset-id>.bundle.json"))));
List<String> missing = rules.missingOperators(); // benutzerdefinierte Operatoren, die die Regeln benötigen
if (!missing.isEmpty()) throw new IllegalStateException("unregistered operators: " + missing);
Wenn der Dienst JSON mit Jackson parst, aktiviere DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS,
damit Beträge exakt in die Engine gelangen.
Bevorzuge rules.enforce(request) (seit 1.0.0-alpha.7): es gibt das erlaubte Ergebnis zurück oder wirft
com.rulescascade.RuleViolationException, dessen problem() die 422-Antwort ist, die in jeder
Rule-Cascade-Laufzeitumgebung gleich ist. Ein Handler beantwortet es für jeden Endpunkt:
@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());
}
}
Für eine Annotation an Controller-Methoden kopiere @EnforceRules und RuleEnforcementAspect aus dem
Spring-Boot-Beispiel (https://rulescascade.com/reference/examples/backend-spring-boot/); es benötigt
spring-boot-starter-aspectj (spring-boot-starter-aop vor Spring Boot 4). Jeder zustandsändernde Endpunkt
folgt vier Schritten:
- Auf dem Server auswerten:
rules.evaluate(EvaluationRequest.builder(entity, operation).data(data).original(original).actor(...).build(), Channel.SERVER). Der Actor stammt aus dem Sicherheitskontext, niemals aus einem vom Aufrufer gesteuerten Header. - Bei Verweigerung ablehnen: wenn
!result.allowed(), wirf eineRuleViolationException(result)und bilde sie einmalig, in einem@RestControllerAdvice/ Exception Mapper, auf eine 422ProblemDetailmittype: urn:rule-cascade:rule-violationund dem Ergebnis (result.toMap()) als Eigenschaft ab. - Persistieren, nach Anwendung der
compute-Effekte (effect.type()istvalue): der Serverwert wird gespeichert. result.commands()ausführen nach dem Commit (@TransactionalEventListener(phase = AFTER_COMMIT)oder einer Outbox), einmal proidempotencyKey.
Platzierung: Spring eine @Configuration mit einem RuleSet @Bean und einem Dienst pro Entität, der die Anfrage
aufbaut; Quarkus/Micronaut ein @Singleton-Producer. Kotlin und Scala rufen dieselbe API auf.
RuleSetHolder lädt Bundles in einem Intervall oder nach einem Cron-Zeitplan neu und behält die letzte gültige Regeln.
Ersetzen vorhandener Validierungen
rcas analyze --module <module> listet Bean-Validation-Annotationen, Spring-Validatoren und
@PreAuthorize, Drools .drl-Regeln, handgeschriebene Prüfungen, benannte Limits (MAX_QUANTITY = 10),
Vergleiche mit Zahlen und switch-Zweige zu einem Status auf. Ein Kandidat mit drift bedeutet, dass derselbe Name
im Frontend einen anderen Wert hat: Ein Regelsatz muss ihn für beide entscheiden. Behalte @NotNull/@Size-Typ-
Prüfungen auf dem DTO, wenn du möchtest; verschiebe Geschäftsentscheidungen in Regeln. Füge einen Paritätstest hinzu (JUnit
@ParameterizedTest über dieselben Eingaben für die alte Prüfung und die Engine) und lösche dann die alte Prüfung.
Tests
Behaupte decision und Finding-Codes, nicht Nachrichtentexte. Goldene Tests im Regelsatz (rcas check)
testen die Regeln; CI führt rcas check und rcas compile --all vor mvn verify / gradle test aus.
Referenz: https://rulescascade.com/usage/java/ und https://rulescascade.com/playbooks/backend-api/