rules-engine

Feature Flag Evaluator - System Architecture & Design Documentation

Executive Summary

The Feature Flag Evaluator is a modern, high-performance rules engine designed to determine whether specific feature flags are enabled or disabled for given target audiences. Built with Spring Boot 3.5.x and leveraging Camunda Platform 7.24.0, the system provides two distinct pathways: a sub-millisecond, low-latency API evaluation mechanism for real-time application requests, and an interactive BPMN workflow simulation path for workflow modeling, compliance, operator audit, and human-in-the-loop operational checks.


1. Detailed System Design & Boundaries

The application is structured into clearly bounded layers. Communication between the user browser, the Spring core container, the Camunda embedded workflow engine, and the persistent storage database is mapped in the diagram below:

Detailed System Design Component Diagram

graph TB
    subgraph Client Layer
        Browser[Web Browser / API Client]
        Modeler[Camunda Modeler]
    end

    subgraph Flag Evaluator Spring Boot Application Boundary
        subgraph REST API Controller Layer
            FC[FlagController]
            JerseyREST[Camunda Jersey REST Servlet]
        end

        subgraph Spring Managed Business Context
            RulesService[RulesService <br> - Reads YAML Feature Config <br> - Implements Rule Evaluation]
            Cache[FeatureDetailService <br> - Concurrent In-Memory Cache <br> - Thread-Safe Map]
            Jackson[JacksonConfig <br> - Custom Serialization Mapping]
        end

        subgraph Embedded Camunda 7.24 Engine Layer
            Runtime[RuntimeService <br> - Starts process instances]
            TaskService[TaskService <br> - Manages User Tasks & Claims]
            JobExec[SpringJobExecutor <br> - Handles asyncBefore/asyncAfter background jobs <br> - Thread Pool: 3-10 workers]
            Deployer[BPMN Resource Deployer <br> - Deploys classpath:processes/*.bpmn]
        end

        subgraph Data & Persistence Infrastructure Layer
            Hikari[HikariDataSource <br> - Connection Pool]
            JPA[Hibernate JPA / Hibernate ORM]
        end
    end

    subgraph Database Storage Layer
        H2[(H2 Local File DB <br> camunda-h2-database)]
    end

    %% Network & Interface Protocols
    Browser -->|HTTP REST / JSON / api/eval| FC
    Browser -->|HTTP REST / JSON / api/executetask| FC
    Browser -->|HTTP REST / HTML / Web Console| JerseyREST
    Modeler -->|BPMN 2.0 XML File Write| Deployer
    
    %% Engine & Service Wiring
    FC -->|Java API: Query Cache| Cache
    FC -->|Java API: Trigger Process| Runtime
    Runtime -->|Service Delegation / JavaDelegate| RulesService
    RulesService -->|Cache Writes| Cache
    TaskService -->|Context Variables Read/Write| Hikari
    JobExec -->|Async Job Thread| Runtime
    
    %% Storage connections
    Hikari -->|JDBC Connections| H2
    JPA -->|ORM Transactions| Hikari
    Deployer -->|Saves Deployed XML Definitions| JPA
    Runtime -->|Persists Token State| JPA

2. Java Spring Backend Overview

The backend is built as a lightweight, reactive-friendly monolithic Spring Boot application, optimized for execution under Java 25. It incorporates modern language paradigms such as Module Import Declarations (import module java.base;) and Java Records to achieve optimal type safety and readable, concise code structures.

Tech Stack Details

Core Component Breakdown

 net.ironoc.rules.engine
  ├── ApiApplication.java             # Main Application Bootstrapper
  ├── config
  │    └── JacksonConfig.java         # Customized JSON Serializers & ObjectMapper
  ├── controller
  │    └── FlagController.java        # Inbound REST API Endpoint Handler
  ├── service
  │    ├── DetailCacheI.java          # In-Memory Cache Interface
  │    ├── FeatureDetailService.java  # Concurrent In-Memory Cache Implementation
  │    ├── RuleServiceI.java          # Core Rules Engine Evaluation Interface
  │    └── RulesService.java          # Bind Configuration & Matches Rules Logic
  ├── dto
  │    ├── Feature.java               # Feature Flag DTO Record
  │    ├── RuleGroups.java            # All & Any Rules Record
  │    └── Rule.java                  # Individual Rule Condition Record
  └── enums
       ├── FeatureFlag.java           # Enumerated Evaluated Attributes (TIER, APPVERSION, COUNTRY)
       ├── RuleOperator.java          # Evaluation Operators (IN, EQ, GTE, GT)
       ├── RuleGroup.java             # Logic Groups (ALL, ANY)
       └── Country.java               # Region Enums

A. FlagController

Exposes three distinct endpoints:

B. FeatureDetailService (Memory Cache)

Implements DetailCacheI using a ConcurrentHashMap. To eliminate slow external configurations or DB reads during flag evaluation, this cache holds pre-compiled feature and rule records in memory.

C. RulesService

The brain of the evaluation system. It handles two jobs:

  1. Config Binding (execute method): Programmatically binds the prefix feature configurations in application.yml directly into rich Java record representations (Feature, RuleGroups, Rule) using Spring’s Binder API.
  2. Rules Evaluation (rulesMatcher method): Matches incoming client parameters against target rule attributes (FeatureFlag attributes) utilizing targeted criteria operators. Supported operations include:
    • IN & EQ: Validates exact string containment/equality (e.g., verifying a country is in [ES, PT] or the user’s tier matches gold).
    • GTE & GT: Evaluates numerical strings (e.g., validating the user’s appVersion is greater than or equal to 120).

D. Typed DTO Records & Enums

By relying on Java 16+ records (Feature, RuleGroups, Rule), the application guarantees structural immutability, thread safety, and standard serialization behaviors with minimal boilerplate.


3. Startup Rule-Loading & Manual Triggering Mechanics

The system supports automatic bootstrapping of rules on system boot, alongside multiple operational pathways for manual execution, reloads, and configurations.

┌────────────────────────────────────────────────────────────────────────────────────────────┐
│                             BOOTSTRAPPING & TRIGGERING PATHWAYS                            │
│                                                                                            │
│  [Startup Lifecycle]                                                                       │
│          │                                                                                 │
│          ▼                                                                                 │
│   (PostDeployEvent) ──> [Auto-Trigger Rules_matcher Process] ──> [Memory Cache Loaded]     │
│                                                                                            │
│                                                                                            │
│  [Operational Inputs]                                                                      │
│          │                                                                                 │
│          ├─► (REST Endpoint GET /api/executetask) ────────┐                                │
│          │                                                ├─► [Trigger Rules_matcher]      │
│          ├─► (Camunda REST API: /process-definition/...) ─┘                                │
│          │                                                                                 │
│          └─► (Camunda Tasklist UI) ─────────────────────────► [Interactively Run Process]  │
└────────────────────────────────────────────────────────────────────────────────────────────┘

A. Automatic Rule-Loading on Startup

To ensure that rule evaluations are ready for immediate consumption upon application launch, the system implements an automated deployment-event listener:

  1. Camunda Deployment Completion: As the Spring Boot container starts, the embedded Camunda Process Engine loads, registers, and deploys the BPMN files found inside src/main/resources/processes/ (such as rules-matcher.bpmn).
  2. Post-Deployment Lifecycle Interception: Under ApiApplication.java, a Spring-managed event listener intercepts the deployment phase using the @EventListener annotation bound to Camunda’s PostDeployEvent:
    @EventListener
    public void processPostDeploy(PostDeployEvent event) {
        runtimeService.startProcessInstanceByKey("Rules_matcher");
    }
    
  3. Execution Kick-off: Immediately upon receiving the PostDeployEvent, Camunda starts an instance of the Rules_matcher process in the background.
  4. Cache Initialization: The newly created process instance immediately moves to its first executable node: Service Task rules-init (linked to RulesService). This service task executes RulesService.execute(...), programmatically binds the rules prefix feature configurations directly from application.yml, and populates the FeatureDetailService in-memory concurrency cache.
  5. Interactive Pause State: The process then advances to the User Task Activity_UserConfirmInit (“Review rules-init output”), parking a token there for manual operator validation in the Camunda Tasklist, concluding the startup bootstrap safely.

B. Manual Rule-Loading & Workflow Triggering Options

When configurations change in real-time, or operators need to run manual verification loops, they can trigger rule loading through three pathways:

Pathway 1: Dedicated REST Trigger (GET /api/executetask)

Designed for testing, programmatic refreshes, and quick re-trigger loops:

Pathway 2: Camunda Engine REST API

Enterprise orchestration platforms or external CD pipelines can manually trigger a rule execution flow using Camunda’s native REST interface:

Pathway 3: Camunda Tasklist Web UI

Operators can visually trigger manual execution flows with custom, interactive values:

  1. Log into the Camunda Tasklist portal at http://localhost:8080/app/tasklist/.
  2. Click “Start process” in the top navigation panel.
  3. Select “Rules Matcher Workflow (In Progress)” from the list of deployed definitions.
  4. (Optional) Provide start variables directly in the generic process starter modal.
  5. Click “Start” to visually run the workflow token, inspect user tasks, complete review blocks, and trace matches interactively.

4. Operator UX Flow & Tasklist Lifecycle

When manual testing or operations personnel execute, interact with, or audit the Rules_matcher workflow process, they navigate through a structured graphical interface. This end-to-end user experience flow across the Camunda Tasklist and Cockpit dashboards is detailed in the state chart below:

Operator UX Flow & Tasklist Lifecycle Diagram

stateDiagram-v2
    [*] --> Operator_Login : Access http://localhost:8080/
    
    state Operator_Login {
        [*] --> Enter_Credentials : sa / passw
        Enter_Credentials --> Dashboard_Redirect : Click Login
    }

    Dashboard_Redirect --> Camunda_Tasklist : Select Tasklist App
    Dashboard_Redirect --> Camunda_Cockpit : Select Cockpit App
    
    state Camunda_Tasklist {
        [*] --> Start_Process_Modal : Click "Start process"
        Start_Process_Modal --> Select_Rules_Matcher : Select "Rules Matcher Workflow"
        Select_Rules_Matcher --> Input_Start_Variables : (Optional) Input "feature", "country", etc.
        Input_Start_Variables --> Process_Started : Click "Start"
        
        state Task_Lifecycle_Phase_1 {
            Process_Started --> Fetch_Pending_Tasks : Apply "All tasks" Filter
            Fetch_Pending_Tasks --> Claim_Init_Task : Select "Review rules-init output"
            Claim_Init_Task --> View_Feature_Details : Inspect Process Variables
            View_Feature_Details --> Complete_Init_Task : Click "Complete"
        }
        
        state Task_Lifecycle_Phase_2 {
            Complete_Init_Task --> Token_Routing_In_Engine : (System routes via Gateways & Delegates)
            Token_Routing_In_Engine --> Fetch_New_Review_Task : Poll / Refresh list
            Fetch_New_Review_Task --> Claim_Review_Task : Select "Review matched rules JSON"
            Claim_Review_Task --> Inspect_rulesJson : View matched rules in serialized output
            Inspect_rulesJson --> Complete_Review_Task : Click "Complete"
        }
        
        Complete_Review_Task --> Process_Completed : Process Instance terminates
    }

    state Camunda_Cockpit {
        [*] --> Navigate_Process_Definitions : Click "Processes"
        Navigate_Process_Definitions --> Select_Rules_Matcher_Def : Click "Rules_matcher"
        Select_Rules_Matcher_Def --> Inspect_Live_Tokens : View heat map / token locations
        Inspect_Live_Tokens --> Inspect_Variables : View runtime process variable values
        Inspect_Live_Tokens --> Inspect_Historic_Audit : Track completed delegates & paths taken
    }

5. BPMN Workflow Orchestration: Deep Architectural Analysis

The system relies on executable Business Process Model and Notation (BPMN 2.0) specifications deployed to the embedded Camunda engine.

A. Core BPMN Processes Deployed

The application deploys three separate workflows on boot:

  1. rules-init (Process ID rules-init via first.bpmn): Fully automated startup helper process. Triggered automatically or via internal hooks, it executes a service task linked directly to RulesService.execute(...) to read from physical files (application.yml), perform binding, and load target configurations directly into the concurrent cache map.
  2. Rules_matcher (Process ID Rules_matcher via rules-matcher.bpmn): The comprehensive execution workflow implementing conditional logical routing, custom java delegate evaluations, async state persistence boundaries, and interactive user checkpoint nodes.
  3. loanApproval (Process ID loanApproval via loanApproval.bpmn): A lightweight demo user-task assignment workflow.

B. Logical Gates & Routing Control Flow (Rules_matcher)

The Rules_matcher workflow incorporates specific structures to govern execution based on live evaluation states:

graph TD
    Start([Start Event]) --> Init[rules-init <br> RulesService]
    Init --> UserConfirm[User Task: Review rules-init output <br> Pause for Tasklist]
    UserConfirm --> CheckEnabled[Enabled <br> FeatureEnabledDelegate]
    CheckEnabled --> Gateway{Feature Enabled?}
    
    %% Enabled branch
    Gateway -->|Yes / featureEnabled| AppVer[Application Version Supported? <br> AppVersionDelegate]
    AppVer --> Tier[Tier Valid for User? <br> TierDelegate]
    Tier --> Country[Country Supported? <br> CountryDelegate]
    Country --> Merge[Gateway: MergeBeforeAggregator]
    
    %% Disabled branch
    Gateway -->|No / !featureEnabled| Disabled[Disabled <br> FeatureDisabledDelegate]
    Disabled --> Pass[Empty Rule Set <br> PassEngineDelegate]
    Pass --> Merge
    
    Merge --> Aggregator[Return Rules Set <br> RulesAggregatorDelegate]
    Aggregator --> UserReview[User Task: Review matched rules JSON <br> Pause for Tasklist]
    UserReview --> End([End Event])

1. Exclusive Gateways (Gateway_0ejhppi and Gateway_MergeBeforeAggregator)

2. Human-In-The-Loop Interactive Checkpoints (userTask)

BPMN user tasks represent operational safety checkpoints:

3. Asynchronous Execution Boundaries (camunda:async)

The Service Task Activity_1pygerh (“Return Rules Set”) is configured with camunda:asyncBefore="true" and camunda:asyncAfter="true":


6. Java Delegates: Deep-Dive Implementation & State Transitions

Each service node in the Rules_matcher process is backed by a specific Java class implementing org.camunda.bpm.engine.delegate.JavaDelegate. These classes orchestrate process state transitions by reading, updating, and removing execution variables.

Below is an exhaustive account of each delegate’s responsibilities, input/output variables, and internal execution logic:

┌────────────────────────────────────────────────────────────────────────────────────────────┐
│                                 RULES_MATCHER PROCESS                                      │
│                                                                                            │
│  [Start] ──> [rules-init]                                                                  │
│                   │                                                                        │
│                   ▼                                                                        │
│          (Task: Review rules-init)                                                         │
│                   │                                                                        │
│                   ▼                                                                        │
│            [FeatureEnabled] ─────────────────────────────────────────┐                     │
│                   │                                                  │                     │
│         (featureEnabled == true)                           (featureEnabled == false)       │
│                   │                                                  │                     │
│                   ▼                                                  ▼                     │
│            [AppVersion]                                      [FeatureDisabled]             │
│                   │                                                  │                     │
│                   ▼                                                  ▼                     │
│               [Tier]                                           [PassEngine]                │
│                   │                                                  │                     │
│                   ▼                                                  │                     │
│              [Country]                                               │                     │
│                   │                                                  │                     │
│                   └─────────────────────────► ◄──────────────────────┘                     │
│                                               │                                            │
│                                               ▼                                            │
│                                      [RulesAggregator]                                     │
│                                               │                                            │
│                                               ▼                                            │
│                                   (Task: Review rules JSON)                                │
│                                               │                                            │
│                                               ▼                                            │
│                                             [End]                                          │
└────────────────────────────────────────────────────────────────────────────────────────────┘

A. FeatureEnabledDelegate


B. FeatureDisabledDelegate


C. PassEngineDelegate


D. AppVersionDelegate


E. TierDelegate


F. CountryDelegate


G. RulesAggregatorDelegate


7. Camunda Modeler and Console Usage

The integration of Camunda provides a robust framework to visualize, monitor, audit, and walk through business rules interactively.

A. Modeler Usage

Architects, product owners, and developers use the Camunda Modeler to edit .bpmn files. Modeler features utilized in this codebase include:

B. Embedded Camunda Web Console

Upon booting the application, the Camunda Platform Cockpit, Tasklist, and Admin interfaces are hosted at http://localhost:8080/ (Admin credentials default to: sa / passw).

1. Camunda Cockpit (Execution Monitor)

Provides an overview of running processes. Operators use it to:

2. Camunda Tasklist (Human Workflow Handler)

The interactive interface for operations teams. Because the Rules_matcher workflow incorporates User Tasks, executing a workflow creates a task entry here.

3. Camunda Admin Panel (Security & Configuration)

Controls user authentication, authorizations, and filter creation (e.g., configures the “All tasks” filter used to display pending user tasks in the Tasklist).


8. Detailed Data Flow & Execution Lifecycles

The Feature Flag Evaluator supports two distinct execution paths depending on performance and audit requirements:

Path A: Low-Latency REST API Path (/api/eval)

Designed for live production traffic requiring sub-millisecond responses.

sequenceDiagram
    autonumber
    actor Client as API Caller
    participant Controller as FlagController
    participant Cache as FeatureDetailService (In-Memory Cache)
    participant Service as RulesService
    
    Client->>Controller: GET /api/eval (feature, country, appVersion, tier)
    Controller->>Cache: getFeaturesById()
    Cache-->>Controller: Feature DTO (enabled, ruleGroups)
    
    alt Feature not found or disabled
        Controller-->>Client: 400 Bad Request (empty list)
    else Feature is enabled and has ruleGroups
        Controller->>Service: getRuleMatchByRuleGroup(RuleGroup.ALL)
        Service->>Service: Evaluate GTE, GT, IN, EQ conditions
        Service-->>Controller: allRuleMatch List
        
        Controller->>Service: getRuleMatchByRuleGroup(RuleGroup.ANY)
        Service->>Service: Evaluate GTE, GT, IN, EQ conditions
        Service-->>Controller: anyRuleMatch List
        
        Controller->>Service: createResponseFromMatches(allRuleMatch, anyRuleMatch)
        Service-->>Controller: ResponseEntity<ApiResponse>
        
        alt Matches found
            Controller-->>Client: 200 OK (ApiResponse with matched Rules JSON)
        else No matches
            Controller-->>Client: 400 Bad Request (empty list)
        end
    end

Path B: BPMN-Driven Interactive Path (/api/executetask)

Designed for process tracing, visual auditing, and manual user checkpoints.

  1. Triggering: The client calls GET /api/executetask. The FlagController starts the Rules_matcher BPMN process via the Camunda Runtime Service.
  2. Rule Binding (rules-init Service Task): Executes RulesService. It binds rule specifications from application.yml and updates FeatureDetailService.
  3. Manual Gateway Pause (Review rules-init output User Task): The process pauses. The operator claims and completes the task in Camunda Tasklist.
  4. Context Evaluation (Enabled Service Task): Runs FeatureEnabledDelegate. It extracts the process variable feature. It checks the cache, sets featureEnabled to true or false, and pushes rule configurations into process variables (ruleGroupsAll, ruleGroupsAny).
  5. Gateway Routing (Feature Enabled? Exclusive Gateway):
    • If Enabled: Routes to evaluation delegates:
      • AppVersionDelegate: Captures and logs appVersion.
      • TierDelegate: Captures and logs tier.
      • CountryDelegate: Captures and logs country.
    • If Disabled: Routes to teardown delegates:
      • FeatureDisabledDelegate: Formally overrides featureEnabled to false and clears remaining cache variables.
      • PassEngineDelegate: Signals that rules should be skipped (skipRulesEngine=true) and initializes an empty matched rules array.
  6. Merging & Aggregation (Return Rules Set Service Task): Merges both flows and routes to RulesAggregatorDelegate.
    • If rules are skipped or the feature is disabled, it constructs an empty match list.
    • Otherwise, it reads the input variables (country, appVersion, tier, feature), uses RulesService to match criteria against rule attributes, aggregates all valid rules, saves them to matchedRules, and serializes the list to a process variable string rulesJson.
  7. Final Review Pause (Review matched rules JSON User Task): Pauses the process. An operator reviews the compiled rules in rulesJson inside the Tasklist. Once approved, the task is marked complete, and the instance finishes.

9. Testing & Architectural Integrity

The system guarantees robust operations via its comprehensive, self-contained test suite containing 9 distinct unit and integration tests distributed across these target segments:

  1. JacksonConfigTest: Validates customized JSON serialization configurations, ensuring records and complex maps serialize smoothly.
  2. ContextLoadsTest: Boots up the full Spring Application Context, verifies Hibernate mappings, initializes the Hikari Connection Pool to the local H2 file database, and checks that Camunda BPMN processes (rules-matcher.bpmn, first.bpmn, loanApproval.bpmn) deploy cleanly.
  3. RulesServiceTest (Unit Tests): Tests the parsing engine logic in isolation. It verifies:
    • Logical country string inclusions (IN).
    • Numerical application version comparisons (GTE, GT).
    • Safe handling of unsupported criteria operators.
  4. FlagControllerTest (Controller Mock Tests): Exercises flag-evaluation REST endpoints directly, validating appropriate HTTP response codes (200 OK vs 400 Bad Request) for diverse execution scenarios (e.g., missing features, disabled flags, or composite rule criteria matches across both ALL and ANY groups).

This comprehensive architecture maintains highly performant runtime capabilities alongside rigorous operational oversight, meeting both enterprise-grade API performance demands and corporate compliance goals.