Use o Drools e o JPA para criação contínua e em tempo real de perfil de dados

Programe POJOs do JPA como fatos na sua memória de trabalho do Drools 5

É possível integrar o Drools com o JPA e com códigos do aplicativo baseado em Spring e fazer isso sem recorrer à programação imperativa intrusiva. Aprenda como programar necessidades de negócios de forma econômica no seu monitoramento de sistemas em tempo real e processo de criação contínua de perfil de dados com POJOs. O autor Xinyu Liu compartilha seu conhecimento em persistência Java™ e tecnologias de integração de negócios, incluindo dicas avançadas de uso do Drools 5 para o desenvolvimento de aplicativos que possuem eficiência de memória e são à prova de balas.

Xinyu Liu, VP Product Development, eHealthObjects

Como arquiteto corporativo certificado pela Sun Microsystems, Xinyu Liu tem bastante experiência em desenvolvimento e design de aplicativos nas tecnologias de ponta do lado do servidor. Ele se formou na Universidade George Washington e trabalha atualmente como VP de Desenvolvimento de Produtos da eHealthObjects, uma empresa de tecnologia de assistência médica que fornece produtos, soluções e serviços inovadores de assistência médica e plataformas de troca que podem ser facilmente integradas com outros sistemas, serviços e aplicativos. O Dr. Liu escreve para Java.net, JavaWorld.com e IBM developerWorks sobre tópicos como JSF, Spring Security, Hibernate Search, Spring Web Flow e a especificação Servlet 3.0. Ele também trabalhou para a Packt Publishing revisando os livros Spring Web Flow 2 Web Development, Grails 1.1 Web Application DevelopmenteApplication Development for IBM WebSphere Process Server 7 and Enterprise Service Bus 7.



26/Jul/2012

Os desenvolvedores corporativos que são encarregados de gerenciar fluxos de trabalhos complexos, regras de negócios e inteligência de negócios percebem rapidamente o valor de uma plataforma corporativa que integra um mecanismo de fluxo de trabalho, um barramento de serviço corporativo (ESB) e um mecanismo de regras. Até agora, esse ponto central era preenchido com produtos comerciais como o IBM WebSphere® Process Server/WebSphere Enterprise Service Bus (consulte a seção Recursos) e o Oracle SOA Suite. O Drools 5, da comunidade JBoss, é uma alternativa de software livre que integra facilmente um mecanismo do fluxo de trabalho jBPM e um mecanismo de regras através de um conjunto de APIs unificadas e uma sessão de conhecimento stateful compartilhada.

Para iniciantes

Observe que este artigo presume que você está familiarizado com a plataforma do Spring, a API de persistência do Java e os fundamentos do Drools. Consulte a seção Recursos para obter artigos mais introdutórios sobre esses tópicos.

A plataforma Drools 5 de integração de lógica de negócios é composta principalmente do Drools Expert e do Drools Fusion, que juntos compõem a infraestrutura e o mecanismo de regras da plataforma para processamento de eventos complexos/raciocínio temporal. O aplicativo de amostra deste artigo é desenvolvido a partir desses recursos principais. Consulte a seção Recursos para saber mais sobre os pacotes adicionais disponíveis no Drools 5.

POJOs no Drools 5

Os Plain old Java objects (POJOs) foram implementados a primeira vez com destaque na estrutura do Spring. Os POJOs, juntamente com a injeção de dependência (DI) e a programação orientada ao aspecto (AOP), marcaram um retorno para a simplicidade que ajudou a estabelecer o Spring como um padrão de mercado para o desenvolvimento de aplicativos da web. A adoção do POJO passou do Spring para o EJB 3.0 e o JPA e também para tecnologias de ligação de XML com Java como JAXB e XStream. Mais recentemente, os POJOs foram integrados no Lucene, o mecanismo de procura de texto completa, através de Hibernate Search (consulte a seção Recursos).

Hoje, como resultado desses avanços incrementais, o modelo de dados POJO de um aplicativo pode ser propagado para diversas camadas e exposto diretamente através de páginas da web ou terminais de serviço da web SOAP/REST. Como um modelo de programação, o POJO possui custo reduzido e não é intrusivo, o que economiza tempo dos desenvolvedores e simplifica as arquiteturas empresariais.

Agora, o Drools 5 levou a simplicidade de programação do POJO para um novo nível, permitindo que os programadores insiram POJOs como fatos diretamente em uma sessão de conhecimento ou o que o mecanismo de regras chama de "memória de trabalho". Este artigo apresenta uma abordagem com custo reduzido e não intrusiva que manipula entidades JPA como fatos na memória de trabalho do Drools. Dessa forma, a criação de perfis de dados em tempo real nunca foi tão fácil.

Um desafio de programação no Drools

Muitos provedores de assistência médica usam um sistema de gerenciamento de casos como uma forma econômica para controlar registros médicos como cuidados, prescrições e avaliações. Nosso programa de exemplo, baseado em tal sistema, possui o fluxo e os requisitos a seguir:

  • Os casos são acessíveis por todos os clínicos no sistema.
  • Os clínicos são responsáveis por pelo menos uma tarefa de avaliação por semana ou uma notificação é enviada para o supervisor do clínico.
  • O sistema agenda automaticamente as tarefas de avaliação para os clínicos.
  • Se um caso não foi avaliado por mais de 30 dias, um lembrete será enviado para todos os clínicos do grupo de caso.
  • Se não houver resposta, o sistema executará as ações definidas nas regras de negócios do sistema, como notificar o grupo de clínicos sobre o problema e propor outro planejamento.

A escolha de um mecanismo de regra e de um fluxo de trabalho de gerenciamento de processos de negócios (BPM) para esse caso de uso faz sentido: o sistema usa criação de perfis de dados/regras de análise (escritas em itálico na lista acima), cada casso pode ser tratado como um processo de execução longa/fluxo de trabalho no jBPM e nós podemos usar o Drools Planner para o requisito de agendamento automático. Neste artigo, iremos focar apenas nas regras de negócios do programa. Vamos presumir que o sistema demanda que os lembretes e as notificações sejam gerados instantaneamente em tempo real quando uma condição da regra é cumprida. Esse é um caso de uso de criação contínua e em tempo real de perfil de dados.

Lista 1 mostra as três classes de entidade que são declaradas em nosso sistema: MemberCase, Cliniciane CaseSupervision:

Lista 1. Entity classes
@Entity
@EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
public class MemberCase implements Serializable 
{
  private Long id; // pk
  private Date startDtm;
  private Date endDtm;
  private Member member; // not null (memberId)
  private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>();
  //...
}
 
@Entity
@EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
public class Clinician implements Serializable 
{ 
  private Long id; // pk
  private Boolean active;
  private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>();
	//...
}

@Entity
@EntityListeners({SupervisionStreamWorkingMemoryPartitionEntityListener.class})
public class CaseSupervision implements Serializable 
{ 
  private Long id; // pk
  private Date entryDtm;
  private MemberCase memberCase;
  private Clinician clinician;
  //...
}

Cada instância de MemberCase representa um caso de paciente. Clinician representa os clínicos na instalação. Um registro CaseSupervision é criado cada vez que uma avaliação de caso é conduzida por um clínico. Juntas, essas três entidades são os tipos de fato nas regras de negócios a serem definidas. Observe também que a classe CaseSupervision acima é declarada como um tipo de evento no Drools.

Da perspectiva de um aplicativo, nós podemos modificar as entidades dos três tipos de qualquer lugar no sistema, em telas diferentes e em fluxos de trabalho diferentes. Nós podemos até usar uma ferramenta como o Spring Batch para atualizar as entidades em lote. Entretanto, neste exemplo, vamos presumir que iremos atualizar as entidades somente através do contexto de persistência do JPA.

Observe que o aplicativo de amostra é uma integração Spring-Drools que usa Maven para desenvolvimentos. Nós iremos analisar alguns detalhes de configuração posteriormente no artigo, mas é possível fazer o download do zip do código de origem a qualquer momento. Por enquanto, vamos considerar alguns recursos conceituais do trabalho com o Drools 5.

Fato e FactHandle

Um conceito geral dos mecanismos de regras é que fatos são objetos de dados que controlam os motivos. No Drools, os fatos são Java beans arbitrários que você retira do aplicativo e declara na memória de trabalho do mecanismo. Ou, como está escrito no manual de referência do JBoss Drools:

O mecanismo de regras não "clona" fatos de modo algum, eles são referências/ponteiros no fim do dia. Os fatos são os dados dos seus aplicativos. Cadeias de caracteres e outras classes sem coletores e configuradores não são fatos válidos e não podem ser usadas com restrições de campo que contam com o padrão JavaBean de coletores e configuradores para interagir com o objeto.

A menos que você tenha especificado a palavra-chave no-loop ou lock-on-active na parte superior da regra, as regras no mecanismo de regras do Drools serão reavaliadas sempre que ocorrer uma alteração do fato na memória de trabalho. Também é possível usar anotações @PropertyReactive e @watch para especificar propriedades de fato que o Drools deve inspecionar em busca de alterações. O Drools irá ignorar atualizações em todas as outras propriedades do fato.

Para executar uma manutenção verdadeira, existem três formas de atualizar com segurança um fact na memória de trabalho do Drools:

  1. Na sintaxe do Drools, o lado direito (RHS) é a seção de ação/consequência de uma regra, que é possível atualizar em um bloco modify . Use essa abordagem ao alterar um fato como a consequência de uma regra sendo ativada.
  2. Externamente através de um FactHandle em uma classe Java; use para alterações de fato feitas por aplicativos de código Java.
  3. Fazer com que a classe Fact implemente PropertyChangeSupport conforme definido pela especificação do JavaBeans; use isso para registrar o Drools como PropertyChangeListener no objeto Fact .

Como observadores silenciosos, nossas regras não irão atualizar nenhum fato de entidade JPA na memória de trabalho do Drools, elas irão gerar fatos lógicos como resultados de raciocínio. (Consulte a Lista 6 abaixo.) Entretanto, a atualização de entidades JPA em regras requer atenção adicional, visto que as entidades atualizadas podem estar em um estado separado ou pode acontecer de não haver nenhuma transação ou de haver uma transação somente leitura associada ao encadeamento atual. Como resultado, as alterações feitas nas entidades não serão salvas no banco de dados.

Apesar dos objetos de fato serem de "passar por referência", o Drools (diferente do JPA/Hibernate) é incapaz de rastrear as alterações de fato feitas fora das regras. É possível evitar resultados inconsistentes de regra-raciocínio usando o FactHandle para notificar o Drools sobre alterações de fato feitas no código Java dos aplicativos. O Drools irá então reavaliar as regras adequadamente. O FactHandle é o símbolo representando o seu objeto de fato declarado na memória de trabalho. É assim que você normalmente interage com a memória de trabalho quando deseja modificar ou retrair um fato. Nos nossos aplicativos de amostra (Lista 2 e Lista 3), nós usamos o FactHandle para manipular os fatos da entidade na memória de trabalho.

É possível solucionar a incapacidade do Drools de controlar alterações de fato implementando o PropertyChangeSupport (que captura todas as alterações feitas nas propriedades de um bean). Lembre-se, no entanto, que o desempenho pode ser comprometido com as reavaliações frequentes da regra.

Uso de entidades JPA como fatos

É possível inserir entidades JPA como objetos de dados de domínio, através de fatos POJO, na memória de trabalho do Drools. Isso permite que você evite a modelagem de dados para o objeto de valor/camada DTO, assim como a camada de transformação correspondente entre as entidades JPA e DTOs.

Apesar do uso de entidades como fatos simplificar o código do seu aplicativo, você terá que prestar uma atenção adicional nas fases do ciclo de vida da entidade. Os fatos da entidade devem ser mantidos no estado gerenciado (persistente) ou separado. Você não deve nunca inserir entidades temporárias na memória de trabalho do Drools porque elas ainda não estão salvas no banco de dados. Da mesma forma, você deve retrair as entidades removidas da memória de trabalho. Caso contrário, o banco de dados do seu aplicativo e a memória de trabalho do mecanismo de regras não ficarão sincronizados.

E isso nos leva a pergunta de um milhão de dólares: Como nós devemos notificar de forma eficiente o mecanismo de regras sobre as alterações de entidade feitas no código do aplicativo através de FactHandle?

Programação imperativa versus AOP

Se nós tentarmos enfrentar esse desafio com uma mentalidade de programação imperativa, nós vamos acabar invocando os métodos insert(), update()e retract() na sessão de conhecimento ao lado dos métodos de API JPA correspondentes. Essa abordagem seria um uso invasivo das APIs do Drools e deixaria um código espaguete no aplicativo. Para piorar ainda mais as coisas, as entidades atualizadas (sujas) no JPA são automaticamente sincronizadas com o banco de dados no final da transação de leitura/gravação, sem nenhuma invocação explícita ao contexto de persistência. Como nós podemos interceptar e notificar o Drools sobre essas alterações? Outra opção: a pesquisa de alterações da entidade em um processo separado, como as ferramentas comuns de inteligência de negócios (BI) fazem, manteria as funções dos negócios principais limpas, mas seria difícil, cara de implementar e os resultados não seriam instantâneos.

O EntityListener do JPA é um tipo de interceptor de AOP que é adequado para o nosso caso de uso. Na Lista 2, nós iremos definir dois EntityListeners que interceptam todas as alterações feitas nos três tipos de entidades no aplicativo. Essa abordagem mantém um ciclo de vida da entidadeno JPA constantemente sincronizado com o seu ciclo de vida no Drools.

Nos métodos de retorno de chamada do ciclo de vida da entidade, nós pesquisamos um FactHandle para a instância da entidade determinada e, em seguida, atualizamos ou retraímos o fato através de um FactHandle de retorno dependendo da fase do ciclo de vida do JPA. Se o FactHandle estiver ausente, a entidade será inserida como um fato novo na memória de trabalho para persistência ou atualização da entidade. Como a entidade não existe na memória de trabalho, não há necessidade de removê-la da memória de trabalho quando a exclusão do JPA é chamada. Os dois EntityListeners do JPA mostrados na Lista 2 são para dois pontos de entrada diferentes, ou partições, da memória de trabalho. O primeiro ponto de entrada é compartilhado entre MemberCase e Clinician e o segundo ponto é para o tipo de evento CaseSupervision .

Lista 2. EntityListeners
@Configurable
public class DefaultWorkingMemoryPartitionEntityListener 
{
  @Value("#{ksession}") //unable to make @Configurable with compile time weaving work here
  private StatefulKnowledgeSession ksession;   
   
  @PostPersist
  @PostUpdate
  public void updateFact(Object entity)
  {       
    FactHandle factHandle = getKsession().getFactHandle(entity);
    if(factHandle == null)
      getKsession().insert(entity);
    else
      getKsession().update(factHandle, entity);
  }        
		   
  @PostRemove
  public void retractFact(Object entity)
  {
    FactHandle factHandle = getKsession().getFactHandle(entity);
    if(factHandle != null)
      getKsession().retract(factHandle);
  }
 
  public StatefulKnowledgeSession getKsession() 
  {
    if(ksession != null)
    {
      return ksession;
    }
    else
    {
      // a workaround for @Configurable
      setKsession(ApplicationContextProvider.getApplicationContext()
        .getBean("ksession", StatefulKnowledgeSession.class));
      return ksession;
    }
  }
  //...
}
 
@Configurable
public class SupervisionStreamWorkingMemoryPartitionEntityListener
{ 
  @Value("#{ksession}")  
  private StatefulKnowledgeSession ksession;   
	
  @PostPersist 
  // CaseSupervision is an immutable event, 
  // thus we don’t provide @PostUpdate and @PostRemove implementations.
  public void insertFact(Object entity)
  {   
    WorkingMemoryEntryPoint entryPoint = getKsession()
      .getWorkingMemoryEntryPoint("SupervisionStream");
    entryPoint.insert(entity);
  }        
  //...
}

Assim como a AOP, a abordagem EntityListener na Lista 2 mantém a lógica de negócios principais do sistema limpa. Observe que essa abordagem requer que uma ou mais sessões de conhecimento global do Drools sejam injetadas nos dois EntityListeners. Nós iremos declarar uma sessão de conhecimento como um bean singleton do Spring posteriormente no artigo.

Dica: sessões compartilhadas de conhecimento global

A sessão compartilhada de conhecimento global basicamente torna a abordagem EntityListener adequada para os requisitos de análise e de criação de perfis de dados de BI em todo o sistema. Ela não é muito adequada para processos específicos do usuário e execuções de regra como as usadas para sistemas de compras online, onde a sessão de conhecimento seria geralmente gerada de forma dinâmica para processar os dados específicos do usuário e em seguida descartá-los.

Inicialização da memória de trabalho

Quando o aplicativo é ativado, todos os registros existentes dos três tipos de entidade são pré-carregados do banco de dados para a memória de trabalho para avaliação da regra, conforme mostrado na Lista 3. Dessa forma, a memória de trabalho será notificada de todas as alterações feitas nas entidades através dos dois EntityListeners.

Lista 3. Inicialização da memória de trabalho e execução de consultas do Drools
@Service("droolsService")
@Lazy(false)
@Transactional
public class DroolsServiceImpl 
{
  @Value("#{droolsServiceUtil}")
  private DroolsServiceUtil droolsServiceUtil;
    
  @PostConstruct
  public void launchRules()
  {
    droolsServiceUtil.initializeKnowledgeSession();
    droolsServiceUtil.fireRulesUtilHalt();    
  }
   
  public Collection<TransientReminder> findCaseReminders()
  {
    return droolsServiceUtil.droolsQuery("CaseReminderQuery", 
      "caseReminder", TransientReminder.class, null);
  }
   
  public Collection<TransientReminder> findClinicianReminders()
  {
    return droolsServiceUtil.droolsQuery("ClinicianReminderQuery", 
      "clinicianReminder", TransientReminder.class, null);
  }
}  
 
@Service
public class DroolsServiceUtil
{
  @Value("#{ksession}")
  private StatefulKnowledgeSession ksession;
            
  @Async
  public void fireRulesUtilHalt()
  {
    try{
      getKsession().fireUntilHalt(); 
    }catch(ConsequenceException e) 
    {
      throw e;
    }
  }
   
  public void initializeKnowledgeSession()
  {  
    getKsession().setGlobal("droolsServiceUtil", this);
    syncFactsWithDatabase();
  }

  @Transactional //a transaction-scoped persistence context
  public void syncFactsWithDatabase()
  {
    synchronized(ksession)
    {       
      // Reset all the facts in the working memory
      Collection<FactHandle> factHandles = getKsession().getFactHandles(
        new ObjectFilter(){public boolean accept(Object object)
        {
          if(object instanceof MemberCase)
            return true;
          return false;
        }
      });
      for(FactHandle factHandle : factHandles)
      {
        getKsession().retract(factHandle);
      }

      factHandles = getKsession().getFactHandles(
        new ObjectFilter(){public boolean accept(Object object)
        {
          if(object instanceof Clinician)
            return true;
          return false;
        }
      });
      for(FactHandle factHandle : factHandles)
      {
        getKsession().retract(factHandle);
      }           

      WorkingMemoryEntryPoint entryPoint = getKsession()
        .getWorkingMemoryEntryPoint("SupervisionStream");
      factHandles = entryPoint.getFactHandles();
      for(FactHandle factHandle : factHandles)
      {
        entryPoint.retract(factHandle);
      }               

      List<Command> commands = new ArrayList<Command>();
      commands.add(CommandFactory.newInsertElements(getMemberCaseService().findAll()));
      getKsession().execute(CommandFactory.newBatchExecution(commands));

      commands = new ArrayList<Command>();
      commands.add(CommandFactory.newInsertElements(getClinicianService().findAll()));
      getKsession().execute(CommandFactory.newBatchExecution(commands));    
	 
      for(CaseSupervision caseSupervision : getCaseSupervisionService().findAll())
      {
        entryPoint.insert(caseSupervision);
      }  
           
    }
  }
 
  public <T> Collection<T> droolsQuery(String query, String variable, 
    Class<T> c, Object... args)
  {
    synchronized(ksession)
    {       
      Collection<T> results = new ArrayList<T>();
      QueryResults qResults = getKsession().getQueryResults(query, args);  
      for(QueryResultsRow qrr : qResults)
      {
        T result = (T) qrr.get("$"+variable);
        results.add(result);
      }       
      return results;
    }
  }
}

Observação sobre o fireAllRules()

Observe que na Lista 3 nós tivemos a opção de chamar o fireAllRules() em cada método de retorno de chamada do EntityListener. Eu simplifiquei isso invocando o método fireUntilHalt() apenas uma vez dentro do método "@PostConstruct" do bean do Spring carregado. Um método fireUtilHalt deve ser invocado uma vez em um encadeamento separado (consulte a anotação @Async do Spring), e em seguida ele continuará disparando ativações de regra até uma parada ser chamada. Se não houver nenhuma ativação para disparar, o método fireUtilHalt irá esperar até uma ativação ser incluída em um grupo de agenda ativo ou grupo de fluxo de regra.

Eu poderia ter escolhido disparar regras ou mesmo iniciar processos no arquivo de configuração XML do Spring (mostrado abaixo). Entretanto, eu detectei um possível problema de manipulação de encadeamento no método fireUntilHalt() quando eu tentei configurá-lo. O resultado foi um "erro de conexão encerrada com o banco de dados" ao efetuar o carregamento lento de relacionamentos de entidades durante a avaliação da regra (consulte os tópicos avançados).

Integração Spring-Drools

Agora vamos olhar alguns detalhes de configuração da integração Spring-Drools. Lista 4 é um fragmento do pom.xml do Maven do aplicativo que inclui dependências do Drools principal, do compilador do Drools e do pacote de integração Drools Spring:

Lista 4. Parte do pom.xml do Maven
<dependency>
  <groupId>org.drools</groupId>
  <artifactId>drools-core</artifactId>
  <version>5.4.0.Final</version>
  <type>jar</type>
</dependency>               
<dependency>
  <groupId>org.drools</groupId>
  <artifactId>drools-compiler</artifactId>
  <version>5.4.0.Final</version>
  <type>jar</type>
</dependency>
<dependency> 
  <groupId>org.drools</groupId> 
  <artifactId>drools-spring</artifactId> 
  <version>5.4.0.Final</version> 
  <type>jar</type> 
  <exclusions>
    <!-- The dependency pom includes spring and hibernate dependencies by mistake. -->	
  </exclusions>
</dependency>

Identidade versus igualdade

Na Lista 5, eu configurei uma sessão global de conhecimento stateful como um bean singleton do Spring. (Uma sessão de conhecimento stateless não funcionaria como uma sessão duradoura porque ela não mantém o seu estado durante chamadas iterativas.) Uma configuração importante para observar na Lista 5 é a <drools:assert-behavior mode="EQUALITY" />.

No JPA/Hibernate, as entidades gerenciadas são comparadas com identity, enquanto as entidades separadas são comparadas com equality. As entidades inseridas em uma sessão de conhecimento stateful se tornam separadas da perspectiva do JPA, visto que um contexto de persistência no escopo da transação, mesmo um contexto de persistência "ampliado" ou "no fluxo do escopo" (consulte a seção Recursos) é efêmero comparado com o tempo de vida de uma sessão de conhecimento stateful singleton. A mesma entidade buscada através de diferentes objetos de contexto de persistência é um objeto Java diferente cada vez. Por padrão, o Drools usa comparação de identidades. Da mesma forma, quando você consulta o FactHandle em um fato de entidade existente na memória de trabalho através de ksession.getFactHandle(entity), provavelmente o Drools não irá encontrar uma correspondência. Para corresponder entidades separadas, nós temos que escolher EQUALITY no arquivo de configuração.

Lista 5. Parte do applicationContext.xml do Spring
<drools:kbase id="kbase">
  <drools:resources>
    <drools:resource  type="DRL" source="classpath:drools/rules.drl" />
  </drools:resources>
  <drools:configuration>
    <drools:mbeans enabled="true" />
    <drools:event-processing-mode mode="STREAM" />
    <drools:assert-behavior mode="EQUALITY" />
  </drools:configuration>
</drools:kbase>
<drools:ksession id="ksession" type="stateful" name="ksession" kbase="kbase" />

Consulte o código fonte do aplicativo para obter detalhes de configuração completos.

Regras do Drools

Lista 6 define duas regras de processamento de eventos complexos (CEP). Além dos dois tipos de fato como entidades JPA, MemberCase e Clinician, a classe de entidade CaseSupervision é declarada como um evento. Cada tarefa de avaliação de caso de um clínico gera um registro CaseSupervision . Depois de criado, o registro provavelmente não irá sofrer nenhuma alteração posterior.

A condição da regra Case Supervision na Lista 6 testa se houve uma supervisão no caso nos últimos 30 dias. Em caso negativo, a parte de consequência/ação da regra gera um fato TransientReminder (definido na Lista 7) e insere logicamente o fato na memória de trabalho. A regra Clinician Supervision define que um clínico deve ter concluído pelo menos uma supervisão de caso nos últimos sete dias; em caso negativo, a parte de consequência/ação da regra gera um fato TransientReminder similar, que da mesma forma é inserido na memória de trabalho.

Lista 6. Regras Case supervision
package ibm.developerworks.article.drools;

import ibm.developerworks.article.drools.service.*
import ibm.developerworks.article.drools.domain.*
 
global DroolsServiceUtil droolsServiceUtil;

declare Today
  @role(event)
  @expires(24h)
end

declare CaseSupervision
  @role(event)
  @timestamp(entryDtm)
end

rule "Set Today"
  timer (cron: 0 0 0 * * ?)
  salience 99999  // optional
  no-loop
  when
  then
    insert(new Today()); 
end

rule "Case Supervision"
  dialect "mvel"
  when
    $today : Today()
    $memberCase : MemberCase(endDtm == null, startDtm before[30d] $today)
    not CaseSupervision(memberCase == $ memberCase) 
      over window:time(30d) from entry-point SupervisionStream
    then
      insertLogical(new TransientReminder($memberCase, (Clinician)null, 
        "CaseReminder", "No supervision on the case in last 30 days."));
end
 
query "CaseReminderQuery"
  $caseReminder : TransientReminder(reminderTypeCd == "CaseReminder")
end
 
rule "Clinician Supervision"
  dialect "mvel"
  when
    $clinician : Clinician()
    not CaseSupervision(clinician == $clinician) 
      over window:time(7d) from entry-point SupervisionStream
  then
    insertLogical(new TransientReminder((MemberCase)null, $clinician, 
      "ClinicianReminder", "Clinician completed no evaluation in last 7 days."));
end
 
query "ClinicianReminderQuery"
  $clinicianReminder : TransientReminder(reminderTypeCd == "ClinicianReminder")
end

Observe que o fato TransientReminder mostrado na Lista 7 não é uma entidade JPA e sim um POJO regular.

Lista 7. TransientReminder
public class TransientReminder implements Comparable, Serializable
{			
  private MemberCase memberCase;
  private Clinician clinician;
  private String reminderTypeCd;
  private String description;

  public String toString() 
  {
    return ReflectionToStringBuilder.toString(this);
  }

  public boolean equals(Object pObject) 
  {
    return EqualsBuilder.reflectionEquals(this, pObject);
  }

  public int compareTo(Object pObject) 
  {
    return CompareToBuilder.reflectionCompare(this, pObject);
  }

  public int hashCode() 
  {
    return HashCodeBuilder.reflectionHashCode(this);
  } 	
}

Fatos versus eventos

Os eventos são fatos decorados com metadados temporais como @timestamp, @duratione @expires. A diferença mais significativa entre fatos e eventos é que os eventos são imutáveis no contexto do Drools. Se um evento estiver sujeito a alterações, as alterações (descritas como "melhoramento dos dados do evento") não devem afetar os resultados de uma execução de regra. É por isso que nós monitoramos apenas a fase do ciclo de vida da entidade @PostPersist no EntityListener deCaseSupervision (consulte a Lista 2).

O suporte do Drools para o protocolo de janela deslizante torna os eventos especialmente eficientes para raciocínio temporal. Uma janela deslizante é uma forma de abranger eventos de interesse como se eles pertencessem a uma janela que está constantemente em movimento. As duas implementações mais comuns de janela deslizante são janelas baseadas em tempo e janelas baseadas em tamanho.

Nas regras de amostra mostradas na Lista 6, over window:time(30d) sugere que os eventos CaseSupervision criados nos últimos 30 dias sejam avaliados pelo mecanismo de regras. Depois de 30 dias, os eventos imutáveis nunca mais entrarão na janela novamente, o Drools irá automaticamente retrair os eventos da memória de trabalho e as regras serão reavaliadas de forma adequada. Como os eventos são imutáveis, o Drools gerencia automaticamente o ciclo de vida do evento. Então os eventos são mais eficientes em questão de memória do que os fatos. (Observe, entretanto, que você deve configurar o modo de processamento de evento como STREAM em uma configuração Drools-Spring, caso contrário os operadores temporários como as janelas deslizantes não funcionarão.)

Trabalho com tipos declarados

Outra coisa a ser observada na Lista 6 é que o fato MemberCase (que não possui o tipo de evento) também é avaliado em relação à restrição de tempo, visto que nós avaliamos apenas casos criados há mais de 30 dias. Um caso pode ter 29 dias hoje e terá 30 dias amanhã, por isso a regra Case Supervision deve ser reavaliada no começo de cada dia. Infelizmente o Drools não oferece uma variável de janela deslizante "hoje". Então, como solução alternativa eu inclui um tipo de evento chamado Today; ele é um tipo declarado do Drools ou uma construção de dados declarada no idioma de regra e não no código Java.

Esse tipo de evento especial não declara nenhum atributo explícito, exceto um metadado @timestamp implícito, que é preenchido automaticamente no momento que um evento Today é declarado na memória de trabalho. Outro metadado, @expires(24h), especifica que um evento Today expira 24 horas após a asserção.

Para reconfigurar o evento Today no começo de cada dia, eu também inclui um timer na parte superior da regra Set Today. Essa regra é ativada e disparada no começo de cada dia para inserir um evento Today novo, que substitui o evento que acabou de expirar. Subsequentemente, o evento Today novo dispara a reavaliação da regra Case Supervision. Observe também que sem alterações do fato nas condições da regra, o timer sozinho não consegue disparar uma reavaliação da regra. O timer não reavalia funções e também não alinha evals, porque o Drools usa esses retornos de construção como restrições de tempo e armazena os valores deles em cache.

Quando usar fatos versus eventos

Ao entender a diferença entre fatos e eventos fica mais fácil decidir quando usar um ou outro:

  • Use eventos para cenários onde os dados representam uma captura instantânea imutável do estado do sistema em um determinado momento ou duração, ele é sensível ao tempo e irá expirar rapidamente ou a quantidade de dados está prevista para crescer rapidamente e continuamente.
  • Use fatos para cenários nos quais os dados são mais vitais para o domínio de negócios e nos quais os dados passarão por alterações continuas que requerem a reavaliação constante das regras.

Consultas do Drools

Nossa próxima etapa é extrair os resultados da execução da regra, que nós fazemos consultando os fatos na memória de trabalho. (Uma abordagem alternativa seria fazer com que o mecanismo de regras passasse os resultados para o aplicativo, através da chamada de métodos no globals, no lado direito da sintaxe de regras.) Neste exemplo, as asserções de fato e o disparo da regra acontecem instantaneamente sem atrasos, assegurando que as nossas consultas na Lista 6 irão retornar relatórios em tempo real. Como os fatos TransientReminder são declarados logicamente, o mecanismo de regras irá retraí-los automaticamente da memória de trabalho quando as suas condições não forem mais cumpridas.

Digamos que um reminder foi gerado em um caso específico pelo mecanismo de regras esta manhã. Subsequentemente, nós executamos a consulta "CaseReminderQuery" em um código Java conforme mostrado na Lista 3, dessa forma um lembrete foi retornado e exibido para todos os clínicos no sistema. Se durante a tarde um clínico concluir uma avaliação no caso e gerar um novo registro de supervisão de caso, esse evento irá quebrar as condições do fato reminder . O Drools irá então retraí-lo automaticamente. Nós podemos confirmar que o fato de lembrete foi encerrado executando a mesma consulta logo após a avaliação do caso ser concluída,. A asserção lógica mantém seus resultados de raciocínio atualizados e o mecanismo de regras em execução em um modo com eficiência de memória, assim como acontece com os eventos.

O contador de fato lógico

Observe que cada fato declarado logicamente é acompanhado com um contador, que é incrementado cada vez que um fato equal é declarado. Se uma regra das regras que declararam o equal repetidamente não for mais mantida, o contador do fato lógico será reduzido. Quando o contador chega a zero, o fato é automaticamente retraído.

As consultas em tempo real são a cereja do bolo. Uma consulta em tempo real permanece aberta, criando uma visualização dos resultados da consulta e publicando alterações de eventos para os conteúdos da visualização determinada. Isso significa que uma consulta em tempo real precisa ser executada corretamente uma vez, e a visão resultante é automaticamente atualizada com as alterações contínuas publicadas pelo mecanismo de regras.

Até agora, você viu que somente com algumas informações sobre Drools, JPA e Spring não é difícil implementar um aplicativo de criação contínua e em tempo real de perfil de dados. Nós iremos concluir com algumas etapas de programação avançada que irão fortalecer a nossa solução de gerenciamento de caso.

Programação avançada do Drools

Gerenciamento de relacionamentos

Uma restrição interessante do FactHandle é que ele é somente vinculado ao fato atual e não aos relacionamentos aninhados do fato. O Drools será informado sobre as alterações feitas no id do MemberCase (apesar disso nunca acontecer porque a chave primária é imutável), startDtmou endDtm através do FactHandle em getKsession().update(factHandle, memberCase). Entretanto, ele não será informado sobre as alterações nas propriedades member e caseSupervisions quando você invocar o mesmo método.

Da mesma forma, os EntityListeners no JPA não são notificados sobre as notificações em relacionamentos "um para muitos" e "muitos para muitos". Isso ocorre porque a chave estrangeira reside na tabela related ou na tabela link .

Para se conectar com esses relacionamentos como fatos atualizados, nós podemos desenvolver uma lógica recursiva para obter o FactHandle de cada relacionamento aninhado. Uma solução melhor seria colocar EntityListeners em todas as entidades, incluindo as tabelas de links, que estão embutidas nas condições da regra. Nós fizemos isso com Member e CaseSupervision, onde as alterações são manipuladas pelo EntityListener e FactHandle de cada entidade (consulte a Lista 2 e a Lista 3).

Carregamento lento da entidade durante a avaliação da regra

A menos que nós tenhamos especificado uma partição de base de conhecimento (isto é, um processamento paralelo), as regras serão avaliadas no mesmo encadeamento no qual um ksession.insert(), ksession.update()ou ksession.retract() for chamado. As asserções de fato naLista 2 e na Lista 3 ocorrem em um contexto de transação, no qual um contexto de persistência JPA no escopo da transação (sessão do Hibernate) está disponível. Isso permite ao mecanismo de regras avaliar os relacionamentos de entidades com carregamento lento. Se uma partição de base de conhecimento for ativada, será necessário configurar os relacionamentos de entidades como carregamento rápido para evitar uma LazyInitializationException do JPA.

Ativação de transações

Por padrão, o Drools não suporta transações porque ele não mantém nenhum histórico de captura instantânea dos dados na memória de trabalho. Isso é um problema para os nossos EntityListeners porque os métodos de retorno de chamada do ciclo de vida são chamados após uma limpeza do banco de dados mas antes da confirmação da transação. E se houver retrocesso em uma transação? Nesse caso, as entidades no contexto de persistência do JPA se tornariam separadas e inconsistentes com as linhas nas tabelas de banco de dados, assim como os fatos na memória de trabalho. Os resultados de raciocínio do mecanismo de regras não seriam mais confiáveis.

A ativação de transações irá tornar o nosso sistema de gerenciamento à prova de balas, assegurando que os dados na memória de trabalho e o banco de dados do aplicativo estarão sempre sincronizados e que os resultados do raciocínio da regra serão sempre precisos. No Drools, com implementações do JPA e do JTA em vigor e um pacote "drools-jpa-persistence" no caminho de classe, um JPAKnowledgeService (consulte a seção Recursos) pode ser configurado para criar nossa sessão de conhecimento stateful. Toda a sessão de conhecimento stateful com instâncias de processo, variáveis e objetos de fato é mapeada como a coluna binária de uma linha na tabela "SessionInfo" com ksessionId como a chave primária.

Quando nós especificamos limites de transação no código do nosso aplicativo através de anotações ou XML, a transação iniciada no aplicativo propaga-se no mecanismo de regras. Sempre que ocorrer um retrocesso de transação, a sessão de conhecimento stateful retornará para o estado anterior salvo no banco de dados. Isso mantém a consistência e a integração entre o banco de dados do aplicativo e o banco de dados do Drools. A sessão de conhecimento stateful singleton na memória deve se comportar como REPEATABLE READ quando acessada simultaneamente de diversas transações JPA; caso contrário, a única instância da entidade SessionInfo pode ter mudanças de estado combinadas feitas de diferentes transações, quebrando a demarcação da transação. Observe que no momento que este artigo foi escrito ainda não foi confirmado se REPEATABLE READ é implementado pelo gerenciador de transações do pacote drools-jpa-persistence .

Armazenamento em cluster

Se o seu aplicativo for desenvolvido para ser executado em um ambiente em cluster, a abordagem descrita anteriormente irá falhar rapidamente. Cada instância do mecanismo de regras integrado irá receber eventos de entidades que estão ocorrendo no mesmo nó, o que fará com que as memórias de trabalho nos diferentes nós percam a sincronização. Nós poderíamos solucionar esse problema com um servidor Drools remoto universal (consulte a seção Recursos). Os listeners de entidade em nós diferentes iriam publicar todos os seus eventos no servidor Drools centralizado através de comunicações de serviço da web REST/SOAP e o aplicativo poderia então se inscrever para receber os resultados de raciocínio do servidor Drools. Observe que atualmente a implementação Apache CXF do SOAP no servidor do Drools não suporta ws-transaction. Eu espero que isso seja disponibilizado em breve, visto os requisitos de transação obrigatórios descritos para este caso de uso do mundo real.

Conclusão

Neste artigo, você teve a oportunidade de reunir algumas coisas que você já conhecia sobre programação POJO no Spring e no JPA juntamente com alguns recursos novos disponíveis no Drools 5. Eu demonstrei como fazer um uso inteligente dos EntityListeners, uma sessão global do Drools, e o método fireUtilHalt() para desenvolver um aplicativo de criação contínua e em tempo real de perfil de dados baseada em POJO. Você aprendeu os principais conceitos do Drools como o trabalho com assuntos como fatos versus eventos e como escrever asserções lógicas, além de tópicos mais avançados e usos como o gerenciamento de transações e a ampliação de uma implementação do Drools em um ambiente em cluster. Consulte o código fonte dos aplicativos para saber mais sobre o Drools 5.


Download

DescriçãoNomeTamanho
Sample code for this articlej-drools5-src.zip5KB

Recursos

Aprender

Obter produtos e tecnologias

  • Download do Drools 5: Drools Expert e Drools Fusion, usados neste artigo, implemente o mecanismo de regras e uma estrutura CEP.

Discutir

  • Participe da comunidade do My developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora blogs, fóruns, grupos e wikis orientados a desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre, Tecnologia Java
ArticleID=827422
ArticleTitle=Use o Drools e o JPA para criação contínua e em tempo real de perfil de dados
publish-date=07262012