A complete tutorial on the Drools business rule engine (2022)

Business rules work very well to represent the logic for certain domains. They work well because they result intuitive and close to the way of thinking of many types of domain experts. The reason for that it is that they permit to decompose a large problem in single components. In this way the user has not to deal with the orchestration of all the single rules: this is the added value provided by the business rule engine.

In this article we will discuss one specific example of application written by using business rules. We will write the rules to decide which email to send to the subscribers to a newsletter. We will see different types of rules and how we could express them using the Drools Rule Language. We will also see how to configure Drools (spoiler: it will be easy) and have the system elaborate the rules to produce a result we can use.

I think that business rules are quite interesting because they permit to look at problems in a different way. As developers we are very used to the imperative paradigm or functional paradigms. However there are other paradigms, like state machines and business rules, which are not so commonly used and which can be a much better fit in some contexts.

As always we share the code presented in the tutorial in a companion repository: EmailSchedulingRules.

What problem we are trying to solve

Let’s consider the domain of email marketing. As marketers we have an email list of persons interested in our content. Each of them may have demonstrate interest in a specific topic, read some of our articles and bought certain products. Considering all their history and preferences we want to send to them at each time the most appropriate content. This content may be either educative or proposing some deal. The problem is that there are constraints we want to consider (i.e., not sending emails on sunday or not sending emails promoting a product to someone who already bought it).

All these rules are simple per se, but the complexity derives by how they are combined and how they interact.The business rule engine will deal with that complexity for us, all we have to do is to express clearly the single rules. Rules will be expressed in the terms of our domain data so let’s focus on our domain model first.

The model of our domain

In our domain model we have:

  • Emails: the single emails we want to send, described by their title and content
  • Email Sequences: groups of emails that have to be sent in a specific order, for example a set of emails representing a tutorial or describing different features of a product
  • Subscribers: the single subscriber to the mailing list. We will need to know which emails we sent to him, what things he is interested in, and which products he bought
  • Products: the products we sell
  • Purchases: the purchases subscribers have made
  • Email Sending:the fact we sent or are about to send a certain email, on a certain date to a certain subscriber
  • Email Scheduling:the plan for sending an email, with some additional information

The latter two elements of our domain model could seem less obvious compared to the others, but we will see in the implementation for which reasons we need them.

A complete tutorial on the Drools business rule engine (1)

What our system should do

Our system should execute all the rules, using the Drools engine, and to determine for each user which email we should send on a specific day. The result could be the decision to not send any email, or to send an email, selecting one among many possible emails.

An important thing to consider is that these rules may evolve over time. The people in charge of marketing may want to try new rules and see how they affect the system. Using Drools it should be easy for them to add or remove rules or tweak the existing rules.

Let’s stress this out:

these domain experts should be able to experiment with the system and try things out quickly, without always needing help from developers.

The rules

Ok, now that we know which data do we have, we can express rules based on that model.

(Video) Drools Tutorial Part - 1| Java based Rule Engine | Drools getting started | Basic Setup

Let’s see some examples of rules we may want to write:

  • We may have sequences of emails, for example the content of a course. They have to be sent in order
  • We may have time sensitive emails that should either be sent in a specific time window or not sent at all
  • We may want to avoid sending emails on specific days of the week, for example on the public holidaysin the country where the subscriber is based
  • We may want to send certain type of emails (for example proposing a deal) only to persons who received certain other emails (for example at least 3 informative emails on the same subject)
  • We do not want to propose a deal on a certain product to a subscriber who has already bought that product
  • We may want to limit the frequency we send emails to users. For example, we may decide to not send an email to a user if we have sent already one in the last 5 days

Setting up drools

Setting up drools can be very simple. We are looking into running drools in a standalone application. Depending on your context this may or may not be an acceptable solution and in some cases you will have to look into JBoss, the application server supporting Drools. However if you want to get started you can forget all of this and just configure your dependencies using Gradle (or Maven). You can figure out the boring configuration bits later, if you really have to.

buildscript { ext.droolsVersion = "7.20.0.Final" repositories { mavenCentral() }}plugins { id "org.jetbrains.kotlin.jvm" version "1.3.21"}apply plugin: 'java'apply plugin: 'idea'group 'com.strumenta'version '0.1.1-SNAPSHOT'repositories { mavenLocal() mavenCentral() maven { url 'https://repository.jboss.org/nexus/content/groups/public/' }}dependencies { compile "org.kie:kie-api:${droolsVersion}" compile "org.drools:drools-compiler:${droolsVersion}" compile "org.drools:drools-core:${droolsVersion}" compile "ch.qos.logback:logback-classic:1.1.+" compile "org.slf4j:slf4j-api:1.7.+" implementation "org.jetbrains.kotlin:kotlin-stdlib" implementation "org.jetbrains.kotlin:kotlin-reflect" testImplementation "org.jetbrains.kotlin:kotlin-test" testImplementation "org.jetbrains.kotlin:kotlin-test-junit"}

In our Gradle script we use:

  • Kotlin, because Kotlin rocks!
  • IDEA, because it is my favorite IDE
  • Kotlin StdLib, reflect and test
  • Drools

And this is how our program will be structured:

fun main(args: Array<String>) { try { val kbase = readKnowledgeBase(listOf( File("rules/generic.drl"), File("rules/book.drl"))) val ksession = kbase.newKieSession() // typically we want to consider today but we may decide to schedule // emails in the future or we may want to run tests using a different date val dayToConsider = LocalDate.now() loadDataIntoSession(ksession, dayToConsider) ksession.fireAllRules() showSending(ksession) } catch (t: Throwable) { t.printStackTrace() }}

Pretty simple, pretty neat.

What we do in, details is:

  • We load the rules from file. For now we just load the file rules/generic.drl
  • We setup a new session. Think of the session as the universe as seen by the rules: all data they can access is there
  • We load our data model into the session
  • We fire all the rules. They could change stuff in the session
  • We read the modified data model (a.k.a. the session) to figure out which emails we should send today

Writing the classes for the data model

We have previously seen how our data model looks like, let’s now see the code for it.

Given we are using Kotlin it will be pretty concise and obvious.

package com.strumenta.funnelimport java.time.DayOfWeekimport java.time.LocalDateimport java.util.*enum class Priority { TRIVIAL, NORMAL, IMPORTANT, VITAL}data class Product(val name: String, val price: Float)data class Purchase(val product: Product, val price: Float, val date: LocalDate)data class Subscriber(val name: String, val subscriptionDate: LocalDate, val country: String, val email: String = "[emailprotected]", val tags: List<String> = emptyList(), val purchases: List<Purchase> = emptyList(), val emailsReceived: MutableList<EmailSending> = LinkedList()) { val actualEmailsReceived get() = emailsReceived.map { it.email } fun isInSequence(emailSequence: EmailSequence) = hasReceived(emailSequence.first) && !hasReceived(emailSequence.last) fun hasReceived(email: Email) = emailsReceived.any { it.email == email } fun hasReceivedEmailsInLastDays(nDays: Long, day: LocalDate) : Boolean { return emailsReceived.any { it.date.isAfter(day.minusDays(nDays)) } } fun isOnHolidays(date: LocalDate) : Boolean { return date.dayOfWeek == DayOfWeek.SATURDAY || date.dayOfWeek == DayOfWeek.SUNDAY } fun emailReceivedWithTag(tag: String) = emailsReceived.count { tag in it.email.tags }}data class Email(val title: String, val content: String, val tags: List<String> = emptyList())data class EmailSequence(val title: String, val emails: List<Email>, val tags: List<String> = emptyList()) { val first = emails.first() val last = emails.last() init { require(emails.isNotEmpty()) } fun next(emailsReceived: List<Email>) = emails.first { it !in emailsReceived }}data class EmailSending(val email: Email, val subscriber: Subscriber, val date: LocalDate) { override fun equals(other: Any?): Boolean { return if (other is EmailSending) { this.email === other.email && this.subscriber === other.subscriber && this.date == other.date } else { false } } override fun hashCode(): Int { return this.email.title.hashCode() * 7 + this.subscriber.name.hashCode() * 3 + this.date.hashCode() }}data class EmailScheduling @JvmOverloads constructor(val sending: EmailSending, val priority: Priority, val timeSensitive: Boolean = false, var blocked: Boolean = false) { val id = ++nextId companion object { private var nextId = 0 }}

Nothing surprising here: we have the seven classes we were expecting. We have a few utility methods here and there but nothing that you cannot figure out by yourself.

Writing a rule to schedule an email

It is now time to write our first business rule. This rule will state that, given a sequence and given a person, we will schedule the first email of the sequence to be sent to a person if that person is not already receiving an email from that sequence.

dialect "java"rule "Start sequence" when sequence : EmailSequence () subscriber : Subscriber ( !isInSequence(sequence) ) then EmailSending $sending = new EmailSending(sequence.getFirst(), subscriber, day); EmailScheduling $scheduling = new EmailScheduling($sending, Priority.NORMAL); insert($scheduling);end

In the header of the rule we specify the language we are using for writing the clauses. In this tutorial we will consider only Java. There is another possible value:mvel. We will not look into that. Also, while in this example we specify the dialect on the rule it can be instead specified once for the whole file. There is even a better option: not specifing the dialect at all, as Java is the default anyway and the usage of mvel is discouraged.

The whensection determines on which elements our rule will operate. In this case we state that it will operate on an EmailSequence and a Subscriber. It will not work just on any person but only on a person for which the condition !isInSequence(sequence)is satisfied. This condition is based on a call to the methodisInsequencethat we will show below:

data class Subscriber(...) { fun isInSequence(emailSequence: EmailSequence) = hasReceived(emailSequence.first) && !hasReceived(emailSequence.last) fun hasReceived(email: Email) = emailReceived.any { it.email == email }}

Let’s now look at the thensection of our rule. In such section we specify what happens when the rule is fired. The rule will be fired when elements satisfying the whensection can be found.

In this case we will create an EmailScheduling and add it to the session. In particular we want to send to the considered person the first email of the sequence, on the day considered. We also specify the priority of this email (NORMAL in this case). This is necessary to decide which email effectively to send when we have more than one. Indeed we will have another rule looking at these values to decide which emails to prioritize (hint: it will be the email with the highest priority).

(Video) First Steps: Rules Engine Kie Drools

In general you may want to typically add things into the session in the thenclause. Alternatively you may want to modify objects which are part of the session. You could also call methods on objects which have side-effects. While the recommended approach is to limit yourself to manipulate the session you may want to add side effects for logging, for example. This is especially useful when learning Drools and trying to wrap your head around your first rules.

Writing a rule to block an email from being sent

We will see that we have two possible types of rules: rules to schedule new emails and rules to prevent scheduled emails to be sent. We have seen before how to write a rule to send an email and we will now see how to write an email to prevent an email from being sent.

In this rule we want to check if an email is scheduled to be sent to a person who has received already emails in the last three days. If this is the case we want to block that email from being sent.

rule "Prevent overloading" when scheduling : EmailScheduling( sending.subscriber.hasReceivedEmailsInLastDays(3, day), !blocked ) then scheduling.setBlocked(true);end

In the whensection we specify that this rule will operate on an EmailScheduling. So, every time another rule will add an EmailSchedulingthis rule could be triggered to decide if we have to block it from being sent.

This rule will apply to all scheduling which are directed to subscribers who have received emails in the last 3 days. In addition to that we will check if the EmailSchedulingwas not already blocked. If that is the case we will not need to apply this rule.

We use the setBlocked method of the scheduling object to modify an element which is part of the session.

At this point we have seen the pattern we will use:

  • We will create `EmailScheduling` when we think it makes sense to send an email to the user
  • We will check if we have reasons to block those emails. If that is the case we will set the blockedflag to true, effectively removing the EmailScheduling

Using a flag to mark elements to remove/invalidate/block is a common pattern used in business rules. It can sound a bit unfamiliar at the beginning but it is actually quite useful. You may think that you could justdeleteelements from the session, however doing so it becomes easy to create infinite loops in which you create new elements with some rules, remove them with others and keep recreating them again. The block-flag pattern avoids all of that.

The session

Rules operate on data which is part of the session. Data is typically inserted into the session during the initialization phase. Later we could have rules inserting more data into the session, potentially triggering other rules.

This is how we could populate the session with some example data:

fun loadDataIntoSession(ksession: KieSession, dayToConsider: LocalDate) { val products = listOf( Product("My book", 20.0f), Product("Video course", 100.0f), Product("Consulting package", 500.0f) ) val persons = listOf( Subscriber("Mario", LocalDate.of(2019, Month.JANUARY, 1), "Italy"), Subscriber("Amelie", LocalDate.of(2019, Month.FEBRUARY, 1), "France"), Subscriber("Bernd", LocalDate.of(2019, Month.APRIL, 18), "Germany"), Subscriber("Eric", LocalDate.of(2018, Month.OCTOBER, 1), "USA"), Subscriber("Albert", LocalDate.of(2016, Month.OCTOBER, 12), "USA") ) val sequences = listOf( EmailSequence("Present book", listOf( Email("Present book 1", "Here is the book...", tags= listOf("book_explanation")), Email("Present book 2", "Here is the book...", tags= listOf("book_explanation")), Email("Present book 3", "Here is the book...", tags= listOf("book_explanation")) )), EmailSequence("Present course", listOf( Email("Present course 1", "Here is the course...", tags= listOf("course_explanation")), Email("Present course 2", "Here is the course...", tags= listOf("course_explanation")), Email("Present course 3", "Here is the course...", tags= listOf("course_explanation")) )) ) ksession.insert(Email("Question to user", "Do you...")) ksession.insert(Email("Interesting topic A", "Do you...")) ksession.insert(Email("Interesting topic B", "Do you...")) ksession.insert(Email("Suggest book", "I wrote a book...", tags= listOf("book_offer"))) ksession.insert(Email("Suggest course", "I wrote a course...", tags= listOf("course_offer"))) ksession.insert(Email("Suggest consulting", "I offer consulting...", tags= listOf("consulting_offer"))) ksession.setGlobal("day", dayToConsider) ksession.insert(products) persons.forEach { ksession.insert(it) } sequences.forEach { ksession.insert(it) }}

Of course in a real application we would access some database or some form of storage to retrieve the data to be used to populate the session.

Global objects

In rules we will not only access elements which are part of the session but also global objects.
Global objects are inserted in the session using setGlobal. We have seen an example in loadDataIntoSession:

fun loadDataIntoSession(ksession: StatefulKnowledgeSession, dayToConsider: LocalDate) : EmailScheduler { ... ksession.setGlobal("day", dayToConsider) ...}

In the rules we declare the globals:

package com.strumenta.funnellangimport com.strumenta.funnel.Email;import com.strumenta.funnel.EmailSequence;import com.strumenta.funnel.EmailSchedulingimport com.strumenta.funnel.EmailScheduler;import com.strumenta.funnel.Personimport java.time.LocalDate;global LocalDate day;

At this point we can refer to these globals in all rules. In our example we use dayvalue to know which day we are considering for the scheduling. Typically it would be tomorrow, as we would like to do the scheduling one day in advance. However for testing reasons we could use any day we want. Or we may want to use days in the future for simulation purposes.

(Video) Drools Tutorial 02-what is rule engine

Global should not be abused. Personally I like to use them to specify configuration parameters. Others prefer to insert this data into the session and this is the recommended approach. The reason why I use globals (carefully and rarely) is because I like to distinguish between the data I am working on (stored in the session) and the configuration (for that I use globals).

Writing the generic rules

Let’s now see the whole set of generic rules that we have written. By generic rules we mean rules that could be applied to all email schedulings we want to do. To complement these rules we may have others for specific products or topics we are promoting.

package com.strumenta.funnellangimport com.strumenta.funnel.Email;import com.strumenta.funnel.EmailSequence;import com.strumenta.funnel.EmailSchedulingimport com.strumenta.funnel.EmailSending;import com.strumenta.funnel.Subscriberimport java.time.LocalDate;import com.strumenta.funnel.Priorityglobal LocalDate day;rule "Continue sequence" when sequence : EmailSequence () subscriber : Subscriber ( isInSequence(sequence) ) then EmailSending $sending = new EmailSending(sequence.next(subscriber.getActualEmailsReceived()), subscriber, day); EmailScheduling $scheduling = new EmailScheduling($sending, Priority.IMPORTANT, true); insert($scheduling);endrule "Start sequence" when sequence : EmailSequence () subscriber : Subscriber ( !isInSequence(sequence) ) then EmailSending $sending = new EmailSending(sequence.getFirst(), subscriber, day); EmailScheduling $scheduling = new EmailScheduling($sending, Priority.NORMAL); insert($scheduling);endrule "Prevent overloading" when scheduling : EmailScheduling( sending.subscriber.hasReceivedEmailsInLastDays(3, day), !blocked ) then scheduling.setBlocked(true);endrule "Block on holidays" when scheduling : EmailScheduling( sending.subscriber.isOnHolidays(scheduling.sending.date), !blocked ) then scheduling.setBlocked(true);endrule "Precedence to time sensitive emails" when scheduling1 : EmailScheduling( timeSensitive == true, !blocked ) scheduling2 : EmailScheduling( this != scheduling1, !blocked, sending.subscriber == scheduling1.sending.subscriber, sending.date == scheduling1.sending.date, timeSensitive == false) then scheduling2.setBlocked(true);endrule "Precedence to higher priority emails" when scheduling1 : EmailScheduling( !blocked ) scheduling2 : EmailScheduling( this != scheduling1, !blocked, sending.subscriber == scheduling1.sending.subscriber, sending.date == scheduling1.sending.date, timeSensitive == scheduling1.timeSensitive, priority < scheduling1.priority) then scheduling2.setBlocked(true);endrule "Limit to one email per day" when scheduling1 : EmailScheduling( blocked == false ) scheduling2 : EmailScheduling( this != scheduling1, blocked == false, sending.subscriber == scheduling1.sending.subscriber, sending.date == scheduling1.sending.date, timeSensitive == scheduling1.timeSensitive, priority == scheduling1.priority, id > scheduling1.id) then scheduling2.setBlocked(true);endrule "Never resend same email" when scheduling : EmailScheduling( !blocked ) subscriber : Subscriber( this == scheduling.sending.subscriber, hasReceived(scheduling.sending.email) ) then scheduling.setBlocked(true);end

Let’s examine all these rules, one by one:

  • Continue sequence: if someone started receiving an email sequence and he did not receive the last email yet, then he should get the next email in the sequence
  • Start sequence:if someone did not yet receive the first email of a sequence he should. Note that technically speaking this rule alone would cause everyone who has finished a sequence to immediately restart it. This does not happen because of theNever resend same email rule. However you could decide to rewrite this rule to explicitly forbidding someone who has already received a certain sequence to be re-inserted in it.
  • Prevent overloading:if someone has received an email in the last three days then we should block any email scheduling directed to that person
  • Block on holidays:if someone is on holidays we should not send emails to them
  • Precedence to time sensitive emails:given a pair of email schedulings directed to the same person on the same date, if only one of the two is time sensitive we should block the other
  • Precedence to higher priority emails:given a pair of email schedulings directed to the same person on the same date being both time sensitive or both not time sensitive, we should block the one with lower importance
  • Limit to one email per day:we should not schedule to send more than one email per day to the same person. If this happens we have to pick one somehow. We use the internal ID to discriminate between the two
  • Never resend same email:if someone has already received a certain email he should not receive it again in the future

Writing the rules specific to the book emails

Our marketing experts may want to write specific rules for specific products or topics. Let’s assume they want to create a set of emails to promote and sell a book. We could write these rules in a separate file, perhaps maintained by the marketing expert in charge of selling that book.

To write rules regarding a specific topic we will take advantage of tags, a mechanism that will give us a certain amount of flexibility. Let’s see the rules we can write:

package com.strumenta.funnellangimport com.strumenta.funnel.Subscriber;import com.strumenta.funnel.EmailScheduling;import java.time.DayOfWeek;rule "Send book offer only after at least 3 book presentation emails" when subscriber : Subscriber ( emailReceivedWithTag("book_explanation") < 3 ) scheduling : EmailScheduling( !blocked, sending.subscriber == subscriber, sending.email.tags contains "book_offer" ) then scheduling.setBlocked(true);endrule "Block book offers on monday" when scheduling : EmailScheduling( !blocked, sending.date.dayOfWeek == DayOfWeek.MONDAY, sending.email.tags contains "book_offer" ) then scheduling.setBlocked(true);endrule "Block book offers for people who bought" when subscriber : Subscriber ( tags contains "book_bought" ) scheduling : EmailScheduling( !blocked, sending.subscriber == subscriber, sending.email.tags contains "book_offer" ) then scheduling.setBlocked(true);end

Let’s examine our rules:

  • Send book offer only after at least 3 book presentation emails:we want to block any email selling the book if the subscriber did not receive at least three emails explaining the content of the book
  • Block book offers on monday:we want to block book offers to be sent on monday, for example because we have seen that subscribers are less inclined to buy on that day of the week
  • Block book offers for people who bought:we do not want to propose a deal on the book to subscribers who already bought it

Testing the business rules

There are different types of tests we may want to write to verify that our rules behave as expected. On one side of the spectrum we may want to have tests that verify complex scenarios and check for unexpected interactions between rules. These tests will run considering complex data sets and the whole set of business rules. On the other side of the spectrum we may want to write simple unit tests to verify single rules. We will see an example of these unit tests, but most of what we will see could be adapted to test the whole set of rules instead of single rules.

What do we want to do in our unit tests?

  1. We setup the knowledge base
  2. We want to load some data into the session
  3. We want to run the rule business engine, enablingjust the one business rule we want to test
  4. We want to verify that the resulting email schedulings are the one expected

To satisfy point 1 we load all the files containing our rules and we verify there are no issues.

private fun prepareKnowledgeBase(files: List<File>): InternalKnowledgeBase { val kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder() files.forEach { kbuilder.add(ResourceFactory.newFileResource(it), ResourceType.DRL) } val errors = kbuilder.errors if (errors.size > 0) { for (error in errors) { System.err.println(error) } throw IllegalArgumentException("Could not parse knowledge.") } val kbase = KnowledgeBaseFactory.newKnowledgeBase() kbase.addPackages(kbuilder.knowledgePackages) return kbase}

How do we load data into the session? We do that by loading some default data and then giving the possibility to change this data a little bit in each test. In the following piece of code you will see that we can pass a function as thedataTransformer parameter. Such function can operate on the data before we load them into the session. This is our hook to tweak the data in each test.

fun loadDataIntoSession(ksession: KieSession, dayToConsider: LocalDate, dataTransformer: ((Subscriber, Email) -> Unit)? = null) { val amelie = Subscriber("Amelie", LocalDate.of(2019, Month.FEBRUARY, 1), "France") val bookSeqEmail1 = Email("Present book 1", "Here is the book...", tags= listOf("book_explanation")) val products = listOf( Product("My book", 20.0f), Product("Video course", 100.0f), Product("Consulting package", 500.0f) ) val persons = listOf(amelie) val sequences = listOf( EmailSequence("Present book", listOf( bookSeqEmail1, Email("Present book 2", "Here is the book...", tags= listOf("book_explanation")), Email("Present book 3", "Here is the book...", tags= listOf("book_explanation")) )) ) dataTransformer?.invoke(amelie, bookSeqEmail1) ksession.insert(Email("Question to user", "Do you...")) ksession.insert(Email("Interesting topic A", "Do you...")) ksession.insert(Email("Interesting topic B", "Do you...")) ksession.insert(Email("Suggest book", "I wrote a book...", tags= listOf("book_offer"))) ksession.insert(Email("Suggest course", "I wrote a course...", tags= listOf("course_offer"))) ksession.insert(Email("Suggest consulting", "I offer consulting...", tags= listOf("consulting_offer"))) ksession.setGlobal("day", dayToConsider) ksession.insert(products) persons.forEach { ksession.insert(it) } sequences.forEach { ksession.insert(it) }}

We achieve point 3 by specifying a filter on the rules to be executed:

ksession.fireAllRules { match -> match.rule.name in rulesToKeep }

At this point we can simply check the results.

Once this infrastructure has been put in place the tests we will write will look like this:

@test fun startSequencePositiveCase() { val schedulings = setupSessionAndFireRules( LocalDate.of(2019, Month.MARCH, 17), listOf("Start sequence")) assertEquals(1, schedulings.size) assertNotNull(schedulings.find { it.sending.email.title == "Present book 1" && it.sending.subscriber.name == "Amelie" })}@test fun startSequenceWhenFirstEmailReceived() { val schedulings = setupSessionAndFireRules( LocalDate.of(2019, Month.MARCH, 17), listOf("Start sequence")) { amelie, bookSeqEmail1 -> amelie.emailsReceived.add( EmailSending(bookSeqEmail1, amelie, LocalDate.of(2018, Month.NOVEMBER, 12))) } assertEquals(0, schedulings.size)}

In the first test we expectAmelie to receive the first email of a sequence, given she did not receive yet. In the second test instead we set in the session athatAmelie already received the first email of the sequence, so we expect it to not receive it again (no email schedulings expected at all).

(Video) Drools Rule Engine - Create a Timesheet Management System using Drools rule engine

This is the whole code of the test class:

package com.strumenta.funnelimport org.drools.core.impl.InternalKnowledgeBaseimport org.drools.core.impl.KnowledgeBaseFactoryimport org.kie.api.io.ResourceTypeimport org.kie.api.runtime.KieSessionimport org.kie.internal.builder.KnowledgeBuilderFactoryimport org.kie.internal.io.ResourceFactoryimport java.io.Fileimport java.time.LocalDateimport java.time.Monthimport kotlin.test.assertEqualsimport kotlin.test.assertNotNullimport org.junit.Test as testclass GenericRulesTest { private fun prepareKnowledgeBase(files: List<File>): InternalKnowledgeBase { val kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder() files.forEach { kbuilder.add(ResourceFactory.newFileResource(it), ResourceType.DRL) } val errors = kbuilder.errors if (errors.size > 0) { for (error in errors) { System.err.println(error) } throw IllegalArgumentException("Could not parse knowledge.") } val kbase = KnowledgeBaseFactory.newKnowledgeBase() kbase.addPackages(kbuilder.knowledgePackages) return kbase } fun loadDataIntoSession(ksession: KieSession, dayToConsider: LocalDate, dataTransformer: ((Subscriber, Email) -> Unit)? = null) { val amelie = Subscriber("Amelie", LocalDate.of(2019, Month.FEBRUARY, 1), "France") val bookSeqEmail1 = Email("Present book 1", "Here is the book...", tags= listOf("book_explanation")) val products = listOf( Product("My book", 20.0f), Product("Video course", 100.0f), Product("Consulting package", 500.0f) ) val persons = listOf(amelie) val sequences = listOf( EmailSequence("Present book", listOf( bookSeqEmail1, Email("Present book 2", "Here is the book...", tags= listOf("book_explanation")), Email("Present book 3", "Here is the book...", tags= listOf("book_explanation")) )) ) dataTransformer?.invoke(amelie, bookSeqEmail1) ksession.insert(Email("Question to user", "Do you...")) ksession.insert(Email("Interesting topic A", "Do you...")) ksession.insert(Email("Interesting topic B", "Do you...")) ksession.insert(Email("Suggest book", "I wrote a book...", tags= listOf("book_offer"))) ksession.insert(Email("Suggest course", "I wrote a course...", tags= listOf("course_offer"))) ksession.insert(Email("Suggest consulting", "I offer consulting...", tags= listOf("consulting_offer"))) ksession.setGlobal("day", dayToConsider) ksession.insert(products) persons.forEach { ksession.insert(it) } sequences.forEach { ksession.insert(it) } } private fun setupSessionAndFireRules(dayToConsider: LocalDate, rulesToKeep: List<String>, dataTransformer: ((Subscriber, Email) -> Unit)? = null) : List<EmailScheduling> { val kbase = prepareKnowledgeBase(listOf(File("rules/generic.drl"))) val ksession = kbase.newKieSession() loadDataIntoSession(ksession, dayToConsider, dataTransformer) ksession.fireAllRules { match -> match.rule.name in rulesToKeep } return ksession.selectScheduling(dayToConsider) } @test fun startSequencePositiveCase() { val schedulings = setupSessionAndFireRules( LocalDate.of(2019, Month.MARCH, 17), listOf("Start sequence")) assertEquals(1, schedulings.size) assertNotNull(schedulings.find { it.sending.email.title == "Present book 1" && it.sending.subscriber.name == "Amelie" }) } @test fun startSequenceWhenFirstEmailReceived() { val schedulings = setupSessionAndFireRules( LocalDate.of(2019, Month.MARCH, 17), listOf("Start sequence")) { amelie, bookSeqEmail1 -> amelie.emailsReceived.add( EmailSending(bookSeqEmail1, amelie, LocalDate.of(2018, Month.NOVEMBER, 12))) } assertEquals(0, schedulings.size) }}

Conclusions

Marketers should be able to experiment and try out their strategies and ideas easily: for example, do they want to create a special offer just to be sent at 20 subscribers per day? Do they want to send special offers to subscribers in a certain country? Do they want to consider the birthday or the national holiday of a subscriber to send him a special message? Our domain experts, marketers in this case, should have a tool to pour these ideas into the system and see them applied. Thanks to business rules they could be able to implement most of them by themselves. Not having to go through developers or other “gate keepers” could mean having the freedom to experiment, to try things and in the end to make the business profit.

There are things to consider: giving the possibility to write business rules could not be enough. To make our domain experts confident in the rules they write we should give them the possibility to play with them and try them out in a safe environment: a testing or simulation mechanism should be put in place. In this way they could try things and see if they translated correctly into code the idea that they had in mind.

Of course business rules are much easier to write compared to typical code. This is the case because they have a predefined format. In this way we can pick an existing rule and tune a little bit. Still, it requires some training for the domain experts to get used to them. They need to develop the ability to formalize their thoughts and this could be easy or hard depending on their background. For example, for marketers it could be doable while for other professionals it could require more exercise. What we could do to simplify their life and make domain experts more productive is to put a Domain Specific Language in front of our business rules.

By creating a simple DSL we could make things easier for our marketers. This DSL would permit to manipulate the domain model we have seen (subscribers, emails, etc) and perform the two actions marketers are interested into: scheduling and blocking emails. We could provide a simple editor, with auto-completion and error checking, and integrate a testing and simulation environment in it. In this scenario marketers would be fully independent and able to design and verify their rules quickly and with very limited supported needed.

Acknowledgments

Mario Fusco (a Java champion) and Luca Molteni, both working on Drools at RedHat, were so very kind to review the article and suggest significant improvements. I am extremely thankful to them.

Thank you!

FAQs

What is Drools rule engine used for? ›

A rules engine (RE) is a module that automates the management of certain highly variable processes. The fundamental concept consists of separating the objects that are involved in processes from the logic that implements those processes.

What is DRL file in drools? ›

DRL (Drools Rule Language) rules are business rules that you define directly in . drl text files. These DRL files are the source in which all other rule assets in Business Central are ultimately rendered.

Which is the best rules engine? ›

Top 5 Java Rule Engine for 2017-2018
PopularityRule Engine
1Drools
2OpenRules
3EasyRules
4JLisa
1 more row
9 Dec 2017

Is Drools a programming language? ›

Drools is a Business Logic integration Platform (BLiP). It is written in Java. It is an open source project that is backed by JBoss and Red Hat, Inc. It extends and implements the Rete Pattern matching algorithm.

How does business rule engine work? ›

A business rules engine is a software system and its set of rules to guide processes. It executes one or more business rules in a runtime productive environment, to automate decisions based on predefined conditions loaded into it.

How do you write a rule file? ›

To Create a rules File
  1. Use a text editor to create a text file that is named rules . ...
  2. Add a rule in the rules file for each group of systems on which you want to install the Solaris software. ...
  3. Save the rules file in the JumpStart directory.
  4. Ensure that root owns the rules file and that the permissions are set to 644.

Why do we need Drools? ›

It is a Java-based, open-source project backed by JBoss and Red Hat. It allows solutions to separate data and business logic. Implementing Drools requires two things: authoring and runtime. Authoring is the creation of rules files, and runtime involves working memory to execute the rules.

Does Drools have a UI? ›

Drools Workbench (web UI for authoring and management)

What is kie session? ›

KieSession is the most common way to interact with the engine. A KieSession allows the application to establish an iterative conversation with the engine, where the state of the session is kept across invocations. The reasoning process may be triggered multiple times for the same set of data.

How can you improve Drools performance of rule execution? ›

Literal Restrictions using the operator '==' provide for faster execution as we can index using hashing to improve performance. One can bind variables to facts and their fields and then use them in subsequent field constraints. A bound variable is called a declaration.

How do you write a rule engine? ›

The rule engine is pretty simple, the final action could be just one of two actions, sending to inpatient or outpatient. The operators involved in an expression could be =,>,<,!= and logical operators between expressions are AND, OR and NOT .

Why rule engine is required? ›

For software developers, a rule engine is useful only if it liberates them from expressing the rule in the code. In order to avoid this pitfall, it is commonly accepted that we should use rule engines only if appropriate, or not use them at all. Over the past decades, that has become a self-fulfilling prophecy.

Does Drools work with Python? ›

This is a python package that allow you to interact with the REST API exposed by your KIE Server instance powered by Drools .

Is Elon Musk programming? ›

He is a self-taught programmer and while his programming skills may not be as proficient as the top programmers, yet, he knows Java, Python, C, Pearl, Shell, ML stacks, and wrote OpenAI libraries.

Does Disney use Python? ›

Walt Disney Feature Animation uses Python as a scripting language for animation. All the magic that happens in Disneyland has a bit of Python behind it.

What are the 3 rules of business? ›

  • Better Before Cheaper. ...
  • Revenue Before Cost. ...
  • There Are No Other Rules.

What is the rule of 7 in business? ›

The rule of seven quite simply states that it takes an average of seven interactions with your brand before a purchase will take place.

Why do we need rule engine? ›

1. Advantages of a Rule Engine. Rule engines allow you to say "What to do" not "How to do it". The key advantage of this point is that using rules can make it easy to express solutions to difficult problems and consequently have those solutions verified (rules are much easier to read then code).

When would you use a rule engine? ›

For software developers, a rule engine is useful only if it liberates them from expressing the rule in the code. In order to avoid this pitfall, it is commonly accepted that we should use rule engines only if appropriate, or not use them at all. Over the past decades, that has become a self-fulfilling prophecy.

What is rules engine in AWS IoT? ›

The Rules Engine is a component of AWS IoT Core. The Rules Engine evaluates inbound messages published into AWS IoT Core and transforms and delivers them to another device or a cloud service, based on business rules you define.

What is Rule engine in Salesforce? ›

Rule engine allows you to configure rules in accordance with your business workflow. Let anyone with basic knowledge of Salesforce create dependencies and manage complex business rules which can be easily configured and modified. Automate the flow and get a significant transparency on your actions.​

What are the two main components of the Rule Engine? ›

There are two main rules engine types: forward and backward chaining. Forward chaining in its turn divides into production and reaction rules. To make business rules engine work, it is essential to fine-tune the integration between business process management and business rules management platforms.

How do you code a Rule Engine? ›

Steps to implement Rule Engine Design pattern

Write all rules in one method using if-then-else conditional statements. Step 2: Create classes for each rule. Create separate classes for each rule. Add a ShouldRun() method in each class file to determine whether the business rule has to be executed or not.

How do you write a Rule Engine? ›

The rule engine is pretty simple, the final action could be just one of two actions, sending to inpatient or outpatient. The operators involved in an expression could be =,>,<,!= and logical operators between expressions are AND, OR and NOT .

What is one of the greatest benefits of a rules engine? ›

One of the biggest benefits of a business rules engine is its ability to manage business rules outside of code, allowing it to be maintained by less technical users. This allows users to make updates to business rules without making changes to the application code itself.

What is rule purpose? ›

Rules are established to protect the weaker class in the society since they are at a disadvantage if such regulations are broken. When rules are properly set and followed, they provide a stable environment and human co-existence in a community, resulting in peace and order.

What is rule logic? ›

Rule logic describes what a rule evaluates and how the evaluation is completed.

What is rule in IoT? ›

Rules give your devices the ability to interact with AWS services. Rules are analyzed and actions are performed based on the MQTT topic stream. You can use rules to support tasks like these: Augment or filter data received from a device. Write data received from a device to an Amazon DynamoDB database.

How do I Create a rule in IoT AWS? ›

Under Choose or create a role to grant AWS IoT access to perform this action, choose Create role. Enter a name for the role (for example, LowMoistureTopicRole ), and then choose Create role. Choose Add action. Choose Create rule.

How do you Create rules in AWS? ›

To create a rule and add conditions

Sign in to the AWS Management Console and open the AWS WAF console at https://console.aws.amazon.com/wafv2/ . If you see Switch to AWS WAF Classic in the navigation pane, select it. In the navigation pane, choose Rules. Choose Create rule.

How many rules are there in Salesforce? ›

Each user can manage up to 400 rules.

What is rule template? ›

Rule templates allow users of the IBM® Cognos® Real-time Monitoring Dashboard to quickly create business rules without having to define the logic of the rule conditions, or its alert message. Instead, the user chooses the template and provides the values for which the rule will test.

What is Rules API? ›

An API rule completes the processing of API requests or completes the operations that are required by the API requests. To configure an API rule, specify whether to use dynamic actions or explicit actions. Dynamic actions take configuration values at run time and override the default properties in assembly actions.

Videos

1. Business Rule Management Systems: JBoss Drools
(Ilko Kovacic)
2. Engaging Business Users with Drools - Full Webinar Recording
(Decision Management Solutions)
3. Advanced Rule Engine Examples
(Optmyzr)
4. Business Rule Engine- Part 1
(Integration GigDJ)
5. Drools - Rule Engine Decision Table Example | Java Techie
(Java Techie)
6. Drools Tutorial 04-Advantages of a Rule Engine
(Drools Tutorial)

You might also like

Latest Posts

Article information

Author: Patricia Veum II

Last Updated: 09/22/2022

Views: 5773

Rating: 4.3 / 5 (64 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Patricia Veum II

Birthday: 1994-12-16

Address: 2064 Little Summit, Goldieton, MS 97651-0862

Phone: +6873952696715

Job: Principal Officer

Hobby: Rafting, Cabaret, Candle making, Jigsaw puzzles, Inline skating, Magic, Graffiti

Introduction: My name is Patricia Veum II, I am a vast, combative, smiling, famous, inexpensive, zealous, sparkling person who loves writing and wants to share my knowledge and understanding with you.