When I started becoming interested in business rules engines  , I first found the concept hard to grasp. Why should I externalize application logic to a rules engine, when I can just code it? Now that I understand the concept better, I see use cases for business rules engines everywhere. For a basic understanding of a rules engine, Wikipedia summaries it nicely in one sentence: "A business rules engine is a software system that executes one or more business rules in a runtime production environment"  . I also like the answer given here on Stackoverflow  as it explains the concept of a rules engine more from a programmer perspective.
For me, a rules engine is like a container I can fill with different typed domain objects and then activate the rules engine to execute a set of predefined functions which might modify the content of the container and/or the objects already in there and/or have other side effects. Each of these functions can be imagined like a simple if-statement. If that, then that. If there is modification happening, the whole process keeps going like in a while-loop until none of the functions is applicable any more1.
So why not  just implement the whole thing using a while-loop and if-statements iterating over an untyped collection? Because a good rules engine is much more powerful than that. It allows to add more objects to it while it is running. The rules may be changed at runtime. The rules are separate from the source code, so they might be changed by users unfamiliar with the source code of the application. Some rules engines even allow setting up rules using Excel and other tools  . It is in general also easier to understand the rules, because they are described in a strict schema. Similar to logic programming languages like Prolog  , a rules engine can be used to discover knowledge about the data which was unknown before2. Related to that, the programmer does not need to care about the algorithms  the rules engine uses. Using a rules engine, a programmer just needs to state the rules and the rules engine processes the data using those. A rules engine might also be used in a reactive manner. Similar to the example given in the linked Stackoverflow post  , a rules engine might be constantly filled with new data and users or other services can independently specify in which data they are interested in and forward this data to themselves or react otherwise on it.
Drools  is a business rules engines  with all the features described in 1.1 Motivation. It is developed open source under the Apache licence  by JBoss  which is owned by Red Hat  . It is written in Java  and is a very popular rules engine on the JVM  . Being part of the Red Hat JBoss BRMS (Business rules management system)  product, it means that it has commercial enterprise support. As the name KIE shows up a lot in the source code, it is good to know that it stands for Knowledge is everything and is the new group name for Drools and JBoss projects in this domain as described here  . To find out more about Drools, I recommend the detailed and excellent documentation  of the project.
As Drools runs on the JVM and is built to use with Java, it can also be used within Scala  thanks to the interoperability between Scala and Java  . In practice however, I found a few things I did not like when using Drools in combination with Scala. All of them can be experienced in the example project described in 2 The example project. Here are the points and how I fixed them in the example project:
- Drools expects to be part of a Maven project. It depends on some pom properties which it uses to generate a release id. As Scala is usually used in combination with sbt  , the workaround is to provide a pom.properties file (manually)  .
- The rules used by Drools which are typically found in files with a .drl extension are, aside from some keywords and templates, written mostly in Java  . Drools has alternatives for that, e.g. writing the rules in Excel and similar programs  , but to use the full power of Drools, Java is necessary. That fact has impact on the domain objects used within Drools. Those can be written in Scala, but they need to confirm to certain Java standards. In the example code, I am using case classes annotated with @BeanInfo  to create Java getter and setter methods in Scala and I provide methods to retrieve Scala lists as Java lists.
- Another point inherited from the strong identification with Java is the mutable object oriented paradigm for which Drools is build. The default session used in Drools is stateful and it is okay to modify objects (using their setter methods)  . While it is possible to use Drools in an immutable and stateless manner (e.g. by calling methods from the rules instead of modifying the objects), in my example I went for a mixed approach using immutable objects in a stateful session which saves results in a mutable list. This way the only object which gets modified is the list.
- Some weird dependency on Java's classes.jar3 while starting it out of sbt. I do not know what exactly is going on here, but I am fine starting my example application out of IntelliJ  , because the IDE automatically puts all Java jars on the classpath. When starting out of sbt however, Drools wants to compile the .drl file and fails to find simple Java classes like java.util.List. The temporary fix is to add Java's classes.jar to the runtime classpath using sbt  . I really hope there is something better to fix this problem and I have just not found an adequate solution to it4.
In the following sections I describe the example project  found on Github with the name drools-scala-example. The example project uses Drools in version 6.0.1 and generally follows the guidelines found in the documentation  of Drools. The goal of the example project is to show some of the basic capabilites of Drools and how Drools can be used in a Scala environment. The theme of the example application is immigration. I am currently once more applying to immigrate to another country and facing all the requirements and different Visa scenarios I think it is also a great topic for a rules engine. The example application gets a list of applicants for immigration and some details, among them age, qualifications and work experience. Using the rules engine, the application tries then to find out if applicants are eligible by comparing their details to the given rules. The simplified rules for my fantasy immigration country are that everyone is eligible who...
- ...is an adult and has at least 5 years of work experience in one field of expertise.
- ...is an adult and has at least a Bachelor and at least 2 years of work experience in the same field of expertise.
- ...is an adult and has at least a Master.
- ...is an adult and is related to an eligible adult who has at least a master and at least 2 years of work experience in the same field of expertise.
- ...is a child and related to an eligible adult.
To get a glimpse on how the rules look like in Drools, this file  on Github describes them. In the following chapters, I will explain the details of how the rules work and how they are integrated within the application.
The basic requirements to get Drools to work with Scala are actually not that many. Besides the dependencies which can be added to sbt, in the example, I am basically using just one Scala object to get access to the rules engine. It is called Kie and can be seen in Code snippet 1. It provides methods to get a new stateless or stateful session out of the rules engine and to execute facts on a new stateless session. There is only one more file needed which is an almost empty kmodule.xml found in src/main/resources/META-INF. The complete content of this file can be seen in Code snippet 2. In the example, there is only one rules file ImmigrationRules.drl  which will be automatically used by the rules engine.
To further abstract from Drools, I have implemented a second object which is also very small. It is called ImmigrationEngine and can be seen in Code snippet 3. It has one method on it determineEligibleApplicants which takes a list of immigration facts and returns eligible applicants. What immigration facts and applicants are, will be shown in 2.3 Domain objects. For now it is important to know, that this method uses a stateful Drools session, inserts all the facts in it, starts the rule engine and receives the eligible applicants from it. The eligible applicants are saved in a mutable list which is put into the rules engine before it is started. The rules engine then populates the list and it gets converted to an immutable Scala list before it is returned. This way I encapsulate the side effects of the rules engine to one place (this mutable list) and do not need to make my objects mutable. An alternative would be, to give the applicants a mutable boolean field isEligible which the rules engine modifies as a result of the process.
In the example application, there are two kinds of domain objects. The first kind are the ones shared between the rules engine and the application itself. They can be found in Code snippet 4. They are simply a bunch of Scala case classes annotated with @BeanInfo  so they have the standard Java getter methods. As they are immutable, they do not have setter methods. Besides that, there is a Family object which makes it easier to create a list of applicants which are related to each other. The enum QualificationLevel is used to express the achieved qualification of an applicant.
The second part of domain objects are only used by the rules engine and can be seen in Code snippet 5. The purpose of the IsAdult class is to infer if an applicant is an adult. By using this inferring approach, knowledge responsibilities get decoupled and knowledge gets encapsulated  . The corresponding rule can be seen in Code snippet 6. It simple looks for applicants which are older than 18 years and inserts the isAdult objects for them into the state of the rules engine. The insertLogical statement differs from the insert statement in the way that insertLogical keeps on testing if the requirements for it are still valid. So for example, if there would be a rule inside the rules engine which would modify the age of the adult applicant to be less than 18, the isAdult object for the applicant would be removed from the state of the rules engine. On the other hand the IsEligible object is just used to make sure, that already eligible applicants do not get checked for eligibility again and again. It is not used in a logical fashion as the isAdult object.
This section shows some of the rules found in the example in detail. I have written some tests to show how they basically work. Let us start with a simple one. Adult applicants with at least a Master qualification are eligible. A test for this rule can be seen in Code snippet 7. The rule itself in Code snippet 8. The logic for the rule is that we need an applicant, who is not determined to be eligible yet (there does not exist an isEligible object in the rules engine), is an adult (isAdult needs to exist for them) and there is a Qualification object for them which level is higher or equal to the level of a Master qualification. When these requirements are fulfilled, the rules engine prints a notification line to sysout, creates the isEligible object for the applicant which is added to the state of the rules engine and adds the applicant to the eligibleApplicants list.
The next rule to be examined is the one that relatives of a very qualified and experienced applicant are eligible. This applicant needs to have at least a Master qualification and two years of work experience. Similar to the example before a test for this rule can be seen in Code snippet 9. The rule itself in Drools rule language can be seen in Code snippet 10. This rule is more complex than the one before. It picks up an applicant and looks at their relatives. This applicant needs to be eligible and have the required qualification and experience to allow their relatives to be also eligible. Now if those requirements are fulfilled, one of the names of the relatives is picked from the list and the rules engine searches for the applicant with that name. If the applicant is found and they are not eligible yet, they will be added to the list of eligibleApplicants.
Please note that there might be applicants which are eligible according to more than one rule. This is no problem for this scenario as we are only interested in if they are eligible or not and not why exactly they are eligible. If you want Drools to prefer some rules over other rules, you can do that by using salience  .