De força ao Usuário com o Drools parte 2

No post anterior fiz uma contextualização sobre parametrização em sistemas e sobre BRMS. Agora mostrar através de um exemplo simples como o Drools poderia ser utilizado para parametrizações em um sistema.

O meu exemplo vai ser simples. A arquitetura esta 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 a 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 sera 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 a regra sem a necessidade de recompilação ou de derrubar o sistema.

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

Carteira.java
package com.blogspot.diegopacheco.drools.model;

import java.util.Date;

/**
* POJO Carteira de motorista.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class Carteira {

private Long numero;
private Date criacao;
private Date validade;
private String tipo;

private Motorista dono;

public Carteira() {}

public Long getNumero() {
return numero;
}
public void setNumero(Long numero) {
this.numero = numero;
}

public Date getCriacao() {
return criacao;
}
public void setCriacao(Date criacao) {
this.criacao = criacao;
}

public Date getValidade() {
return validade;
}
public void setValidade(Date validade) {
this.validade = validade;
}

public Motorista getDono() {
return dono;
}
public void setDono(Motorista dono) {
this.dono = dono;
}

public String getTipo() {
return tipo;
}
public void setTipo(String tipo) {
this.tipo = tipo;
}

}

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

Motorista.java
package com.blogspot.diegopacheco.drools.model;

import java.util.Date;

/**
* POJO Motorista.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class Motorista {

private String nome;
private Date nascimento;

private Carteira carteira;

public Motorista() {}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public Date getNascimento() {
return nascimento;
}
public void setNascimento(Date nascimento) {
this.nascimento = nascimento;
}

public Carteira getCarteira() {
return carteira;
}
public void setCarteira(Carteira carteira) {
this.carteira = carteira;
}   

}

O pojo a cima 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;


/**
* POJO Carro
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class Carro {

private String modelo;
private String cor;

public Carro() {}

public String getModelo() {
return modelo;
}
public void setModelo(String modelo) {
this.modelo = modelo;
}

public String getCor() {
return cor;
}
public void setCor(String cor) {
this.cor = cor;
}

@Override
public String toString() {
return "(" + modelo + "-" + cor + ")";
}

}

O pojo Carro representa o carro que será alugado, podem existir vários carros iguais e nesse caso o peç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;

import java.math.BigDecimal;

/**
* POJO ValorLocacao
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class ValorLocacao {

private BigDecimal valor;

public ValorLocacao() {}   

public ValorLocacao(BigDecimal valor) {
super();
this.valor = valor;
}

public ValorLocacao(String valor) {
this.valor = new BigDecimal(valor);
}

public BigDecimal getValor() {
return valor;
}
public void setValor(BigDecimal valor) {
this.valor = valor;
}   
}

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

Aluguel.java
package com.blogspot.diegopacheco.drools.model;

import java.util.Date;

/**
* POJO Aluguel
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class Aluguel {

private Carro carro;
private Motorista motorista;   
private Date locacao;
private ValorLocacao valor;

public Aluguel() {}   

public Aluguel(Carro carro, Motorista motorista, Date locacao,
ValorLocacao valor) {
super();
this.carro = carro;
this.motorista = motorista;
this.locacao = locacao;
this.valor = valor;
}

public Carro getCarro() {
return carro;
}
public void setCarro(Carro carro) {
this.carro = carro;
}

public Motorista getMotorista() {
return motorista;
}
public void setMotorista(Motorista motorista) {
this.motorista = motorista;
}

public Date getLocacao() {
return locacao;
}
public void setLocacao(Date locacao) {
this.locacao = locacao;
}

public ValorLocacao getValor() {
return valor;
}
public void setValor(ValorLocacao valor) {
this.valor = valor;
}   

}

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;

import com.blogspot.diegopacheco.drools.model.Aluguel;
import com.blogspot.diegopacheco.drools.model.Carro;
import com.blogspot.diegopacheco.drools.model.Motorista;
import com.blogspot.diegopacheco.drools.model.ValorLocacao;

/**
* Serviço de aluguel de carros.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public interface LocarCarroService {

/**
* Metodo que efetua a locação do Carro.
* @param c Carro
* @param m motorista
*/
Aluguel locar(Carro c,Motorista m,ValorLocacao vl);

}

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 a implementação do serviço.

LocarCarroServiceImpl.java
package com.blogspot.diegopacheco.drools.model.service;

import java.util.GregorianCalendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.blogspot.diegopacheco.drools.model.Aluguel;
import com.blogspot.diegopacheco.drools.model.Carro;
import com.blogspot.diegopacheco.drools.model.Motorista;
import com.blogspot.diegopacheco.drools.model.ValorLocacao;

/**
* Serviço de aluguel de carros implementado.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class LocarCarroServiceImpl implements LocarCarroService {

private static final Log logger = LogFactory.getLog(LocarCarroServiceImpl.class);

@Override
public Aluguel locar(Carro c, Motorista m,ValorLocacao vl) {

Aluguel al = new Aluguel();
al.setCarro(c);
al.setLocacao(GregorianCalendar.getInstance().getTime());
al.setMotorista(m);
al.setValor(vl);

logger.info("Aluguel estabelecido com sucesso. Carro: " + c + " com valor: "
+ vl.getValor());

return al;
}
}

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;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;


/**
* Responsável por dar os servicos.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class ServiceFacadesLocator implements ApplicationContextAware{

private static ApplicationContext ac;

public static LocarCarroService getLocarCarroService(){
return (LocarCarroService)ac.getBean("locarCarroService");
}

@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
ac  = arg0;
}

}

Como 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 do 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;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* Classe responsável por subir o contexto do Spring.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class SpringContextHolder {

private static ApplicationContext ac;

static{
ac = new ClassPathXmlApplicationContext("classpath:service-beans.xml");
}

public static ApplicationContext getAc() {
return ac;
}

}

Como podem ver o contexto do Spring é carregado por esse classe. Na sequencia vamos ver a definição dos beans de arquivo.

service-beans.xml






   


   



Ok mas e cade 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 a memória de trabalho do drools também.

DroolsKnowledBaseStatefullSessionHolder.java
package com.blogspot.diegopacheco.drools.bootstrap;

import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.DecisionTableConfiguration;
import org.drools.builder.DecisionTableInputType;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderError;
import org.drools.builder.KnowledgeBuilderErrors;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;

/**
* Classe resposável por criar uma base de conhecimentos no Drools.
* Essa classe trabalha com uma sessao do tipo statefull. Aqui existem facilidades
* para adicionar fatos e disparar as regras.
*
* @author Diego Pacheco
* @since  22/03/2009
* @version 1.0
*
*/
public class DroolsKnowledBaseStatefullSessionHolder {

private KnowledgeBase kb;
private StatefulKnowledgeSession kbStatefullSession;

public DroolsKnowledBaseStatefullSessionHolder() throws Throwable {
kb = createKnowledgeBase(); 
kbStatefullSession = kb.newStatefulKnowledgeSession();
}  

/**
* Insere um fato.
* @param facts Um objeto ou grafo com fatos.
*/
public void insertFacts(Object facts){       
kbStatefullSession.insert(facts);   
}

/**
* Insere um fato e dispara as regras.
* @param facts Um objeto ou grafo com fatos.
*/
public void insertFactsAndFireRules(Object facts){       
kbStatefullSession.insert(facts);
kbStatefullSession.fireAllRules();
}

/**
* Dispara as regras no drools.
*/
public void fireRules(){
kbStatefullSession.fireAllRules();
}

/**
* Cria a base de conhecimento levando em consideração a planilha do Excel.
*
* @return KnowledgeBase
* @throws Exception
*/
private KnowledgeBase createKnowledgeBase() throws Exception {       

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

DecisionTableConfiguration config = KnowledgeBuilderFactory.newDecisionTableConfiguration();
config.setInputType(DecisionTableInputType.XLS);
kbuilder.add(ResourceFactory.newClassPathResource("tarifas.xls"), ResourceType.DTABLE, config);

KnowledgeBuilderErrors errors = kbuilder.getErrors();
if (errors.size() > 0) {
for (KnowledgeBuilderError error: errors) {
System.err.println(error);
}
throw new IllegalArgumentException("Could not parse knowledge.");
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
return kbase;
}

}

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 lebrar dos fatos anteriores, poderiamos utilizar uma sessão do tipo estateless nesse caso não faria diferença. Para rodar a apalicação vamos a seguinte classe java.

RunApp.java
package com.blogspot.diegopacheco.drools.bootstrap;

import com.blogspot.diegopacheco.drools.model.Carro;
import com.blogspot.diegopacheco.drools.model.service.ServiceFacadesLocator;

/**
* Classe resposável por rodar a aplicação.
*
* @author Diego Pacheco
* @since 22/03/2009
* @version 1.0
*
*/
public class RunApp {

public static void main(String[] args) {

DroolsKnowledBaseStatefullSessionHolder kbStfullHolder =        
(DroolsKnowledBaseStatefullSessionHolder) SpringContextHolder.getAc().getBean("droolsKnowledBaseStatefullSessionHolder");

ServiceFacadesLocator sfl =
(ServiceFacadesLocator) SpringContextHolder.getAc().getBean("serviceFacadesLocator");   


Carro c1 = new Carro();
c1.setCor("branco");
c1.setModelo("Gol");

Carro c2 = new Carro();
c2.setCor("bordo");
c2.setModelo("Gol");

kbStfullHolder.insertFacts(sfl);
kbStfullHolder.insertFacts(c1);   
kbStfullHolder.insertFactsAndFireRules(c2);

}
}

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

Nota: Aqui não executamos todas as regras do Drools, as regreas são executadas de acordo com as suas condições e de acordo com o que existe na memória de trabalho, em outro post vou explicar como isso funciona melhor, mas é inteligencia 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 a baixo primeiro.

tarifas

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 estar operado, isso serve para uamentar a performance do Drools.
  • Import: Importo todos os objetos Java que eu necessito, os importes deven estar separados por virgula.
  • 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 a esquerda a partir dessa palavra serão ignoradas.
  • CONDITION: Posso usar quantas eu quizer, aqui ficam as condições.
  • ACTION: Este é o que será feito se a regra vir a ser executada.
Na tabela do excel cada linha ma verdade é uma regra e as colunas podem ser ou condições ou podem ser 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, segue a baixo as depedê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ê quizer 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.

Popular posts from this blog

Telemetry and Microservices part2

Installing and Running ntop 2 on Amazon Linux OS

Fun with Apache Kafka