Deep Dive
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
Solid Principle Adherence
- Single Responsibility: Each classes above (e.g Rule engine) always delegated the task to the `Condition` class for eval and to the `Action` class for execute.
- Open/Close Principle: Each of the interfaces are open for extension but closed to the modification
- Liskov Substitution Principle: This is not directly applicable here because we are not using inheritance. Superclass should replaceable with Subclass
- Interface Segregation Principle: Client are not forced to implement abstract method that they don't use.
- Dependency Inversion Principle: High level module should not depend on low level module , both should depend on abstractions
Horizontal Scaling
- Multiple Rule Engine instances — each handles a subset of rules (shard by rule type or tenant)
- Evaluation Engine is stateless by design — easy to spin up more workers
- Facts come in → placed on a queue → workers pick up and evaluate
Potential Bottleneck
Our current design shows the Evaluation Engine passing results to the Execution Engine. If rules modify shared state (facts), We need coordination across instances — otherwise two workers might evaluate stale facts simultaneously.
Options
- Partition by fact scope — each engine instance owns a partition of facts (e.g., rules for customer region A vs B)
- Optimistic locking — workers evaluate against a fact version, retry if stale
- Sequential processing per rule group — rules that touch the same facts run on the same worker
- forward chaining — What happens when rule A fires, modifies fact X, and that change triggers re-evaluation of rule A itself? We need a termination strategy
- Limit the number of re-evaluation passes
- Mark rules as "already fired this cycle"
- Detect cyclic dependencies
Rete Algorithm — For large rule sets at scale, evaluating every rule against every fact each time is O(rules × facts). The Rete algorithm pre-compiles conditions into a discrimination tree, so when a fact changes, only affected partial matches are re-evaluated — near O