Provides a lightweight framework to create, run and explain rules. Any application that needs rules coded in a structured manner may use this. The goal is to isolate functional rules from the technical code and thus make the rules more accessible to non-technical personnel.
Inspired by dp-quiz.
A set of classes inheriting AbstractRuleComponent make up the core of the framework:
- AbstractRuleService Entrypoint for the service.
- AbstractRuleflow Organizes the flowlogic.
- AbstractRuleset Wraps a set of rules that relate to a single topic.
- Rule A single functional decision.
- Faktum A name-value pair used by Subsumtion.
- Predicate Wraps the boolean expression function for technical expressions (null checks and similar).
- AbstractSubsumtion Base class for functional expressions.
- PairSubsumtion Compares two Faktums using PairComparator.
- ListSubsumtion Compares a Faktum's relationship to a list of AbstractRuleComponent.
- AbstractSubsumtion Base class for functional expressions.
All AbstractRuleComponent are organized in a visitor-accepting tree structure. This tree enables tracking of the context a rulecomponent was executed in.
regeltjeneste: BeregnAlderspensjonService
regelflyt: BeregnAlderspensjonFlyt
regelsett: BeregnFaktiskTrygdetidRS
regel: JA BeregnFaktiskTrygdetidRS.SettFireFemtedelskrav
regel: NEI BeregnFaktiskTrygdetidRS.Skal ha redusert fremtidig trygdetid
NEI 'virkningstidspunkt' (1990-05-01) må være etter eller lik '1991-01-01'
JA 'faktisk trygdetid i måneder' (224) er mindre enn 'firefemtedelskrav' (480)
Noteworthy methods are:
- root() returns the top level AbstractRuleComponent.
- debug() debug string of all decendants.
- xmlDebug() debug xml of all decendants.
- trace( searchfunction ) searches for, and traces path to, AbstractRuleComponent
- find( searchfunction ) searches the ruleComponent tree for target AbstractRuleComponents
See InspectionTest for examples.
AbstractRuleComponents have a resourceMap containing AbstractResource instantiated per service call. These objects typically contain resources like rates ("satser"), loggers and other global assets. See AbstractDemoRuleService for demonstration.
A kotlin "mini-DSL", inspired by Kotlins type-safe builders, provides a simple syntax for creating rules and describing logic flow control in ruleflows. The domain in the DSL is generic ruledevelopment and not specific to NAV.
All DSL syntax is in norwegian.
In AbstractRuleflow:
forgrening("Sivilstand gift?") {
gren {
/**
* Et boolsk uttrykk betinger eksekveringen av påfølgende flyt-blokk.
*/
betingelse { parameter.input.person.erGift }
flyt {
grunnpensjonSats = 0.90
}
}
gren { } // andre gren
}
A standard rule in AbstractRuleset with technical predicate and functional Subsumtion. A Functional Subsumtion is any expression that produces an object of type AbstractSubsumsjon. See custom Operators.
regel("RedusertTrygdetid") {
HVIS { trygdetid != null } // technical predicate
OG { trygdetid erMindreEnn 40 } // functional subsumtion
SÅ {
netto = grunnbeløp * sats * trygdetid / 40.0
}
}
A rule with else-statement and a value return:
regel("Trygdetid") {
HVIS { anvendtFlyktning erLik OPPFYLT }
SÅ { // action-statement runs if all predicates are true
RETURNER( 40 ) // returns a value and stops further evaluation of the ruleset.
}
ELLERS { // else-statement runs if one or more predicates are false
RETURNER( faktiskTrygdetid )
}
}
Optional feature for writing rules on lists. A Pattern object wraps a List and ensures a Rule instance is created for each item in the list.
val norskeBoperioder = boperiodeListe.createPattern { it.land == LandEnum.NOR }
regel("BoPeriodeStartFør16år", norskeBoperioder) { boperiode ->
HVIS { boperiode.fom < dato16år }
SÅ {
svar.faktiskTrygdetidIMåneder += ChronoUnit.MONTHS.between(dato16år, boperiode.tom)
}
}
A rudimentary plugin for Intellij is in development for ruleflow visualization.
Maven:
<dependency>
<groupId>no.nav.system</groupId>
<artifactId>rule.dsl</artifactId>
<version>1.4.7</version>
</dependency>
External: Raise issues on GitHub
Internal: On slack #pensjon-regler