有关Drools业务规则引擎的完整教程
与往常⼀样,我们在配套存储库共享本教程中提供的代码。
业务规则很好地表现了某些领域的逻辑。 它们之所以有效,是因为它们可以直观直观地接近许多类型的领域专家的思维⽅式 。 其原因在于它们允许分解单个组件中的⼤问题。 这样,⽤户不必处理所有单个规则的编排:这是业务规则引擎提供的附加值。
在本⽂中,我们将讨论⼀个使⽤业务规则编写的应⽤程序的特定⽰例。 我们将编写规则,以确定将哪些电⼦邮件发送给新闻订阅者。 我们将看到不同类型的规则,以及如何使⽤Drools规则语⾔表达它们。 我们还将看到如何配置 (扰流器:这很容易),并使系统详细说明规则以产⽣可使⽤的结果。
我认为业务规则⾮常有趣,因为它们允许以不同的⽅式看待问题。 作为开发⼈员,我们⾮常习惯命令式范式或功能式范式。 但是,还有其他范式,例如状态机和业务规则,它们并不是很常⽤,在某些情况下可能更适合。
与往常⼀样,我们在配套存储库共享本教程中提供的代码。
我们正在尝试解决什么问题
让我们考虑⼀下电⼦邮件营销领域。 作为营销⼈员,我们有对我们的内容感兴趣的⼈员的电⼦邮件列表。 他们每个⼈都可能对某个特定主题表现出兴趣,阅读了⼀些⽂章并购买了某些产品。 考虑到他们的所有历史记录和偏好,我们希望每次都向他们发送最合适的内容。 此内容可能具有教育意义或提出了⼀些建议。 问题在于,我们要考虑⼀些限制因素(即,不于星期⽇发送电⼦邮件或不向已购买产品的⼈发送促销产品的电⼦邮件)。
所有这些规则本⾝都是简单的,但是复杂性是由它们如何组合以及如何相互作⽤得出的。 业务规则引擎将为我们处理这种复杂性,我们要做的就是清楚地表达单个规则。 规则将以域数据的形式表达,因此让我们⾸先关注域模型。
我们领域的模型
在我们的域模型中,我们有:
电⼦邮件 :我们要发送的单个电⼦邮件,按其标题和内容进⾏描述
电⼦邮件序列 :必须按特定顺序发送的电⼦邮件组,例如代表教程或描述产品不同功能的⼀组电⼦邮件
订户 :邮件列表的单个订户。 我们将需要知道我们发送给他的电⼦邮件,他对哪些东西感兴趣以及
他购买了哪些产品
产品 :我们出售的产品
购买 :订户已进⾏的购买
电⼦邮件发送:我们在某个⽇期或将要发送特定电⼦邮件给特定订户的事实
电⼦邮件计划 :发送电⼦邮件的计划,以及⼀些其他信息
与其他域元素相⽐,我们域模型的后两个元素似乎不太明显,但是我们会在实现中看到我们需要它们的原因。
我们的系统应该做什么
我们的系统应使⽤Drools引擎执⾏所有规则,并确定每个⽤户在特定⽇期应发送的电⼦邮件。 结果可能是决定不发送任何电⼦邮件,或者
发送电⼦邮件,从许多可能的电⼦邮件中选择⼀个。
要考虑的重要⼀点是,这些规则可能会随着时间的推移⽽发展。 市场营销负责⼈可能想尝试新规则,看看它们如何影响系统。 使⽤
Drools,他们应该可以轻松添加或删除规则或调整现有规则。
让我们强调⼀下:
这些领域专家应该能够对系统进⾏试验并快速尝试,⽽⽆需始终需要开发⼈员的帮助 。
规则
好的,现在我们知道我们拥有哪些数据,我们可以基于该模型表达规则。
让我们看⼀些我们可能要编写的规则⽰例:
我们可能会有⼀系列电⼦邮件,例如课程内容。 必须按顺序发送
我们可能有时间敏感的电⼦邮件,应该在特定的时间范围内发送,或者根本不发送
我们可能希望避免在⼀周的特定⽇期发送电⼦邮件,例如在订户所在国家/地区的公共假⽇
我们可能只想发送某些类型的电⼦邮件(例如,提议交易)给收到某些其他电⼦邮件的⼈(例如,⾄少3则关于同⼀主题的信息性电⼦邮件)
我们不想向已经购买该产品的订户提议某种产品的交易
我们可能希望限制向⽤户发送电⼦邮件的频率。 例如,如果我们在过去5天内已经发送过⼀封电⼦邮件,我们可能决定不向⽤户发送电⼦邮件
设置流⼝⽔
设置流⼝⽔可能⾮常简单。 我们正在研究在独⽴应⽤程序中运⾏流⼝⽔。 根据您的情况,这可能是也可能不是⼀个可接受的解决⽅案,在
某些情况下,您将不得不研究⽀持Drools的应⽤服务器JBoss。 但是,如果您想⼊门,则可以忘记所有这些,⽽只需使⽤Gradle(或
800小说网
Maven)配置依赖项即可。 如果确实需要,您可以稍后找出⽆聊的配置位。
buildscript {
ext.droolsVersion = "7.20.0.Final"
repositories {
mavenCentral()
} } plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.21" "org.jetbrains.kotlin.jvm" "1.3.21" } apply plugin: 'java' apply plugin: 'idea' group 'com.strumenta' version '0.1.1-S mavenLocal()进击的刘备
mavenCentral()
maven {
url ' repository.jboss/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" }
在我们的Gradle脚本中,我们使⽤:
,因为Kotlin摇滚!
IDEA,因为它是我最喜欢的IDE
Kotlin StdLib,反映和测试
流⼝⽔
这就是我们程序的结构:
fun main(args: Array<String>) {
try {
val kba = readKnowledgeBa(listOf(
File( "rules/generic.drl" ),
File( "rules/book.drl" )))
val kssion = wKieSession()
// 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 the future or we may want to run tests using a different val dayToConsider = w()
loadDataIntoSession(kssion, dayToConsider)
多肉多少天浇一次水
kssion.fireAllRules()
showSending(kssion)
} catch (t: Throwable) {
t.printStackTrace()
} }
很简单,很整洁。
我们在做什么,细节是:
我们从⽂件加载规则。 现在,我们只加载⽂件rules/generic.drl
我们设置了⼀个新的会话。 将会话视为规则所看到的宇宙:他们可以访问的所有数据都在那⾥
我们将数据模型加载到会话中
我们执⾏所有规则。 他们可以在会议中更改内容
我们阅读了修改后的数据模型(⼜称会话),以确定我们今天应该发送哪些电⼦邮件
编写数据模型的类
前⾯我们已经看到了数据模型的外观,现在让我们看⼀下它的代码。
鉴于我们正在使⽤Kotlin,它将⾮常简洁明了。
package com.strumenta.funnel import java. time .DayOfWeek import java. time .LocalDate import java.util.* enum class Priority { TRIVIAL,
NORMAL,
IMPORTANT,
VITAL } data class Product(val name: String,
val price: Float) data class Purcha(val product: Product,
val price: Float,
val date : LocalDate) data class Subscriber(val name: String,
val subscriptionDate: LocalDate,
val country: String,
val email: String = "$" ,
val tags: List<String> = emptyList(),
val purchas: List<Purcha> = emptyList(),
val emailsReceived: MutableList<EmailSending> = LinkedList()) {
val actualEmailsReceived
get() = emailsReceived.map { it.email }
ps材质
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) =
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 } 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) {
} el {
fal
}
}
override fun hashCode(): Int {
ail.title.hashCode() * 7 + this.subscriber.name.hashCode() * 3 + this. date .hashCode()
} } data class EmailScheduling @JvmOverloads constructor(val nding: EmailSending,
val priority: Priority,
val timeSensitive: Boolean = fal ,
var blocked: Boolean = fal ) {
val id = ++nextId
companion object {
private var nextId = 0
} }
这⾥不⾜为奇:我们有七个班级。 我们到处都有⼀些实⽤程序⽅法,但您⽆法⾃⼰解决。
编写规则以安排电⼦邮件
现在是时候编写我们的第⼀个业务规则了。 该规则将说明,在给定序列和给定⼈员的情况下,如果该⼈尚未从该序列接收电⼦邮件,我们将安排该序列的第⼀封电⼦邮件发送给该⼈。
dialect "java" rule "Start quence"
when
quence : EmailSequence ()
subscriber : Subscriber ( !isInSequence(quence) )
扫墓时间有什么讲究then
EmailSending $nding = new First(), subscriber, day);
EmailScheduling $scheduling = new EmailScheduling($nding, Priority.NORMAL);
校园暴力inrt($scheduling); end
在规则的标题中,我们指定⽤于编写⼦句的语⾔。 在本教程中,我们将仅考虑Java。 还有另⼀个可能的值: mvel 。 我们不会对此进⾏调查。 同样,尽管在此⽰例中,我们在规则中指定了⽅⾔,但也可以为整个⽂件指定⼀次⽅⾔。 甚⾄还有⼀个更好的选择:根本不指定⽅⾔,因为Java仍然是默认语⾔,不⿎励使⽤mvel。
when部分确定我们的规则将对哪些元素进⾏操作。 在这种情况下,我们声明它将在EmailSequence和Subscriber上运⾏ 。 它不仅对任何⼈都有效,⽽仅对满⾜!isInSequence(quence)条件的⼈有效。 此条件基于对⽅法isInquence的调⽤,我们将在下⾯显⽰:
data class Subscriber(...) {
fun isInSequence(emailSequence: EmailSequence) =
hasReceived(emailSequence.first) &&
!hasReceived(emailSequence.last)
fun hasReceived(email: Email) =
emailReceived.any { it.email == email } }
现在让我们看⼀下规则的then部分。 在此部分中,我们指定触发规则时会发⽣什么。 when找到满⾜when部分的元素when将触发该规则。
在这种情况下,我们将创建⼀个EmailScheduling并将其添加到会话中。 特别是,我们希望在考虑的当天将序列的第⼀封电⼦邮件发送给考虑的⼈。 我们还指定了此电⼦邮件的优先级(在这种情况下为NORMAL )。 当我们有多个电⼦邮件时,有必要决定有效发送的电⼦邮件。的确,我们还有另⼀条规则将根据这些值来确定要优先处理的电⼦邮件(提⽰:这将是优先级最⾼的电⼦邮件)。
通常,您可能通常希望在then⼦句中将内容添加到会话中。 或者,您可能要修改属于会话⼀部分的对象。 您也可以在有副作⽤的对象上调⽤⽅法。 虽然建议的⽅法是限制⾃⼰来操纵会话,但是例如,您可能希望添加副作⽤以进⾏⽇志记录。 这在学习Drools并尝试围绕您的第⼀条规则时特别有⽤。
编写规则以阻⽌发送电⼦邮件沉默的意思是什么
我们将看到我们有两种可能的规则类型:⽤于调度新电⼦邮件的规则和⽤于阻⽌调度电⼦邮件发送的规则。 之前我们已经了解了如何编写规则以发送电⼦邮件,现在我们将看到如何编写电⼦邮件以防⽌发送电⼦邮件。
在此规则中,我们要检查是否计划将电⼦邮件发送给最近三天内已收到电⼦邮件的⼈。 如果是这种情况,我们希望阻⽌该电⼦邮件的发送。
谢神rule "Prevent overloading"
when
scheduling : EmailScheduling(
nding.subscriber.hasReceivedEmailsInLastDays(3, day),
!blocked )
then
scheduling.tBlocked( true ); end
在when部分,我们指定此规则将在EmailScheduling上EmailScheduling 。 因此,每当另⼀个规则将添加EmailScheduling时,都会触发此规则来决定是否必须阻⽌其发送。