Primeiros passos com o Spring Integration

O Spring Integration é o módulo do Spring dedicado a implementação dos patterns de integração entre sistemas. De longe não é um projeto trivial, até porque quando o tema é integração, o assunto nunca é simples. O objetivo deste post é dar uma visão inicial e já usar um exemplo que foi necessário no meu último projeto.

Precisava fazer um agregador de noticias, já com alguns canais selecionados e focados num determinado público. A ideia é puxar vários feed relacionados a um tema, transformar os dados para objetos do meu domínio e gravar no meu banco de dados. Pode perceber que acabei de descrever um fluxo para você:

  1. Tenho um canal de entrada
  2. Manipulo os dados para deixar do jeito que eu preciso
  3. Jogo para um canal de saída

Esse fluxo poderia ser bem mais complicado, vamos pensar em outro cenário:

  1. Ficar buscando dados de uma tabela de outro sistema, já que ele não expõe via serviço.
  2. Criar vários objetos do seu domínio representando o conjunto de novas informações que foram buscadas
  3. Realizar essa atividade a cada 1 hora
  4. Enviar mensagens através do sistema de mensageria contendo esses novos objetos
  5. Caso dê algum problema, grava num arquivo e tenta fazer de novo em 30 minutos

Tudo vai depender do tipo de aplicação que estamos trabalhando. Dois projetos bem famosos para ajudar neste tipo de cenário são o Apache Camel e o Spring Integration. Baseado no nome do blog, já dá para saber que vamos trabalhar com o último.

O projeto foi criado usando o Spring Boot. Para quem ainda não conhece, é só dar uma olhada em outro post aqui do blog. Além disso, para criar o projeto, você pode usar o SetupMyProject. Com tudo pronto, nosso primeiro trabalho é adicionar as dependências necessárias.

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-integration</artifactId>
		<scope>compile</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-feed</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-redis</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-java-dsl</artifactId>
		<version>1.1.0.RELEASE</version>
	</dependency>

Agora vamos montar nosso fluxo. Você tem duas opções, ou usa a DSL que eles criaram ou pode optar por fazer tal configuração em um arquivo xml. Em geral sou adepto sempre da configuração programática, mas dessa vez optei por começar via xml porque, inicialmente, achei mais simples.

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

	<int-feed:inbound-channel-adapter id="feedAdapter" channel="inputFeedChannel" auto-startup="true" metadata-store="metadataStore" url="url">
		<int:poller fixed-rate="10000" max-messages-per-poll="10" />
	</int-feed:inbound-channel-adapter>

	<int:channel id="inputFeedChannel">
		<int:queue capacity="100000"/>
	</int:channel>

	<int:poller default="true" fixed-rate="10000"/>	

	<int:transformer input-channel="inputFeedChannel" ref="feedToDownloadedContentTransformer" output-channel="storeFeedContentChannel"/>

	<int:outbound-channel-adapter id="storeFeedContentChannel" ref="storeFeedContentEndpoint" method="handle"/>	    	
		
	</beans>

Vamos começar analisando de baixo para cima. A última configuração, feita pela tag outbound-channel-adapter, indica o ponto final do meu fluxo, ela simplesmente aponta para uma classe minha que vai receber o objeto pronto para eu salvar no banco. Perceba que inclusive eu falo qual método deve ser chamado.

package br.com.ideiasaleatorias.tudosobreesporte.integration;

...

@MessageEndpoint
public class StoreFeedContentEndpoint {

	@Autowired
	private DownloadedContentDao downloads;
	private static Logger logger = LoggerFactory
			.getLogger(StoreFeedContentEndpoint.class);

	public void handle(Message<DownloadedContent> message) {
		DownloadedContent payload = message.getPayload();

		try {
				downloads.save(payload);
		} catch (Exception e) {
			logger.error("Não foi possivel gravar a informacao do {}",payload.getLink());
		}

	}
}

Só que precisamos de alguém que seja capaz de pegar uma entrada do feed e transformar para o objeto do meu domínio. Esse é o objetivo da tag transformer. Perceba que foi usado dois atributos: input-channel e o output-channel, justamente para ele puxar informação de um lugar e jogar para outro. Além disso, precisamos configurar um intervalo para que o transformer fique verificando o canal de entrada, aí entra a tag poller. Configuramos que o comportamento default para a realização do polling é de dez em dez segundos. Inclusive você pode configurar de outras formas,  por exemplo com expressões estilo crontabA tag ref faz referencia a um bean configurado dentro da aplicação.

	@Component
	public class FeedToDownloadedContentTransformer {

		@Transformer
		public DownloadedContent transform(SyndEntry payload){
			return DowloadedContentFactory.createFromPayload(payload);
		}
	}

Perceba que anoto o método com @Transformer para que o Spring Integration saiba qual método deve ser invocado. O tipo do argumento depende da fonte que você está puxando a informação, no meu caso, como é um feed, o Spring Integration já fornece a interface SyndEntry.  Para quase fechar, é importante especificar a fonte de dados. É para isso que serve a parte inicial da configuração.

	<int-feed:inbound-channel-adapter id="feedAdapter" channel="inputFeedChannel" auto-startup="true" metadata-store="metadataStore" url="url">
		<int:poller fixed-rate="10000" max-messages-per-poll="10" />
	</int-feed:inbound-channel-adapter>

	<int:channel id="inputFeedChannel">
		<int:queue capacity="100000"/>
	</int:channel>

A tag inbound-channel-adapter, do namespace referenciado pelo prefixo int-feed, é a responsável por fazer a busca do feed em si. Informamos uma url, de onde ela deve baixar as novas noticias e indicamos um local onde deve ser guardado o conteúdo baixado, até que o transformer comece a consumir. Isso é feito através do atributo channel.  Ele é o primeiro consumidor do conteúdo, tanto que o mesmo também é referenciado pelo transformer. Existem várias implementações para um channel, todas elas implementam a interface chamada MessageChannel. No nosso caso, usamos uma QueueChannel, pois ela pode ser associada com um pooler para consumir o que chega de tempos em tempos.

Ainda em relação a configuração acima, pode ser que você tenha ficado curioso em relação ao atributo metadata-store. O problema é que não queremos ficar baixando feeds que já foram gravados, então podemos passar para ele uma implementação da interface MetadataStore, que já fica responsável por controlar essa funcionalidade. A interface é bem simples, basicamente segue a ideia de um Map. Para não ter que implementar e também para manter os dados que já foram baixados entre os restarts do servidor, eu escolhi a implementação baseado no redis. Basta que você faça o download do projeto e suba o servidor dele. Aí é só fornecer o bean para o Spring Integration.

	@Bean
	public JedisConnectionFactory redisFactory() {
		JedisConnectionFactory jcf = new JedisConnectionFactory();
		jcf.afterPropertiesSet();
		return jcf;
	}

	@Bean
	public RedisMetadataStore metadataStore(JedisConnectionFactory factory) {
		return new RedisMetadataStore(factory);
	}

Para quem ficou curioso em relação a configuração programática, abaixo segue o exemplo de como ficaria para o mesmo exemplo.

	@Bean
	public IntegrationFlow feedFlow(MetadataStore metadataStore) throws MalformedURLException {
		URL feedUrl = new URL("enderecoDoFeed");
		return IntegrationFlows
				.from(s -> s.feed(feedUrl, "MetaData").metadataStore(metadataStore),
						e -> e.poller(p -> p.fixedDelay(10,TimeUnit.SECONDS)))				
				.channel(c -> c.queue("feedEntries"))				
				.transform(new FeedToDownloadedContentTransformer())				
				.handle("storeFeedContentEndpoint","handle")
				.get();
	}

Esse para mim é um uso meio exagerado dos lambdas, então realmente eu ainda prefiro ficar com o xml.

O nosso exemplo foi baseado em consumo de feed, mas existem várias opções. A entrada podia ser uma fila do seu servidor de mensageria, um banco de dados, um serviço baseado em HTTP etc. Para a saída você pode considerar as mesmas possibilidades. Além disso, aqui implementamos alguns passos com nossas próprias classes, muitas vezes você pode usar as configurações do próprio Spring Integration para fazer todos os passos!

Caso sua aplicação necessite desses tipos de fluxos, o Spring Integration é uma ótima opção. Ele não é tão simples de começar, por isso que é importante você associar o aprendizado dele com o estudo dos patterns de integração. Lembre que ele é só a ferramenta, sem a teoria a tendência é que tudo mais complicado.

Advertisements

4 thoughts on “Primeiros passos com o Spring Integration

  1. Excelente post, Alberto!

    Integração de sistemas é uma das coisas mais difíceis e propícias a erros que um desenvolvedor pode implementar, não existe integração fácil quando começamos a analisar os “corner-cases”. Quando precisei de um ferramenta para fazer integração de sistemas de forma decente eu optei pelo Apache Camel, pois achei sua documentação e DSL muito melhor do que do Spring Integration (na época ela não tinha DSL). E concordo com você, o uso de Lambdas, ao menos para mim, torna difícil a leitura, embora assegure a sintaxe type-safe.

    Outro detalhe importante que você comentou é que não basta correr para Camel ou Spring-Integration sem antes ter uma boa visão sobre os EIPs. Pois estas ferramentas são 100% baseadas nesses patterns, sendo, é preciso entender o problema na qual o pattern nasceu para resolver!

    Um abraço!

    Like

    • Opa Rafael, seus comentários são sempre muito bons 🙂 :). A DSL do Camel está bem melhor mesmo, nisso eu concordo totalmente. E entender os patterns é fundamental mesmo, para entender cada um dos casos e se realmente é necessário partir para um projeto tipo esse.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s