Integrando Spock com Maven/Eclipse

Introdução

Neste post vamos ver uma breve introdução sobre o framework de testes Spock e como inegrá-lo ao Maven a ao Eclipse.

Spock

Spock é um framework para construção e especificação de testes usando-se a linguagem Groovy e cuja adoção vem crescendo bastante ultimamente. Embora Groovy seja a linguagem com a qual você deve escrever seus testes usando Spock, ele pode ser usado tanto em aplicações Groovy quanto Java e é compatível com a maioria das IDEs Java, como Eclipse.
O fato de ser necessário escrever os testes em Groovy pode criar um pouco de aversão para quem programa em Java, mas não tem familiaridade com Groovy. Porém, o conhecimento que é preciso ter de Groovy é bem básico e a própria documentação do Spock ajuda com essa questão. Eu mesmo, no momento, sei muito pouco de Groovy e tenho usado Spock sem problemas.

Exemplo

Com o objetivo de mostrar o Spock na prática em conjunto com o Maven, vamos criar uma aplicação Java bastante simples, sem o uso de nenhum outro framework. Essa aplicação consiste basicamente de uma entidade Customer, uma classe de serviço CustomerServices que permite que Customers sejam adicionados e consultados e, por fim, uma classe de exceção InvalidCustomerException usada quando um Customer a ser inserido não é válido.
Para começar, vamos definir a estrutura do nosso pom.xml, conforme listagem abaixo:

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.lucianomolinari</groupId>
	<artifactId>spockbasics</artifactId>
	<version>1.0.0</version>

	<name>Spock with Maven</name>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
					<compilerId>groovy-eclipse-compiler</compilerId>
				</configuration>
				<dependencies>
					<dependency>
						<groupId>org.codehaus.groovy</groupId>
						<artifactId>groovy-eclipse-compiler</artifactId>
						<version>2.8.0-01</version>
					</dependency>
					<dependency>
						<groupId>org.codehaus.groovy</groupId>
						<artifactId>groovy-eclipse-batch</artifactId>
						<version>2.1.8-01</version>
					</dependency>
				</dependencies>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>2.9</version>
				<configuration>
					<additionalProjectnatures>
						<projectnature>org.eclipse.jdt.groovy.core.groovyNature</projectnature>
					</additionalProjectnatures>
					<sourceIncludes>
						<sourceInclude>**/*.groovy</sourceInclude>
					</sourceIncludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-core</artifactId>
			<version>0.7-groovy-2.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-all</artifactId>
			<version>2.1.8</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

No plugin “maven-compiler-plugin”, além de configurarmos a versão do Java como de costume, também preenchemos a tag “compilerId” com o valor “groovy-eclipse-compiler”. Essa configuração, juntamente com a definição das dependências “groovy-eclipse-compiler” e “groovy-eclipse-batch” são necessárias para instruir o maven a como compilar as classes escritas em Groovy. Na sequência, também definimos o plugin “maven-eclipse-plugin”. Essa definição é feita para que, quando usamos o comando “mvn eclipse:eclipse” para importar o projeto no Eclipse, esse projeto também venha com a característica de um projeto Groovy.
Após a definição dos plugins, simplesmente importamos a biblioteas JUnit, Spock e Groovy.

Com isso, podemos dar sequência com a criação do código Java. A primeira classe a ser criada é a Customer, que nada mais é do que um simples Pojo com 2 atributos.

Customer.java
package com.wordpress.lucianomolinari.spockbasics;

/**
 * Simple entity that represents a customer.
 * 
 * @author Luciano Molinari
 */
public class Customer {

	/**
	 * Identifies uniquely a {@link Customer}.
	 */
	private Long id;

	/**
	 * Name of the {@link Customer}.
	 */
	private String name;

	/**
	 * Creates a new {@link Customer} with the given id and name.
	 * 
	 * @param id
	 *            The identification of the customer.
	 * @param name
	 *            The name of the customer.
	 */
	public Customer(Long id, String name) {
		this.id = id;
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Customer other = (Customer) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Customer [id=" + id + ", name=" + name + "]";
	}

}

Para mapear os erros possíveis de validação, iremos criar uma classe de exceção chamada InvalidCustomerException.

InvalidCustomerException.java
package com.wordpress.lucianomolinari.spockbasics;

/**
 * Excpetion that must be thrown when a {@link Customer} to be persisted is not
 * valid.
 * 
 * @author Luciano Molinari
 */
public class InvalidCustomerException extends Exception {

	private static final long serialVersionUID = -6532930714866940079L;

	/**
	 * Declares the possible reasons of why a {@link Customer} is not valid.
	 */
	public enum InvalidCustomerCause {
		ID_NOT_INFORMED, NAME_NOT_INFORMED, DUPLICATED_ID
	}

	private InvalidCustomerCause invalidCustomerCause;

	/**
	 * Creates a new {@link InvalidCustomerException} with the given cause.
	 * 
	 * @param invalidCustomerCause
	 *            The reason why the {@link Customer} is invalid.
	 */
	public InvalidCustomerException(InvalidCustomerCause invalidCustomerCause) {
		this.invalidCustomerCause = invalidCustomerCause;
	}

	public InvalidCustomerCause getInvalidCustomerCause() {
		return invalidCustomerCause;
	}

}

Conforme podemos observar no enum InvalidCustomerCause, para que um Customer possa ser inserido, ele deve possuir os campos “id” e “name” preenchidos e, além disso, o valor do campo “id” não pode ter sido usado por nenhum outro Customer do sistema. Para finalizar o código da nossa aplicação, vamos criar a classe CustomerServices, responsável por manter os Customers adicionados em memória e garantir que somente Customers válidos sejam adicionados.

CustomerServices.java
package com.wordpress.lucianomolinari.spockbasics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.wordpress.lucianomolinari.spockbasics.InvalidCustomerException.InvalidCustomerCause;

/**
 * Responsible for managing customers.
 * 
 * @author Luciano Molinari
 */
public class CustomerServices {

	/**
	 * Simple map to keep customers in memory.
	 */
	private static Map<Long, Customer> customers = new LinkedHashMap<Long, Customer>();

	/**
	 * Initializes the map {@link #customers}.
	 */
	public void init() {
		customers = new LinkedHashMap<>();
	}

	/**
	 * Persists a new {@link Customer} in the system.
	 * 
	 * @param customer
	 *            The {@link Customer} to be persisted.
	 * @throws InvalidCustomerException
	 *             If the {@link Customer} is not valid to be persisted.
	 */
	public void add(Customer customer) throws InvalidCustomerException {
		if (customer.getId() == null) {
			throw new InvalidCustomerException(InvalidCustomerCause.ID_NOT_INFORMED);
		}
		if (customer.getName() == null) {
			throw new InvalidCustomerException(InvalidCustomerCause.NAME_NOT_INFORMED);
		}
		if (customers.containsKey(customer.getId())) {
			throw new InvalidCustomerException(InvalidCustomerCause.DUPLICATED_ID);
		}
		customers.put(customer.getId(), customer);
	}

	/**
	 * @return A {@link List} with all the {@link Customer} of the system.
	 */
	public List<Customer> findAll() {
		return Collections.unmodifiableList(new ArrayList<>(customers.values()));
	}

}

Com isso, nossa aplicação está completa e podemos criar uma classe de testes usando Spock para garantir que tudo está funcionando conforme esperado. Para isso, criaremos a classe com.wordpress.lucianomolinari.spockbasics.TestCustomerServices.groovy dentro de src/test/java, como qualquer outra classe de testes.

TestCustomerServices.groovy
package com.wordpress.lucianomolinari.spockbasics

import com.wordpress.lucianomolinari.spockbasics.InvalidCustomerException.InvalidCustomerCause;

import spock.lang.Specification;

class TestCustomerServices extends Specification {

	private CustomerServices customerServices
	
	def setup() {
		customerServices = new CustomerServices()
		customerServices.init()
	}
	
	def "adds a new customer and finds him"() {
		given: "There is no customer in the system"
		customerServices.findAll().size() == 0
		
		and: "A customer called 'Joe' and identified by id 1"
		Customer joe = new Customer(1, "Joe")
		
		when: "Joe is inserted in the system"
		customerServices.add(joe)
		
		then: "Only one customer should exist in the system"
		List<Customer> customers = customerServices.findAll()
		customers.size() == 1
		
		and: "This customer should be Joe"
		customers.get(0).id == 1
		customers.get(0).name == "Joe"
	}
	
	def "adds a new customer with invalid data"(Long id, String name, InvalidCustomerCause expectedCause) {
		setup: "There is already a customer called John with id 1 in the system"
		customerServices.add(new Customer(1, "John"))
		
		when: "There is a request to insert a customer with the given parameters"
		customerServices.add(new Customer(id, name))
		
		then: "An error of type InvalidCustomerException should be thrown"
		InvalidCustomerException error = thrown(InvalidCustomerException.class)
		error.invalidCustomerCause == expectedCause
		
		where:
		id		| name			| expectedCause
		null	| "John"		| InvalidCustomerCause.ID_NOT_INFORMED
		1		| null			| InvalidCustomerCause.NAME_NOT_INFORMED
		1		| "Carl"		| InvalidCustomerCause.DUPLICATED_ID
	}
	
	def "adds a new customer with invalid data - version 2"(Long id, String name, InvalidCustomerCause expectedCause) {
		setup: "There is already a customer called John with id 1 in the system"
		customerServices.add(new Customer(1, "John"))
		
		when: "There is a request to insert a customer with the given parameters"
		customerServices.add(new Customer(id, name))
		
		then: "An error of type InvalidCustomerException should be thrown"
		InvalidCustomerException error = thrown(InvalidCustomerException.class)
		error.invalidCustomerCause == expectedCause
		
		where:
		id << [null, 1, 1]
		name << ["John", null, "Carl"]
		expectedCause << [InvalidCustomerCause.ID_NOT_INFORMED, InvalidCustomerCause.NAME_NOT_INFORMED, 
			InvalidCustomerCause.DUPLICATED_ID]
	}
	
}

Algumas explicações gerais:

  • Toda classe de testes que use o Spock deve extender spock.lang.Specification
  • O método setup() é chamado antes da execução de cada um dos testes definidos na classe.
  • Os métodos com código de teste são os que tem seu nome definido entre “”.

Vamos agora analizar cada um dos testes:

“adds a new customer and finds him”

Essa string é o nome do caso de teste e é esse nome que você verá quando executá-lo via Maven ou Eclipse. O Spock provê as diretivas “given/when/then” para a construção de testes e permite que cada diretiva dessa receba uma String explicando o objetivo daquele bloco. Quando alguma dessas diretivas possue mais de 1 instrução, você pode escrevê-las uma embaixo da outra, ou separá-las com a diretiva “and”, conforme fizemos nesse caso de teste.
O código que estiver no bloco “then” e nos seus “and”s é responsável por fazer as asserções do teste e verificar o resultado esperado.

“adds a new customer with invalid data”

Esse caso de teste traz 2 conceitos interessantes: testes parametrizados e verificação de erros. Para a parametrização desse teste, definimos na assinatura do método quais são os valores parametrizáveis e através da diretiva “where”, informamos ao Spock que ele deve executar esse método 3 vezes, uma para cada linha da tabela com os respectivos valores.
Para verificação de exceções que devem ser lançadas, usamos o método thrown na cláusula then. Isso informa ao Spock que, caso uma exceção do tipo informado não seja lançada, o teste deve falhar. A atribuição da exceção à uma variável é opcional e só faz sentido em casos que precisamos fazer algo com a variável, como no caso mostrado.

“adds a new customer with invalid data – version 2″

Exatamente igual ao caso de teste acima, a única diferença é na forma de utilização da diretiva “where” para definição dos parâmetros de execuções do teste. Nesse caso, ao invés de criarmos uma tabela, definimos uma variável para cada um dos parâmetros e seus possíveis valores.

Para executarmos a classe de teste, podemos usar o Maven ou o Eclipse.

Executando os testes via Maven

Basta executar o comando “mvn clean install” ou “mvn clean package” que todas as classes de teste serão executadas normalmente.

Executando os testes via Eclipse

Basta clicar com o botão direito sobre o nome da classe de teste, “Run as” -> “JUnit Test”. Importante notar para que o Eclipse reconheça a classe de teste groovy e forneça recursos como auto-complete, é necessário instalar o Plugin para o Groovy “Groovy/Grails Tool Suite (GGTS)”, de acordo com a sua versão do Eclipse. Esse plugin pode ser instalado facilmente através do Eclipse Marketplace.

Conclusão

O objetivo desse artigo foi de apresentar o framework Spock e como integrá-lo ao Maven e ao Eclipse. O site do Spock fornece uma documentação bem completa com todas as features do framework explicadas em detalhes e com exemplos. O código fonte do projeto pode ser encontrado aqui.

Publicado em Testes | Etiquetas , , , | Deixe o seu comentário

Processamento assíncrono com ExecutorServices – Parte 2

Seguindo o que foi apresentado na primeira parte do artigo, vamos falar mais um pouco sobre processamento assíncrono com ExecutorServices. Nessa segunda parte vamos abordar o seguinte caso de uso.
Nosso software receberá e processará um lote de transações. Ao final do processamento desse lote, o sistema deve fazer um “resumo” do processamento, indicando quantas transações foram processadas com sucesso e quantas foram processadas com erro. Esse resumo poderia ser enviado à um outro sistema, por exemplo, que poderia analisar a taxa de sucesso desse lote de transações que foi processado ou algo similar.

Vamos começar com a definição da classe que representa a transação. É uma classe bastante simples, que possui um id e um método que faz o processamento e retorna um booleano indicando se a transação foi ou não executada com sucesso.

Transaction.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

import org.apache.log4j.Logger;

import com.wordpress.lucianomolinari.taskexecutor.unrelatedtask.SimpleTask;

public final class Transaction {
	private final Long id;
	private static final Logger logger = Logger.getLogger(SimpleTask.class);

	public Transaction(Long id) {
		this.id = id;
	}

	/**
	 * Returns a boolean indicating if the transaction was processed
	 * successfully
	 * 
	 * @return
	 */
	public boolean process() {
		// waits 5 milliseconds(simulates a slow task, such as accessing a remote system)
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// error every 10 transactions
		boolean success = id % 10 != 0;
		logger.debug("Transaction processed: " + id + "; Result: " + success);
		return success;
	}

	public Long getId() {
		return id;
	}

	@Override
	public String toString() {
		return "Transaction [id=" + id + "]";
	}

}

Como o output final do sistema deve ser um resumo de execução do lote, vamos criar uma classe para armazenar esses dados.

ExecutionSummary.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

public class ExecutionSummary {
	private int numberOfOk;
	private int numberOfError;

	public void addOk() {
		numberOfOk++;
	}

	public void addError() {
		numberOfError++;
	}

	public void addExecutionSummary(ExecutionSummary executionSummary) {
		numberOfOk += executionSummary.getNumberOfOk();
		numberOfError += executionSummary.getNumberOfError();
	}

	public int getNumberOfOk() {
		return numberOfOk;
	}

	public int getNumberOfError() {
		return numberOfError;
	}

	@Override
	public String toString() {
		return "ExecutionSummary [numberOfOk=" + numberOfOk + ", numberOfError=" + numberOfError + "]";
	}

}

Agora que já temos as classes usadas no input e output do sistema, podemos começar a atacar a parte do processamento. Como a idéia é mostrar mais uma vez a diferença entre o processamento sequencial e paralelo, vamos definir uma interface comum de execução e uma implementação para cada um dos tipos de processamento. A interface é bastante simples, recebendo uma lista de transações a serem processadas e retornando o resumo da execução.

TransactionExecutor.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

import java.util.List;

public interface TransactionExecutor {

	/**
	 * Executes a {@link List} of {@link Transaction} and returns its execution
	 * summary, containing the number of transactions which were executed with
	 * success and with error
	 * 
	 * @param transactions
	 * @return
	 */
	ExecutionSummary execute(List<Transaction> transactions);

}

A implementação sequencial simplesmente itera sobre a lista e, conforme vai processando as transações, vai contabilizando os resultados.

TransactionExecutorSequential.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

import java.util.List;

public class TransactionExecutorSequential implements TransactionExecutor {

	@Override
	public ExecutionSummary execute(List<Transaction> transactions) {
		ExecutionSummary summary = new ExecutionSummary();
		for (Transaction transaction : transactions) {
			if (transaction.process()) {
				summary.addOk();
			} else {
				summary.addError();
			}
		}
		return summary;
	}

}

A classe de processamento paralelo é um pouco mais complexa. A idéia é dividir o lote de transações em sub-lotes, passar o processamento de cada um desses sub-lotes para uma Thread separada, obter o resultado parcial do processamento de cada sub-lote e, ao final, criar um resultado consolidado em cima desses dados. A figura abaixo ilustra essa idéia.

O interessante é que, como os processos estão sendo executados em paralelo, precisamos usar algum artifício para saber que as threads terminaram suas execuções e que já podemos obter os resultados parciais para fazer a consolidação.
O framework ExecutorServices oferece todos os recursos que necessitamos, conforme apresentado na classe abaixo. Assim como na primeira parte do artigo, estamos criando um pool de 10 Threads para executar as tarefas. Sendo assim, no início do método execute() calculamos a quantidade de transações que cada sub-lote deverá conter, considerando 10 sub-lotes (1 para cada Thread do pool). Feito isso, passamos cada sub-lote desses para ser executado. Diferentemente da primeira parte do artigo, onde usamos a interface Runnable para execução, nesse caso estamos utilizando a Callable. A grande diferença é que essa última permite que retornemos um resultado ao final da execução, como um método qualquer (lembre-se de que precisamos obter o resultado parcial da execução de cada processo).
Mas então surge outra pergunta: Se os processos estão sendo executados em outra Thread, como podemos obter esses resultados. A “mágica” está no objeto Future retornado pela Callable. Como o próprio nome já diz, esse objeto permite que possamos obter a resposta no “futuro”. Dessa forma, o código armazena cada um dos 10 objetos Future em uma lista para obtenção da resposta posteriormente.
O último loop do código faz justamente isso. O método get() do objeto Future fica “travado” até que a Thread a qual ele está associada termine sua execução. Assim, esse simples loop que realizamos garante que ele só será finalizado quando todas as Threads já estiverem terminado suas execuções. Por final, o programa consolida todos os resultados em um único resultado final.

TransactionExecutorParallel.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TransactionExecutorParallel implements TransactionExecutor {

	private final ExecutorService executor = Executors.newFixedThreadPool(10);

	@Override
	public ExecutionSummary execute(List<Transaction> transactions) {
		int numberOfElementsPerList = transactions.size() / 10;
		List<Future<ExecutionSummary>> summariesToBeGet = new ArrayList<Future<ExecutionSummary>>();
		int iniPos = 0;
		for (int i = 0; i < 10; i++) {
			final List<Transaction> subList = transactions.subList(iniPos, iniPos + numberOfElementsPerList);
			Future<ExecutionSummary> future = executor.submit(new Callable<ExecutionSummary>() {
				@Override
				public ExecutionSummary call() throws Exception {
					ExecutionSummary summary = new ExecutionSummary();
					for (Transaction transaction : subList) {
						if (transaction.process()) {
							summary.addOk();
						} else {
							summary.addError();
						}
					}
					return summary;
				}
			});
			summariesToBeGet.add(future);
			iniPos += numberOfElementsPerList;
		}
		ExecutionSummary summaryFinal = new ExecutionSummary();
		for (Future<ExecutionSummary> futureSummary : summariesToBeGet) {
			try {
				summaryFinal.addExecutionSummary(futureSummary.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}

		return summaryFinal;
	}

}

Para verificar a diferença de processamento entre as implementações paralela e sequencial, criamos uma classe de testes.

TestTransactionExecutor.java
package com.wordpress.lucianomolinari.taskexecutor.relatedtask;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.junit.Test;

public class TestTransactionExecutor {
	private final Logger logger = Logger.getLogger(TestTransactionExecutor.class);

	@Test
	public void testSequential() {
		doTest(new TransactionExecutorSequential());
	}

	@Test
	public void testParallel() {
		doTest(new TransactionExecutorParallel());
	}

	private void doTest(TransactionExecutor transactionExecutor) {
		logger.debug("--------------");
		List<Transaction> transactions = getListOfTransactions();
		logger.debug("Executing transactions using " + transactionExecutor.getClass().getSimpleName());
		ExecutionSummary summary = transactionExecutor.execute(transactions);
		logger.debug("Transactions executed successfully");
		assertEquals(90, summary.getNumberOfOk());
		assertEquals(10, summary.getNumberOfError());
	}

	private List<Transaction> getListOfTransactions() {
		List<Transaction> transactions = new ArrayList<Transaction>();
		for (int i = 1; i <= 100; i++) {
			transactions.add(new Transaction(Long.valueOf(i)));
		}
		return transactions;
	}

}

Ao executar essa classe, obtemos o seguinte output.

23:59:05,668 - --------------
23:59:05,670 - Executing transactions using TransactionExecutorSequential
23:59:05,675 - Transaction processed: 1; Result: true
23:59:05,680 - Transaction processed: 2; Result: true
23:59:05,686 - Transaction processed: 3; Result: true
23:59:05,691 - Transaction processed: 4; Result: true
....
23:59:06,188 - Transaction processed: 97; Result: true
23:59:06,194 - Transaction processed: 98; Result: true
23:59:06,199 - Transaction processed: 99; Result: true
23:59:06,204 - Transaction processed: 100; Result: false
23:59:06,204 - Transactions executed successfully
23:59:06,217 - --------------
23:59:06,217 - Executing transactions using TransactionExecutorParallel
23:59:06,224 - Transaction processed: 1; Result: true
23:59:06,224 - Transaction processed: 11; Result: true
23:59:06,224 - Transaction processed: 21; Result: true
23:59:06,224 - Transaction processed: 31; Result: true
...
23:59:06,272 - Transaction processed: 80; Result: false
23:59:06,272 - Transaction processed: 20; Result: false
23:59:06,272 - Transaction processed: 90; Result: false
23:59:06,273 - Transaction processed: 100; Result: false
23:59:06,273 - Transactions executed successfully

Assim como na primeira parte do artigo, repare que o processamento paralelo (56ms) foi muito mais rápido que o processamento sequencial(534ms).

Conclusão

Essa técnica utilizada nessa parte do artigo lembra o conceito do Map Reduce, onde o processamento de uma tarefa é dividido entre várias máquinas e, ao final, é feita a consolidação dos resultados. Porém, o Map Reduce é muito mais poderoso para processamento de grandes volumes de dados, pois permite que esse seja feito entre várias máquinas, enquanto que o exemplo desse artigo permite a divisão entre processos da mesma máquina. Entretanto, como pudemos ver, a implementação apresentada é bastante simples e pode ser usada com bastante eficiência em cenários onde o volume de dados não seja tão big assim!
Outro detalhe é que essa técnica é conhecida como fork-join e foi adicionada como uma feature nativa do Java 7. No artigo preferi usar o ExecutorServices, mas o conceito é exatamente o mesmo.
Espero que tenha ficado claro como o ExecutorServices pode ajudar no melhor uso dos recursos disponíveis das máquinas existentes nos dias atuais, com vários núcleos de processadores. Como pudemos ver, os ganhos são bastante expressivos.

O código completo pode ser obtido aqui.

Publicado em Paralelismo | Etiquetas , , , | Deixe o seu comentário

Processamento assíncrono com ExecutorServices

Introdução

Com máquinas cada vez mais potentes e com mais núcleos de processadores, é fundamental que seus programas consigam tirar proveito desses recursos. Do que adianta ter uma máquina com 16 processadores, se seu programa executa toda sua lógica de forma sequencial em uma única thread?Sistemas de mensageria, como JMS e AMQP, ajudam bastante no processamento assíncrono, desacoplado e distribuído. Porém, em alguns casos, eles podem ser uma “bala para matar um mosquito”, pois além de trazerem o overhead de um message broker na arquitetura, podemos estar apenas querendo usar os processadores da máquina, quebrando uma única tarefa que teria que ser executada de forma sequencial e em uma única thread em um conjunto de tarefas a serem executadas de forma assíncrona e em várias threads.
Antes do Java 5, para realizar esse tipo de tarefa, era necessário criar e gerenciar as threads manualmente, o que não era uma tarefa muito agradável. O Java 5 trouxe um framework para processamento concorrente que ajuda bastante: Executor Services.

Exemplos

Para ilustrar o uso desse recurso, vamos abordar 2 exemplos.
O primeiro exemplo vai tratar da execução de uma lista de tarefas independentes, enquanto que o segundo irá tratar da execução de uma lista de tarefas dependentes, ou seja, o resultado final depende da execução de todas elas.
Nesse post iremos abordar o primeiro exemplo e, no próximo post, o segundo.

Projeto

Como usual, iremos utilizar o maven para o gerenciamento de dependências e de build. O pom.xml ficou definido como a seguir.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.lucianomolinari</groupId>
	<artifactId>taskexecutor</artifactId>
	<version>1.0.0</version>

	<properties>
		<junit.version>4.8.2</junit.version>
		<log4j.version>1.2.17</log4j.version>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

Vamos agora criar a classe que representa a tarefa a ser executada. Seu método de processamento é bastante simples. Ele apenas recebe uma String e, para simular uma tarefa demorada, “trava” a execução por alguns milissegundos e então faz o log da mensagem recebida.

SimpleTask.java
package com.wordpress.lucianomolinari.taskexecutor.unrelatedtask;

import org.apache.log4j.Logger;

/**
 * 
 * @author Luciano Molinari
 */
public final class SimpleTask {
	private static final Logger logger = Logger.getLogger(SimpleTask.class);
	private final String message;

	public SimpleTask(String message) {
		this.message = message;
	}

	public void execute() {
		// waits 5 milliseconds
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		logger.debug("Message processed: " + message);
	}

}

Como teste e para analisar as diferenças para tempo de processamento, o mesmo conjunto de tarefas será executado sequencialmente e em paralelo. Para isso, será definida uma interface para os executors sequencial e paralelo.

SimpleTaskExecutor.java
package com.wordpress.lucianomolinari.taskexecutor.unrelatedtask;

/**
 * Interface for executing {@link SimpleTask}
 * 
 * @author Luciano Molinari
 */
public interface SimpleTaskExecutor {

	void execute(final SimpleTask simpleTask);

}

A implementação sequencial é bastante simples e apenas delega a execução das tarefas para a classe SimpleTask definida anteriormente.

SimpleTaskExecutorSequential.java
package com.wordpress.lucianomolinari.taskexecutor.unrelatedtask;

/**
 * Execute {@link SimpleTask} in a sequential manner
 * 
 * @author Luciano Molinari
 */
public class SimpleTaskExecutorSequential implements SimpleTaskExecutor {

	@Override
	public void execute(final SimpleTask simpleTask) {
		simpleTask.execute();
	}

}

A implementação que executa as tarefas em paralelo utiliza o Executor do Java, conforme pode ser visto abaixo.

SimpleTaskExecutorParallel.java
package com.wordpress.lucianomolinari.taskexecutor.unrelatedtask;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Execute {@link SimpleTask} in a parallel manner
 * 
 * @author Luciano Molinari
 */
public class SimpleTaskExecutorParallel implements SimpleTaskExecutor {

	/**
	 * Creates a pool with 10 threads to execute jobs
	 */
	private final ExecutorService executor = Executors.newFixedThreadPool(10);

	@Override
	public void execute(final SimpleTask simpleTask) {
		executor.execute(new Runnable() {
			@Override
			public void run() {
				simpleTask.execute();
			}
		});
	}

}

Repare que é bem simples utilizar a classe Executors e a interface ExecutorService. O método newFixedThreadPool() cria um pool de Threads com a quantidade informada por parâmetro. Existem outros tipos de pool, que podem ser consultados na documentação da classe Executors.
Com o executor em mãos, é possível executar tanto objetos Runnable quanto Callable. A principal diferença é que o Runnable executa tarefas void, enquanto que o Callable pode retornar um valor. O uso do Callable será mostrado no próximo post sobre esse assunto.
Para validar a diferença de tempo de execução para as 2 implementações, vamos criar uma classe de testes com JUnit que executa 50 tarefas usando cada uma delas. A verificação do tempo gasto em cada abordagem será feito via mensagens de log mesmo, por simplicidade. Para isso, vamos configurar o log4j para que o mesmo mostre as mensagens no console.

log4j.properties
# Root logger option
log4j.rootLogger=DEBUG, stdout
 
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} - %m%n
TestSimpleTaskExecutor.java
package com.wordpress.lucianomolinari.taskexecutor.unrelatedtask;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.junit.Test;

/**
 * 
 * @author Luciano Molinari
 */
public class TestSimpleTaskExecutor {
	private final Logger logger = Logger.getLogger(TestSimpleTaskExecutor.class);

	@Test
	public void testSequential() {
		logger.debug("--------------");
		logger.debug("Executing tasks in a sequential manner...");
		SimpleTaskExecutor executor = new SimpleTaskExecutorSequential();
		for (SimpleTask task : getListToBeExecuted()) {
			executor.execute(task);
		}
	}

	@Test
	public void testParallel() {
		logger.debug("--------------");
		logger.debug("Executing tasks in a parallel manner...");
		SimpleTaskExecutor executor = new SimpleTaskExecutorParallel();
		for (SimpleTask task : getListToBeExecuted()) {
			executor.execute(task);
		}
		// it is necessary to wait for a moment so the threads can finish their
		// jobs
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private List<SimpleTask> getListToBeExecuted() {
		List<SimpleTask> tasks = new ArrayList<SimpleTask>();
		for (int index = 1; index <= 50; index++) {
			tasks.add(new SimpleTask("Message " + index));
		}
		return tasks;
	}

}

Ao executar essa classe, obtemos o seguinte output.

23:21:55,062 - --------------
23:21:55,063 - Executing tasks in a sequential manner...
23:21:55,069 - Message processed: Message 1
23:21:55,074 - Message processed: Message 2
23:21:55,079 - Message processed: Message 3
23:21:55,085 - Message processed: Message 4
23:21:55,090 - Message processed: Message 5
23:21:55,095 - Message processed: Message 6
....
23:21:55,307 - Message processed: Message 45
23:21:55,312 - Message processed: Message 46
23:21:55,318 - Message processed: Message 47
23:21:55,323 - Message processed: Message 48
23:21:55,328 - Message processed: Message 49
23:21:55,333 - Message processed: Message 50

23:21:55,334 - --------------
23:21:55,334 - Executing tasks in a parallel manner...
23:21:55,344 - Message processed: Message 1
23:21:55,344 - Message processed: Message 2
23:21:55,344 - Message processed: Message 3
23:21:55,344 - Message processed: Message 4
23:21:55,344 - Message processed: Message 5
...
23:21:55,365 - Message processed: Message 42
23:21:55,365 - Message processed: Message 48
23:21:55,365 - Message processed: Message 45
23:21:55,365 - Message processed: Message 49
23:21:55,365 - Message processed: Message 50

Repare que no teste sequencial, todas as mensagens são mostradas na ordem em que foram enviadas, o que não ocorre com o teste parelelo, já que existem 10 threads executando as tarefas.
O que realmente chama a atenção é a questão do tempo de processamento em cada cenário. Veja que, no primeiro teste, entre o print do início do método de teste e o da última mensagem, existe um intervalo de 270ms (23:21:55,063 e 23:21:55,333), enquanto que no segundo esse tempo é de apenas 31ms (23:21:55,334 e 23:21:55,365).
Vale ressaltar que o resultado desse teste depende da quantidade de processadores presentes na máquina, podendo fazer com que esse tempo seja um pouco pior ou até melhor.

O código fonte apresentado pode ser encontrado aqui.

Na próximo posto daremos continuidade sobre esse assunto. Espero que tenham gostado, até a próxima!

Publicado em Paralelismo | Etiquetas , , , | 3 Comentários

TDC 2012 – Java e AMQP: Uma alternativa ao JMS

Durante o TDC desse ano, dei uma palestra sobre Java e AMQP, no lado B da trilha de Java EE. Infelizmente, na hora de mostrar uma aplicação integrando Java com o Rabbit MQ, o notebook com Ubuntu travou e não teve jeito, provavelmente por algum problema com o driver da placa de vídeo ou algo do tipo. :(
Esse post é para apresentar o código que eu fiquei “devendo” durante a palestra. Lembrando que, para poder executá-lo, antes é necessário instalar o Rabbit MQ. Em seu site, existe o procedimento para instalação em diversas plataformas.

Aplicação

A aplicação é bem simples e é consituída de uma classe que envia e outra que recebe mensagens via AMQP, além de uma classe de testes para chamar a classe de envio. Como o projeto é baseado no spring, teremos 3 arquivos de configuração, sendo 1 com a parte genérica (declaração de exchange, queue e binding), 1 com a configuração do sender e outro para o consumer.
Como ferramenta de build, como sempre, está sendo utilizado o maven. O pom do projeto ficou da seguinte maneira:

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>br.com.tdc.javaee</groupId>
	<artifactId>javaamqp</artifactId>
	<version>1.0.0</version>

	<properties>
		<spring.version>3.1.0.RELEASE</spring.version>
		<spring-amqp.version>1.1.1.RELEASE</spring-amqp.version>
		<amqp-client.version>2.8.1</amqp-client.version>
		<log4j.version>1.2.17</log4j.version>
		<cglib.version>2.2</cglib.version>
		<junit.version>4.8.2</junit.version>
	</properties>

	<repositories>
		<repository>
			<id>spring-milestone</id>
			<name>Spring Maven MILESTONE Repository</name>
			<url>http://maven.springframework.org/milestone</url>
		</repository>
	</repositories>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit</artifactId>
			<version>${spring-amqp.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-amqp</artifactId>
			<version>${spring-amqp.version}</version>
		</dependency>
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>${amqp-client.version}</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib-nodep</artifactId>
			<version>${cglib.version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

Arquivos de configuração do Spring.

applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

		http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Arquivo com as configuracoes principais do Rabbit MQ -->

	<!-- define o connection factory -->
	<rabbit:connection-factory id="connectionFactory"
		host="localhost" username="guest" password="guest" port="5672"
		channel-cache-size="10" />

	<!-- define o exchange a ser criado, caso ainda nao exista -->
	<rabbit:direct-exchange name="exchange.tdc">
		<!-- faz o binding do exchange com uma fila -->
		<rabbit:bindings>
			<rabbit:binding queue="test.queue" key="test.queue" />
		</rabbit:bindings>
	</rabbit:direct-exchange>

	<!-- cria a fila, caso ainda nao exista -->
	<rabbit:queue name="test.queue" />

	<rabbit:admin connection-factory="connectionFactory" />

</beans>
applicationContext-sender.xml
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

		http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Arquivo com as configuracoes do enviador da fila de mensagens via Rabbit MQ -->

	<!-- Importa o arquivo com as configuracoes principais do Rabbit MQ -->
	<import resource="applicationContext.xml" />

	<context:annotation-config />

	<context:component-scan base-package="br.com.tdc.javaee.javaamqp.sender" />

	<!-- configura o template usado para enviar mensagens -->
	<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
		queue="test.queue" routing-key="test.queue" exchange="exchange.tdc" />

</beans>
applicationContext-consumer.xml
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

		http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Arquivo com as configuracoes do consumidor da fila de mensagens via Rabbit MQ -->

	<!-- Importa o arquivo com as configuracoes principais do Rabbit MQ -->
	<import resource="applicationContext.xml" />

	<context:annotation-config />

	<context:component-scan base-package="br.com.tdc.javaee.javaamqp.consumer" />

	<!-- Define o consumidor da fila, com ate 10 consumidores concorrentes -->
	<rabbit:listener-container
		connection-factory="connectionFactory" concurrency="10">
		<rabbit:listener ref="consumer" queue-names="test.queue" />
	</rabbit:listener-container>

</beans>

Consumidor

A classe consumidora de mensagens é um bean do Spring, com um método main para que ela possa ser executada de forma standalone. Ela implementa a interface org.springframework.amqp.core.MessageListener e sobreescreve o método onMessage(), que é chamado toda vez que chega uma nova mensagem na fila.

Consumer.java
package br.com.tdc.javaee.javaamqp.consumer;

import org.apache.log4j.Logger;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;

@Service
public class Consumer implements MessageListener {

	private Logger logger = Logger.getLogger(Consumer.class);

	public static void main(String[] args) {
		new ClassPathXmlApplicationContext("/applicationContext-consumer.xml");
	}

	@Override
	public void onMessage(Message message) {
		String msg = new String(message.getBody());
		logger.info("----------Recebeu msg " + msg);
	}

}

Produtor

A classe que publica mensagens na fila é ainda mais simples, pois quase todo o trabalho é feito pela classe org.springframework.amqp.core.AmqpTemplate, injetada automaticamente pelo Spring.

AMQPSender.java
package br.com.tdc.javaee.javaamqp.sender;

import org.apache.log4j.Logger;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AMQPSender {

	@Autowired
	private AmqpTemplate amqpTemplate;

	private Logger logger = Logger.getLogger(AMQPSender.class);

	public void sendMessage(String message) {
		logger.info("Enviando mensagem " + message);
		amqpTemplate.convertAndSend(message.getBytes());
	}

}

Classe de teste

Para finalizar, foi criada uma simples classe com JUnit que chama a classe AMQPSender para fazer o envio de mensagens.

TestAMQPSender.java
package br.com.tdc.javaee.javaamqp.sender;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import br.com.tdc.javaee.javaamqp.sender.AMQPSender;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext-sender.xml")
public class TestAMQPSender {

	@Autowired
	private AMQPSender amqpSender;

	@Test
	public void testSend() {
		amqpSender.sendMessage("Test Message");
	}

}

Executando a aplicação

Primeiramente execute a classe Consumer normalmente. Pelo Eclipse, basta executar como “Java Application”. Na primeira execução, o Spring irá criar os elementos no Rabbit MQ, como o exchange, a queue e o binding entre o exchange e o queue. Essa classe ficará rodando “eternamente”, esperando e recebendo as mensagens que vão chegando na fila.
Para fazer o envio de mensagens e ver o consumidor recebendo-as, basta executar a classe de teste TestAMQPSender como “JUnit Test”.

Informações

O post de hoje foi bem mais objetivo e com menos explicação, pelo fato de ter mais informações na palestra.
O código do projeto pode ser obtido no github. A apresentação pode ser vista no
slideshare. O site do RabbitMQ possui bastante informação útil e vale a pena dar uma olhada.
Qualquer dúvida, é só usar o espaço para comentários. Abraços.

Publicado em Jee, Spring | Etiquetas , , , , | 1 Comentário

Entendendo os atributos de transação

Nesse post vamos falar sobre um assunto pouco explorado pela maioria dos desenvolvedores: Atributos de Transação. Quando estamos desenvolvendo um serviço (seja com EJB ou Spring), podemos fazer o controle manual da transação (BTM – Bean Managed Transaction) ou deixar com que o container faça isso para nós de forma automática (CMT – Container Managed Transaction). Na maioria dos casos é usado o gerenciamento CMT e a configuração é feita de forma declarativa. É justamente para essa configuração declarativa que existem os atributos de transação. Eles definem por exemplo se seu método deve obrigatoriamente ou não ser invocado dentro de um escopo transacional.

Apenas para facilitar, sempre que for citado o termo cliente, significa que estamos falando do código “chamador”, que pode ser uma aplicação standalone, um outro EJB/Serviço Spring, etc.

Tipos de atributos de Transação

Existem 6 tipos de atributos de transação.

Required

Esse é o atributo padrão. Significa que o método a ser invocado pode ou não ser invocado dentro do escopo de uma transação, mas que obrigatoriamente ele será executado dentro de uma transação.
Se o cliente o invocar dentro de uma transação, o método será executado dentro dessa mesma transação, ficando a cargo do cliente efetuar o commit/rollback. Se o cliente não estiver dentro de uma transação, será criada uma nova transação para execução do método e, no final de sua execução, essa transação sofrerá o commit/rollback. Esse atributo é o usado na grande maioria dos casos.

RequiresNew

Significa que independentemente do cliente estar rodando ou não dentro de uma transação, uma nova transação será criada para a execução desse método e, no final de sua execução, essa transação sofrerá o commit/rollback.
Se o cliente já estiver dentro de uma transação, essa fica suspensa até o término da transação que foi criada para a execução do método. É importante notar que são 2 transações distintas, dessa forma uma não afeta a outra.

NotSupported

Significa que o método sempre será executado fora de uma transação. Se o cliente estiver dentro de uma transação, essa é suspensa até que o método execute seu código e retorne. Como o código é executado fora de uma transação, ele não afeta a transação que já estava aberta. Se o cliente não estiver no escopo de uma transação, o método será executado normalmente sem criar nenhuma transação.

Supports

Significa que o método será executado dentro ou fora de uma transação, dependendo do cliente. Se o cliente chamar o método fora de uma transação, nenhuma será criada e o método será executado fora de uma transação. Se o cliente chamar dentro de uma transação, o método será executado dentro dessa mesma transação.

Mandatory

Significa que sempre que o cliente for chamar o método, ele já deve estar em uma transação. Nesse caso, o método será executado dentro dessa mesma transação. Caso o cliente chame o método fora de uma transação, um erro será lançado e o código do método não será executado.

Never

Significa que o cliente nunca deve estar envolvido em uma transação do momento da chamada do método. Nesse caso, o método será executado normalmente sem criar nenhuma transação. Caso o cliente esteja envolvido em uma transação no momento da chamada, um erro será lançado e o código do método não será executado.

Definindo o atributo de transação

Vamos ver agora brevemente, como declarar o atributo de transação para seus serviços EJB ou Spring.

EJB

A forma mais simples de definir um atributo de transação em um EJB é através de anotações. Essa declaração pode ser feita a nível de classe, de método ou de ambos. Para a declaração, a anotação @javax.ejb.TransactionAttribute deve ser usada e são aceitos os seguintes valores (do tipo javax.ejb.TransactionAttributeType):

  • TransactionAttributeType.REQUIRED
  • TransactionAttributeType.REQUIRES_NEW
  • TransactionAttributeType.NOT_SUPPORTED
  • TransactionAttributeType.SUPPORTS
  • TransactionAttributeType.MANDATORY
  • TransactionAttributeType.NEVER

Exemplo:

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;

@Stateless
//TransactionAttibute só é válido se TransactionManagement = TransactionManagementType.CONTAINER.
//Esse é o valor default, então essa anotação pode ser omitida
@TransactionManagement(TransactionManagementType.CONTAINER)
//Required é o default, pode-se omitir essa anotação
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Services {

	//sem anotação assume o definido no nível da classe
	public void service1() {

	}

	//sobreescreve para esse método o valor definido a nível de classe
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void service2() {

	}

}

Como qualquer configuração de EJB, pode-se usar XML se preferir. Maiores informações podem ser obtidas aqui.

Spring

A configuração de transação de forma declarativa no Spring é bem parecida com EJB e também pode ser feita através de anotações ou xml. Maiores detalhes podem ser encontrados aqui e aqui.

Conclusões

Apesar das configurações default de atributos de transação serem suficientes para a grande maioria dos casos, é importante conhecer quais são as opções que podem ser usadas, dessa forma o desenvolvedor pode realizar ajustes finos e necessários para que aplicação funcione corretamente de acordo com seus requisitos.

Publicado em Jee | Etiquetas , , , , | 4 Comentários

Testando aplicações JEE6 com Arquillian

Criar testes automatizados para aplicações JEE sempre foi um desafio, pois exige que um container esteja funcionando para prover os serviços necessários que a aplicação precisa para funcionar e consequentemente para que os testes possam ser executados.

O framework Arquillian vem para facilitar -e muito- essa tarefa. Seu objetivo é prover uma plataforma de testes poderosa que cuida de vários aspectos ao testar uma aplicação JEE, como fazer o deploy da aplicação no container, subir o container, executar os testes e derrubar o container. Ele também permite que você enriqueça suas classes de testes usando anotações como @Inject, @EJB e @Resource. Dessa forma você pode criar testes funcionais bem mais reais e eficazes.

Ele também permite que o usuário escolha tanto o JUnit como com o TestNG para criação e execução dos testes, além de permitir a execução em vários containers, como JBoss, Glasshfish e Tomcat.

O objetivo desse post não é mostrar os benefícios de escrever testes automatizados -usando ou não TDD-, e sim focar no uso do Arquillian através de um exemplo prático. Apenas um comentário sobre escrever testes para seu código: Não acho que seja necessário escrever testes para 100% do código que você produz, mas entregar código sem teste pelo menos para as partes mais importantes, é entregar código pela metade.

Aplicação usando Arquillian

Para mostrar o uso do Arquillian, vamos criar um simples módulo/componente JEE6 cujo objetivo é permitir o cadastro e busca de clientes. Para empacotamento e gerenciamento de builds, será utilizado o maven. O container utilizado será o JBoss 7.1.0-final e para executar os testes será usado o JUnit 4.8.2. Como banco de dados, será usado o próprio HQSLDB,
banco de dados embarcado do JBoss e o datasource ExampleDS que já vem configurado.
No final do post é disponibilizado link para download do projeto.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.lucianomolinari</groupId>
	<artifactId>exemplo_arquillian</artifactId>
	<version>1.0.0</version>

	<properties>
		<version.arquillian_core>1.0.0.CR7</version.arquillian_core>
		<version.arquillian_container>7.1.0.Final</version.arquillian_container>
	</properties>

	<repositories>
		<repository>
			<id>JBoss repository</id>
			<url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
		</repository>
	</repositories>

	<!-- Define o profile para execucao dos testes unitarios com Arquillian -->
	<profiles>
		<profile>
			<id>jbossas-7</id>
			<dependencies>
				<dependency>
					<groupId>org.jboss.arquillian.junit</groupId>
					<artifactId>arquillian-junit-container</artifactId>
					<version>${version.arquillian_core}</version>
					<scope>test</scope>
				</dependency>
				<dependency>
					<groupId>org.jboss.as</groupId>
					<artifactId>jboss-as-arquillian-container-managed</artifactId>
					<version>${version.arquillian_container}</version>
					<scope>test</scope>
				</dependency>
			</dependencies>
		</profile>
	</profiles>

	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

O pom do projeto é bem simples. O destaque fica por conta do profile jbossas-7, que define as dependências para execução dos testes com o arquillian.
A primeira dependência importa a biblioteca core do arquillian, que contém as classes e anotações necessárias para criação das classes de testes. A segunda importa o módulo para execução dos testes no jboss-7.1.0.Final, que será executado no modo managed. O legal é que o seu código de teste não referencia nada dessa lib, somente da core. Isso significa que o mesmo código de teste pode ser usado em vários containers sem mudança no seu código. Na página do arquillian pode-se encontrar quais containers são suportados pelo framework e qual a diferença entre os modos de execução remote, managed e embedded.
No nosso exemplo estamos usando o modo managed, o que significa que o próprio arquillian vai subir e descer o servidor, além de fazer o deploy/undeploy da aplicação. É necessário que você tenha o JBoss instalado na sua máquina e que a variável JBOSS_HOME esteja corretamente configurada.

Código do módulo/componente

O componente a ser construído é bem simples e é constituido por:

  • Uma entidade Cliente para o mapeamento ORM
  • Uma interface Local com os métodos de negócio
  • Um Stateless Session Bean implementado a interface de negócio
  • Um Stateless Session Bean fazendo papel de DAO

O código Java criado no projeto é extremamente simples e dispensa maiores comentários.

Entidade Cliente – Cliente.java
package com.wordpress.lucianomolinari.exemplo_arquillian.cliente.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "cliente")
public class Cliente implements Serializable {
	private static final long serialVersionUID = 7796759140893817571L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	private String nome;

	//construtores, getters/setters, equals, hashCode e toString omitidos

}
Interface de negócio – ClienteServices.java
package com.wordpress.lucianomolinari.exemplo_arquillian.cliente.services;

import java.util.List;

import javax.ejb.Local;

import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.entity.Cliente;

@Local
public interface ClienteServices {

	Cliente add(Cliente cliente);

	List<Cliente> findAll();

}
Dao – ClienteDao.java
package com.wordpress.lucianomolinari.exemplo_arquillian.cliente.dao;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.entity.Cliente;

@Stateless
public class ClienteDao {

	@PersistenceContext
	private EntityManager em;

	public Cliente add(Cliente cliente) {
		em.persist(cliente);
		return cliente;
	}

	@SuppressWarnings("unchecked")
	public List<Cliente> findAll() {
		return em.createQuery("from Cliente c order by c.id").getResultList();
	}

}
Implementação da interface de negócio – ClienteServicesImpl.java
package com.wordpress.lucianomolinari.exemplo_arquillian.cliente.services.impl;

import java.util.List;

import javax.ejb.Stateless;
import javax.inject.Inject;

import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.dao.ClienteDao;
import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.entity.Cliente;
import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.services.ClienteServices;

@Stateless
public class ClienteServicesImpl implements ClienteServices {

	@Inject
	private ClienteDao clienteDao;

	public Cliente add(Cliente cliente) {
		return clienteDao.add(cliente);
	}

	public List<Cliente> findAll() {
		return clienteDao.findAll();
	}

}

E deve ser criado o arquivo de marcação beans.xml dentro de src/main/resources/META-INF.

Códigos/Recursos de teste

É composto por uma classe com o teste propriamente dita e pelo arquivo test-persistence.xml dentro de src/test/resources/.

test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
	<persistence-unit name="livrariaxPU" transaction-type="JTA">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
		<properties>
			<property name="hibernate.hbm2ddl.auto" value="create-drop" />
			<property name="hibernate.show_sql" value="true" />
		</properties>
	</persistence-unit>
</persistence>
TestClienteServices.java
package com.wordpress.lucianomolinari.exemplo_arquillian.cliente.services;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.List;

import javax.inject.Inject;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.wordpress.lucianomolinari.exemplo_arquillian.cliente.entity.Cliente;

@RunWith(Arquillian.class)
public class TestClienteServices {

	@Inject
	private ClienteServices clienteServices;

	@Deployment
	public static JavaArchive createTestArchive() {
		return ShrinkWrap.create(JavaArchive.class, "testCliente.jar")
				.addPackages(true, "com.wordpress.lucianomolinari.exemplo_arquillian.cliente")
				.addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"))
				.addAsManifestResource("test-persistence.xml", "persistence.xml");
	}

	@Test
	public void testAdicionaEConsultaClientes() {
		List<Cliente> clientes = clienteServices.findAll();

		assertNotNull(clientes);
		assertEquals(0, clientes.size());

		clienteServices.add(new Cliente("Cliente 1"));
		clienteServices.add(new Cliente("Cliente 2"));

		clientes = clienteServices.findAll();
		assertNotNull(clientes);
		assertEquals(2, clientes.size());
		assertEquals("Cliente 1", clientes.get(0).getNome());
		assertEquals("Cliente 2", clientes.get(1).getNome());
	}

}

Essa é classe mais importante do projeto, então vamos por partes:

A anotação @RunWith(Arquillian.class) indica que esse codigo JUnit deve ser executado pelo Arquillian. Sem essa anotação, a execução dos testes nao funcionaria. Repare na anotação @Inject, é como se estivesse em um código de produção, sem nenhuma diferença. Pode-se injetar qualquer tipo de classe CDI, EJB e Resource.

Para executar os testes o arquillian pega o codigo de teste, empacota e coloca pra rodar direto no servidor. Para isso, deve-se definir o tipo de empacotamento para os testes e quais arquivos serão empacotados. Isso é feito através de um método public e static com a anotação @Deployment e o tipo de retorno deve ser a forma como as classes ncessárias para a execução do teste serão empacotadas. Existem quatro formas: JavaArchive, EnterpriseArchive, ResourceAdapterArchive e WebArchive. No nosso exemplo, será utilizado JavaArchive.
Nesse método são feitas várias tarefas para o empacotamento:

  • O Arquillian usa a biblioteca ShrinkWrap para criar o pacote java em runtime
  • Na primeira linha é informado o tipo de empacotamento e o nome do pacote
  • Na segunda linha é definido quais classes serão empacotadas. No exemplo, é importado o pacote com.wordpress.lucianomolinari.exemplo_arquillian.cliente e todos abaixo dele, como cliente.entity, cliente.services e cliente.dao. Isso só é possível devido ao valor true no primeiro parâmetro, que diz que a importação deve ser recursiva. Também pode-se usar os métodos addClass e addClasses e todos esses podem ser usados em conjunto.
  • Na terceira linha é informado para criar um arquivo vazio chamando beans.xml e adicioná-lo como um manifest resource. O CDI precisa desse arquivo para funcionar.
  • Na última linha o arquivo test-persistence.xml é adicionado como um manifest resource com o nome persistence.xml. Isso é muito útil, pois pode-se ter um persistence.xml dentro src/main/resources/META-INF com os dados do banco real e outro (no caso src/test/resources/test-persistence.xml) para ser usado durante a execução dos testes. Lembrando que esse pacote gerado pelo arquillian não tem nada a ver com o artefato real, esse é um .jar a parte em que só são empacotados os arquivos/códigos informados por nós e a classe de teste. Por isso é possível copiar o test-persistence.xml como persistence.xml.

Por fim, para criação dos testes, basta usar JUnit da forma usual. Veja que não existe nenhum mock, é um teste de integração real, usando recursos reais.

Eu geralmente não uso o HSQLDB do JBoss para os testes. Eu prefiro ter 2 bases no banco real, uma para produção/testes manuais e outra para os testes automatizados. Porém isso pode gerar um problema, já que precisaríamos declarar os 2 datasources no JBoss. Como comentei em meu outro post sobre datasources no JBoss 7, é possível criar datasources de dentro da aplicação. Então, para resolver essa questão eu sigo a seguinte abordagem:

  • Crio um arquivo test-ds.xml (pode ser outro nome) dentro de src/test/resources e declaro um datasource apontando para o banco de dados real, porém para uma base de testes
  • Altero o arquivo src/test/resources/test-persistence.xml para apontar para o datasource de testes
  • Adiciono o arquivo test-ds.xml como um manifest resource na criação do arquivo de testes, adicionando a linha addAsManifestResource(“test-ds.xml”)

Dessa forma, o Arquillian vai empacotar o datasource de testes, o JBoss vai reconhecer o arquivo como um datasource (tem o sufixo *-ds.xml) e fazer seu deploy. Com isso, além do seu teste já ser bem funcional e não usar mocks, você também usa o BD real.

IDE

Para configurar o projeto e executar os testes em sua IDE preferida, basta dar uma conferida na seção “Getting Started” do Reference Guide.

Executando os testes via linha de comando

Para executar os testes pelo Maven via linha de comando, o seguinte comando deve ser utilizado:
mvn test -Pjbossas-7

Dessa forma os testes serão executados usando o profile jbossas-7, previamente configurado no pom.xml

Conclusões

A criação e execução de testes automatizados é uma tarefa imprescindível no desenvolvimento de software. Quanto mais real os testes, mais eficazes eles serão. O arquillian endereça justamente esse ponto de tornar seus testes mais reais e consequentemente mais eficazes, além de facilitar a criação de testes para aplicações JEE6.

O código fonte do projeto pode ser baixado aqui. Qualquer dúvida, fique a vontade em usar o espaço de comentários aqui do blog ou via twitter. Também não deixe de conferir a documentação do arquillian.

Publicado em JBoss, Jee | Etiquetas , , , , | 11 Comentários

Filas JMS com JEE 6 e JBoss 7

Nesse post será mostrado como configurar uma fila JMS no JBoss 7.1.0-Final e como utilizá-la através de uma aplicação JEE 6. Além disso, serão apresentados alguns pontos importantes como session transacional e configuração do número de consumidores da fila JMS.

Como exemplo, estará sendo usado o modo standalone do JBoss. Se preferir usar o modo domain, basta alterar os arquivos correspondentes aos apresentados aqui.

JMS

A idéia desse post não é mostrar em detalhes as características do JMS ou da sua especificação, mas vale a pena uma descrição alto nível.
JMS pode ser considerado um MOM (Message Oriented Middleware) que permite o envio e recebimento de mensagens assíncronas entre 1 ou mais clientes e que faz parte da especificação do Java EE. JMS provê dois modelos para troca de mensagens:

  • Queue: É uma fila ponto-a-ponto (point-to-point) na qual uma ponta (produtor) produz uma mensagem e a coloca em uma fila JMS e a outra ponta (consumidor) consome a mensagem da fila. No exemplo desse post, será usado esse modelo.
  • Topic: Segue o modelo publish/subscribe no qual uma ponta (publicador) coloca uma mensagen em um Tópico JMS e, para esse trópico, podem existir 0 ou mais “assinantes” que recebem todas as mensagens publicadas.

Para maiores detalhes, pode-se pesquisar no google ou olhar diretamente o JEE 6 Tutorial

Configurando fila JMS no JBoss

Ao invés de implementar a especificação JMS ele mesmo, o JBoss usa o HornetQ (http://www.jboss.org/hornetq), um projeto open-source que provê um sistema de mensageria bem robusto, performático e escalável. Para configurar uma fila JMS, será necessário alterar o arquivo JBOSS_HOME/standalone/configuration/standalone.xml que, por padrão, não traz o módulo JMS configurado. Sendo assim, será necessário copiar as configurações do arquivo standalone-full.xml que traz essas configurações por default. Abra o arquivo JBOSS_HOME/standalone/configuration/standalone.xml e edite os seguintes pontos:

Adicione o módulo de mensageria dentro de <extensions>

<extensions>
	...
	<extension module="org.jboss.as.messaging"/>
</extensions>

Adicione as configurações do sub-sistema de mensageria após <subsystem xmlns=”urn:jboss:domain:weld:1.0″/>

        <subsystem xmlns="urn:jboss:domain:messaging:1.1">
            <hornetq-server>
                <persistence-enabled>true</persistence-enabled>
                <journal-file-size>102400</journal-file-size>
                <journal-min-files>2</journal-min-files>

                <connectors>
                    <netty-connector name="netty" socket-binding="messaging"/>
                    <netty-connector name="netty-throughput" socket-binding="messaging-throughput">
                        <param key="batch-delay" value="50"/>
                    </netty-connector>
                    <in-vm-connector name="in-vm" server-id="0"/>
                </connectors>

                <acceptors>
                    <netty-acceptor name="netty" socket-binding="messaging"/>
                    <netty-acceptor name="netty-throughput" socket-binding="messaging-throughput">
                        <param key="batch-delay" value="50"/>
                        <param key="direct-deliver" value="false"/>
                    </netty-acceptor>
                    <in-vm-acceptor name="in-vm" server-id="0"/>
                </acceptors>

                <security-settings>
                    <security-setting match="#">
                        <permission type="send" roles="guest"/>
                        <permission type="consume" roles="guest"/>
                        <permission type="createNonDurableQueue" roles="guest"/>
                        <permission type="deleteNonDurableQueue" roles="guest"/>
                    </security-setting>
                </security-settings>

                <address-settings>
                    <address-setting match="#">
                        <dead-letter-address>jms.queue.DLQ</dead-letter-address>
                        <expiry-address>jms.queue.ExpiryQueue</expiry-address>
                        <redelivery-delay>0</redelivery-delay>
                        <max-size-bytes>10485760</max-size-bytes>
                        <address-full-policy>BLOCK</address-full-policy>
                        <message-counter-history-day-limit>10</message-counter-history-day-limit>
                    </address-setting>
                </address-settings>

                <jms-connection-factories>
                    <connection-factory name="InVmConnectionFactory">
                        <connectors>
                            <connector-ref connector-name="in-vm"/>
                        </connectors>
                        <entries>
                            <entry name="java:/ConnectionFactory"/>
                        </entries>
                    </connection-factory>
                    <connection-factory name="RemoteConnectionFactory">
                        <connectors>
                            <connector-ref connector-name="netty"/>
                        </connectors>
                        <entries>
                            <entry name="RemoteConnectionFactory"/>
                            <entry name="java:jboss/exported/jms/RemoteConnectionFactory"/>
                        </entries>
                    </connection-factory>
                    <pooled-connection-factory name="hornetq-ra">
                        <transaction mode="xa"/>
                        <connectors>
                            <connector-ref connector-name="in-vm"/>
                        </connectors>
                        <entries>
                            <entry name="java:/JmsXA"/>
                        </entries>
                    </pooled-connection-factory>
                </jms-connection-factories>

                <jms-destinations>
                    <jms-queue name="testQueue">
                        <entry name="queue/test"/>
                        <entry name="java:jboss/exported/jms/queue/test"/>
                    </jms-queue>
                    <jms-topic name="testTopic">
                        <entry name="topic/test"/>
                        <entry name="java:jboss/exported/jms/topic/test"/>
                    </jms-topic>
                </jms-destinations>
            </hornetq-server>
        </subsystem>

Essa é a configuração padrão e para o exemplo será utilizada a própria testQueue que já vem pré-configurada. Caso deseje criar outras filas, basta ir adicionando mais <jms-queue>. Para o exemplo, usaremos o connection-factory e a própria jms-queue.
Sobre esses, precisamos saber que temos 2 ConnectionFactory (um normal e outro transacional) que podem ser acessados pelos nomes java:/ConnectionFactory e java:/JmsXA respectivamente e uma fila que pode ser acessada tanto pelo nome java:/queue/test quanto pelo java:jboss/exported/jms/queue/test.

Uma descrição mais detalhada da configuração pode ser encontrada nas documentações do JBoss e do HornetQ.

Para finalizar a configuração do JBoss, é necessário configurar os socket-binding que foram referenciados em <connectors> e <acceptors>. Vá até a seção <socket-binding-group> e adicione as entradas:

...
        <socket-binding name="messaging" port="5445"/>
        <socket-binding name="messaging-throughput" port="5455"/>
...

Pronto, agora que o JBoss já está devidamente configurado, já podemos ir para a aplicação exemplo.

Aplicação exemplo

Nossa aplicação será bem simples e será empacotada em um simples .war. Ela terá:

  • Um simples POJO (Usuario) com um atributo String nome
  • Um Servlet responsável por criar uma mensagem (Usuario) e enviar para um EJB que fará o envio da mensagem para fila JMS
  • Um MDB responsável por ler mensagens da fila testQueue

Como ferramenta de build será usado o maven. A IDE a ser utilizada como livre escolha.

O pom.xml fica da seguinte maneira:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.lucianomolinari</groupId>
	<artifactId>exemplo_jms</artifactId>
	<version>1.0.1</version>
	<packaging>war</packaging>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

</project>

POJO que será enviado na mensagem JMS

package com.wordpress.lucianomolinari.exemplo_jms.entidade;

import java.io.Serializable;

public final class Usuario implements Serializable {
	private static final long serialVersionUID = 8734596722276424601L;

	private final String nome;

	public Usuario(String nome) {
		this.nome = nome;
	}

	public String getNome() {
		return nome;
	}

}

EJB responsável por fazer o envio da mensagem JMS

package com.wordpress.lucianomolinari.exemplo_jms.jms;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;

import com.wordpress.lucianomolinari.exemplo_jms.entidade.Usuario;

@Stateless
public class ProdutorDeMensagem {
	@Resource(mappedName = "java:/ConnectionFactory")
	private ConnectionFactory connectionFactory;

	@Resource(mappedName = "java:/queue/test")
	private Destination destination;

	private Connection connection;
	private Session session;
	private MessageProducer messageProducer;

	@PostConstruct
	public void init() {
		try {
			connection = connectionFactory.createConnection();
			session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			messageProducer = session.createProducer(destination);
		} catch (JMSException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	@PreDestroy
	public void destroy() {
		if (connection != null) {
			try {
				connection.close();
			} catch (JMSException e) {
				e.printStackTrace();
			}
		}
	}

	public void enviarMensagem(Usuario usuario) {
		ObjectMessage message;
		try {
			message = session.createObjectMessage(usuario);
			messageProducer.send(message);
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

}

MDB responsável por receber as mensagens JMS

package com.wordpress.lucianomolinari.exemplo_jms.jms;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

import com.wordpress.lucianomolinari.exemplo_jms.entidade.Usuario;

@MessageDriven(activationConfig = {
		@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
		@ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/queue/test") })
public class ConsumidorDeMensagem implements MessageListener {

	@Override
	public void onMessage(Message message) {
		ObjectMessage objMsg = (ObjectMessage) message;
		try {
			System.out.println("<<<<<<<<Recebendo mensagem com o usuario com nome "
					+ ((Usuario) objMsg.getObject()).getNome());
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

}

Sevlet que chama o produtor da mensgaem

package com.wordpress.lucianomolinari.exemplo_jms.web.servlet;

import java.io.IOException;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.wordpress.lucianomolinari.exemplo_jms.entidade.Usuario;
import com.wordpress.lucianomolinari.exemplo_jms.jms.ProdutorDeMensagem;

@WebServlet("/test")
public class ServletCriaMensagem extends HttpServlet {
	private static final long serialVersionUID = -973490316445738120L;

	@Inject
	private ProdutorDeMensagem produtorDeMensagem;

	@Override
	protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
		System.out.println(">>>>Criando mensagem de teste..");
		produtorDeMensagem.enviarMensagem(new Usuario("Usuario Teste"));
	}

}

Acessando a URL http://localhost:8080/exemplo_jms/test é possível perceber a mensagem sendo enviada e consumida.

Detalhes Importantes

Session Transacional

Quando é criado uma session JMS, o primeiro parâmetro informado (boolean) é se aquela session deve ou não ser transacional. No exemplo acima, criamos a session sem ser transacional:

session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

Porém nem sempre esse é o cenário desejado, principalmente porque em muitas vezes o envio da mensagem JMS estará dentro de uma transação que faz outras tarefas, como atualizar o BD por exemplo. Então para transformar a session em transacional, bastaria passar true como parâmetro:

session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

Infelizmente se você fizer apenas isso e rodar a aplicação, vai perceber que a mensagem nunca é enviada/consumida da fila. Isso ocorre pois o ConnectionFactory que foi usado (java:/ConnectionFactory) não foi configurado no JBoss para ser transacional. Para fazer com que a mensagem seja enviada normalmente com o session transacional, existem 2 abordagens:

Dar o commit explicitamente

Uma forma é explicitamente dar o commit na session após o envio da mensagem:

	public void enviarMensagem(Usuario usuario) {
		ObjectMessage message;
		try {
			message = session.createObjectMessage(usuario);
			messageProducer.send(message);
			session.commit();
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

Porém essa abordagem não é recomendada, pelo mesmo motivo que não é recomendado usar uma session não transacional: o envio da mensagem pode fazer parte de uma transação maior. Mas qual seria a solução então?

Usar um ConnectionFactory que suporte transação

Existe um outro ConnectionFactory configurado no JBoss (java:/JmsXA) que está configurado como transacional (<transaction mode=”xa”/>). Então pegando nosso código inicial, as únicas mudanças necessárias seriam:

Alterar o mappedName do nosso ConnectionFactory para java:/JmsXA

	@Resource(mappedName = "java:/JmsXA")
	private ConnectionFactory connectionFactory;

E criar a session como transacional

session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

Com isso o envio de mensagens volta a funcionar corretamente e o melhor: ele pode fazer parte de uma transação que realiza outras tarefas.

Configurando do número de consumidores da fila JMS

Para fechar esse post, será mostrado agora uma configuração simples, mas que pode ajudar muito na performance/escalabilidade da sua aplicação no que diz respeito ao consumo de mensagens de uma fila JMS.

Imagine o seguinte cenário:

  • O processamento de uma mensagem pelo seu MDB é um tanto quanto pesado e pode levar alguns segundos (5s por exemplo)
  • A quantidade de mensagens que são colocadas na fila é “grande” (10 mensagens por segundo)
  • Você tem apenas 5 instâncias de MDB consumindo as mensagens

Considerando um período de 10 segundos, sua fila teria recebido 100 mensagens (10 por segundo) e sua aplicação teria consumido apenas 10 mensagens, já que cada MDB teria consumido 2 mensagens (10s / 5s) * 5 instâncias de MDBs. Veja que isso pode se tornar um gargalo.
Para configurar a quantidade de instâncias do MDB, basta abrir o arquivo de configuração do JBoss (standalone.xml) e ir até a sessão do subsistema <subsystem xmlns=”urn:jboss:domain:ejb3:1.2″>.

..
            <mdb>
                <resource-adapter-ref resource-adapter-name="hornetq-ra"/>
                <bean-instance-pool-ref pool-name="mdb-strict-max-pool"/>
            </mdb>
            <pools>
                <bean-instance-pools>
                    <strict-max-pool name="slsb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                    <strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                </bean-instance-pools>
            </pools>
..

Veja que você tem a linha <bean-instance-pool-ref pool-name=”mdb-strict-max-pool”/> que indica que para MDB será usado o pool mdb-strict-max-pool e logo abaixo tem sua configuração:

<strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>

O default do JBoss é de até 20 instâncias para cada MDB. Quando achar conveniente aumentar/diminuir esse valor, basta alterar o valor do atributo max-pool-size.

Conclusão

O uso de JMS com JEE 6 é simples e exige apenas algumas anotações e poucas linhas de código. Apesar disso, é um mecanismo bem robusto e tem a vantagem de poder fazer com que o envio de uma mensagem faça parte de uma transação, além de fazer parte da Espec do JEE, o que significa que sua aplicação poderia rodar em outro servidor sem maiores problemas.

A configuração do JBoss é um pouco chata de início, pois o arquivo standalone.xml não vem com o módulo de mensageria habilitado por padrão. Mas após a habilitação do módulo, as demais configurações se tornam simples, como criar filas/tópicos e configurar o pool de MDBs.

O código apresentado pode ser encontrado aqui e qualquer dúvida é só usar o espaço de comentários.

Publicado em JBoss, Jee | Etiquetas , , , , , , | 17 Comentários