Dev (Back & Front)ARTIGO

Dê força ao usuário com Drools – parte 2

No artigo anterior fiz uma contextualização sobre parametrização em sistemas e sobre BRMS.
Agora, vou mostrar como o Drools poderia ser
utilizado para parametrizações em um sistema.

O meu exemplo vai
ser simples. A arquitetura está longe de ser a mais adequada, mas ela é
funcional e serve para mostrar como usar o Drools em conjunto com o Spring Framework.
Nesse exemplo, criei um pequeno modelo de objeto. A idéia principal é
mostrar como as regras de negocio são parametrizadas através de uma
planilha MS Excel e como isso é processado através de um service que
fica no Contexto do Spring.

Aqui temos uma aplicação de locação
de carros. Imagine que o valor do aluguel varia muito, dependendo do
humor do locador e da cor e do modelo do carro, então provavelmente
você teria que atender a diversas solicitações de mudança para isso.
Vamos colocar essas regras de tarifa em uma planilha MS Excel.

Não
se atenha à baixa complexidade da regra, pois meu goal aqui é mostrar
como é simples usar o Drools e integrar o mesmo com o Spring. Não estou
acessando banco de dados, mas eu poderia. Por sinal, já existe um
TransactionalManager do Drools para o Spring. Isso significa que você
pode ter transação declarativa no drools e gerenciada de forma
transparente pelo contexto do Spring.

Os Pojos

Os objetos do domínio são:

  • Aluguel
  • Carro
  • Carteira
  • Motorista
  • ValorLocacao

O
pojo ValorLocacao será criado a partir da regra de tarifa de locação,
que ficará no excel. Sendo que a regra utilizará os pojo de Carro para
decidir o valor, qualquer pojo listado acima poderia ser adicionado ou
removido da regra sem a necessidade de recompilação ou de derrubar o
sistema.

Vamos ao código dos pojos então. Como disse antes o
modelo é só para mostrar as possibilidades, repare que alguns deles mal
têm o método toString(), uma boa prática é sempre prover esse método, não fiz isso aqui por motivo de brevidade e porque não é o meu foco neste post.

Carteira.java

package com.blogspot.diegopacheco.drools.model;<br /><br />import java.util.Date;<br /><br />/**<br /> * POJO Carteira de motorista.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class Carteira {<br />   <br />    private Long numero;<br />    private Date criacao;<br />    private Date validade;<br />    private String tipo;<br />   <br />    private Motorista dono;<br />   <br />    public Carteira() {}<br /><br />    public Long getNumero() {<br />        return numero;<br />    }<br />    public void setNumero(Long numero) {<br />        this.numero = numero;<br />    }<br /><br />    public Date getCriacao() {<br />        return criacao;<br />    }<br />    public void setCriacao(Date criacao) {<br />        this.criacao = criacao;<br />    }<br /><br />    public Date getValidade() {<br />        return validade;<br />    }<br />    public void setValidade(Date validade) {<br />        this.validade = validade;<br />    }<br /><br />    public Motorista getDono() {<br />        return dono;<br />    }<br />    public void setDono(Motorista dono) {<br />        this.dono = dono;<br />    }<br /><br />    public String getTipo() {<br />        return tipo;<br />    }<br />    public void setTipo(String tipo) {<br />        this.tipo = tipo;<br />    }<br />   <br />}

Esse
pojo acima representa a carteira de motorista, porque normalmente
quando você aluga um carro eles exigem que a sua carteira tenha sido
expedida há pelo menos dois anos. Isso poderia ser usado nas regras.

Motorista.java

package com.blogspot.diegopacheco.drools.model;<br /><br />import java.util.Date;<br /><br />/**<br /> * POJO Motorista.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class Motorista {<br /><br />    private String nome;<br />    private Date nascimento;<br /><br />    private Carteira carteira;<br /><br />    public Motorista() {}<br /><br />    public String getNome() {<br />        return nome;<br />    }<br />    public void setNome(String nome) {<br />        this.nome = nome;<br />    }<br /><br />    public Date getNascimento() {<br />        return nascimento;<br />    }<br />    public void setNascimento(Date nascimento) {<br />        this.nascimento = nascimento;<br />    }<br /><br />    public Carteira getCarteira() {<br />        return carteira;<br />    }<br />    public void setCarteira(Carteira carteira) {<br />        this.carteira = carteira;<br />    }   <br /><br />}

O
pojo acima representa o motorista, nesse caso é quem quer locar o
carro. A modelagem poderia ser mais efetiva mas, como disse, meu foco
aqui é outro.

Carro.java

package com.blogspot.diegopacheco.drools.model;<br /><br /><br />/**<br /> * POJO Carro<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class Carro {<br /><br />    private String modelo;<br />    private String cor;<br /><br />    public Carro() {}<br /><br />    public String getModelo() {<br />        return modelo;<br />    }<br />    public void setModelo(String modelo) {<br />        this.modelo = modelo;<br />    }<br /><br />    public String getCor() {<br />        return cor;<br />    }<br />    public void setCor(String cor) {<br />        this.cor = cor;<br />    }<br /><br />    @Override<br />    public String toString() {<br />        return "(" + modelo + "-" + cor + ")";<br />    }<br /><br />}

O
pojo Carro representa o carro que será alugado. Podem existir vários
carros iguais e, nesse caso, o preço será o mesmo. Depois você poderia
adicionar a propriedade \\’quilometragem\\’ ao carro e assim cobrar mais ou
menos.

ValorLocacao.java

package com.blogspot.diegopacheco.drools.model;<br /><br />import java.math.BigDecimal;<br /><br />/**<br /> * POJO ValorLocacao<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class ValorLocacao {<br /><br />    private BigDecimal valor;<br /><br />    public ValorLocacao() {}   <br /><br />    public ValorLocacao(BigDecimal valor) {<br />        super();<br />        this.valor = valor;<br />    }<br /><br />    public ValorLocacao(String valor) {<br />        this.valor = new BigDecimal(valor);<br />    }<br /><br />    public BigDecimal getValor() {<br />        return valor;<br />    }<br />    public void setValor(BigDecimal valor) {<br />        this.valor = valor;<br />    }   <br />}

O pojo
ValorLocacao será gerado a partir das regras, como dito antes. Na prática
ele teria ligações com outros objetos, mas aqui simplifiquei e o deixei sozinho.

Aluguel.Java

package com.blogspot.diegopacheco.drools.model;<br /><br />import java.util.Date;<br /><br />/**<br /> * POJO Aluguel<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class Aluguel {<br /><br />    private Carro carro;<br />    private Motorista motorista;   <br />    private Date locacao;<br />    private ValorLocacao valor;<br /><br />    public Aluguel() {}   <br /><br />    public Aluguel(Carro carro, Motorista motorista, Date locacao,<br />            ValorLocacao valor) {<br />        super();<br />        this.carro = carro;<br />        this.motorista = motorista;<br />        this.locacao = locacao;<br />        this.valor = valor;<br />    }<br /><br />    public Carro getCarro() {<br />        return carro;<br />    }<br />    public void setCarro(Carro carro) {<br />        this.carro = carro;<br />    }<br /><br />    public Motorista getMotorista() {<br />        return motorista;<br />    }<br />    public void setMotorista(Motorista motorista) {<br />        this.motorista = motorista;<br />    }<br /><br />    public Date getLocacao() {<br />        return locacao;<br />    }<br />    public void setLocacao(Date locacao) {<br />        this.locacao = locacao;<br />    }<br /><br />    public ValorLocacao getValor() {<br />        return valor;<br />    }<br />    public void setValor(ValorLocacao valor) {<br />        this.valor = valor;<br />    }   <br /><br />}

Esse é o pojo que será gerado e teoricamente seria
persistido pelo serviço de aluguel. Agora que vimos os pojos, vamos ver
o serviço do Spring que será chamado pelo Drools.

LocarCarroService.java

package com.blogspot.diegopacheco.drools.model.service;<br /><br />import com.blogspot.diegopacheco.drools.model.Aluguel;<br />import com.blogspot.diegopacheco.drools.model.Carro;<br />import com.blogspot.diegopacheco.drools.model.Motorista;<br />import com.blogspot.diegopacheco.drools.model.ValorLocacao;<br /><br />/**<br /> * Serviço de aluguel de carros.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public interface LocarCarroService {<br /><br />    /**<br />     * Metodo que efetua a locação do Carro.<br />     * @param c Carro<br />     * @param m motorista<br />     */<br />    Aluguel locar(Carro c,Motorista m,ValorLocacao vl);<br /><br />}

Serviço para efetuar a locação de um carro. Como
exemplo de boas práticas, estou utilizando interface para o serviço.
Agora vamos à implementação do serviço.

LocarCarroServiceImpl.java

package com.blogspot.diegopacheco.drools.model.service;<br /><br />import java.util.GregorianCalendar;<br /><br />import org.apache.commons.logging.Log;<br />import org.apache.commons.logging.LogFactory;<br /><br />import com.blogspot.diegopacheco.drools.model.Aluguel;<br />import com.blogspot.diegopacheco.drools.model.Carro;<br />import com.blogspot.diegopacheco.drools.model.Motorista;<br />import com.blogspot.diegopacheco.drools.model.ValorLocacao;<br /><br />/**<br /> * Serviço de aluguel de carros implementado.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class LocarCarroServiceImpl implements LocarCarroService {<br /><br />    private static final Log logger = LogFactory.getLog(LocarCarroServiceImpl.class);<br /><br />    @Override<br />    public Aluguel locar(Carro c, Motorista m,ValorLocacao vl) {<br /><br />        Aluguel al = new Aluguel();<br />        al.setCarro(c);<br />        al.setLocacao(GregorianCalendar.getInstance().getTime());<br />        al.setMotorista(m);<br />        al.setValor(vl);<br /><br />        logger.info("Aluguel estabelecido com sucesso. Carro: " + c + " com valor: "<br />                    + vl.getValor());<br /><br />        return al;<br />    }<br />}

Essa implementação é muito simples. Estou só
criando os pojos com os valores vindos do Drools. Aqui, na prática, você
aplicaria outras regras de negócio, isso mesmo, todas as regras não
ficam no Drools. Você poderia fazer esse serviço acessar um ORM como
JPA ou Hibernate.

ServiceFacadesLocator.java

package com.blogspot.diegopacheco.drools.model.service;<br /><br />import org.springframework.beans.BeansException;<br />import org.springframework.context.ApplicationContext;<br />import org.springframework.context.ApplicationContextAware;<br /><br /><br />/**<br /> * Responsável por dar os servicos.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class ServiceFacadesLocator implements ApplicationContextAware{<br /><br />    private static ApplicationContext ac;<br /><br />    public static LocarCarroService getLocarCarroService(){<br />        return (LocarCarroService)ac.getBean("locarCarroService");<br />    }<br /><br />    @Override<br />    public void setApplicationContext(ApplicationContext arg0) throws BeansException {<br />        ac  = arg0;<br />    }<br /><br />}

Com esse ServiceLocator você acessa todos os
serviços do sistema. Neste caso só existe o serviço para locação de
carros, essa é uma maneira não muito elegante para acessar o Spring,
apesar de o design não ser dos melhores, ele funciona. Agora vamos ver a
classe que sobe o contexto do Spring Framework.

SpringContextHolder.java

package com.blogspot.diegopacheco.drools.bootstrap;<br /><br />import org.springframework.context.ApplicationContext;<br />import org.springframework.context.support.ClassPathXmlApplicationContext;<br /><br />/**<br /> * Classe responsável por subir o contexto do Spring.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class SpringContextHolder {<br /><br />    private static ApplicationContext ac;<br /><br />    static{<br />        ac = new ClassPathXmlApplicationContext("classpath:service-beans.xml");<br />    }<br /><br />    public static ApplicationContext getAc() {<br />        return ac;<br />    }<br /><br />}

Como podem ver o contexto do Spring é carregado por essa classe. Na sequência vamos ver a definição dos beans de arquivo.

service-beans.xml

<?xml version="1.0" encoding="UTF-8"?><br /><beans xmlns="http://www.springframework.org/schema/beans" xsi="http://www.w3.org/2001/XMLSchema-instance" p="http://www.springframework.org/schema/p" aop="http://www.springframework.org/schema/aop" context="http://www.springframework.org/schema/context" schemalocation="http://www.springframework.org/schema/beans                                                 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd                                                http://www.springframework.org/schema/aop                            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd                            http://www.springframework.org/schema/context                            http://www.springframework.org/schema/context/spring-context-2.5.xsd" autowire="no" check="none" init="true"><br />    <!-- Servicos do sistema  --><br />    <bean id="locarCarroService" class="com.blogspot.diegopacheco.drools.model.service.LocarCarroServiceImpl"><br /><br />    <!-- FacadeServiceLocator que prove acesso as facadas do sistemas. --><br />    <bean id="serviceFacadesLocator" class="com.blogspot.diegopacheco.drools.model.service.ServiceFacadesLocator">   <br /><br />    <!-- Bean que mantem e opera sobre a base do drools.  --><br />    <bean id="droolsKnowledBaseStatefullSessionHolder" class="com.blogspot.diegopacheco.drools.bootstrap.DroolsKnowledBaseStatefullSessionHolder">   <br /><br /></beans>

Ok, mas e cadê o Drool?

Certo,
chegamos a esse ponto. Antes de falarmos do excel e como ele funciona,
vamos ver como criar uma base de conhecimento e como operar sobre essa
base, mostrarei como adicionar fatos à memória de trabalho do drools
também.

DroolsKnowledBaseStatefullSessionHolder.java

package com.blogspot.diegopacheco.drools.bootstrap;<br /><br />import org.drools.KnowledgeBase;<br />import org.drools.KnowledgeBaseFactory;<br />import org.drools.builder.DecisionTableConfiguration;<br />import org.drools.builder.DecisionTableInputType;<br />import org.drools.builder.KnowledgeBuilder;<br />import org.drools.builder.KnowledgeBuilderError;<br />import org.drools.builder.KnowledgeBuilderErrors;<br />import org.drools.builder.KnowledgeBuilderFactory;<br />import org.drools.builder.ResourceType;<br />import org.drools.io.ResourceFactory;<br />import org.drools.runtime.StatefulKnowledgeSession;<br /><br />/**<br /> * Classe resposável por criar uma base de conhecimentos no Drools.<br /> * Essa classe trabalha com uma sessao do tipo statefull. Aqui existem facilidades<br /> * para adicionar fatos e disparar as regras.<br /> *<br /> * @author Diego Pacheco<br /> * @since  22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class DroolsKnowledBaseStatefullSessionHolder {<br /><br />    private KnowledgeBase kb;<br />    private StatefulKnowledgeSession kbStatefullSession;<br /><br />    public DroolsKnowledBaseStatefullSessionHolder() throws Throwable {<br />        kb = createKnowledgeBase(); <br />        kbStatefullSession = kb.newStatefulKnowledgeSession();<br />    }  <br /><br />    /**<br />     * Insere um fato.<br />     * @param facts Um objeto ou grafo com fatos.<br />     */<br />    public void insertFacts(Object facts){       <br />        kbStatefullSession.insert(facts);   <br />    }<br /><br />    /**<br />     * Insere um fato e dispara as regras.<br />     * @param facts Um objeto ou grafo com fatos.<br />     */<br />    public void insertFactsAndFireRules(Object facts){       <br />        kbStatefullSession.insert(facts);<br />        kbStatefullSession.fireAllRules();<br />    }<br /><br />    /**<br />     * Dispara as regras no drools.<br />     */<br />    public void fireRules(){<br />        kbStatefullSession.fireAllRules();<br />    }<br /><br />    /**<br />     * Cria a base de conhecimento levando em consideração a planilha do Excel.<br />     *<br />     * @return KnowledgeBase<br />     * @throws Exception<br />     */<br />    private KnowledgeBase createKnowledgeBase() throws Exception {       <br /><br />        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();<br /><br />        DecisionTableConfiguration config = KnowledgeBuilderFactory.newDecisionTableConfiguration();<br />        config.setInputType(DecisionTableInputType.XLS);<br />        kbuilder.add(ResourceFactory.newClassPathResource("tarifas.xls"), ResourceType.DTABLE, config);<br /><br />        KnowledgeBuilderErrors errors = kbuilder.getErrors();<br />        if (errors.size() > 0) {<br />            for (KnowledgeBuilderError error: errors) {<br />                System.err.println(error);<br />            }<br />            throw new IllegalArgumentException("Could not parse knowledge.");<br />        }<br />        KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();<br />        kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());<br />        return kbase;<br />    }<br /><br />}

Aqui criamos a base de conhecimento lendo a
planilha do excel. A sessão utilizada aqui foi do tipo statefull, ou
seja, teremos possibilidade de lembrar dos fatos anteriores. Poderiamos
utilizar uma sessão do tipo estateless, nesse caso não faria diferença.
Para rodar a aplicação vamos à seguinte classe java.

RunApp.java

package com.blogspot.diegopacheco.drools.bootstrap;<br /><br />import com.blogspot.diegopacheco.drools.model.Carro;<br />import com.blogspot.diegopacheco.drools.model.service.ServiceFacadesLocator;<br /><br />/**<br /> * Classe resposável por rodar a aplicação.<br /> *<br /> * @author Diego Pacheco<br /> * @since 22/03/2009<br /> * @version 1.0<br /> *<br /> */<br />public class RunApp {<br /><br />    public static void main(String[] args) {<br /><br />        DroolsKnowledBaseStatefullSessionHolder kbStfullHolder =        <br />            (DroolsKnowledBaseStatefullSessionHolder) SpringContextHolder.getAc().getBean("droolsKnowledBaseStatefullSessionHolder");<br /><br />        ServiceFacadesLocator sfl =<br />                (ServiceFacadesLocator) SpringContextHolder.getAc().getBean("serviceFacadesLocator");   <br /><br /><br />        Carro c1 = new Carro();<br />        c1.setCor("branco");<br />        c1.setModelo("Gol");<br /><br />        Carro c2 = new Carro();<br />        c2.setCor("bordo");<br />        c2.setModelo("Gol");<br /><br />        kbStfullHolder.insertFacts(sfl);<br />        kbStfullHolder.insertFacts(c1);   <br />        kbStfullHolder.insertFactsAndFireRules(c2);<br /><br />    }<br />}

Aqui o contexto do Spring é levantado e a classe
que cria a base de conhecimento do drools é criada. Por fim, adicionamos
fatos à memória de trabalho do Drools e disparamos as regras.

Nota:
Aqui não executamos todas as regras do Drools. Elas são
executadas de acordo com as suas condições e de acordo com o que existe
na memória de trabalho. Em outro artigo vou explicar como isso funciona
melhor, mas é inteligência artificial pura, mais especificamente
falando de engile de rules.

E a tão esperada planilha…

Não
existe mágica aqui. Essa planilha poderia ser em open office, mas usei
o bom e velho MS Excel. Vamos dar uma olhada na planilha abaixo
primeiro. Clique aqui para fazer o download.

Aqui existem palavras reservadas como: RuleSet,
Import, Notes, RuleTable, CONDITION e ACTION. Vamos uma a uma. Existem
outras, mas vou focar apenas nas quais utilizei.

  • RuleSet: Aqui informo sobre qual conjunto de regras vou operar, isso serve para aumentar a performance do Drools.
  • Import: Importo todos os objetos Java que eu necessito, os importes devem estar separados por vírgula.
  • Notes: Notas em geral, um breve comentário é util aqui.
  • RuleTable:
    A brincadeira começa aqui. A partir daqui começa a tabela de regreas.
    As colunas à esquerda a partir dessa palavra serão ignoradas.
  • CONDITION: Posso usar quantas eu quiser, aqui ficam as condições.
  • ACTION: Este é o que será feito se a regra vir a ser executada.

Na
tabela do excel cada linha, na verdade, é uma regra e as colunas podem
ser condições ou ações. O drools não sabe o que é uma
planilha, na prática ele transforma a planilha em um drl e compila o
mesmo.

Para que possamos executar esse projeto serão necessários
os jars de algums outros frameworks. Seguem abaixo as dependências
necessárias:

  • eclipse jdt core (vem no eclipse ide)
  • Spring Framework
  • Commons-Logging
  • Log4j
  • Drools 5

Baixe
o Drools 5 e adicione os jars como uma biblioteca do usuário no
eclipse. Se você quiser estou disponibilizando os fontes no meu sandbox no beanstalk. O beanstalk é um server para o Subversion, você pode baixar o projeto de lá, ou verificar os fontes via navegador neste endereço.

Abraços e até a próxima.

é Técnico em Processamento de Dados e graduando em Ciências da Computação(7º sem.) na Ulbra. Já trabalhou com desenvolvimento de software em VB, ASP, .NET e PHP. Atualmente é Arquiteto de Software criando soluções corporativas em Java. Certificado pela SUN com SCJP 5 e SCWD 5. Mantém o blog (diego-pacheco.blogspot.com).

Ver perfil