Utilizando-se a arquitetura orientada a serviço (SOA), é possível criar diversos serviços (WebServices) que interagem com algumas bases de dados. Normalmente, estes serviços se compõem para que seja criado um serviço mais robusto, como por exemplo, um serviço que registra o empréstimo de um livro de uma biblioteca e outro serviço que altera o status do livro para 'emprestado'.

Para este tipo de orquestração, tecnologias como BPEL ou Biztalk são comumente utilizadas. Mas nem sempre é possível utilizar tais tecnologias, que permitem o controle transacional atômico, sendo que, ao retornar uma exception em algum dos serviços, toda a transação sofre rollback (retorna ao seu estado original, sem registrar as alterações realizadas). Em alguns casos, este rollback precisa ser escrito para que, ao retornar uma exception, uma rotina previamente escrita seja executada, revertendo as alterações realizadas nas bases de dados.

O propósito deste Post é mostrar como o protocolo WS-AT(Web Services Atomic Transaction), juntamente com o framework Spring pode ser utilizado para que os commits que devem ser realizados nos diversos serviços da composição (empréstimo de livros de uma biblioteca, por exemplo) sejam realizados apenas no final do processo, quando todos os passos já foram realizados.

Ferramentas utilizadas

Para este artigo, utilizaremos o application server da Oracle, o WebLogic, na sua versão 11g. Para a criação dos WebServices, o framework utilizado será o Spring, na versão 3.0.7.

As interações com a base de dados serão realizadas através de SQL puro, mas pode ser feito, sem qualquer alteração, utilizando-se HQL ou outra linguagem de banco. A forma como a interação com o banco é feita não interfere na demonstração.

O protocolo a ser utilizado para a comunicação entre os serviços e o envio do contexto da aplicação é o WS-AT.

A base de dados que foi utilizada para este Post é o MySQL. Qualquer base de dados que permita conexão XA pode ser utilizada para este fim.

Regras de negócio

Para demonstrar como funciona,será criado um serviço que registra o empréstimo de livros. Para tal, dois WebServices serão criados, um para inclusão de movimentação de livros e outro para alteração do status do livro.

Repositório de dados

Duas tabelas são utilizadas para este fim. Uma contendo o cadastro de livros e outra o registro de movimentações dos livros, como um histórico dos empréstimos. É claro que num sistema de biblioteca existem diversas outras tabelas para armazenar todas as informações pertinentes, mas para o que iremos demonstrar, não se fazem necessárias. Segue os dois scripts de criação:


CREATE
TABLE  historico_emprestimo (
historico_emprestimo_sq int(11) NOT NULL
auto_increment,
data_emprestimo timestamp NOT NULL default
CURRENT_TIMESTAMP,
livro_sq int(11) NOT NULL,
 
PRIMARY KEY 
(historico_emprestimo_sq),
 
CONSTRAINT historico_emprestimo_fk1 FOREIGN KEY (livro_sq) REFERENCES
livro (livro_sq) ON DELETE NO ACTION ON UPDATE CASCADE,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
CREATE TABLE livro (
livro_sq int(11) NOT NULL auto_increment,
nome_livro
varchar(50) NOT NULL,
status_livro int(1) NOT NULL default 0,
PRIMARY KEY (livro_sq)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Listagem 1. Script de criação das tabelas utilizadas

Um pouco de código

Vamos ver agora os DAOs (Data Access Object) dos dois WebServices. DAO, por definição é um padrão introduzido no ambiente JEE para simplificar e desacoplar a interação das aplicações Java com a API JDBC. Neles podemos ver as alterações nas tabelas da base de dados que serão feitas pelos dois serviços:


@Repository
public class DaoService1 {
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
public
DaoService1(@Qualifier("BDBIBLIOTECA") DataSource dataSource) {
this.namedParameterJdbcTemplate = new
NamedParameterJdbcTemplate(dataSource);
}
 
public int alterar(int livro_sq, int status)
throws SQLException {
String sql = "UPDATE LIVRO SET
STATUS_LIVRO = " + status + " WHERE LIVRO_SQ = " + livro_sq;
Map<String, String> parametros = new
java.util.HashMap<String, String>();
return namedParameterJdbcTemplate.update(sql,
parametros);
}
}
Listagem 2. DaoService1

@Repository
public class DaoService2 {
 
private NamedParameterJdbcTemplate
namedParameterJdbcTemplate;
@Autowired
public
DaoService2(@Qualifier("BDBIBLIOTECA") DataSource dataSource) {
this.namedParameterJdbcTemplate = new
NamedParameterJdbcTemplate(dataSource);
}
 
public int incluir(int livro_sq) throws
SQLException {
String sql = "INSERT INTO
HISTORICO_EMPRESTIMO (LIVRO_SQ) VALUES (" + livro_sq + ")";
Map<String, String> parametros = new
java.util.HashMap<String, String>();
return
namedParameterJdbcTemplate.update(sql, parametros);
}
}
Listagem 3. DaoService2

No constructor das duas classes existe um 'Autowired' para o dataSource. Este dataSource deve ser configurado no application-context.xml dos serviços, como segue:


application-context.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
         
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         
http://www.springframework.org/schema/context
         
http://www.springframework.org/schema/context/spring-context-2.5.xsd
         
http://www.springframework.org/schema/tx 
         
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
         
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
 
<context:component-scan
base-package="br.com.biblioteca" />
 
<bean
id="BDBIBLIOTECA" name="BDBIBLIOTECA"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>jdbc/biblioteca</value>
</property>
</bean>
 
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<ref bean="BDBIBLIOTECA" />
</property>
</bean>
 
<bean id="transactionManager"
autowire="autodetect"
class="org.springframework.transaction.jta.WebLogicJtaTransactionManager">
 
<property name="transactionManagerName"
value="javax.transaction.TransactionManager" />
 
<property name="userTransactionName"
value="javax.transaction.UserTransaction" />
</bean>
 
<tx:annotation-driven
transaction-manager="transactionManager" />
 
</beans>
Listagem 4. Configuração do application-context.xml

Como o propósito deste Post não é Spring, e sim sua interação com WS-AT, vamos dar uma passada bem rápida neste XML.

Criamos um 'JndiObjectFactoryBean' para apontar o dataSource no application Server.

Criamos um 'AnnotationSessionFactoryBean' para gerar as sessões utilizadas pelo serviço.

Por fim, criamos um 'transactionManager' para gerenciar as transações. Este é o bean principal neste post. Observe que este 'transactionManager' possui um UserTransaction, para que o serviço não tenha que abrir um manualmente. Outro ponto importante é que ele é um 'WebLogicJtaTransactionManager', indicando que quem deve cuidar das transações é o application server, e não o serviço em si.

Para uma boa organização no código, vamos criar agora duas classes de serviço, que devem fazer o 'meio de campo' entre o WSImpl e o Dao, como segue:


@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = 
Isolation.READ_UNCOMMITTED, rollbackFor =
Exception.class)
public class Servico1 {
 
  @Autowired
  private DaoService1 dao;
 
  @weblogic.wsee.wstx.wsat.Transactional
  @WebServiceRef(value = WsatService2_Service.class)
  WsatService2 service2;
  public void emprestarLivro(int livro_sq) throws Exception {
    service2.registrarEmprestimo(livro_sq);
    dao.alterar(livro_sq);
  }
}
Listagem 5. Servico1

@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = 
Isolation.READ_UNCOMMITTED)
public class Servico2 {
  @Autowired
  DaoService2 dao;
 
  public int incluir(int livro_sq) throws SQLException {
    return dao.incluir(livro_sq);
  }
}
Listagem 6. Servico2

Nesta camada, o Spring, através do annotation '@org.springframework.transaction.annotation.Transactional', pega uma sessão com o banco de dados diretamente do application server, dentro de uma transação já criada pelo próprio application server.

No 'Servico1', o campo service2, do tipo WsatService2 está anotado com '@weblogic.wsee.wstx.wsat.Transactional', para indicar que, ao realizar a chamada para este serviço, levar em consideração a transação do contexto. Existem alguns parâmetros para esta anotação, porém, os defaults são suficientes para este exemplo.

Por fim, vamos mostrar agora a implementação dos WebServices, onde recebemos o contexto do application.


@WebService(serviceName = "WsatService1", targetNamespace = 
"http://br.com.biblioteca", portName = "wsatPort1")
@Transactional
public class WsatService1 {
 
  private Servico1 service = new Servico1();
 
  public WsatService1() {
    service = (Servico1)
    ApplicationContextHelper.getBeanForClass(Servico1.class);
  }
 
  public String emprestarLivro(@WebParam(name="livro_sq") int livro_sq) {
    try {
      service.emprestarLivro(livro_sq);
      return "OK";
    } catch (Exception e) {
      e.printStackTrace();
      return "NOK";
    }
  }
}
Listagem 7. WsatService1

@WebService(serviceName = "WsatService2", targetNamespace = http://br.com.biblioteca, 
portName = "wsatPort2")
@Transactional
public class WsatService2 {
 
  private Servico2 service;
 
  public WsatService2() {
    service = (Servico2) ApplicationContextHelper.getBeanForClass(Servico2.class);
  }
 
  public String registrarEmprestimo(@WebParam(name = "livro_sq") int livro_sq) 
  throws Exception {
    service.incluir(livro_sq);
    return "OK";
  }
}
Listagem 8. WsatService2

As classes que implementam o WebService devem ser anotadas com '@weblogic.wsee.wstx.wsat.Transactional'. Isso indica que elas suportam o recebimento de um contexto do application server.

Descrição da lógica

Com todas as classes relevantes apresentadas, vamos descrever um pouco como deve funcionar este exemplo.

Ao realizar uma chamada no WsatService1, no método 'emprestarLivro', a classe Servico1 é instanciada e inicia a orquestração.

Na orquestração, dois passos devem ser realizados:

  • Registrar o empréstimo do livro;
  • Alterar o status do livro emprestado.

Para isso, a classe Servico1 realiza uma chamada ao serviço WsatService2, método 'registrarEmprestimo', passando o id do livro e depois chama o DaoService1 para alterar o status do livro.

Até agora não tem nada de diferente de qualquer outro serviço. A diferença existe graças à anotação'@weblogic.wsee.wstx.wsat.Transactional'[SF-CV10]que permite o envio/recepção do contexto, para que o commit seja realizado apenas no final do processo. Através desta anotação, o primeiro serviço executado envia o contexto do application server para os demais serviços da composição. Sem ela, cada serviço executado dentro da composição criaria um contexto diferente no servidor, criando assim várias transações, uma para cada serviço. Tendo apenas um contexto e este contexto sendo trafegado entre os serviços, através do WS-AT (utilizando apenas a anotação acima citada), tem-se apenas uma transação para todos os serviços executados.

Se o serviço WsatService2 for chamado e incluir um registro na base de dados, mas ao tentar alterar o status do livro, der algum problema, o registro incluído na base de dados não é comitado.

Por outro lado, se o serviço WsatService2 for chamado diretamente, sem fazer parte de uma orquestração com o WsatService1, a inclusão do registro na base de dados é feita normalmente.

Conclusão

Como pudemos ver neste Post, é possível realizar uma transação com a base de dados que vá além de um serviço, que contemple uma orquestração através do Java, utilizando Spring.

Vimos que esta transação atômica faz com que a execução de todos os serviços do composto seja feita dentro de uma mesma transação, dentro de um mesmo contexto do servidor, graças ao protocolo WS-AT e à simples anotação ‘Transactional’ do weblogic.

Pode-se obter os mesmos resultados com um rollback ativo, mas seria preciso uma rotina para a realização deste rollback, o que implica em mais código para ser gerenciado.