ALAN DE OLIVEIRA SILVA
APLICANDO DESIGN BY CONTRACT EM WEB SERVICES
RESTFUL
Universidade Federal de Pernambuco [email protected] www.cin.ufpe.br/~posgraduacao
RECIFE 2017
Aplicando Design by Contract em Web Services RESTful
Este trabalho foi apresentado à Pós-Graduação em Ciência da Computação do Centro de Informática da Universidade Federal de Pernambuco como requisito parcial para obtenção do grau de Mestre Profissional em Ciência da Computação.
ORIENTADOR: Prof. Dr. Henrique Emanuel Mostaert Rebêlo
RECIFE 2017
S586a Silva, Alan de Oliveira
Aplicando design by contract em web services RESTful / Alan de Oliveira Silva. – 2017.
63 f.: il., fig., tab.
Orientador: Henrique Emanuel Mostaert Rebêlo.
Dissertação (Mestrado) – Universidade Federal de Pernambuco. CIn, Ciência da Computação, Recife, 2017.
Inclui referências.
1. Ciência da computação. 2 Web services. I. Rebêlo, Henrique Emanuel Mostaert (orientador). II. Título.
APLICANDO DESIGN BY CONTRACT EM WEB SERVICES RESTFUL
Dissertação apresentada ao Programa de Pós-Graduação em Ciência da Computação da Universidade Federal de Pernambuco, como requisito parcial para a obtenção do título de Mestre Profissional em 28 de dezembro de 2017.
Aprovado em: 28/12/2017.
BANCA EXAMINADORA
______________________________________________________ Prof Dr. Leopoldo Motta Teixeira
Centro de Informática/UFPE
__________________________________________________ Prof Dr. Gilberto Amado de Azevedo Cysneiros Filho
UFRPE
_________________________________________________ Prof Dr. Henrique Emanuel Mostaert Rebêlo
Centro de Informática / UFPE (Orientador)
Gostaria de agradecer a minha esposa Helilciane, meus filhos João Víctor e Heitor pela apoio, carinho e compreensão pelos vários momentos em que estive ausente. Agradeço também aos meus pais Gildásio (em memória) e Hilda pelo cuidado e educação a mim concedido e aos meus irmãos e familiares pelo incentivo.
Agradeço ao meu orientador dr. Henrique Rebêlo pela ajuda em diversas reuniões, mesmo tarde da noite, não mediu esforços para me ajudar nesta pesquisa.
Agradeço também ao pessoal da nossa turma de mestrado, em especial aos colegas de Rondônia, todos do Flat CDU e também ao Gil por dar mais alegria a nossa turma. Ao Instituto Federal de Rondônia tanto pela disponibilidade quanto pelo apoio financeiro e também a equipe DGTI/IFRO pelo apoio.
Amém.” Romanos 11:35,36
REST é um estilo arquitetural definido em 2000 por Roy Thomas Fielding, baseado no protocolo HTTP, estando hoje em constante adoção, inclusive pelas grandes empresas de tecnologia do mundo, como o Google e Facebook. Design by Contract é uma técnica de programação concebida em 1992 por Bertrand Meyer afim de aumentar a segurança e robustez de softwares através da utilização de contratos. Como o estilo REST não possui especificação de contratos ou validação, na presente pesquisa foi efetuada a aplicação de Design by Contract em Web Services RESTful. Com o objetivo de validar esta proposta, apresentamos provas de conceito entre a especificação oficial de validação Bean Validation do Java EE e do Design by Contract implementada por uma adaptação da linguagem AspectJML, abordando comparações de funcionalidades, desempenho e produção de mensagens de erro. Os resultados demonstraram que esta versão adaptada da linguagem AspectJML possui mais recursos para a implementação de contratos do que as validações que são possíveis de ser criadas com Bean Validation, tendo também um desempenho de retorno de requisição ligeiramente superior para implementar o mesmo contrato e sendo capaz de produzir respostas de erro compatíveis com boas práticas RESTful.
REST is an architectural style defined in 2000 by Roy Thomas Fielding, based on the HTTP protocol, being nowadays in constant adoption, including by the great companies of technology of the world, like Google and Facebook. Design by Contract is a programming technique conceived in 1992 by Bertrand Meyer to increase the security and robustness of software through the use of contracts. As the REST style does not have a contract specification or validation, in the present research will be made the application of Design by Contract in Web Services RESTful. In order to validate this proposal, we present proofs of concept between the official validation specification Bean Validation of Java EE and Design by Contract implemented by an adaptation of the AspectJML language, addressing comparisons of functionalities, performance and production of error messages. The results showed that this version of the AspectJML language has more features for the implementation of contracts than the validations that are possible to be created with Bean Validation, and also have slightly higher request return performance to implement the same contract and being able to to produce error responses compatible with RESTful best practices.
Figura 1 - Requisição Datausa... 16
Figura 2 - Requisição World Bank API...17
Figura 3 - Requisição Bikewise... 17
Figura 4 - Formato XML...22
Figura 5 - Formato JSON... 23
Figura 6 - Formato YAML...23
Figura 7 - Requisição e resposta HTTP simplificada...23
Figura 8 - Boa prática de resposta de erro...24
Figura 9 - Classe de serviço Jax-RS... 26
Figura 10 - Exemplo de pré e pós-condição em AspectJML...28
Figura 11 - Exemplo de classe invariante em AspectJML...29
Figura 12 - Exemplo de signals clause em AspectJML... 30
Figura 13 - Exemplo de modularização de contratos em AspectJML... 30
Figura 14 - Exemplo de sentença...33
Figura 15 - Mensagem de erro de Pré-condição...33
Figura 16 - Validador de cep... 35
Figura 17 - Anotação para o validador de cep... 35
Figura 18 - Uso da anotação Bean Validation...36
Figura 21 - Apresentação código AspectJML...41
Figura 22 - Classe de recurso implementado com Bean Validation...41
Figura 23 - Classe de entidade ContactCard com anotações Bean Validation...42
Figura 24 - Anotação AsLeastOneContract... 42
Figura 25 - Anotação HasId...43
Figura 26 - Pré-condição AspectJML...46
Figura 27 - Pré-Condição em Bean Validation... 46
Figura 28 - Pré-condição com diversos argumentos em AspectJML...46
Figura 29 - Pré-condição com diversos argumentos em Bean Validation...47
Figura 30 - Classe de validação RangeParams... 47
Figura 31 - Pós-condição em AspectJML... 49
Figura 32 - Pós-condição com uso de argumentos em AspectJML... 49
Figura 33 - Contrato modularizado...51
Figura 34 - Método mensagens por grupo... 53
Figura 35 - Resposta grupo não encontrado...54
Figura 36 - Método mensagem por id... 55
Figura 37 - Resposta requisição não encontrada... 55
URI - Identificador Uniforme de Recurso.
HTTP - Protocolo de Transferência de Hipertexto. Java EE - Java Enterprise Edition.
Java ME - Java Mobile Edition.
Soap - Protocolo Simples de Acesso a Objetos. REST - Transferência de Estado Representacional. DbC - Design by Contract.
RPC - Chamada de procedimento remoto. XML - Linguagem de marcação extensível. POJO - Velho e simples objeto Java.
Tabela 1 - Lista de código de resposta HTTP...21
Tabela 2 - Exemplos de modularização...31
Tabela 3 - Exemplo de modularização... 31
Tabela 4 - Pacote de validações... 34
Tabela 5 - Ambiente de comparação de desempenho...51
Tabela 6 - Tempo de execução entre Bean Validation e AspectJML... 52
1. INTRODUÇÃO...15
1.1. PROBLEMATIZAÇÃO...16
1.2. OBJETIVOS... 18
1.3. ORGANIZAÇÃO DA DISSERTAÇÃO... 19
2. REFERENCIAL TEÓRICO...20
2.1. RESTFUL WEB SERVICES...20
2.2. JAX-RS...25 2.3. DESIGN BY CONTRACT... 26 2.4. ASPECTJML...28 2.4.1. Pré e pós-condições...28 2.4.2. Invariantes... 29 2.4.3. Signals Clause...29 2.4.4. Modularização de contratos...30 2.4.5. Mensagem de Erro...33 2.5. BEAN VALIDATION...34
3. ADEQUAÇÃO DO ASPECTJML PARA RESTFUL...37
3.1. CORREÇÃO DE DEFEITO...37
4.1. APRESENTAÇÃO DA CLASSE DE RECURSO... 40
4.1.1. Quantidade de código...45
4.1.2. Pré-condições...45
4.1.3. Pré-condições que envolvem diversos argumentos...46
4.1.4. Pós-condições...48
4.1.5. Pós-condições com validação de argumentos...49
4.1.6. Classes invariantes...50
4.1.7. Signal Clauses...50
4.1.8. Modularização de Contratos... 50
4.2. COMPARAÇÃO DE DESEMPENHO...51
4.3. PRODUZINDO MENSAGENS DE ERRO...53
4.3.1. Erros de requisição...53 4.3.2. Erros do servidor... 56 4.4. CONSIDERAÇÕES...57 5. CONCLUSÕES...58 5.1. TRABALHOS RELACIONADOS...58 5.2. CONSIDERAÇÕES FINAIS... 59 5.3. TRABALHOS FUTUROS...59 REFERÊNCIAS...61
1.
INTRODUÇÃO
Desde o início do presente milênio, sistemas computacionais têm migrado massivamente para a WEB. De acordo com um estudo da União Internacional de Telecomunicações (UIT)1, há mais de 3,7 bilhões de usuários da Internet no mundo. O mesmo
estudo diz que há atualmente mais de 15 bilhões de dispositivos móveis conectados a rede mundial de computadores, devendo aumentar essa quantidade para 21 bilhões de dispositivos até 2020. Esses dispositivos, em geral, fazem uso de diversos tipos de serviços, como páginas web, mensageiros instantâneos, redes sociais, sistemas empresariais, streaming multimídia, dispositivos de IoT (Internet das coisas), dentre outros.
Neste contexto de Internet, os Web Services têm impulsionado a adoção de computação em nuvem2. De acordo com uma pesquisa da International Data Group (IGD)3
de 2016, 70% das empresas de tecnologia entrevistadas possui pelo menos uma aplicação na nuvem e 16% tem planos de migrar.
Os Web Services são uma maneira comum de fornecer informações e sistemas através da Internet. As duas arquiteturas mais amplamentes utilizadas são: Soap e REST. Porém de acordo com Laine (2012), Web Services RESTful possuem diversas vantagens sobre Web Services Soap para dispositivos IoT, como um menor overhead, menor complexidade de análise do protocolo, o fato de não manter estado e a completa integração com o protocolo HTTP.
RESTful são aplicações que implementam o conceito de REST (Representational State Transfer) (FIELDING, 2000) e são utilizadas majoritariamente como uma maneira de fornecer serviços web através da utilização do protocolo HTTP (Hyper Text Transport Protocol).
1 UIT: disponível em: https://www.itu.int/
2 Disponível em: https://www.infoq.com/news/2011/01/rest-cloud
1.1. PROBLEMATIZAÇÃO
Em Web Services RESTful, os dados são disponibilizados como uma forma de representar um recurso. Embora diferentemente de Web Services SOAP, que funcionam como um serviço para chamada de funções remotas, em RESTful podemos identificar parâmetros em filtros na query string, nos identificadores de recurso e em objetos do corpo da requisição HTTP. Através desses parâmetros podemos aplicar contratos para garantir maior confiabilidade do serviço.
No exemplo da Figura 1, efetuamos uma requisição para o serviço RESTful da DATAUSA4 solicitando informação de classificação de programas institucionais dos Estados
Unidos da América, onde na query string é enviado o parâmetro limit com o valor inválido. Deveríamos esperar uma resposta HTTP com o código 400 (bad request) e com informações identificando o motivo porque a requisição não foi aceita. Porém recebemos o código de erro 500 (internal error) e um texto contendo uma mensagem de erro emitida pelo banco de dados expondo detalhamento de tabelas e formato dos dados.
Figura 1 - Requisição Datausa REQUEST GET /api/?show=cip&sumlevel=2&limit=-1 HOST: api.datausa.io accept: application/json RESPONSE Content-Type: application/json; Status: 500 Internal Error
{"error": "(psycopg2.DataError) LIMIT must not be negative\n [SQL: 'SELECT ... \\nWHERE length(ipeds.grads_yc.cip) = %(length_1)s \\n LIMIT %(param_1)s'] [parameters: {'length_1': u'2', 'param_1': -1}]"}
Fonte: Autor (2017).
No exemplo da Figura 2, uma requisição é efetuada ao Web Service do Banco
Mundial5 onde enviamos uma requisição solicitando uma lista de países, porém passando no
parâmetro per_page da query string um valor inválido. Recebemos como resposta o código HTTP 200 (OK) com uma mensagem que diz que o parâmetro não é válido, porém não especifica qual parâmetro.
Figura 2 - Requisição World Bank API REQUEST GET /v2/countries?per_page=-1&incomeLevel=LIC HOST: api.worldbank.org RESPONSE Content-Type: application/xml; Status: 200 OK <?xml version="1.0" encoding="utf-8" ?> <wb:error>
<wb:message id="120" key="Invalid value"> The provided parameter value is not valid </wb:message>
</wb:error>
Fonte: Autor (2017).
Em outro caso na Figura 3, efetuamos uma requisição para o Web Service da Bikewise6 solicitando uma lista de incidentes de bicicletas, e conforme os outros exemplos,
passando na query string o parâmetro per_page um valor inválido (-1). Recebemos uma resposta HTTP com código 200 (OK) e uma lista contendo vinte e cinco (25) objetos.
Figura 3 - Requisição Bikewise REQUEST GET /api/v2/incidents?page=1&per_page=-1 HOST: bikewise.org:443 accept: application/json 5 Disponível em <api.worldbank.org> 6 Disponível em <https://bikewise.org/>
RESPONSE
Content-Type: application/json; Status: 200 OK
{"incidents":[...]}
Fonte: Autor (2017).
A especificação HTTP 1.1 não provê controle ou implementação de contratos, sendo necessária esta implementação por parte da aplicação. Desta forma, caso um parâmetro ou o objeto do corpo da requisição esteja incorreto ou em desacordo com o que é esperado pelo servidor, a aplicação deverá retornar uma mensagem HTTP contendo informações necessárias para que o consumidor deste serviço possa saber o motivo e onde ocorreu a quebra de contrato.
Design by Contract (DbC) é uma técnica para especificação de contratos em sistemas, porém ainda há poucas utilizações da presente técnica em serviços RESTful. Considerando as falhas de segurança, na necessidade de que cada aplicação utilize seu próprio mecanismo de validação e as dificuldades na compreensão das mensagens de retorno, esta pesquisa vai propor a utilização de Design by Contract como uma implementação do AspectJML como um método para validação em aplicações RESTful.
1.2. OBJETIVOS
O objetivo geral deste trabalho é avaliar a aplicação de Design by Contract em Web Services RESTful, fazendo uma comparação direta com Bean Validation, a especificação padrão do Java EE. Os objetivos específicos são:
● Avaliar as funcionalidades de DbC que podem ser aplicadas a RESTful; ● Avaliar o desempenho do tempo de resposta na aplicação de contratos;
● Identificar possíveis deficiências da linguagem AspectJML para integração com Web Services RESTful;
1.3. ORGANIZAÇÃO DA DISSERTAÇÃO
O Capítulo 1 - Introdução - Apresenta o tema abordado na presentepesquisa.
O Capítulo 2 - Referencial Teórico - Neste capítulo são abordados os principais conceitos e tecnologias sobre o tema.
O Capítulo 3 - Adequação do AspectJML para RESTful - Neste capítulo são
apresentadasas adequações que foram necessárias para a utilização da linguagem AspectJML. O Capítulo 4 - Aplicando Design by Contract em RESTful - Neste capítulo são apresentadas as avaliação de funcionalidades e desempenho bem como uma análise sobre a possibilidade da adequação de AspectJML em seguir as melhores práticas RESTful para produção de mensagens de erro.
O Capítulo 5 - Conclusões - Neste capítulo são apresentadas as conclusões sobre os resultados da presentepesquisa, bem comotrabalhosfuturos.
2.
REFERENCIAL TEÓRICO
Neste capítulo introduzimos os principais conceitos abordados na presentepesquisa.
2.1. RESTFUL WEB SERVICES
Web Services é um tipo de serviço específico identificado por um identificador uniforme de recurso (URI), que deve estar disponível programaticamente e implementado utilizando linguagens e protocolos padrão da internet (PAPAZOGLOU, 2003).
Outra definição fornecida pela World Wide Web Consortium (W3C):
Um web services é um sistema de software projetado para suportar interações interoperáveis de máquina-a-máquina através de uma rede. Possui uma interface descrita em um formato processável em máquina (especificamente WSDL). Outros sistemas interagem com o serviço da Web de uma maneira prescrita por sua descrição usando mensagens SOAP, normalmente transmitidas usando HTTP com uma serialização XML em conjunto com outros padrões relacionados à Web.
Há dois tipos principais de Web Services: SOAP e RESTful (aplicações que implementam o conceito REST). Ambos podem ser utilizados em diversos tipos de aplicação, porém há grandes diferenças entre estas duas arquiteturas. Web Services SOAP foi inicialmente criado pela Microsoft para fornecer chamada de procedimento remoto (RPC) através de XML e outros padrões da internet (CURBERA et al, 2002).
RESTful foi originalmente concebido como um estilo arquitetural de Web Services destinado a criação de sistemas distribuídos (FIELDING, 2000). Sua arquitetura é uma abstração do protocolo HTTP e seu uso foi importante para comprovar a excelente escalabilidade que o protocolo pode fornecer em sistemas distribuídos (PAUTASSO; ZIMMERMAN; LEYMANN, 2008). Segundo Richardson (2008), RESTful não é uma arquitetura, mas um conjunto de princípios e critérios de design.
As principais características da arquiterua RESTful são: Endereçamento global através da identificação de recurso, interface uniforme compartilhada por todos os recursos,
interações sem estado entre chamadas de serviço, mensagens auto-descritivas e hipermídia como mecanismo para descoberta descentralizada de recursos por referência (PAUTASSO, 2014). Para Zhao (2013) Web Service RESTful tem como foco a exposição dos recursos e representação.
Segundo Richardson (2008), os recursos podem ser representados por entidades, informações abstraídas no sistema ou mesmo qualquer coisa que seja considerado suficientemente importante para ser referenciada. Todo recurso deve possuir uma URI, e através dos verbos do protocolo HTTP GET para recuperar, POST para criar, DELETE para remover e UPDATE para atualizar (RODRIGUEZ, 2008). Outra característica importante do protocolo HTTP são os código de respostas como mostra a Tabela 1.
Tabela 1 - Lista de código de resposta HTTP Código Descrição
100 Continuar
101 Mudando protocolos 102 Processamento
122 Pedido-URI muito longo
200 OK 201 Criado 202 Aceito 203 Não-autorizado 204 Nenhum conteúdo 205 Reset 206 Conteúdo parcial 207 Status Multi 300 Múltipla escolha 301 Movido 302 Encontrado 304 Não modificado 305 Use proxy 306 Proxy switch 307 Redirecionamento temporário 400 Requisição inválida 401 Não autorizado 402 Pagamento necessário 403 Proibido 404 Não encontrado 405 Método não permitido
406 Não aceitável
407 Autenticação de proxy necessária 408 Tempo de requisição esgotou 409 Conflito
410 Gone
411 Comprimento necessário 412 Pré-condição falhou
413 Entidade de solicitação muito grande 414 Pedido-URI too long
415 Tipo de mídia não suportado 416 Solicitada de faixa não satisfatória 417 Falha na expectativa
418 Eu sou um bule de chá 422 Entidade improcessável 423 Fechado
424 Falha de dependência 425 Coleção não ordenada 426 Upgrade obrigatório
450 Bloqueado pelo controle de pais do windows 499 Cliente fechou pedido
500 Erro interno do servidor 501 Não implementado 502 Bad gateway 503 Serviço indisponível 504 Gateway time-out
505 HTTP version not supported
Fonte: Adaptado de https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml.
Um recurso é uma fonte de representações, e uma representação é apenas alguns dados sobre o estado atual de um recurso (RICHARDSON, 2008). A representação de um recurso pode ocorrer por exemplo através de texto, página HTML, imagens, vídeos, audios para conteúdo multimídia, linguagem de marcação extensível (XML), notação de objetos JavaScript (JSON), YAML para informações de objetos. Nas Figuras 4, 5 e 6 temos um mesmo objeto representado em XML, JSON e YAML respectivamente.
Figura 4 - Formato XML 1 2 3 4 <pessoa> <nome>João Calvino</nome> <dataNascimento>10/07/1509</dataNascimento> </pessoa>
Fonte: Autor (2017). Figura 5 - Formato JSON 1 2 3 4 5 6 { “pessoa” : {
“nome” : “João Calvino”,
“dataNascimento” : “10/07/1509” }
}
Fonte: Autor (2017). Figura 6 - Formato YAML 1
2 3
pessoa:
nome: “João Calvino”
dataNascimento: “10/07/1509”
Fonte: Autor (2017).
Embora RESTful não seja uma arquitetura na qual haja um padrão a ser seguido, encontramos um conjunto de boas práticas desenvolvidas na comunidade de desenvolvedores, como é o caso dos padrões de API web da Casa Branca americana7 dentre outros8,9,10,11.
Ao contrário dos Web Services SOAP, em RESTful não é recomendado de se utiliza-lo como RPC. Uma boa prática é utilizar como um recurso, aplicando o mesmo padrão de estrutura de sistema arquivo, onde deve obedecer uma estrutura hierárquica de objetos, utilizando nomes no plural e verbos HTTP. A Figura 7 mostra um exemplo de boa prática de URL em uma requisição GET para um recurso “1” de telefone que está contido no objeto “205” de contatos.
Figura 7 - Requisição e resposta HTTP simplificada <= GET /contatos/205/telefones/1 => 200 OK Fonte: Autor (2017). 7 Disponível em <https://github.com/WhiteHouse/api-standards> 8 Disponível em <https://blog.philipphauer.de/restful-api-design-best-practices/> 9 Disponível em <http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api> 10 Disponível em <https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9> 11 Disponível em <https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/>
Outra importante boa prática é, assim como um portal web exibe informações de erro de forma amigável para o visitante, o serviço RESTful deve também fornecer informações de erro em um formato amigável para o consumidor do serviço. Utilizando um formato de representação de objetos, deve-se fornecer informações importantes juntamente com o código de erro HTTP para a plena compreensão do erro, a exemplo da Figura 6, onde o serviço responde com um objeto JSON contendo informações sobre o erro.
Figura 8 - Boa prática de resposta de erro RESPONSE
Content-Type: application/json; Server: localhost
Status: 400 BAD REQUEST {
"status" : 400,
"developerMessage" : "Verbose, plain language description of the problem. Provide developers
suggestions about how to solve their problems here",
"userMessage" : "This is a message that can be passed along to end-users, if needed.",
"errorCode" : "444444",
"moreInfo" : "http://www.example.gov/developer/path/to/help/for/444444, http://drupal.org/node/444444",
}
Fonte:Adaptado de https://github.com/WhiteHouse/api-standards
Como uma forma de medir a maturidade de um serviço RESTful, Leonard Richardson criou níveis de maturidade para Serviços RESTful (2014 apud. PAUTASSO). ● Nível 0: HTTP é usado como um túnel para troca de documentos seguindo o estilo RPC. ● Nível 1: Cada recurso tem sua identificação e cada operação é destinado a um recurso
específico, no entanto é usado de maneira incorreta as operações. ● Nível 2: Os verbos HTTP são utilizados corretamente.
● Nível 3: Há um Web Services onde é permitido negociar diversos formatos de representação.
Web Services RESTful têm se tornado muito comum, muitas das principais empresas de tecnologia do mundo disponibilizam seus Web Services publicamente utilizando a arquitetura RESTful, como o Google12, Youtube13, Facebook14, Twitter15, Instagram16,
Microsoft17 e Amazon18.
2.2. JAX-RS
Jax-RS é a especificação JSR 339 para Java EE, que habilita o desenvolvimento de Web Services segundo o padrão arquitetural REST. Como uma especificação Java, o Jax-RS possui não apenas uma implementação mas diversas, como: Jersey, Resteasy, Apache CXF, dentre outros.
Os principais objetivos dessa especificação, de acordo com Pericas-geertsen(2013), são:
● Baseada em POJO (Velho e simples objeto Java), a API fornecerá anotações, classes e interfaces para serem utilizadas sobre o POJO, afim de expor os recursos REST;
● Centralização no protocolo HTTP;
● Independência de formatos, possibilitando a utilização de uma grande variedade de formatos de dados;
● Independência de de contêiner; ● Inclusão no Java EE;
12 Disponível em: <http://developers.google.com> Acesso em: 15, Setembro de 2017.
13 Disponível em: <https://developers.google.com/youtube> Acesso em: 15, Setembro de 2017. 14 Disponível em: <https://developers.facebook.com> Acesso em: 15, Setembro de 2017. 15 Disponível em: <https://developer.twitter.com/en/docs> Acesso em: 15, Setembro de 2017. 16 Disponível em: <https://www.instagram.com/developer> Acesso em: 15, Setembro de 2017.
17 Disponível em: <https://azure.microsoft.com/pt-br/services/api-management> Acesso em: 15, Setembro de 2017. 18 Disponível em: <https://developer.amazon.com/services-and-apis> Acesso em: 15, Setembro de 2017.
Uma classe de recurso é uma classe Java que usa anotações para implementar um recurso web correspondente. Classes de recurso são classes POJO que possuem pelo menos um método anotado com @Path ou um designador de método solicitado (Pericas-geertsen, 2013 p 11). Na Figura 6 temos um exemplo de uma classe de recurso anotada com @Path e contendo o método getMessage que por sua vez é anotado com @GET @Produces e @Path.
A anotação @GET se refere ao designador para o método HTTP GET. Também há outros designadores para métodos HTTP como @POST @PUT @DELETE @HEAD e @OPTIONS. A anotação @Produces indica que o formato do objeto de resposta será o formato passado por parâmetro na própria anotação, neste caso json. Por fim a anotação @Path dá a localização ao qual o recurso se encontra, neste exemplo o recurso está em “message/{id}”. No parâmetro id do método há a anotação @PathParam o qual passa o valor do recurso {id} para o parâmetro.
Figura 9 - Classe de serviço Jax-RS 1 2 3 4 5 6 7 8 9 10 11 @Path("/message")
public class MessageController {
@GET
@Produces(MediaType.APPLICATION_JSON) @Path("/{id}")
public Message getMessage(@PathParam("id") long id) {
Message message = messageService.getByID( id);
return message;
} }
Fonte: Autor (2017).
2.3. DESIGN BY CONTRACT
De acordo com Meyer (1992), os estudos sobre programação defensiva ensinam que cada módulo do sistema deve ser protegido contra a imprecisão dos dados. Os programadores são encorajados a fazerem verificação de parâmetros e objetos repetitivamente mesmo se feitos por quem faz a requisição.
Em particular, elementos de software devem ser considerados como implementações destinadas a satisfazer especificações bem compreendidas, não como textos executáveis arbitrários (MEYER, 1992).
Design by Contract (DbC) é um termo introduzido por Meyer (1992) para a linguagem de programação Eiffel como uma forma de aumentar a confiabilidade e robustez na programação orientada a objetos através da utilização de contratos. Os contratos se referem a um acordo que é estabelecido entre o requisitante e o serviço. O cliente deve garantir certas condições antes de chamar o método definido pela classe (pré-condições), e a classe deve garantir as condições estabelecidas no retorno do método (pós-condições). Os contratos são especificações comportamentais, geralmente escritas no código fonte do software, dessa forma a assinatura do método pode facilitar a documentação e compreensão do contrato.
Para Joe Verzulli (2003), os benefícios de adicionar anotações de contratos no código fonte são:
● Descrições mais precisas do que o código faz; ● Descoberta e correção eficiente de erros;
● Redução da chance de introduzir erros à medida que a aplicação evolui; ● Descoberta antecipada do uso incorreto de classes de clientes;
● Documentação precisa que é sempre sincronizada com o código da aplicação;
Uma das principais vantagens de se utilizar DbC, de acordo com Leavens (2006), é a eficiência, pois ele permite evitar checagens defensivas ineficientes. Outra característica vantajosa segundo Leavens (2006), é que ele pode ser usado para atribuir responsabilidade a uma parte defeituosa do software.
Há diversas implementações de DbC em diversas linguagens, como: Eiffel, Contract4J, Code Contracts, JML, AspectJML, dentre outros. A linguagem AspecJML é a linguagem de implementação de DbC que está em estudo neste trabalho devido estar utilizar a
JVM (Maquina Virtual Java) e estar em desenvolvimento.
2.4. ASPECTJML
Conforme Rebêlo (2008), AspectJML é uma linguagem que implementa o conceito de DbC, é derivado da linguagem Java Modeling Language (JML), porém utiliza programação orientado a aspectos para a implementação de contratos, tendo sido concebida originalmente com o intuito de compilar JML para Java Mobile Edition (JME). Mas além das funcionalidades encontrados no JML, AspectJML também implementa modularização de contratos naturalmente, semelhante a linguagem AspectJ, Rebêlo et al. (2014). Além disso, ainda segundo Rebêlo et al. (2014), a linguagem possui as seguintes características:
● Permitir modularização de contratos transversais, mantendo os princípios DbC, como documentação e raciocínio modular;
● permitir uma interface bem definida entre os contratos transversais e o código orientado a objetos,
● habilitar a seleção de dados, compilação e verificação de tempo de execução de contratos transversais.
2.4.1. Pré e pós-condições
AspectJML é uma linguagem de especificação de interface comportamental derivada do Java, assim como na linguagem Eiffel utiliza sintaxe de expressões do Java em asserções (REBÊLO et al., 2014). A Figura 10 mostra um exemplo de contrato sobre o método “setSize”, onde são definido as pré-condições através da palavra reservada “requires” e as pós-condições através de “ensures”, exigindo o uso de comentários “//@” no início de cada linha.
Figura 10 - Exemplo de pré e pós-condição em AspectJML 1
2 3 4 5
//@ requires width > 0 && height > 0;
//@ requires width * height <= 400;
//@ ensures this.width == width;
//@ ensures this.height == height;
Fonte: Rebêlo et al. (2014)
Neste código de exemplo temos dois parâmetros do tipo double sobre o referido método: width (largura) e height (altura), os quais representam o tamanho bidimensional de um objeto. Nas pré-condições é exigido que ambos os parâmetros sejam maior que 0 e menor ou igual a 400. As cláusulas de pós-condições exigem que sejam garantido que, ao final de execução do método, os atributos widht e heitght do objeto tenham os mesmos valores dos parâmetros do método.
2.4.2. Invariantes
De acordo com Rebêlo et al. (2014), Invariantes são contratos que devem ser satisfeito durante a construção da classe ou execução de um método. Em AspectJML é utilizado a palavra reservada invariant.
Figura 11 - Exemplo de classe invariante em AspectJML 1 2 3 4 5 class Package {
double width, height;
//@ invariant this.width > 0 && this.height > 0; }
Fonte: Rebêlo et al. (2014)
Na Figura 11 o conceito de invariantes é aplicado sobre os atributos width e height, exigindo que estes atributos sejam maior que 0. Durante a execução e construção de um objeto do tipo Package esse contrato especificado deve ser mantido, caso contrário uma exceção será emitida pelo AspectJML.
2.4.3. Signals Clause
Signals clauses são outra forma de pós condição, de acordo com Rebêlo et al. (2014) ele é utilizado para especificar o comportamento excepcional de um método e o seu contrato deve ser mantido quando um método ou construtor emite uma exceção. Este tipo de cláusula define quais são as exceções permitidas e quando elas podem ser aceitas.
permitido ocorrer uma exceção SizeDimensionException caso a cláusula width * height > 400 não seja atendida, também não é permitido ocorrer a exceção RuntimeException.
Figura 12 - Exemplo de signals clause em AspectJML 1 2 3 4 5 6
//@ signals (SizeDimensionException) width * height > 400;
//@ signals (RuntimeException) false;
void setSize (double width, double height) throws SizeDimensionException { if (width ∗ height > 400 ) throw new SizeDimensionException();
... }
Fonte: Rebêlo et al. (2014)
2.4.4. Modularização de contratos
A principal vantagem da linguagem AspectJML sobre outras linguagens de DbC é a possibilidade de modularizar contratos. A modularização de contratos torna possível o aproveitamento de uma mesma cláusula de pré ou pós-condição para diversos métodos. Sua sintaxe é similar a linguagem AspectJ, porém tendo herdado apenas o recurso de pointcut.
Segungo Kiczales (2001), pointcut é o responsável por identificar coleções de join points no fluxo do programa. Join points, por sua vez, fornecem quadros de referência comum que permitem definir estruturas das preocupações transversais. A Figura 13 exibe um exemplo de modularização, neste caso esse contrato utiliza um pointcut para tornar possível a aplicação a todo método que possui um parâmetro do tipo Size.
Figura 13 - Exemplo de modularização de contratos em AspectJML 1
2 3 4 5
//@ requires width > 0 && height > 0;
//@ requires width ∗ height <= 400 ;
@Pointcut("execution (* Package. ∗Size (double, double))"+ "&& args(width , height)")
void sizes(double width, double height) {} Fonte: Rebêlo et al. (2014)
O reaproveitamento de código é parte fundamental na programação orientado a objetos. Módulos de código podem ser utilizados por diversas partes do código fonte, diminuindo a repetição e acoplamento do código. Partindo para os contratos, da mesma forma
também podemos utilizar este conceito de modularização.
No AspectJML, a modularização de contratos segue o mesmo estilo da linguagem AspectJ. Através da identificação de um pointcut podemos aplicar diversas sentenças do contrato a todo trecho de código que atenda ao pointcut identificado. A identificação de um pointcut pode ser feita de diversas maneiras, nas tabelas Tabela 2 e Tabela 3 temos múltiplos exemplos, os quais podem ser combinados entre si.
Tabela 2 - Exemplos de modularização
Padrão de Assinatura Descrição
public void Package.set*(*) Qualquer método publico na classe Package onde o nome inicia com “set” que retorna void e contém um único argumento de qualquer tipo.
public void Package.*() Qualquer método publico na classe Package que retorna void e não contém argumento.
public * Package.*() Qualquer método publico na classe Package que não possui argumento e retorna qualquer tipo.
public * Package.*(..) Qualquer método público na classe Package que possui qualquer quantidade de argumentos e retorno qualquer tipo. * Package.*(..) Qualquer método da classe Package.
* *.*(..) Qualquer método.
public Package.new() Um construtor público da classe Package que não possui argumentos.
public Package.new(..) Um construtor da classe Package com qualquer número de argumentos.
Fonte: Rebêlo et al. (2014)
Tabela 3 - Exemplo de modularização
Pointcut Descrição
args(double, double) Qualquer método ou construtor onde o primeiro e segundo argumentos são do tipo double.
args(double, ..., double) Qualquer método ou construtor onde o primeiro e o último argumento são do tipo double.
args(double, *) Qualquer método ou construtor onde o primeiro argumento é do tipo double e o segundo é de qualquer tipo.
args(...) Qualquer método ou construtor com qualquer número de argumentos.
within(Package) Qualquer join point dentro do alcance léxico da classe Package, incluindo dentro de qualquer classe aninhada. within(Package+) Qualquer join point dentro do alcance léxico da classe
Package e suas subclasses, incluindo dentro de qualquer classe aninhada.
within(*) Qualquer ponto de junção dentro do alcance lexical de qualquer tipo e suas subclasses, incluindo dentro de qualquer classe aninhada.
withincode(* Package.setSize(...)) Qualquer join point dentro do alcance lexical de qualquer método setSize ()
da classe Package.
this(Package) Qualquer join point onde esta instância de Package avalia como verdadeira. Isso seleciona todos os joint points, como chamadas de métodos onde o objeto de execução atual é Package ou um de seus subtipos, como GiftPackage.
target(Package) Qualquer join point onde o objeto no qual o método está sendo chamado é instância de Package!. Ele seleciona todos os joint point, como chamadas de método, onde o objeto de destino é Package ou seus subtipos como GiftPackage.
cflow(call(* Package.setSize(...))) Todos os join point no fluxo de controle da chamada de qualquer método setSize() em Package, incluindo a execução do próprio método setSize.
cflowbelow(call(* Package.setSize(...))) Todos os join point no fluxo de controle de chamada de qualquer método setSize () em Package, mas excluindo a execução do próprio método setSize.
@annotation(AnnotationType) Qualquer join point dentro do alcance lexical de qualquer tipo marcado com a anotação AnnotationType incluindo classes aninhadas.
Fonte: Rebêlo et al. (2014)
2.4.5. Mensagem de Erro
Quando há uma quebra de contrato, ou seja, quando algum argumento não atendeu aos requisitos do contrato, uma exceção é emitida pela linguagem. Embora existam diversos tipos de exceções, todas contém apenas uma String com informação de contexto sobre a classe e método onde houve a quebra de contrato, porém não indica qual o argumento da função que não atendeu aos requisitos do contrato.
Originalmente, as sentenças de pré-condições são concatenadas pelo compilador, adicionando sempre o operador lógico “&&” na junção das sentenças, de forma que a expressão de pré-condição seja única, como observado na Figura 14. Na mensagem de exceção da pré-condição, encontrada na Figura 15, foi obtida após a chamada da função com os argumentos x 15 e y -10, porém foram exibido os dois argumentos da função presente nas sentenças de pré-condição, mesmo sendo apenas o argumento y que não está de acordo com as sentenças.
Figura 14 - Exemplo de sentença
Fonte: Autor (2017).
Figura 15 - Mensagem de erro de Pré-condição
Caused by: org.aspectjml.ajmlrac.runtime.JMLEntryPreconditionError: by method org.alan.Main.teste regarding specifications at
File "org.alan.Main.java", [spec-case]: line 5, character 25 (org.alan.Main.java:5), and
by method org.alan.Main.teste regarding code at File "org.alan.Main.java", when
'x' is 15 'y' is -10
at org.alan.Main.teste(Main.java:8) at org.alan.Main.main(Main.java:13)
2.5. BEAN VALIDATION
O Java EE e Java SE possuem a especificação JSR 303 para validações. Esta especificação define um modelo de meta-dados e API para JavaBean Validation, sendo seu principal método de utilização através de anotações, mas sendo possível também a utilização de XML, Bernard (2009).
A maneira que o Bean Validation implementa as validações é através de anotações Java. Conforme a Tabela 4, a versão 1.1 do Bean Validation possui 26 anotações de validação disponíveis em seu pacote javax.validation.constraints.
Tabela 4 - Pacote de validações javax.validation.constraints AssertFalse AssertFalse.List AssertTrue AssertTrue.List Future Future.List Past Past.List DecimalMax DecimalMax.List DecimalMin DecimalMin.List Max Max.List Min Min.List Digits Digits.List Size Size.List NotNull NotNull.List Null Null.List Pattern Pattern.List
Fonte: Adaptado de https://docs.jboss.org/hibernate/beanvalidation/spec/1.1/api/.
Caso seja necessário a utilização de validações mais complexas, que não estão disponíveis por padrão, é possível a criação de validações. As classes de validação Java devem implementar a interface ConstraintValidator e sobescrever os métodos abstratos initialize e isValid, também deve ser criado uma anotação Java específica para esta validação. Na Figura 16 temos um exemplo de uma classe que faz validação de CEP e na Figura 17 temos uma anotação Java.
Figura 16 - Validador de cep 1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class CepValidator implements ConstraintValidator<Cep, String> {
private Pattern pattern = Pattern.compile("[0-9]{5}-[0-9]{3}");
@Override
public void initialize(Cep constraintAnnotation) {
}
@Override
public boolean isValid(String value,
ConstraintValidatorContext context) { Matcher m = pattern.matcher(value); return m.matches(); } } Fonte: Autor (2017).
Figura 17 - Anotação para o validador de cep 1 2 3 4 5 6 7 8 9 10 11 @Constraint(validatedBy = CepValidator.class) @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME)
public @interface Cep {
String message() default "Cep inválido"; Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { }; }
Fonte: Autor (2017).
Através da anotação Java de validação da Figura 17, é possível aplicar essa validação sobre qualquer atributo do tipo String, assim como exemplificado na Figura 18. As validações podem ser aplicadas sobre elementos FIELD, METHOD, TYPE, ANNOTATION TYPE e PARAMETER. Caso a validação não seja devidamente atendida será emitida uma exceção ConstraintValidationException contendo informações de contexto da validação.
Figura 18 - Uso da anotação Bean Validation 1 2 3 4 5 6
public class Contato {
@Cep
private String cep;
... }
3.
ADEQUAÇÃO DO ASPECTJML PARA RESTFUL
Neste capítulo abordamos as adequações de código que foram realizadas no
compilador da linguagem AspectJML com o objetivo de identificar quais parâmetros não atendem as cláusulas de pré-condições, tornando possível retornar mensagens de erro com informações mais precisas.
3.1. CORREÇÃO DE DEFEITO
Primeiramente, foi constatado um defeito no compilador AspectJML, onde os parâmetros de contexto da mensagem de exceção referidos na Figura 15 somente são exibidos quando a flag de compilação defaultnonnull estiver habilitada. Caso contrário, os parâmetros e seus conteúdos são omitidos, embora a string “when”, que precede essas informações, permaneça na mensagem, conforme Figura 19.
Figura 19 - Mensagem de exceção com flag desabilitada
Caused by: org.aspectjml.ajmlrac.runtime.JMLEntryPreconditionError: by method org.alan.Main.teste regarding specifications at
File "org.alan.Main.java", [spec-case]: line 6, character 25 (org.alan.Main.java:6), and
by method org.alan.Main.teste regarding code at File "org.alan.Main.java", when
at org.alan.Main.teste(Main.java:8) at org.alan.Main.main(Main.java:13)
Fonte: Autor (2017).
Sendo essas informações, nomes e valores dos parâmetros, essenciais para a elaboração da prova de conceito da seção 4.4.3, foi efetuada a correção deste defeito no compilador do AspectJML.
3.2. SEPARAÇÃO DE SENTENÇAS
Conforme discutido na seção 2.4.5, as exceções emitidas na quebra de contrato da linguagem AspectJML só possuem uma string concatenada com informações de contexto, não sendo possível identificar qual foram os argumentos que estão em desacordo com o contrato
estabelecido, pois nessa string são apresentados todos os argumentos presentes em todas as cláusulas do contrato de pré-condição.
Em aplicações RESTful, quando é efetuada uma requisição inválida, é importante para quem fez a requisição, saber o que não foi aceito pelo servidor. A resposta dessa requisição deve conter informações sobre qual objeto ou parâmetro não foi aceito, mensagem explicativa e onde obter mais informações. Para que possamos retornar essa mensagem, precisamos saber quais argumentos não atenderam ao contrato.
A adequação que foi efetuada no compilador, consistiu em alterar o passo RAC code generation do compilador ajmlc, que é o passo responsável por adicionar os códigos de checagem de contratos aos join points. Essa adequação tem por finalidade tratar cada cláusula de pré-condição como uma clausula distinta. Desta forma, ao invés de tratar internamente todas as sentenças de pré-condição como uma única sentença, é definida uma lista de sentenças na qual cada sentença é verificada separadamente. Com isso os argumentos exibido na mensagem de exceção são apenas os que compõem a sentença que não foi cumprida no processo de verificação de contrato.
Através dessa adequação, a execução do mesmo contrato com os mesmos valores de argumento da seção 2.4.5, a mensagem de erro exibe apenas o argumento “y”, conforme a Figura 20.
Figura 20 - Mensagem de erro com a adequação
Caused by: org.aspectjml.ajmlrac.runtime.JMLEntryPreconditionError: by method org.alan.Main.teste regarding specifications at
File "org.alan.Main.java", [spec-case]: line 6, character 25 (org.alan.Main.java:6), and
by method org.alan.Main.teste regarding code at File "org.alan.Main.java", when
'y' is -10
at org.alan.Main.teste(Main.java:8) at org.alan.Main.main(Main.java:13)
Fonte: Autor (2017).
possível identificar o parâmetro que ocasionou a quebra de contrato e possibilitar a criação de respostas com informações que possam explicitar a causa do erro. As pós-condições não foram inclusas nesta adequação, pois a violação desse tipo de condição pode representar um defeito no software, e a exposição dessas informações internas podem comprometer a segurança do Web Service.
4.
APLICANDO DESIGN BY CONTRACT EM RESTFUL
Neste capítulo conduziremos uma avaliação comparativa entre as duas abordagens já apresentadas neste trabalho: A abordagem de validação padrão do Java EE, Bean Validation e a abordagem de DbC com a versão adaptada da linguagem AspectJML. Faremos comparações de funcionalidades e desempenho e análise de produção de resposta de erro.
4.1. APRESENTAÇÃO DA CLASSE DE RECURSO
Nesse estudo utilizamos a implementação de exemplo de integração de Bean Validation com Jax-RS, disponibilizada no repositório oficial do projeto Jersey19. A aplicação
é um Web Service de uma agenda que fornece uma interface RESTful para cadastro, remoção e pesquisa de contatos.
A fim de fazer essa análise, utilizamos a mesma classe de recurso Jax-RS, com alterações apenas nas validações do Bean Validation e adições de contratos AspectJML. A Figura 21 mostra uma classe de recurso que responde uma requisição HTTP para a URI “contact” de acordo com a anotação @Path. O método addContact na linha 14 é anotado com o designador @POST na linha 5, também com a anotação @Consumes (MediaType.APPLICATION_JSON) na linha 6 para receber no corpo da requisição um objeto Json. As linhas 7 à 13 são implementações de contrato em AspectJML.
Figura 21 - Apresentação código AspectJML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Path("contact") @Produces(MediaType.APPLICATION_JSON) public class ContactCardResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
//@ requires contact.getEmail() != null && PatternValidator
.validate("[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\ .[a-zA-Z] {2,4}", contact.getEmail()) == true;
//@ requires PatternValidator.validate("[0-9]{3,9}", contact.getPhone()) == true;
//@ requires contact.getFullName() != null && //@ contact.getFullName().length() >= 2 &&
contact.getFullName().length() <= 20;
//@ requires StorageService.exist(contact.getEmail()) == false; //@ ensures StorageService.exist(\result.getId()) == true; //@ ensures \result == null || \result.getId() != null; public ContactCard addContact(
final ContactCard contact ) {
return StorageService.addContact(contact); }
Fote: Autor (2017).
As Figuras 22, 23, 24 e 25 exibem a implementação do mesmo exemplo anterior, porém utilizando Bean Validation. Na Figura 22 as linhas 11, 12, 14, 15 e 16 e na Figura 23 nas linhas 4, 9, 14, 19 e 20 representam anotações de contratos do Bean Validation, nas demais linhas os códigos são semelhantes.
Figura 22 - Classe de recurso implementado com Bean Validation 1 2 3 4 5 6 7 8 9 @Path("contact") @Produces("application/json")
public class ContactCardResource {
@POST
@Consumes("application/json")
@NotNull(message = "{contact.already.exist}") @HasId
10 11 12 13 14
@NotNull @AtLeastOneContact(message = "{contact.empty.means}") @Valid final ContactCard contact) {
return StorageService.addContact(contact);
} }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp. Figura 23 - Classe de entidade ContactCard com anotações Bean Validation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @XmlRootElement
public class ContactCard {
@DecimalMin(value = "1")
private Long id;
private String fullName;
@Email(message = "{contact.wrong.email}", regexp =
"[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}")
private String email;
private String phone;
@NotNull(message = "{contact.wrong.name}") @Length(min = 2, max = 20)
public String getFullName() {
return fullName;
}
@Pattern(message = "{contact.wrong.phone}", regexp = "[0-9]{3,9}")
public String getPhone() {
return phone;
} ... }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp. Figura 24 - Anotação AsLeastOneContract
1 2 3 4 5 6 7 8 @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = AtLeastOneContact.Validator.class)
public @interface AtLeastOneContact {
String message() default "{org.glassfish.jersey.examples”
+”.beanvalidation.webapp.constraint.AtLeastOneContact.message}"; Class<?>[] groups() default {};
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Class<? extends Payload>[] payload() default {};
public class Validator implements
ConstraintValidator<AtLeastOneContact, ContactCard> { @Override
public void initialize(final AtLeastOneContact hasId) {
}
@Override
public boolean isValid(final ContactCard contact, final
ConstraintValidatorContext constraintValidatorContext) {
return contact.getEmail() !=null || contact.getPhone() !=null;
} } }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp. Figura 25 - Anotação HasId
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = {HasId.Validator.class, HasId.ListValidator.class})
public @interface HasId {
String message() default "{org.glassfish.jersey.examples. +”beanvalidation.webapp.constraint.HasId.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public class Validator implements ConstraintValidator<HasId,
ContactCard>{ @Override
public void initialize(final HasId hasId) {
}
@Override
public boolean isValid(final ContactCard contact, final
ConstraintValidatorContext constraintValidatorContext) {
return contact == null || contact.getId() != null;
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 }
public class ListValidator implements ConstraintValidator<HasId,
List<ContactCard>> {
private Validator validator = new Validator();
@Override
public void initialize(final HasId hasId) {
}
@Override
public boolean isValid(final List<ContactCard> contacts, final
ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = true;
for (final ContactCard contactCard : contacts) { isValid &= validator.isValid(contactCard,
constraintValidatorContext); } return isValid; } } }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp. Esses exemplos em ambas abordagens apresentam as mesmas pré-condições e pós-condições. A primeira pré-condição na implementação em AspectJML é encontrada na linha 7 da Figura 21, onde é feita uma chamada à uma função estática booleana que verifica se uma string está de acordo com a expressão regular passada por parâmetro. Nesse caso específico o parâmetro utilizado é a string email do objeto contact, e a expressão regular é uma expressão para analisar o email.
No implementação com Bean Validation, a mesma pré-condição é encontrada na Figura 22 na linha 9. A anotação @Email utilizada é uma anotação própria da especificação Bean Validation. Nesta anotação além do parâmetro pattern, o mesmo utilizado na implementação AspectJML, há também o parâmetro message, o qual pode ser utilizado uma mensagem específica para aquela validação.
De forma muito semelhante a segunda pré-condição na implementação com AspectJML encontrada na linha 10 da Figura 21, utiliza a mesma função PatternValidator, porém com o padrão correspondente ao telefone. Já no Bean Validation a anotação utilizada é @Pattern encontrada na Figura 23 linha 16 sobre uma função, o que é caracterizado como uma Pós-Condição, pois a validação é feita na saída da função.
4.1.1. Quantidade de código
O primeiro ponto a ser levado em consideração desta pesquisa é a quantidade de código que é necessário para estabelecer os mesmos contratos. Neste trecho da classe de recurso, as validações com a linguagem AspectJML possuem apenas 7 linhas, enquanto em Bean Validation, além das anotações de validação sobre as funções e atributos, há também classes e interfaces com as implementações específica de validação. O que faz do Bean Validation, quando não são utilizado as validações disponíveis por padrão, uma opção mais custosa para a implementação de contratos.
Quanto a este ponto, nota-se um ganho de produtividade, em termos de quantidade de código a ser escrito, em comparação a implementação em Bean Validation, pois quanto maior a complexidade dos contratos, maior é a diferença de quantidade de código entre ambas as abordagens.
4.1.2. Pré-condições
As primeiras validações a serem feitas em uma chamada de função, são as pré-condições, ou seja, as validações dos dados enviados por parâmetro. A biblioteca Jax-RS transforma cada ítem de identificação da URL, cada ítem da query string e o objeto da requisição HTTP e transforma em parâmetros para o método mapeado na classe de recurso. Quando as pré-condições estabelecidas não são satisfeitas, o Web Service deve retornar um erro de requisição HTTP, com o código de erro conforme Tabela 1. O exemplo em estudo nesta seção exibe um recurso específico dentro da classe de recursos ContactCardResource encontrado na Figura 19 e Figura 20.
argumento id seja positivo. Se a condição não for atendida, nesse caso, o parâmetro for negativo, é emitida uma exceção do tipo JMLEntryPreconditionError.
Figura 26 - Pré-condição AspectJML 1 2 3 4 @GET @Path("{id}") //@ requires id > 0;
public ContactCard getContact(@PathParam("id") long id) { ... }
Fonte: Autor (2017).
Na implementação com Bean Validation a mesma validação é feita apenas com a anotação @Min na Figura 27. Caso o parâmetro não seja satisfeito, uma exceção ConstraintValidationException é emitida.
Figura 27 - Pré-Condição em Bean Validation 1
2 3
@GET
@Path("{id}")
public ContactCard getContact(@Min(1) @PathParam("id") long id) { ... }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp. Em ambos os casos a utilização de pré-condição com Jax-RS pode ser executado. Se a validação em Bean Validation não for possível com as anotações padrão de validação da especificação, pode-se criar uma classe que faça a implementação.
4.1.3. Pré-condições que envolvem diversos argumentos
Avançando nas pré-condições, na Figura 28 temos um exemplo de código implementado em AspectJML. Este código exibe um método que faz parte da mesma classe de recurso Contacts e que retorna uma lista de contatos. Este método recebe dois parâmetros representando o intervalo de alcance de números de identificadores de contatos. O código de pré-condição está localizado na linha 4, o qual certifica que o primeiro argumento init é maior que end.
Figura 28 - Pré-condição com diversos argumentos em AspectJML 1
2
@GET
3 4 5 6 7 8 9 //@ requires init.intValue() > 0;
//@ requires init.intValue() < end.intValue();
public Response getContactsBetweenRange(
@QueryParam("init") Integer init, @QueryParam("end") Integer end) { ...
}
Fonte: Autor (2017).
Este tipo de validação, que envolve mais de um parâmetro numa mesma validação, como demonstrado na Figura 30, torna necessária a criação de uma interface de anotação e uma classe que implemente ConstraintValidator para assim ser possível ser utilizado conforme a Figura 29, neste caso usando a anotação @RangeParams. Além disso, como a função a ser sobrecarregada isValid recebe como parâmetros um vetor de Object, sendo necessário, em tempo de execução, a certificação de que a quantidade de elementos está correta e a conversão para Integer. Diferente das outras pré-condições, esse tipo de validação deve ser anotada sobre o método e não sobre o parâmetro, tornando possível a anotação sobre métodos sem argumentos sem gerar erro de compilação.
Figura 29 - Pré-condição com diversos argumentos em Bean Validation 1 2 3 4 5 6 7 8 @GET @Path("range") @RangeParams
public Response getContactsBetweenRange(
@QueryParam("init") Integer init, @QueryParam("end") Integer end) { ...
}
Fonte: Autor (2017).
Figura 30 - Classe de validação RangeParams 1 2 3 4 5 6 7 @Target({ElementType.METHOD , ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = RangeParams.Validator.class) @Documented
public @interface RangeParams {
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 + "Found: 'init'=${validatedValue[0]}, " + "'end'=${validatedValue[1]}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public static class Validator implements
ConstraintValidator<RangeParams, Integer[]> { @Override
public void initialize(RangeParams constraintAnnotation) {
}
@Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) { if (value == null || value.length != 2) {
return false; }
return (Integer) value[0] < (Integer) value[1];
} } }
Fonte: Adaptado de https://github.com/jersey/jersey/tree/master/examples/bean-validation-webapp.
4.1.4. Pós-condições
As duas abordagens aqui avaliadas fornecem pós-condições (validação de saída). No exemplo a seguir continuamos com a classe de recurso Contacts, dessa vez o método é a de uma requisição HTTP POST para adicionar um novo contato a lista de contatos. A pós-condição é de garantir que o retorno não seja nulo e que o objeto de retorno possua um identificador.
Em AspectJML a implementação é simples, bastando inserir o código do contrato acima do método, utilizando o objeto \result, o qual se refere ao objeto retornado pelo método exemplificado na Figura 31.
Figura 31 - Pós-condição em AspectJML 1
2 3
@Post
//@ ensures \result == null || \result.getId() != null;
public ContactCard addContact(final ContactCard contact) { ... }
Fonte: Autor (2017).
No Bean Validation há algumas anotações de validações disponíveis, como é o caso da anotação @NotNull. Essa anotação garante que o objeto não seja nulo, o que é suficiente para atender parte do contrato. Porém quando não há uma anotação disponível pela especificação, é necessária a criação da classe de validação e a anotação. A Figura 24 se refere a classe de validação e a anotação para certificar que o objeto retornado pelo método possua um código identificador.
4.1.5. Pós-condições com validação de argumentos
As pós-condições do AspectJML, além de fornecer a possibilidade de utilizar o objeto de retorno do método na especificação do contrato, permitem também utilizar argumentos do método em cláusulas do contrato. O trecho de código na Figura 31 se refere a um método da classe de recurso ContactCardResource, respondendo uma requisição HTTP DELETE anotado com o designador @DELETE e recebe como parâmetro um objeto Long id, que se refere ao código identificador de ContactCard.
A pós-condição em questão, localizada na linha 5 (cinco), assegura que após o término da execução do procedimento de remoção do objeto, não exista na base de dados um objeto ContactCard com o identificador enviado pelo parâmetro id, ou seja, a pós condição garante que o objeto não esteja na base de dados, caso contrário haverá uma quebra de contrato.
Figura 32 - Pós-condição com uso de argumentos em AspectJML 1 2 3 4 5 6 @DELETE @Path("{id}") @DocNumber("http://example.com/doc/32.html") //@ requires StorageService.exist(id) == true; //@ ensures StorageService.exist(id) == false;
7 8 9
@PathParam("id") final Long id) {
return StorageService.remove(id);
}
Fonte: Autor (2017).
Pós-condições que envolvam argumentos como parte da validação não é possível de serem desenvolvidas com Bean Validation.
4.1.6. Classes invariantes
O conceito de classe invariante, já exposto na seção 2.4.2, se refere a condições de atributos de um objeto que deve ser mantida durante todo seu ciclo de vida. No caso de aplicações RESTful, podemos utilizá-lo em objetos que são enviados na requisição HTTP.
Apesar de ser uma das características da linguagem AspectJML, não é possível fazer a integração com Jax-RS. Já com Bean Validation, apesar da ausência desse termo, pode-se obter o mesmo resultado ao utilizar anotações de validação sobre atributos do objeto, o qual funciona plenamente com Jax-RS.
4.1.7. Signal Clauses
Assim como classes invariantes, a funcionalidade de Signals Clauses não se demonstrou compatível com Jax-RS. Quando há uma quebra desse tipo de pós-condição, o jax-rs não é capaz de capturar a exceção e retorna um erro de servidor com código HTTP 500 (Internal Error).
4.1.8. Modularização de Contratos
A principal característica da linguagem AspectJML é a possibilidade de modularizar contratos. Esse recurso pode diminuir ainda mais a quantidade de código de especificação de contrato, fazendo o reaproveitamento de partes do contrato que podem ser aplicadas em diferentes métodos de classes de recurso através da padronização da assinatura do método.
Na Figura 33 é modularizada a pré-condição para o parâmetro id, requerendo que o mesmo seja maior que zero. Assim, com o Pointcut, essa pré-condição será aplicada a todos os métodos do pacote Controllers que tiverem um parâmetro do tipo Long chamado id. O
mesmo pode ser feito com pós-condições.
Figura 33 - Contrato modularizado 1
2 3
//@ requires id > 0;
@Pointcut("execution( * *.* (long)) && args(id)")
public void idContract(final Long id) {}
Fonte: Autor (2017).
Com Bean Validation, não há uma forma de se aplicar uma validação através apenas do padrão da assinatura do método, porém uma classe que implemente a validação pode ser reaproveitada em qualquer ponto da aplicação, bastando declarar a anotação sobre o parâmetro a ser validado.
4.2. COMPARAÇÃO DE DESEMPENHO
A comparação de desempenho tem por objetivo medir a diferença de tempo de resposta de requisições HTTP RESTful entre as duas implementações. Para isso foi utilizado o mesmo projeto da seção anterior porém foram modificadas com a inclusão de uma classe que captura as exceções de erro de validação, afim de retornar um conteúdo vazio como resposta HTTP.
Utilizamos uma máquina virtualizada com conforme as especificações da Tabela 5, e desenvolvido um script bash que executa 50.000 vezes a mesma requisição HTTP para ambos os serviços utilizando a aplicação curl. Esta requisição envia um objeto Json usando o verbo HTTP POST que atende todas as pré-condições, mas forçando a quebra de uma pós-condição.
Tabela 5 - Ambiente de comparação de desempenho
Processador Intel Core i5 2500 3.3Ghz
Memória RAM 1024 MB
Sistema Operacional Ubuntu Server 16.04, Kernel Linux
4.10.0-19-generic
Linguagem Java 8
JDK 1.8_131 OpenJDK
Servidor de Aplicação Glassfish 4.1.1
Fonte: Autor (2017).
Este script foi executado 5 vezes e seu tempo de execução foi medido utilizando o comando time. Os resultados são encontrados na Tabela 6 e no Gráfico 1.
Tabela 6 - Tempo de execução entre Bean Validation e AspectJML Rodada Bean Validation
Tempo em milisegundos AspectJML Tempo em milisegundos 1 596292 577430 2 597543 576962 3 596715 577205 4 595621 577259 5 595633 577361 Fonte: Autor (2017).
Gráfico 1: Gráfico de tempo de execução do Bean Validation e AspectJML
Fonte: Autor (2017).
A implementação com AspectJML foi 3,20% mais rápido se comparado a implementação utilizando Bean Validation. De fato, esse teste de desempenho mostrou que o
AspectJML tem um desempenho aceitável como uma alternativa real ao Bean Validation.
4.3. PRODUZINDO MENSAGENS DE ERRO
Uma questão importante a ser analisada no contexto da implementação de sistemas RESTful, de acordo com as melhores práticas, é a resposta que um serviço retorna quando há algum erro na requisição ou no seu processamento. Quanto mais específica e mais clara for a resposta de erro, certamente mais simples será a identificação do erro da requisição. Uma boa prática RESTful é a entrega de um objeto contendo informações sobre o erro, já discutido na seção 2.2.1.
Para avaliar a capacidade de produzir mensagens de erro de acordo com boas práticas, foi desenvolvido um sistema RESTful de troca de mensagens entre usuários e grupos utilizando as tecnologias Java EE já apresentadas e a versão adequada do AspectJML.
4.3.1. Erros de requisição
No código da Figura 34 temos uma classe de recursos “Messages”, focaremos na função getMessagesByGroup que responde por uma requisição HTTP GET. Temos dois parâmetros nessa função: group que corresponde ao nome do grupo do qual o usuário quer obter as mensagens e maxResult que indicia a quantidade máxima de mensagens que devem ser retornados, ambos inseridos em uma query string.
Figura 34 - Método mensagens por grupo 01 02 03 04 05 06 07 08 09 10 11 12 @Path(“messages”)
public class Messages {
@GET
@MoreInfo(value = "https://example.com/doc/item/3") @Produces(MediaType.APPLICATION_JSON)
//@ requires group != null && group.length() >= 3 && group.length() <= 50; //@ requires groupService.getGroupByName(authenticatedUser, group) != null; //@ requires maxResult > 0 && maxResult <= 100;
//@ ensures \result == null || \result.size() <= maxResult;
public List<MocMessage> getMessagesByGroup( @Filtro