Do que se trata o artigo:

O artigo apresenta um exemplo prático completo de como desenvolver um web service utilizando o framework Spring Web Services (Spring-WS) e contract-first. E ainda, apresenta na prática as tecnologias de mapeamento Objeto/XML e WS-Security.


Para que serve:

O exemplo apresentado pode ser utilizado como base para construção de web services com requisitos semelhantes. De fato, o exemplo está didaticamente apresentado de forma a tornar mais simples o entendimento do processo de desenvolvimento contract-first.


Em que situação o tema é útil:

O assunto é relevante para web services de médio e grande porte que necessitem manterem-se estáveis ao longo do tempo, garantindo estabilidade, interoperabilidade e segurança.

Spring-WS de ponta a ponta:

Este artigo possui um foco prático, demonstrando através de um exemplo real, como implementar web services contract-first. O objetivo é construir um web service completo demonstrando a utilização de mapeamento Objeto/XML e WS-Security.

A aplicação de exemplo é um sistema de consulta e reservas de passagens para companhias aéreas, que tem por objetivo expor seus serviços para integração com terceiros. Inicialmente, apresentamos todas as camadas da aplicação antes de começarmos com o web service. Em seguida, construímos o web service utilizando Spring-WS e OXM.

Mapeamento Objeto/XML (OXM) é um termo utilizado para descrever tecnologias que são capazes de transformar objetos em XML e vice-versa, sem que o programador precise criar código de parsing. Utilizamos o JAXB para mapear um XML Schema diretamente para JavaBeans, e então evitar a utilização de um parser de XML.

Por fim, demonstramos como adicionar autenticação ao serviço utilizando a especificação WS-Security. O Spring-WS utiliza endpoint interceptors para adicionar WS-Security de forma transparente, tanto no servidor como no cliente.

Este artigo é a segunda e última parte de uma série de artigos sobre Spring Web Services (Spring-WS). Na primeira parte aprendemos as diferenças entre os estilos de desenvolvimento Contract First e Contract Last. Para relembrar, vimos que com contract-last começamos pelo código Java e geramos o WSDL automaticamente. Com contract-first focamos no XML Schema que fará parte do contrato WSDL e após implementamos o contrato utilizando Java. Em seguida, vimos que o contract-first possui vantagens em relação à consistência, performance, versionamento e validação.

É aconselhável, mas não necessário, que o leitor leia o artigo “Introdução ao Spring Web Services” presente na Edição 71. O artigo apresenta os conceitos básicos relacionados à XML e web services como SOAP, XML schema e WSDL. Além disso, apresenta um exemplo inicial de como criar web services com contract-first no Spring-WS.

Dando seqüência ao anterior, este artigo possui um foco prático, considerando que os conceitos como pré-requisito já foram abordados na primeira parte. O objetivo é construir um web service completo começando do zero. O caso de uso para sua criação deve ser realista, ou seja, deve ser comparável em complexidade a web services em uso já publicados na Internet. Entretanto, devido ao espaço limitado de um artigo, alguns detalhes serão simplificados.

Em geral, um web service é desenvolvido sobre um sistema já pronto, oferecendo apenas uma nova interface de acesso para fins de integração com terceiros. Dessa forma, por exemplo, é comum termos um sistema web e um web service que compartilhem a mesma lógica de negócios.

Primeiramente, iremos apresentar a aplicação sobre a qual o web service será construído. Essa aplicação terá as camadas de negócio e persistência desenvolvidas e testadas por testes de unidade. Em seqüência, concentraremos em implementar um web service que irá expor os serviços desta aplicação para acesso externo.

Apresentando a aplicação de exemplo

A aplicação de exemplo é um sistema de consulta e reservas de passagens para companhias aéreas. Vamos supor que uma companhia do ramo nos contratou para desenvolver tal aplicação. No levantamento de requisitos descobrimos que devemos disponibilizar uma interface de consulta com os seguintes parâmetros: cidade de origem e destino; data de partida e retorno; e a opção de incluir ou não vôos com conexão. A reserva de passagem necessita apenas do código do vôo desejado e do código de identificação do passageiro.

A companhia aérea também deseja integrar seu sistema com aplicações de comparação de preços, como por exemplo, o excelente www.skyscanner.net. Considerando que teremos um número relativamente grande de empresas diferentes acessando nosso serviço, devemos manter um contrato bem definido e estável. É muito importante que alterações subseqüentes no contrato não inviabilizem as empresas que já estejam utilizando o serviço. Outro requisito levantado, na área de segurança, é requerer autenticação para acesso utilizando usuário e senha.

É natural, neste caso, a escolha de XML sobre HTTP como meio de comunicação, dado que não sabemos quais tecnologias tais empresas utilizam. Sendo assim, o que nos resta é decidir entre web services REST ou SOAP. Considerando a existência de um contrato rígido e estável, e a natureza dos nossos serviços, o uso de SOAP é mais favorável. Porém, é importante deixar claro que a utilização de REST seria completamente viável. Supondo que as empresas que irão se integrar com nossa aplicação já estão no mercado há muitos anos, e que já possuem integrações utilizando SOAP, decidimos seguir o mesmo caminho.

REST ou SOAP: REST (Representational State Transfer) se caracteriza pela simplicidade, utilizando apenas os recursos do HTTP e XML (ou JSON, YAML, texto puro, etc.). Já o SOAP agrega um protocolo que adiciona overhead e complexidade, entretanto, o fato da existência do WSDL garante um contrato rígido entre cliente e servidor. A decisão entre qual arquitetura utilizar é delicada. Em geral, deve-se sempre considerar a utilização de REST e recorrer a SOAP apenas quando necessário.

É muito importante um levantamento adequado dos requisitos antes da escolha de uma tecnologia. Principalmente quando se trata de integração de sistemas. Cada caso é um caso e uma escolha inadequada pode trazer dificuldades ou até mesmo condenar o projeto a falha.

Modelo de dados

O projeto do modelo de dados pode ser visualizado na Figura 1. Procuramos manter um modelo simples, para facilitar o entendimento. Um vôo possui um aeroporto de origem e destino, um horário de partida e chegada, classe, preço e capacidade máxima. Um aeroporto está localizado numa cidade em um determinado país. E uma reserva de vôo é feita por um passageiro.

img

Figura 1. Modelo de dados para companhia área.

Escolhemos o banco de dados MySQL para utilizarmos no exemplo. Na aplicação disponível para download encontra-se um script que cria as tabelas do modelo de dados e insere alguns registros de teste. Após executar o script no MySQL, abra o arquivo applicationContext.xml e modifique os parâmetros de acesso ao banco, localizados no bean dataSource.

Com o modelo de dados definido criamos os objetos de domínio que refletem as tabelas da Figura 1. Codificamos as classes manualmente com o mesmo nome das tabelas e as mesmas propriedades.

Camada de persistência

Para simplificar o acesso ao banco de dados utilizaremos diretamente o JDBC. Veja a Listagem 1.

Listagem 1. JdbcFlightDao.java – Responsável pelo acesso ao banco de dados

            public class JdbcFlightDao extends SimpleJdbcDaoSupport implements FlightDao {
   
                public List<Flight> searchFlights(City from, City to, Date departureDay,
                  boolean onlyDirectFlights) {
               
                  String sql = "select f.id, f.flight_number, f.service_class_id, f.departure_day, f.departure_time, f.arrival_day, f.arrival_time, f.number_of_stops, f.capacity, f.price, fa.name as from_airport, ta.name as to_airport, fc.name as from_city, tc.name as to_city, fc.city_code as from_city_code, tc.city_code as to_city_code, ft.name as from_country, tt.name as to_country, ft.country_code as from_country_code, tt.country_code as to_country_code from flight f join airport fa on f.from = fa.id join city fc on fa.city_id = fc.id join country ft on fc.country_id = ft.id join airport ta on f.to = ta.id join city tc on ta.city_id = tc.id join country tt on tc.country_id = tt.id where fc.city_code = ? and tc.city_code = ? and f.departure_day = ?";
               
                  if (onlyDirectFlights) {
                    sql += " and f.number_of_stops = 0";
                  }
                  return this.getSimpleJdbcTemplate().query(sql, this.flightRowMapper,
                    from.getCode(), to.getCode(), departureDay);
                }
               
                public void createBooking(Booking booking) {
                  String sql = "insert into booking (confirmation_code,flight_id,passenger_id) values (?,?,?)";
                  this.getSimpleJdbcTemplate().update(sql, booking.getConfirmationCode(),
                    booking.getFlight().getId(), booking.getPassenger().getId());
                }
               
                public boolean checkBookingExist(Booking booking) {
                  String sql = "select count(*) from booking where flight_id = ? and passenger_id = ?";
                  int count = this.getSimpleJdbcTemplate().queryForInt(sql,
                    booking.getFlight().getId(), booking.getPassenger().getId());
                  return count > 0;
                }
               
                public boolean checkOverCapacity(Flight flight) {
                  int capacity = this.getSimpleJdbcTemplate().queryForInt(
                    "select capacity from flight where id = ?", flight.getId());
                  int bookings = this.getSimpleJdbcTemplate().queryForInt(
                    "select count(*) from booking where flight_id = ?", flight.getId());
                  return capacity == bookings;
                }
              }
         ... 

Quer ler esse conteúdo completo? Tenha acesso completo