TinyRuleKit
Expression Views
Expression views define what rule expressions can read from your domain model. They are the main boundary between configurable rule text and application objects.
Safe Expression Views
Do not expose mutable domain objects directly unless they are intentionally safe. Objects can implement
ExpressionAccessible to return a restricted view for expressions.
record PlayerView(String name, int level, Set<String> permissions) {
boolean hasPermission(String permission) {
return permissions.contains(permission);
}
}
final class Player implements ExpressionAccessible {
private final String name;
private final int level;
private final Set<String> permissions;
private final PlayerView expressionView;
Player(String name, int level, Set<String> permissions) {
this.name = name;
this.level = level;
this.permissions = Set.copyOf(permissions);
this.expressionView = new PlayerView(name, level, this.permissions);
}
@Override
public Object toExpressionView() {
return expressionView;
}
}
When this object is placed in variables, TinyRuleKit stores the expression view:
RuleVariables.create()
.put("player", player);
Expressions can then use the safe view:
{
"type": "expr",
"expr": "player.level() >= 10 && player.hasPermission('vip')"
}
Safe Self Views
Immutable objects that are intentionally safe can expose themselves:
@ExpressionAccess(viewMode = ExpressionViewMode.SELF)
record PlayerView(String name, int level) implements ExpressionAccessible {
@Override
public boolean isExpressionSafe() {
return true;
}
@Override
public Object toExpressionView() {
return this;
}
}
If an object returns itself without declaring safety, TinyRuleKit rejects it during precompilation.
Scalar Variables
Simple values do not need an expression view, but they must be added explicitly:
RuleVariables.create()
.putScalar("level", 10)
.putScalar("permission", "vip")
.putScalar("enabled", true);
putScalar accepts primitive wrapper values and strings. Collections, domain objects, services,
repositories, and other objects must use a safe expression view instead.
Recommended View Shape
- expose read-only data, not mutable domain objects
- prefer primitive values, strings, records, enums, and immutable collections inside views
- keep methods side-effect free
- expose business concepts, not infrastructure objects
- avoid repositories, file APIs, threads, classloaders, reflection, and runtime execution
Cached Views
Expressions are compiled once when the engine is built, but expression views may be created every time
ruleVariables() is called. Prefer cached views in hot paths:
final class Wallet implements ExpressionAccessible {
private int coins;
private final WalletView expressionView = new WalletView(this);
@Override
public Object toExpressionView() {
return expressionView;
}
static final class WalletView {
private final Wallet wallet;
WalletView(Wallet wallet) {
this.wallet = wallet;
}
public int coins() {
return wallet.coins;
}
}
}
Creating a new view on every access is allowed, but TinyRuleKit warns during precompilation so you can avoid unnecessary allocations in hot paths.
Continue with Expression Best Practices before configuring expression security.