Статьи

Таблицы решений слюни с верблюдом и весной

Как я показал в моем предыдущем посте, JBoss Drools — очень полезный движок правил . Единственная проблема заключается в том, что создание правил на языке правил может быть довольно сложным для нетехнического человека. Вот почему можно обеспечить простой способ создания бизнес-правил — таблицы решений, созданные в электронной таблице!

В следующем примере я покажу вам действительно сложный пример бизнес-правила, преобразованный в таблицу решений в электронной таблице. В качестве бэкенда у нас будут Drools, Camel и Spring. Для начала давайте взглянем на нашу воображаемую бизнес-проблему. Давайте предположим, что мы ведем бизнес, который фокусируется на продаже продуктов (медицинских или электронных). Мы отправляем нашу продукцию в несколько стран (PL, США, GER, SWE, UK, ESP), и в зависимости от страны существуют различные законодательные нормы

относительно возраста покупателя. В некоторых странах вы можете купить продукты, когда вы моложе, чем в других. Более того, в зависимости от страны, из которой поступает покупатель и товар, а также от количества товаров, покупатель может получить скидку. Как вы можете видеть, в этом сценарии требуется существенное количество условий, необходимых для полного поля (представьте себе число ifs, необходимое для программирования этого).

Еще одной проблемой будет деловая сторона (как обычно). Любой, кто работал над проектом, знает, как быстро меняются требования. Если кто-то введет все правила в коде, ему придется заново развертывать программное обеспечение каждый раз, когда изменяются требования. Вот почему рекомендуется отделить бизнес-логику от самого кода. В любом случае, давайте вернемся к нашему примеру. Для начала давайте взглянем на электронные таблицы (перед этим стоит взглянуть на веб-сайт JBoss с точным описанием того, как должна выглядеть таблица решений ): точка входа в нашу программу — первая электронная таблица, которая проверяет если данному пользователю должна быть предоставлена ​​возможность купить продукт (будет лучше, если вы загрузите электронные таблицы и поиграете с ними из репозитория Too Much Coding в Bitbucket: user_table.xls и product_table.xls или Github user_table.xls и product_table.xls ):

user_table.xls (таблица таблиц)

Как только пользователь будет одобрен, он может получить скидку:

product_table.xls (таблица таблиц)

product_table.xls (список листов)

Как вы можете видеть на изображениях, проблема бизнеса довольно сложна. Каждая строка представляет правило, а каждый столбец представляет условие. Вы помните синтаксис правил из моего недавнего поста? Таким образом, вы поймете скрытую часть таблицы, которая находится прямо над первой видимой строкой:

Строки от 2 до 6 представляют некоторые фиксированные значения конфигурации, такие как набор правил, импорт ( вы уже видели это в моем недавнем посте ) и функции. Далее в строке № 7 вы можете найти название таблицы правил. Затем в строке № 8 в нашем сценарии вы имеете либо СОСТОЯНИЕ, либо ДЕЙСТВИЕ — другими словами, либо LHS, либо RHE RHS соответственно. Строка номер 9 является как представлением типов, представленных в условии, так и привязкой к переменной. В строке № 10 у нас есть точное условие LHS. Строка № 11 показывает метку столбцов. Из строки № 12 у нас есть правила одно за другим. Вы можете найти таблицы в источниках.

Теперь давайте посмотрим на код. Давайте начнем с рассмотрения схем, определяющих Продукт и Пользователя.

Person.xsd

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:include schemaLocation="user.xsd"/>
 
    <xsd:element name="Product">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Name" type="xsd:string"/>
                <xsd:element name="Type" type="ProductType"/>
                <xsd:element name="Price" type="xsd:double"/>
                <xsd:element name="CountryOfOrigin" type="CountryType"/>
                <xsd:element name="AdditionalInfo" type="xsd:string"/>
                <xsd:element name="Quantity" type="xsd:int"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
 
    <xsd:simpleType name="ProductType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="MEDICAL"/>
            <xsd:enumeration value="ELECTRONIC"/>
        </xsd:restriction>
    </xsd:simpleType>
 
</xsd:schema>

User.xsd

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:include schemaLocation="product.xsd"/>
 
    <xsd:element name="User">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="UserName" type="xsd:string"/>
                <xsd:element name="UserAge" type="xsd:int"/>
                <xsd:element name="UserCountry" type="CountryType"/>
                <xsd:element name="Decision" type="DecisionType"/>
                <xsd:element name="DecisionDescription" type="xsd:string"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
 
    <xsd:simpleType name="CountryType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="PL"/>
            <xsd:enumeration value="USA"/>
            <xsd:enumeration value="GER"/>
            <xsd:enumeration value="SWE"/>
            <xsd:enumeration value="UK"/>
            <xsd:enumeration value="ESP"/>
        </xsd:restriction>
    </xsd:simpleType>
 
    <xsd:simpleType name="DecisionType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="ACCEPTED"/>
            <xsd:enumeration value="REJECTED"/>
        </xsd:restriction>
    </xsd:simpleType>
 
</xsd:schema>

В связи с тем, что мы используем maven, мы можем использовать плагин, который преобразует XSD в классы Java.

часть pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<build>
       <pluginManagement>
           <plugins>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <version>2.5.1</version>
               </plugin>
           </plugins>
       </pluginManagement>
       <plugins>
           <plugin>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>jaxb2-maven-plugin</artifactId>
               <version>1.5</version>
               <executions>
                   <execution>
                       <id>xjc</id>
                       <goals>
                           <goal>xjc</goal>
                       </goals>
                   </execution>
               </executions>
               <configuration>
                   <packageName>pl.grzejszczak.marcin.drools.decisiontable.model</packageName>
                   <schemaDirectory>${project.basedir}/src/main/resources/xsd</schemaDirectory>
               </configuration>
           </plugin>
       </plugins>
   </build>

Благодаря этому плагину у нас есть сгенерированные JAXB классы в пакете pl.grzejszczak.marcin.decisiontable.model . Теперь перейдем к файлу drools-context.xml, где мы определили все необходимые bean-компоненты для Drools:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
       xmlns:drools="http://drools.org/schema/drools-spring"
 
    <!-- Grid Node identifier that is registered in the CamelContext -->
    <drools:grid-node id="node1"/>
 
    <drools:kbase id="productsKBase" node="node1">
        <drools:resources>
            <drools:resource type="DTABLE" source="classpath:rules/product_table.xls"/>
        </drools:resources>
    </drools:kbase>
 
    <drools:ksession id="productsKSession" name="productsKSession" type="stateless" kbase="productsKBase" node="node1"/>
 
    <drools:kbase id="usersKBase" node="node1">
        <drools:resources>
            <drools:resource type="DTABLE" source="classpath:rules/user_table.xls"/>
        </drools:resources>
    </drools:kbase>
 
    <drools:ksession id="usersKSession" name="usersKSession" type="stateless" kbase="usersKBase" node="node1"/>
 
</beans>

Как вы можете видеть в сравнении с контекстом приложения из недавнего поста, есть некоторые отличия. Сначала вместо передачи файла DRL в качестве ресурса внутри базы знаний мы предоставляем таблицу решений (DTABLE). Я решил передать два отдельных файла, но вы можете предоставить один файл с несколькими листами и получить доступ к этим листам (через элемент solutiontable-conf). Также есть дополнительный элемент, называемый узлом. Нам нужно выбрать реализацию интерфейса Node (Execution, Grid…), чтобы маршрут Camel работал правильно, как вы увидите через пару секунд в файле контекста приложения Spring.

applicationContext.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:context="http://www.springframework.org/schema/context"
 
    <import resource="classpath:drools-context.xml"/>
    <!-- Show Spring where to search for the beans (in which packages) -->
    <context:component-scan base-package="pl.grzejszczak.marcin.drools.decisiontable" />
 
    <camel:camelContext id="camelContext">
        <camel:route id="acceptanceRoute">
            <camel:from uri="direct:acceptanceRoute"/>
            <camel:to uri="drools:node1/usersKSession"/>
        </camel:route>
        <camel:route id="discountRoute">
            <camel:from uri="direct:discountRoute"/>
            <camel:to uri="drools:node1/productsKSession"/>
        </camel:route>
    </camel:camelContext>
 
</beans>

Как вы можете видеть, чтобы получить доступ к компоненту Drools Camel, мы должны предоставить узел, через который мы получим доступ к соответствующему сеансу знаний . Мы определили два маршрута — первый заканчивается компонентом Drools, который обращается к сеансу знаний пользователей, а другой — сеансу знаний о продуктах.

У нас есть реализация интерфейса ProductService под названием ProductServiceImpl, в которой входные объекты User и Product передают их через шаблон производителя Camel на два маршрута Camel, каждый из которых заканчивается компонентами Drools. Концепция этого продукта заключается в том, что мы сначала обрабатываем пользователя, если он может даже купить программное обеспечение, а затем мы проверяем, какую скидку он получит. С точки зрения сервиса, на самом деле мы просто отправляем объект и ждем ответа. Наконец, получив ответ, мы передаем Пользователя и Продукт внедрению Финансовой службы, которое выставит счет пользователю за продукты, которые он купил, или отклонит его предложение в случае необходимости.

ProductServiceImpl.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package pl.grzejszczak.marcin.drools.decisiontable.service;
 
import org.apache.camel.CamelContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pl.grzejszczak.marcin.drools.decisiontable.model.Product;
import pl.grzejszczak.marcin.drools.decisiontable.model.User;
 
import static com.google.common.collect.Lists.newArrayList;
 
/**
 * Created with IntelliJ IDEA.
 * User: mgrzejszczak
 * Date: 14.01.13
 */
@Component("productServiceImpl")
public class ProductServiceImpl implements ProductService {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ProductServiceImpl.class);
 
    @Autowired
    CamelContext camelContext;
 
    @Autowired
    FinancialService financialService;
 
    @Override
    public void runProductLogic(User user, Product product) {
        LOGGER.debug("Running product logic - first acceptance Route, then discount Route");
        camelContext.createProducerTemplate().sendBody("direct:acceptanceRoute", newArrayList(user, product));
        camelContext.createProducerTemplate().sendBody("direct:discountRoute", newArrayList(user, product));
        financialService.processOrder(user, product);
    }
 
}

Еще одна важная вещь, о которой следует помнить, это то, что для компонента Camel Drools в качестве входных данных требуется объект Command. Как вы можете видеть, в теле мы отправляем список объектов (и это не объекты Command). Я сделал это нарочно, так как, по моему мнению, лучше не связывать наш код с конкретным решением. Что если мы узнаем, что есть лучшее решение, чем Drools? Изменим ли мы весь созданный код или просто изменим маршрут Camel, чтобы он указывал на наше новое решение? Вот почему у Camel есть TypeConverters. У нас здесь тоже есть свои. Прежде всего, давайте посмотрим на реализацию.

ProductTypeConverter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package pl.grzejszczak.marcin.drools.decisiontable.converter;
 
import org.apache.camel.Converter;
import org.drools.command.Command;
import org.drools.command.CommandFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.grzejszczak.marcin.drools.decisiontable.model.Product;
 
import java.util.List;
 
/**
 * Created with IntelliJ IDEA.
 * User: mgrzejszczak
 * Date: 30.01.13
 * Time: 21:42
 */
@Converter
public class ProductTypeConverter {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ProductTypeConverter.class);
 
    @Converter
    public static Command toCommandFromList(List inputList) {
        LOGGER.debug("Executing ProductTypeConverter's toCommandFromList method");
        return CommandFactory.newInsertElements(inputList);
    }
 
    @Converter
    public static Command toCommand(Product product) {
        LOGGER.debug("Executing ProductTypeConverter's toCommand method");
        return CommandFactory.newInsert(product);
    }
}

На веб-сайте Camel есть хорошее руководство по TypeConverters — если вам нужна более подробная информация об этом. В любом случае, мы аннотируем наш класс и функции, используемые для преобразования различных типов друг в друга. Здесь важно то, что мы показываем Camel, как преобразовать список и отдельный продукт в команды. Из-за стирания типа это будет работать независимо от предоставленного типа, поэтому, даже если мы даем список Product и User, функция toCommandFromList будет выполнена. В дополнение к этому, чтобы преобразователь типов работал, мы должны предоставить полностью квалифицированное имя нашего класса (FQN) в файле / META-INF / services / org / apache / came / TypeConverter .

TypeConverter

1
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter

Чтобы правильно проверить нашу функциональность, нужно написать немало тестов, которые бы проверяли правила. Довольно хорошим способом было бы сохранить входные файлы в папках тестовых ресурсов, которые передаются в механизм правил, и затем результат сравнивался бы с проверенным выводом (к сожалению, заставить бизнес-сторону разработать такой набор ссылок довольно сложно выходов). В любом случае, давайте посмотрим на модульный тест, который проверяет только несколько правил и журналы, которые создаются при выполнении этих правил:

ProductServiceImplTest.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package pl.grzejszczak.marcin.drools.decisiontable.service.drools;
 
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import pl.grzejszczak.marcin.drools.decisiontable.model.*;
import pl.grzejszczak.marcin.drools.decisiontable.service.ProductService;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
 
/**
 * Created with IntelliJ IDEA.
 * User: mgrzejszczak
 * Date: 03.02.13
 * Time: 16:06
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ProductServiceImplTest {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ProductServiceImplTest.class);
 
    @Autowired
    ProductService objectUnderTest;
 
    @Test
    public void testRunProductLogicUserPlUnderageElectronicCountryPL() throws Exception {
        int initialPrice = 1000;
        int userAge = 6;
        int quantity = 10;
 
        User user = createUser("Smith", CountryType.PL, userAge);
        Product product = createProduct("Electronic", initialPrice, CountryType.PL, ProductType.ELECTRONIC, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
 
        assertTrue(product.getPrice() == initialPrice);
        assertEquals(DecisionType.REJECTED, user.getDecision());
    }
 
    @Test
    public void testRunProductLogicUserPlHighAgeElectronicCountryPLLowQuantity() throws Exception {
        int initialPrice = 1000;
        int userAge = 19;
        int quantity = 1;
 
        User user = createUser("Smith", CountryType.PL, userAge);
        Product product = createProduct("Electronic", initialPrice, CountryType.PL, ProductType.ELECTRONIC, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
 
        assertTrue(product.getPrice() == initialPrice);
        assertEquals(DecisionType.ACCEPTED, user.getDecision());
    }
 
    @Test
    public void testRunProductLogicUserPlHighAgeElectronicCountryPLHighQuantity() throws Exception {
        int initialPrice = 1000;
        int userAge = 19;
        int quantity = 8;
 
        User user = createUser("Smith", CountryType.PL, userAge);
        Product product = createProduct("Electronic", initialPrice, CountryType.PL, ProductType.ELECTRONIC, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
        double expectedDiscount = 0.1;
 
        assertTrue(product.getPrice() == initialPrice * (1 - expectedDiscount));
        assertEquals(DecisionType.ACCEPTED, user.getDecision());
    }
 
    @Test
    public void testRunProductLogicUserUsaLowAgeElectronicCountryPLHighQuantity() throws Exception {
        int initialPrice = 1000;
        int userAge = 19;
        int quantity = 8;
 
        User user = createUser("Smith", CountryType.USA, userAge);
        Product product = createProduct("Electronic", initialPrice, CountryType.PL, ProductType.ELECTRONIC, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
 
        assertTrue(product.getPrice() == initialPrice);
        assertEquals(DecisionType.REJECTED, user.getDecision());
    }
 
    @Test
    public void testRunProductLogicUserUsaHighAgeMedicalCountrySWELowQuantity() throws Exception {
        int initialPrice = 1000;
        int userAge = 22;
        int quantity = 4;
 
        User user = createUser("Smith", CountryType.USA, userAge);
        Product product = createProduct("Some name", initialPrice, CountryType.SWE, ProductType.MEDICAL, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
 
        assertTrue(product.getPrice() == initialPrice);
        assertEquals(DecisionType.ACCEPTED, user.getDecision());
    }
 
    @Test
    public void testRunProductLogicUserUsaHighAgeMedicalCountrySWEHighQuantity() throws Exception {
        int initialPrice = 1000;
        int userAge = 22;
        int quantity = 8;
 
        User user = createUser("Smith", CountryType.USA, userAge);
        Product product = createProduct("Some name", initialPrice, CountryType.SWE, ProductType.MEDICAL, quantity);
 
        printInputs(user, product);
 
        objectUnderTest.runProductLogic(user, product);
 
        printInputs(user, product);
        double expectedDiscount = 0.25;
 
        assertTrue(product.getPrice() == initialPrice * (1 - expectedDiscount));
        assertEquals(DecisionType.ACCEPTED, user.getDecision());
    }
 
    private void printInputs(User user, Product product) {
        LOGGER.debug(ReflectionToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE));
        LOGGER.debug(ReflectionToStringBuilder.reflectionToString(product, ToStringStyle.MULTI_LINE_STYLE));
    }
 
    private User createUser(String name, CountryType countryType, int userAge){
        User user = new User();
        user.setUserName(name);
        user.setUserCountry(countryType);
        user.setUserAge(userAge);
        return user;
    }
 
    private Product createProduct(String name, double price, CountryType countryOfOrigin, ProductType productType, int quantity){
        Product product = new Product();
        product.setPrice(price);
        product.setCountryOfOrigin(countryOfOrigin);
        product.setName(name);
        product.setType(productType);
        product.setQuantity(quantity);
        return product;
    }
 
}

Конечно, log.debugs в тестах полностью избыточны, но я хотел, чтобы вы быстро увидели, что правила работают. Извините за длину журналов, но я написал несколько тестов, чтобы показать различные комбинации правил (на самом деле лучше иметь слишком много журналов, чем наоборот)

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@1d48043[
  userName=Smith
  userAge=6
  userCountry=PL
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1e8f2a0[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=10
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Sorry, according to your age (< 18) and country (PL) you can't buy this product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:29 Sorry, user has been rejected...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@1d48043[
  userName=Smith
  userAge=6
  userCountry=PL
  decision=REJECTED
  decisionDescription=Sorry, according to your age (< 18) and country (PL) you can't buy this product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1e8f2a0[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=10
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@b28f30[
  userName=Smith
  userAge=19
  userCountry=PL
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@d6a0e0[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=1
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations, you have successfully bought the product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Sorry, no discount will be granted.
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:25 User has been approved - processing the order...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@b28f30[
  userName=Smith
  userAge=19
  userCountry=PL
  decision=ACCEPTED
  decisionDescription=Congratulations, you have successfully bought the product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@d6a0e0[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=Sorry, no discount will be granted.
  quantity=1
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@14510ac[
  userName=Smith
  userAge=19
  userCountry=PL
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1499616[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=8
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations, you have successfully bought the product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations - you've been granted a 10% discount!
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:25 User has been approved - processing the order...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@14510ac[
  userName=Smith
  userAge=19
  userCountry=PL
  decision=ACCEPTED
  decisionDescription=Congratulations, you have successfully bought the product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1499616[
  name=Electronic
  type=ELECTRONIC
  price=900.0
  countryOfOrigin=PL
  additionalInfo=Congratulations - you've been granted a 10% discount!
  quantity=8
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@17667bd[
  userName=Smith
  userAge=19
  userCountry=USA
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@ad9f5d[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=8
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Sorry, according to your age (< 18) and country (USA) you can't buy this product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:29 Sorry, user has been rejected...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@17667bd[
  userName=Smith
  userAge=19
  userCountry=USA
  decision=REJECTED
  decisionDescription=Sorry, according to your age (< 18) and country (USA) you can't buy this product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@ad9f5d[
  name=Electronic
  type=ELECTRONIC
  price=1000.0
  countryOfOrigin=PL
  additionalInfo=<null>
  quantity=8
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@9ff588[
  userName=Smith
  userAge=22
  userCountry=USA
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1b0d2d0[
  name=Some name
  type=MEDICAL
  price=1000.0
  countryOfOrigin=SWE
  additionalInfo=<null>
  quantity=4
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations, you have successfully bought the product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:25 User has been approved - processing the order...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@9ff588[
  userName=Smith
  userAge=22
  userCountry=USA
  decision=ACCEPTED
  decisionDescription=Congratulations, you have successfully bought the product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@1b0d2d0[
  name=Some name
  type=MEDICAL
  price=1000.0
  countryOfOrigin=SWE
  additionalInfo=<null>
  quantity=4
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@1b27882[
  userName=Smith
  userAge=22
  userCountry=USA
  decision=<null>
  decisionDescription=<null>
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@5b84b[
  name=Some name
  type=MEDICAL
  price=1000.0
  countryOfOrigin=SWE
  additionalInfo=<null>
  quantity=8
]
pl.grzejszczak.marcin.drools.decisiontable.service.ProductServiceImpl:31 Running product logic - first acceptance Route, then discount Route
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations, you have successfully bought the product
pl.grzejszczak.marcin.drools.decisiontable.converter.ProductTypeConverter:25 Executing ProductTypeConverter's toCommandFromList method
pl.grzejszczak.marcin.drools.decisiontable.service.ProductService:8 Congratulations, you are granted a discount
pl.grzejszczak.marcin.drools.decisiontable.service.FinancialServiceImpl:25 User has been approved - processing the order...
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:150 pl.grzejszczak.marcin.drools.decisiontable.model.User@1b27882[
  userName=Smith
  userAge=22
  userCountry=USA
  decision=ACCEPTED
  decisionDescription=Congratulations, you have successfully bought the product
]
pl.grzejszczak.marcin.drools.decisiontable.service.drools.ProductServiceImplTest:151 pl.grzejszczak.marcin.drools.decisiontable.model.Product@5b84b[
  name=Some name
  type=MEDICAL
  price=750.0
  countryOfOrigin=SWE
  additionalInfo=Congratulations, you are granted a discount
  quantity=8
]

В этом посте я представил, как вы можете перенести некоторые из ваших разработок в БА, предоставив ему инструмент, с которым он может работать — таблицы решений в электронной таблице. Более того, теперь вы будете теперь, как интегрировать Drools с Camel. Надеемся, вы увидите, как вы можете упростить (таким образом, минимизировать затраты на внедрение и поддержку) реализацию бизнес-правил, учитывая, насколько они подвержены изменениям. Я надеюсь, что этот пример еще лучше проиллюстрирует, насколько сложно будет реализовать все бизнес-правила в Java, чем в предыдущем посте о Drools. Если у вас есть опыт работы с Drools с точки зрения таблиц решений, интеграции с Spring и Camel, пожалуйста, не стесняйтесь оставлять комментарии — давайте обсудим это. Весь код доступен в хранилище Too Much Coding в Bitbucket и GitHub .