3

I am currently working on an implementation that based on a set of user configurations should output a final decision. The multiple configurations are evaluated several times at different stages of the execution.

Example: Let's say I'm building a system that plans the perfect vacation for a user. The User object is the system's input and each user has several different properties, such as gender, location, height, age, etc. An initial set of user's properties are evaluated. For instance, I want to send a user on a vacation at least 100 miles away from where they live. After that condition has been evaluated, then I introduce the age variable and narrow the set of possible outcomes and so on...

Right now, the system is a very simple sequential set of conditions that are easy to understand and debug but the system is growing and so is the number of conditions. I would like to refactor it making it more testable and easier to add extra conditions without introducing any regressions.

I've been looking at different ways to achieve this, more specifically, at whether to solve it using a decision tree or a state machine but I'm still not convinced either one of those is the best solution.

What would you recommend in this case?

Christophe
  • 74,672
  • 10
  • 115
  • 187
MC.
  • 141
  • 3
  • 4
    I think this is just programming – there's no particular shortcut that will magically simplify all your business rules. You might be tempted to use a rule engine, but chances are you've just rephrased your program in a more complicated language (cf. the [inner platform effect](https://en.wikipedia.org/wiki/Inner-platform_effect)) – amon Aug 05 '18 at 19:11
  • Is it like you have a different set of conditions every week that you want to apply? Are conditions being added periodically and do all existing condition remain relevant/active? I am thinking of a form of dynamic code execution that allows you to plugin conditions and select the ones that should be included for a particular run. – Martin Maat Aug 05 '18 at 19:24
  • Yes to both questions @MartinMaat, new features keep being added to the product, meaning more conditions to check for with additional sets of properties that yield different outcomes. My ideal scenario is to build something that scales while minimizing the introduction of regressions as the system evolves. – MC. Aug 05 '18 at 19:28
  • 1
    For an OOP system I probably would create a `Filter` interface, a class for each filter and an engine that selects all filters to use and execute them against the available data. – Kwebble Aug 05 '18 at 21:53
  • You may consider learning Prolog. You may save quite some time in the long run. – Thorbjørn Ravn Andersen Feb 03 '20 at 22:17

2 Answers2

3

The question is very broad, but from what I understand, it seems that you are building a rule based engine, where each rule has conditions that fire some decisions/actions/deductions. So here some ideas to start with.

One way of doing this is to implement this with a set of rules and forward chaining. Each rule uses the interpreter pattern to evaluate the IF condition; if it is true, it would fire the THEN part, executing a list of actions (for example using the command pattern) either defining some values, or building a filter applying some successive decorators). You'd then iterate through the set of rules, until one iteration fires no new rule.

Another way of doing this is to build a graph, representing the rules. Each input variable of conditions would then be a node that is initialized with the known facts. Each node is linked to some operators that combine input nodes to fire output nodes (which can be input nodes again). The algorithm would then iterate through the graph, propagating the values until no state change occurs anymore to any output nodes.

But this is a simplification. Because, you will face also contradicting rules, or overlapping rules, or cycling rules. You'd then need some truth maintenance mechanism to sort the mess out.

If your restrict your question, providing more details, a more precise answer could perhaps be provided.

Christophe
  • 74,672
  • 10
  • 115
  • 187
  • Thank you @Christophe, you are right in that this is a very broad topic. The goal of my question was to find out about existing algorithms/patterns that I didn't know of that can be useful for tackling these types of problems. The several different alternatives detailed in your answer are very valuable references to make a more informed decision. – MC. Aug 07 '18 at 20:48
3

I would recommend that you make each condition a pure function (which should be easy, since it sounds like you’re already doing that). And ideally, each should take some unsorted candidates and some constant filter data (your user’s info) while returning some unsorted candidates.

Making each function work solely on input with no side effects (or state) means that they are very easy to test. It also means that they can be trivially parellelized. And assuming the inputs and outputs are the same - trivially reordered. Parallelization lets you have options for scaling things out. Reordering can provide a lot of benefits by letting you do slower filters last (so they’re run with fewer candidates) or more effective filters first (so you toss out candidates sooner in the pipeline).

The algorithm itself is dead simple - function chaining (or composition, depending on your perspective). This is one of those cases though where implementation will play a key role in your flexibility and tuning to your particular problem matters a lot.

Telastyn
  • 108,850
  • 29
  • 239
  • 365