ifServ Beta Milestone – October 2024
Is it polished? Nope. This beta-ish is really about the engine itself. The MVP functionality is pretty stable. After working on this solo for about 10 months, I decided to put the pens down on it for a bit to concentrate on some other things.
The UI/UX is a disaster and this is my weakest link skill-wise. I’d be amazed if anyone actually figured out how to use it given the current state of the UI. This post will describe the basic functionality of the Evaluation Engine only.
Trying it out
The application is running in the cloud here.
You can create a user if you like, but it’s probably better for me to create a user for you in the testing area so you can see the demo rules. For a new user, there should be a whole onboarding / getting started flow which I have not done at all yet.
When you “sign up”, your user is created as well as an Organization for that user. It also creates a sibling API user which provides the Auth token to make API calls with.
Your user has Admin rights in your org (which lets you add other users). It also lets you view and copy the API auth token.

Facts In, Results Out
The rule config structure…
- Rules contain Solutions.
- Solutions consist of an optional Condition and a Result.
- The Engine evaluates Your Fact Model and returns Your Result Model
Conditions are built with a simple UI but are actually rendered and executed as plain old javascript. You can modify or extend the code as needed with a built-in VS Code editor.
Results can be static JSON models or generative JavaScript. Like conditions, they have access to Your Facts and can even consume the result of other rules for advanced computations.
Rules can be organized in an optional container hierarchy. The Evaluation API selects rules by Rule IDs or by Container ID and Rule Type to find the closest matching rule, supporting global defaults with specific overrides.
An example would be great, Bob
Let’s say you’re building a fancy service like oh, a rules engine and want to do subscription style billing. You have three subscription levels: Free, StartUp, BigShot. Each tier allows a certain number of included API calls with an overage cost per call and other parameters. At the end of a billing cycle, you want to figure out how much to bill an account. We’ll do this with two rules: One to hold the basic parameters of the subscription tiers and the other to use those parameters to actually compute a cost.
subscription-features will be the rule with the parameters. subscription-billing will do the computation. The latter will consume the output of the former.

The subscription-features rule has three solutions (one for each tier). Each solution has a single condition that matches on the subscription level fact. For example, the free-tier solution has this one condition.

The actual condition code is generated by the widgets and shown in the code editor like so:
// Declare output object
var output = {
result:false,
meta:{}
};
// Set boolean result value
output.result=$ifsSubscription.level=='Free';
If that condition evaluates true, the result (static json in this case) will be returned. The solutions for the other two tiers are similar with different values in the result.
If I edit the Javascript, the condition widgets won’t agree anymore!
Yep, that’s right but it senses that and indicates that the controls have been overridden. There is also a button to revert the script to bare widgets mode again.
Here, I’ve added “&& true” to the boolean expression in the generated script. That disabled the widgets and lit up the button to restore the condition.

Fact Tokens
Note in the script that the fact token starts with a “$”. It represents a path into the fact object passed in to the evaluation API. Conditions can be written to access any arbitrary fact model you throw at it. Fact tokens are replaced at runtime with the actual fact input value.
Where do these fact tokens come from?
The condition editor has a pop-up picker with type-ahead support for browsing and choosing fact tokens. The choices presented are those of several built-in fact models.
In the grand glorious future, you will be able to paste in any arbitrary fact model and the rule will learn all the attribute paths in that model and present them in the picker. We may even be able to make the VS Code editor aware of them for type-ahead.

API Facts Input
In this case, the API will be called with a Facts Model that looks like this:
"facts": {
"ifsSubscription": {
"level": "BigShot"
},
"ifsTransactionData": {
"countCurrentBilling": 98750
}
}
So the fact token $ifsSubscription.level will evaluate to “BigShot”. Notice that we’re passing in two facts: the subscription level and the number of evals done in the current billing period.
The result object of the BigShot solution is
{
"baseCost": {
"amount": 79.95,
"currency": {
"code": "USD",
"name": "US Dollar"
}
},
"evalAllowance": {
"hardLimit": -1,
"included": 50000
},
"allowedUsers": 25,
"overage": {
"costPerEval": 0.0075
}
}
This rule itself is not all that interesting. It’s basically a chunk of json that can be accessed by an ID (sounds like mongo db doesn’t it?). Now let’s look at the rule that does the heavy lifting.

The subscription-billing rule has just one solution. That one solution has no conditions (that is, it is unconditional). The interesting part is the Result which in this case is javascript, not static JSON.
ActiveState controls
You’ll notice these widgets all over the place with the “Active” power button and a trash can. Most of the config objects in ifServ have a three state ActiveState attribute: Active/Inactive/Deleted. Inactive objects are removed from the rule execution. Deleted objects are also removed from the UI. Deleted objects are not really deleted but there is no UI to bring them back to life yet. Maybe an admin role can do that some day.
// call the sub-info rule
Logger.log('Calling sub data rule');
const subInfo = JSON.parse(Rules.evalRule('0HCF6CGRW5R5Z'));
Logger.log(subInfo);
var overageCount =
$ifsTransactionData.countCurrentBilling -
subInfo.evalAllowance.included;
overageCount = Math.max(0, overageCount);
// Build the output object
var output = {
evalCount: $ifsTransactionData.countCurrentBilling,
currency: subInfo.baseCost.currency,
baseCost: {
amount: subInfo.baseCost.amount,
},
evalAllowance: {
level: $ifsSubscription.level,
hardLimit: subInfo.evalAllowance.hardLimit,
included: subInfo.evalAllowance.included
},
evalOverage: {
costPer: subInfo.overage.costPerEval,
count: overageCount,
cost: overageCount * subInfo.overage.costPerEval
},
total: {
amount: subInfo.baseCost.amount +
overageCount * subInfo.overage.costPerEval
}
}
The actual computed result is whatever is put into the output object on line 12.
Is it really the VS Code Editor?
It’s actually the Monaco Editor which is what VS Code uses. It is aware of the content type (json or javascript) and so gives you all the same syntax help. It also opens up all the same type-ahead bits that you are used to. I think it is possible to even tell it about additional types – for example the helpers we inject or the results from a rule call – to give even better type-ahead support.
The result script has access to all of the same facts as the conditions. The most interesting thing is what happens on line 3. It’s calling another rule and setting its result to a local const called subInfo. It is doing this through an ifServ helper class called Rules. Rules is one of the many ifServ engine helpers made available to the javascript runtime. The Rules helper lets you call anther rule with all of the same facts as the current rule has. The rule we’re calling here is the simple one above (subscription-features) which just returns the static parameters for the subscription level.
Where did that Rule ID come from?
In the rule editor there is a “fingerprint” button you can click to copy the Rule’s ID to the clipboard. This will probably expand to some more generic way of copying rule identifiers.

We’re also using a second helper called Logger. Anything logged here is returned in the API output in the jsLogs[] array.
The rest of this result script is pretty straightforward code that is computing the cost given the $ifsTransactionData.countCurrentBilling Fact and the result of the first rule (based on the $ifsSubscription.level fact).
Result Types
Solution Results can be either static JSON or generative Javascript. You can switch back and forth between these modes without losing your content.
You can also convert a JSON type of result to a Javascript type with the click of a button. In either case, the VS Code editor knows which type of content you are editing and gives you all the syntax highlighting and error indication you are used to in that editor.

Trying it out. The request payload might look like this:
{
"ruleSelectors": [
{
"ruleId": "0HK714YJ4583A"
}
],
"facts": {
"ifsSubscription": {
"level": "BigShot"
},
"ifsTransactionData": {
"countCurrentBilling": 98750
}
}
}
And a few milliseconds later, the result:
{
"org": {
"id": "0HBR7YDHR083A",
"name": "ifServe Admin Org",
"activeState": "Active"
},
"engineMilliseconds": 54,
"ruleResults": [
{
"ruleId": "0HK714YJ4583A",
"ruleName": "subscription-billing",
"evaluatedSolutions": [
{
"solutionId": "0HK71APM4701P",
"conditionCombineLogic": "AllTrue",
"trueConditions": [],
"solutionResult": {
"id": "0HK71APN47006",
"resultMode": "Javascript",
"computedModel": {
"evalOverage": {
"cost": 365.625,
"count": 48750,
"costPer": 0.0075
},
"baseCost": {
"amount": 79.95
},
"total": {
"amount": 445.575
},
"evalCount": 98750,
"evalAllowance": {
"level": "BigShot",
"hardLimit": -1,
"included": 50000
},
"currency": {
"code": "USD",
"name": "US Dollar"
}
}
}
}
]
}
],
"contextUid": "9bede0ee-6391-4390-a04e-6596f8683968",
"jsLogs": [
"Calling sub data rule",
"{baseCost: {amount: 79.95, currency: {code: \"USD\", name: \"US Dollar\"}}, evalAllowance: {hardLimit: -1, included: 50000}, allowedUsers: 25, overage: {costPerEval: 0.0075}}"
]
}
Here’s a CURL of the above request (auth header redacted)
curl --location 'https://api.ifserv.com/ruleapi/v1/eval' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI*********lKVDY' \
--header 'Content-Type: application/json' \
--data '{
"ruleSelectors": [
{
"ruleId": "0HK714YJ4583A"
}
],
"facts": {
"ifsSubscription": {
"level": "BigShot"
},
"ifsTransactionData": {
"countCurrentBilling": 98750
}
}
}'
Test Rules in the UI?
Yes it’s planned and should be pretty easy to do. The rule UI will have a place to paste in the “facts” JSON input object. This will probably also double as a rule’s “learn” function for fact tokens so they show up in the picker. A button click will run the facts object through the rule and the computed result will be displayed. That saves you a trip to Postman
AI (of course)
I’ve done a few quick experiments with ChatGPT giving it a list of fact tokens and then decribing a simple rule I would like. I was impressed enough with what it came up with that I think it’s worth pursuing a smart “build what I say” function for rules.
On the flip-side, I have also given ChatGPT the conditions and result object and asked it to explain the rule. Again, I was not unimpressed with the result.
The Javascript Runtime
I’m using the GRAAL VM javascript engine. It’s very powerful and I’ve barely scratched the surface. By definition, this app is letting users inject code to run on MY server. That sounds scary from a security standpoint but GRAAL has what seems to be a pretty robust security model. For the beta, I have not explored it much yet.
Another capability that I have not tried yet is runtime debugging – yes you can actually attach an execution listener and do interactive debugging.
I don’t even want to think about this yet, but GRAAL also supports Python
Tech Stack
Front end is Angular. Back end is SpringBoot (java). Persistence is JPA. Extensive use of Angular Material, Reactive forms and rxjs in the client. User auth is handled with a JWT token and an Auth filter in the Springboot java app.
All code is in github (private)
The current state of the dev-ops is as follows: The front end is a docker image with an nginx instance serving up the Angular App. The whole app is a docker compose app with front-end, back-end and db. Application production secrets are stored in github secrets and used by the various gh actions.
Using github packages for the container repository and also for some shared maven dependencies.
Deploy dev-ops is very rudimentary. No K8s. No Terraform. We just SCP the compose file up to a server, set some secrets environment variables and start it up.
Leave a Reply