Skip to content

perf: Analyze rules before running to limit context [DHIS2-21245]#181

Merged
enricocolasante merged 6 commits intomasterfrom
DHIS2-21245
Apr 13, 2026
Merged

perf: Analyze rules before running to limit context [DHIS2-21245]#181
enricocolasante merged 6 commits intomasterfrom
DHIS2-21245

Conversation

@enricocolasante
Copy link
Copy Markdown
Collaborator

@enricocolasante enricocolasante commented Apr 7, 2026

Summary

Adds a static analysis pass (RuleEngineAnalyzer) that inspects program rules and their variables before evaluation to determine the minimum context data required. The result is surfaced as a new RuleContextRequirements value object in the public API, giving callers (e.g. dhis2-core) enough information to skip fetching data they don't need.

Jira: DHIS2-21245


Changes

New API

  • RuleContextRequirements — immutable data class (in api/ package) with five
    fields:

    • needsAllEvents — true when the ruleset references multi-event variables (RuleVariableNewestEvent, RuleVariableNewestStageEvent, RuleVariablePreviousEvent) or the V{event_count} env var.
    • needsEnrollment — true when enrollment env vars (enrollment_date, enrollment_id, enrollment_count, enrollment_status, incident_date, tei_count) or TEI attributes are referenced.
    • needsDataValues — true when any event data-value variable type is referenced.
    • needsAttributes — true when RuleVariableAttribute is referenced.
    • orgUnitGroups — set of group UIDs appearing in d2:inOrgUnitGroup() calls across all rule conditions and action data expressions.
  • RuleEngine.analyzeContextRequirements(rules, variables): RuleContextRequirements — new method on the public interface, delegating to RuleEngineAnalyzer.

  • RuleContextRequirementsJs + RuleEngineJs.analyzeContextRequirements(…) — JS wrapper exposing the same feature to the JS target (uses Array<String> for orgUnitGroups as required by @JsExport).

Housekeeping

  • RuleEngineUtils constants sorted alphabetically.
  • Dead helper isAllowedAction removed from RuleEventUtils (logic already inlined in Rule).
  • Version bumped 3.7.2-SNAPSHOT3.8.0-SNAPSHOT (breaking interface change).
  • expressionParser dependency bumped 1.4.01.4.1 (provides collectInOrgUnitGroups on Expression).

@enricocolasante enricocolasante force-pushed the DHIS2-21245 branch 4 times, most recently from e4ea99c to e8c9f9f Compare April 9, 2026 08:03
@enricocolasante enricocolasante force-pushed the DHIS2-21245 branch 2 times, most recently from 356a511 to aa96488 Compare April 9, 2026 08:11
@enricocolasante enricocolasante marked this pull request as ready for review April 9, 2026 15:36
Copy link
Copy Markdown
Member

@vgarciabnz vgarciabnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good! This was a missing piece in the rule engine API.
The performance benefit is on the context gathering part, isn't it? So if a client doesn't fetch the requirements in the first place and just uses the rule engine as before, the rule engine will continue to keep working as usual but there wouldn't be performance improvement, is it right? So this is an "optional" feature, it is not directly breaking the clients, right?

val orgUnitGroups: Set<String> = emptySet(),
) {
companion object {
val NONE = RuleContextRequirements()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want to add an annotation here to avoid having to write RuleContextRequirements.Companion.getNONE() in Java.

I think that you could get:

  • with @JvmStatic -> RuleContextRequirements.getNONE()
  • with @JvmField -> RuleContextRequirements.NONE

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually changed it to internal.
This object is only used as a response and shouldn't really be created in the client.

@enricocolasante
Copy link
Copy Markdown
Collaborator Author

Good! This was a missing piece in the rule engine API. The performance benefit is on the context gathering part, isn't it? So if a client doesn't fetch the requirements in the first place and just uses the rule engine as before, the rule engine will continue to keep working as usual but there wouldn't be performance improvement, is it right? So this is an "optional" feature, it is not directly breaking the clients, right?

Yes, this feature is optional and it is not breaking the client.

vgarciabnz
vgarciabnz previously approved these changes Apr 10, 2026
Copy link
Copy Markdown
Contributor

@superskip superskip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a brief look and have a couple of questions: First, is it primarily the backend that is going to make use of this? I can see Capture web app potentially being able to skip some of the conversion steps with this, but probably not any api calls (as we don't make any of those within the rule engine).

The second question is about a technical detail, please see comment below 👇

Comment thread src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleEngineAnalyzer.kt Outdated
@enricocolasante enricocolasante force-pushed the DHIS2-21245 branch 2 times, most recently from 0f9b916 to 4c290c9 Compare April 13, 2026 10:00
superskip
superskip previously approved these changes Apr 13, 2026
Copy link
Copy Markdown
Contributor

@superskip superskip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 🎉

vgarciabnz
vgarciabnz previously approved these changes Apr 13, 2026
package org.hisp.dhis.rules

@JsExport
data class RuleContextRequirementsJs(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data class could be shared (JS+JVM), unless you prefer to leave it separately just in case we want to add something here that is not compatible with JVM.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. It was in here before because we had an Array

@enricocolasante enricocolasante dismissed stale reviews from vgarciabnz and superskip via 379f78d April 13, 2026 12:48
@sonarqubecloud
Copy link
Copy Markdown

@enricocolasante enricocolasante merged commit 4039ff4 into master Apr 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants