Статьи

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


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

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


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


Еще одной проблемой будет деловая сторона (как обычно).
Любой, кто работал над проектом, знает, как быстро меняются требования. Если кто-то введет все правила в коде, ему придется заново развертывать программное обеспечение каждый раз, когда изменяются требования. Вот почему рекомендуется отделить бизнес-логику от самого кода. В любом случае, давайте вернемся к нашему примеру.

Для начала давайте взглянем на электронные таблицы (перед этим стоит взглянуть на
веб-сайт 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

<?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

<?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

<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:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:drools="http://drools.org/schema/drools-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://drools.org/schema/drools-springhttp://drools.org/schema/drools-spring.xsd">

    <!-- 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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/springhttp://camel.apache.org/schema/spring/camel-spring-2.8.0.xsd">

    <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

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

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 / camel / TypeConverter .

TypeConverter

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

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

ProductServiceImplTest.java

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 в тестах полностью избыточны, но я хотел, чтобы вы быстро увидели, что правила работают 🙂 Извините за длину журналов, но я написал несколько тестов, чтобы показать различные комбинации правил (на самом деле это лучше слишком много журналов, чем наоборот :))

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 .