Guia Testes e DevOps

Apache Camel: Um guia completo –Partes 3 e 4

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (1)  (0)

Evoluindo a solução de pagamento bancário através de novas integrações com sistemas externos.

Fique por dentro
Este artigo apresenta recursos importantes do Apache Camel através da implementação dos requisitos da solução de pagamento bancário. Fundamentado na implementação desses requisitos, será possível constatar as facilidades que o framework oferece para encapsular código complexo capaz de integrar sistemas heterogêneos. Portanto, aqueles que desejam aprender na prática os recursos fundamentais para a construção de um sistema integrado com o auxílio do Apache Camel encontrarão neste artigo códigos valiosos para o alcance desse objetivo.

Na parte 2 desta série de artigos foi apresentado o exemplo prático a ser implementado. Este exemplo consiste na construção de uma solução integradora para pagamento bancário com cartão de crédito via aplicativo mobile. Além disso, os requisitos funcionais e não funcionais que devem ser atendidos também foram indicados, juntamente com o fluxo de negócio a ser seguido.

A partir dessas informações, foi definido que a arquitetura da solução será baseada em componentes com comunicação interna utilizando mensageria no formato JSON, permitindo assim baixo acoplamento, processamento assíncrono, performance, flexibilidade, tolerância a falhas, escalabilidade e baixo custo de manutenção. Ademais, foram apresentados conceitos e funcionalidades básicas do Apache Camel através da implementação do primeiro fluxo de negócio.

Nesta terceira parte, novos recursos do framework serão introduzidos, com destaque para a implementação dos EIPs, de componentes para mensageria e a continuação da codificação do roteamento e manipulação de mensagens visando atender as regras de negócio.

Para exercitar e aprender esses recursos do Camel, os requisitos definidos no artigo anterior continuarão a serem implementados, em sua sequência natural, para que as integrações iniciais com os sistemas externos bancários e de segurança do cartão sejam realizadas ao passo que a arquitetura da solução também é evoluída.

Implementando os requisitos RF-02-SISBAN e RNF-02-SISBAN

Com os primeiros requisitos já atendidos, será iniciada a implementação do RF-02-SISBAN, que menciona que, após receber os dados da requisição de pagamento do Aplicativo Mobile, a Solução de Pagamento Bancário deve enviar o número do cartão e o CPF do titular para o Sistema Bancário para validação, e também do RNF-02-SISBAN, que define que a comunicação com o Sistema Bancário deve ser realizada via mensageria com o formato JSON.

Para atender os novos requisitos, será necessário continuar com o desenvolvimento do componente MobileIntegracao, iniciando pelas alterações na classe MobileIntegracaoRouteBuilder, responsável por definir todas as rotas do respectivo componente. Sendo assim, precisamos alterar a classe MobileIntegracaoRouteBuilder conforme a codificação da Listagem 1.

Listagem 1. Alterações no código da classe MobileIntegracaoRouteBuilder.

  01 package br.com.devmedia.mobile.integracao.rota;
  02 
  03 import org.apache.camel.LoggingLevel;
  04 import org.apache.camel.builder.RouteBuilder;
  05 import org.apache.camel.model.rest.RestBindingMode;
  06 import org.springframework.beans.factory.annotation.Autowired;
  07 import org.springframework.stereotype.Component;
  08 
  09 import br.com.devmedia.mobile.integracao.comum.PagamentoRequisicao;
  10 import br.com.devmedia.mobile.integracao.processor.ConverteParaPagamentoBancarioComumProcessor;
  11 
  12 @Component
  13 public class MobileIntegracaoRouteBuilder extends RouteBuilder {
  14  
  15  private ConverteParaPagamentoBancarioComumProcessor converteParaPagamentoBancarioComumProcessor;
  16  
  17  @Override
  18  public void configure() throws Exception {
  19   
  20   rest("/pagamento").id("rotaEntradaWS")
  21    .description("Serviço para efetuar pagamento bancário")
  22    .consumes("application/json")
  23    .produces("application/json")
  24    .post()
  25    .bindingMode(RestBindingMode.json)
  26    .type(PagamentoRequisicao.class)
  27   .to("seda:postPagamento");
  28   
  29   from("seda:postPagamento")
  30    .log(LoggingLevel.INFO, "[MobileIntegracao] Nova requisição de pagamento bancário")
  31    .process(converteParaPagamentoBancarioComumProcessor)
  32    .setHeader("FLUXO", constant("NOVA_REQUISICAO_PAGAMENTO_BANCARIO"))
  33    .log(LoggingLevel.INFO, "[MobileIntegracao] para [PagamentoBancario] Nova requisição de pagamento bancário")
  34    .to("activemq:queue:PagamentoBancario?deliveryPersistent=false")
  35   .end();
  36  }
  37  
  38  @Autowired
  39  public void setConverteParaPagamentoBancarioComumProcessor(ConverteParaPagamentoBancarioComumProcessor converteParaPagamentoBancarioComumProcessor) {
  40   this.converteParaPagamentoBancarioComumProcessor = converteParaPagamentoBancarioComumProcessor;
  41  }
  42 }

Basicamente, as modificações realizadas foram na rota seda:postPagamento, definida nas linhas 29 a 35, que é uma rota interna responsável por consumir as mensagens enviadas pela rota de entrada (id rotaEntradaWS). Esta última, por sua vez, é a fonte de entrada de dados na Solução de Pagamento Bancário.

Portanto, uma variável de nome converteParaPagamentoBancarioComumProcessor, do tipo ConverteParaPagamentoBancarioComumProcessor, foi declarada na linha 15 e utilizada na linha 31 para que o respectivo processor manipule e trate a mensagem recebida. O processor ConverteParaPagamentoBancarioComumProcessor será desenvolvido nos próximos passos.

Na sequência, a linha 32 define no header da mensagem (Exchange) uma constante de nome FLUXO e valor NOVA_REQUISICAO_PAGAMENTO_BANCARIO para que quando o componente seguinte receber a mensagem consiga identificar de qual fluxo a mesma pertence e, então, tratar e dar o destino correto. Essa abordagem é uma implementação do EIP Content-Based Router.

Adiante, verifica-se na linha 33 a inclusão de log com a mensagem “[MobileIntegracao] para [PagamentoBancario] Nova requisição de pagamento bancário”, para registro do componente de origem e destino da mensagem. Essa abordagem é uma boa prática pois em sistemas componentizados e em cenários de integração é muito importante indicar de onde e para onde a mensagem ou fluxo está caminhando, para que seja fácil a análise posterior.

Finalizando esse trecho, a linha 34 faz de fato o envio da mensagem para uma fila, que, no caso, se chama PagamentoBancario. Para isso é utilizado o componente activemq-camel, que foi previamente configurado no arquivo application-context.xml do componente MobileIntegracao. Ainda nessa mesma linha, observa-se o uso do parâmetro deliveryPersistent, para indicar que as mensagens não precisam ser persistidas em disco quando o servidor de mensageria desligar, pois em nosso cenário não há necessidade de manter as mensagens. Além disso, essa configuração deixará o processo de mensageria mais rápido. Uma dica interessante é que não é preciso criar a fila PagamentoBancario previamente no servidor de mensageria, pois no carregamento da aplicação a mesma é criada automaticamente.

Por fim, as linhas 38 a 41 implementam o método setter da variável converteParaPagamentoBancarioComumProcessor, para que, juntamente com a anotação @Autowired, o Spring faça a injeção e gerenciamento do processor.

Realizadas as modificações em MobileIntegracaoRouteBuilder, é preciso criar o processor ConverteParaPagamentoBancarioComumProcessor, que define a última palavra de sua nomenclatura como Processor para que um padrão seja respeitado com o objetivo de facilitar a identificação de classes desse tipo. Na sequência, será necessário criar o pacote br.com.devmedia.mobile.integracao.processor e implementar a classe ConverteParaPagamentoBancarioComumProcessor, conforme a Listagem 2.

Listagem 2. Código da classe ConverteParaPagamentoBancarioComumProcessor.

  01 package br.com.devmedia.mobile.integracao.processor;
  02 
  03 import java.util.Date;
  04 
  05 import org.apache.camel.Exchange;
  06 import org.apache.camel.Processor;
  07 import org.springframework.stereotype.Component;
  08 
  09 import br.com.devmedia.mobile.integracao.comum.PagamentoBancarioComum;
  10 import br.com.devmedia.mobile.integracao.comum.PagamentoRequisicao;
  11 import br.com.devmedia.pagamento.bancario.conversor.ConversorJson;
  12 import br.com.devmedia.pagamento.bancario.processor.ProcessorUtil;
  13 
  14 @Component
  15 public class ConverteParaPagamentoBancarioComumProcessor implements Processor {
  16  
  17  @Override
  18  public void process(Exchange exchange) throws Exception {
  19   PagamentoRequisicao pagamentoRequisicao = exchange.getIn().getBody(PagamentoRequisicao.class);
  20   PagamentoBancarioComum pagamentoBancario = new PagamentoBancarioComum();
  21   populaPagamentoBancarioComum(pagamentoBancario, pagamentoRequisicao);
  22   ProcessorUtil.setObjetoHeader(exchange, pagamentoBancario);
  23   String json = ConversorJson.converteObjetoParaJson(pagamentoBancario);
  24   exchange.getIn().setBody(json);
  25  }
  26 
  27  private void populaPagamentoBancarioComum(PagamentoBancarioComum pagamentoBancario, PagamentoRequisicao pagamentoRequisicao) {
  28   pagamentoBancario.setIdTransacao(pagamentoRequisicao.getIdTransacao());
  29   pagamentoBancario.setCpfTitularCartao(pagamentoRequisicao.getCpfTitularCartao());
  30   pagamentoBancario.setNumeroCartao(pagamentoRequisicao.getNumeroCartao());
  31   pagamentoBancario.setCodigoSegurancaCartao(pagamentoRequisicao.getCodigoSegurancaCartao());
  32   pagamentoBancario.setDataInicialTransacao(new Date());
  33   pagamentoBancario.setValorCompra(pagamentoRequisicao.getValorCompra());
  34  }
  35 }

Observando a implementação anterior, nota-se que na linha 14 é incluída a anotação @Component para que o Spring consiga fazer a injeção de dependência. Na sequência, a linha 15 declara de fato o processor ConverteParaPagamentoBancarioComumProcessor através da implementação da interface Processor do Camel, isto é, com a implementação dessa interface é que a classe em questão é transformada em um processor. Na linha 18 o método void process(Exchange) é sobrescrito para que a lógica de manipulação de mensagens seja realizada.

Na linha 19, o objeto PagamentoRequisicao, que representa os dados da requisição de pagamento bancário — feita através do web service de entrada da solução — é recuperado, e a partir dele um novo objeto, do tipo PagamentoBancarioComum, é instanciado e populado. Nas seções seguintes, a classe PagamentoBancarioComum será implementada e mais detalhes serão apresentados. Neste momento, saiba que o objetivo com a criação dessa classe é servir como um POJO e trafegar os dados entre os componentes da solução.

Continuando, na linha 21 é invocado o método populaPagamentoBancarioComum(PagamentoBancarioComum, PagamentoRequisicao), cujo objetivo é simplesmente popular um objeto PagamentoBancarioComum a partir dos dados de um objeto PagamentoRequisicao. A implementação da lógica contida nesse método é exibida nas linhas 27 a 34.

Na sequência, a linha 22 apresenta o código para incluir no header da mensagem (Exchange) que está sendo manipulada o objeto da classe PagamentoBancarioComum, para que ele seja salvo e posteriormente recuperado quando qualquer componente da solução precisar de dados da transação. Para que essa abordagem funcione corretamente, é necessário que o objeto em questão tenha seus dados incluídos e atualizados no decorrer do fluxo pelos próprios componentes conforme as integrações ocorrem.

Ainda na linha 22, nota-se a utilização de uma biblioteca auxiliar através da classe ProcessorUtil para incluir um objeto qualquer no header da mensagem com o uso do método setObjetoHeader(Exchange, Object). Essa biblioteca, nomeada de ComponenteUtil, será desenvolvida para conter código comum que será utilizado por todos os demais componentes da solução e, assim, evitar repetição, isto é, seguir boas práticas de Engenharia de Software. A implementação da biblioteca será detalhada em breve.

Por fim, a linha 23 faz a conversão do objeto da classe PagamentoBancarioComum para o formato JSON através do método converteObjetoParaJson(Object), contido na classe ConversorJson, respeitando o formato em que as mensagens deverão ser trafegadas entre os componentes. Na linha 24, o JSON é de fato incluído no body do Exchange e assim está apto a ser recebido pelo próximo componente do fluxo.

Desta forma, para que a classe ConverteParaPagamentoBancarioComumProcessor compile e funcione corretamente, será necessária a criação da biblioteca auxiliar ComponenteUtil e também da biblioteca comum PagamentoBancarioComum. Sendo assim, as seções a seguir detalharão a criação de ambas.

Implementando a biblioteca ComponenteUtil

Conforme mencionado anteriormente, a biblioteca ComponenteUtil será responsável por prover código comum que todos os outros componentes da solução necessitam utilizar, evitando, dessa forma, a repetição de código. Para isso, vamos criar uma estrutura de projeto/módulo Java Standalone (JAR) dentro do diretório do projeto agregador com o nome de ComponenteUtil. Feito isso, adicione nesse novo projeto um arquivo pom.xml (vide Listagem 3) para indicar as configurações básicas e as dependências externas de bibliotecas.

Listagem 3. Arquivo pom.xml da biblioteca ComponenteUtil.

  01 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  02  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  03  <modelVersion>4.0.0</modelVersion>
  04 
  05  <parent>
  06   <groupId>br.com.devmedia</groupId>
  07   <artifactId>solucao-pagamento-bancario</artifactId>
  08   <version>1.0</version>
  09  </parent>
  10  
  11  <name>ComponenteUtil</name>
  12  <artifactId>componente-util</artifactId>
  13  <packaging>jar</packaging>
  14 
  15  <dependencies>
  16   <dependency>
  17       <groupId>org.apache.camel</groupId>
  18       <artifactId>camel-jackson</artifactId>
  19   </dependency>
  20  </dependencies>
  21   
  22 </project>

Observa-se que é utilizada a tag parent nas linhas 5 a 9 para informar que as definições feitas no pom.xml do projeto agregador devem ser utilizadas nesse módulo. Dessa forma, não é necessário definir o groupId e o version para o próprio módulo, pois essas informações serão obtidas a partir do SolucaoPagamentoBancario.

Além disso, a tag packaging é definida como JAR, pois assim um JAR dessa biblioteca será gerado para que seja incorporado e utilizado pelos outros componentes. Por fim, nas linhas 15 a 20 é declarada a dependência da biblioteca camel-jackson, que auxilia nas conversões de JSON para objeto e vice-versa.

Seguindo com a implementação, deve-se criar um pacote com o nome br.com.devmedia.pagamento.bancario.conversor para conter a classe ConversorJson, responsável por realizar conversões de objetos em formato JSON. Seu código é apresentado na Listagem 4.

Listagem 4. Código da classe ConversorJson, módulo ComponenteUtil.

  01 package br.com.devmedia.pagamento.bancario.conversor;
  02 
  03 import java.io.IOException;
  04 import java.io.StringWriter;
  05 
  06 import com.fasterxml.jackson.core.JsonGenerator;
  07 import com.fasterxml.jackson.databind.DeserializationFeature;
  08 import com.fasterxml.jackson.databind.MappingJsonFactory;
  09 import com.fasterxml.jackson.databind.ObjectMapper;
  10 
  11 public abstract class ConversorJson {
  12  
  13  public static String converteObjetoParaJson(final Object objeto) throws Exception {
  14   final StringWriter sw = new StringWriter();
  15   final ObjectMapper mapper = new ObjectMapper();
  16   final MappingJsonFactory jsonFactory = new MappingJsonFactory();
  17   final JsonGenerator jsonGenerator = jsonFactory.createGenerator(sw);
  18   try {
  19    mapper.writeValue(jsonGenerator, objeto);
  20    return sw.getBuffer().toString();
  21   } catch (Exception e) {
  22    throw e;
  23   } finally {
  24    sw.close();
  25   }
  26  }
  27 
  28  public static <T> T converteJsonParaObjeto(final String json, final Class<T> objeto) throws IOException, InstantiationException, IllegalAccessException {
  29   final ObjectMapper mapeador = new ObjectMapper();
  30   mapeador.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  31   final T obj = mapeador.readValue(json, objeto);
  32   return obj;
  33  }
  34 }  

Verificando essa classe, nota-se nas linhas 13 a 26 a criação do método converteObjetoParaJson(Object) para realizar conversões de objeto para JSON. Para tal tarefa, é utilizada a biblioteca Jackson, já conhecida e consolidada no mundo Java. Já as linhas 28 a 33 demonstram a criação do método converteJsonParaObjeto(String, Class<T>) utilizando Generics para converter um JSON em objeto, também através da biblioteca Jackson. Vale ressaltar que na linha 30 é indicada a configuração DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES com o valor false para que atributos contidos no JSON que não existam na classe final sejam ignorados e, assim, erros não sejam gerados durante a conversão.

Continuando com o desenvolvimento da biblioteca ComponenteUtil, crie o pacote br.com.devmedia.pagamento.bancario.processor e implemente a classe ProcessorUtil, conforme a Listagem 5. Essa classe será responsável por conter os métodos úteis que são utilizados pelos demais componentes para salvar e recuperar dados de uma mensagem.

Listagem 5. Código da classe ProcessorUtil, módulo ComponenteUtil.

  01 package br.com.devmedia.pagamento.bancario.processor;
  02 
  03 import java.io.IOException;
  04 import org.apache.camel.Exchange;
  05 import br.com.devmedia.pagamento.bancario.conversor.ConversorJson;
  06 
  07 public abstract class ProcessorUtil {
  08 
  09  public static void setObjetoHeader(final Exchange exchange, final Object objeto) throws Exception {
  10   exchange.getIn().setHeader(objeto.getClass().getName(), ConversorJson.converteObjetoParaJson(objeto));
  11  }
  12 
  13  public static void setObjetoHeader(final Exchange exchange, final String chaveAdicional, final Object objeto) throws Exception {
  14   exchange.getIn().setHeader(objeto.getClass().getName() + chaveAdicional, ConversorJson.converteObjetoParaJson(objeto));
  15  }
  16 
  17  public static <T> T getObjetoHeader(final Exchange exchange, final Class<T> tipoClass)
  18    throws IOException, InstantiationException, IllegalAccessException {
  19   final String tipoStr = exchange.getIn().getHeader(tipoClass.getName(), String.class);
  20   if (tipoStr != null) {
  21    final T tipo = ConversorJson.converteJsonParaObjeto(tipoStr, tipoClass);
  22    return tipo;
  23   }
  24   return null;
  25  }
  26 
  27  public static <T> T getObjetoHeader(final Exchange exchange, final String chaveAdicional, final Class<T> tipo)
  28    throws IOException, InstantiationException, IllegalAccessException {
  29   final String tipoStr = exchange.getIn().getHeader(tipo.getName() + chaveAdicional, String.class);
  30   if (tipoStr != null) {
  31    return ConversorJson.converteJsonParaObjeto(tipoStr, tipo);
  32   }
  33   return null;
  34  }
  35 }

Analisando o código apresentado, observa-se que nas linhas 9 a 11 existe a implementação do método setObjetoHeader(Exchange, Object), cuja finalidade é incluir no header de uma mensagem do tipo Exchange um POJO em formato JSO" [...]

A exibição deste artigo foi interrompida :(
Este post está disponível para assinantes MVP

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?