BT
x A sua opinião é importante! Por favor preencha a pesquisa do InfoQ sobre os seus hábitos de leitura!

Adicionando flexibilidade à sua implementação REST com Yoga

Postado por Corby Page , traduzido por Rafael Brito em 27 Jan 2014 |

As APIs REST são extremamente atrativas pela elegância de seus designs e possuem o que Adam Bosworth do Google descreve como "simples, menos rigorosas e extensível com pouca ordem", mas não foram projetadas para terem um desempenho consistente.

Os frameworks REST existentes fazem um ótimo trabalho convertendo o seu modelo de domínio para uma resposta JSON ou XML. Entretanto, eles operam sob o pressuposto de que cada recurso tem uma única visualização de documento. Isto significa que toda requisição para um documento, acaba retornando o documento inteiro, mesmo quando apenas uma parte da informação é solicitada.

O mais importante, é que a cada requisição GET, em uma API REST, retorna apenas um tipo de recurso. Portanto uma requisição na qual seja necessário agregar informações de vários tipos de recursos diferentes, será necessário realizar pelo menos uma requisição para cada recurso que esteja relacionado com a informação final. Pense em uma consulta com joins no mundo dos banco de dados relacionais. Sob um modelo puramente REST, cada linha retornada de uma junção necessita de sua própria requisição GET. Este tipo de mecanismo de conversação rapidamente se torna um gargalo na aplicação web.

Qual é a solução do Yoga?

O Yoga é uma caixa de ferramentas open source que se integra à sua aplicação REST existente, permitindo a customização das APIs que estejam publicadas. Ele trabalha adicionando seletores à sua requisição GET que específica exatamente qual informação é esperada de uma requisição, como se fosse uma cláusula de projeção de coluna do mundo relacional. Ele também inclui a possibilidade de especificar expressões relacionais que podem agregar múltiplos documentos de diferentes tipos de recursos relacionados entre si em uma mesma resposta.

Isto não se trata de um novo conceito. As API's proprietárias, tais como as primeiras versões da API do Linkedln e a especificação da Google's GData já utilizavam. Mas ao contrário destas API's o Yoga permite que se introduza esta sintaxe na aplicação Java REST.

O restante desta seção irá demonstrar um caso de uso no qual o Yoga irá melhorar o desempenho de uma aplicação, enquanto preserva a sintaxe de fácil utilização do REST.

Especificando Campos de Recursos

Aqui esta uma típica requisição REST, para recuperar uma instância de uma recurso. Ele irá retornar todos os campos associados com o tipo de recurso User:

GET /user/1.json

E se, devido a preocupação quanto a segurança e desempenho fosse necessário gerar uma requisição que retorna somente o nome e a localização de uma informação para um determinado usuário? Para propósitos de desenvolvimento, ou disponibilização à clientes de confiança, pode-se associar um seletor a requisição.

GET /user/1.json?selector=(id,name,city,state,country)

Para disponibilizar API's públicas, provavelmente não precisa dar ao seus usuários finais poderes ilimitados de utilização de um seletor. Para evitar isso, será necessário simplesmente publicar um pseudônimo (alias) para o seletor definido.

GET /user/1.json?selector=$locationView

Recuperando Múltiplos Tipos de Recursos

Agora, considere a situação na qual é necessário navegar pelo gráfico de objeto do modelo de domínio e retornar informações de múltiplas classes em uma única chamada a API:

O gráfico anterior reflete o modelo de entidades de dados utilizados por um aparelho móvel ou um cliente Javascript o qual agrega um número de entidades em uma única tela de informação. Tal requisição enviada pelo cliente deve retornar dados sobre um usuário, seus amigos, quais artistas musicais seus amigos gostam, e álbuns e canções produzidas por determinado artista (este caminho no gráfico é destacado pela cor marrom).

Conceitos como Usuário, Artista e Música são recursos REST distintos, por isso usando a forma REST padrão, são necessárias múltiplas chamadas:

GET /user/1.json (Obter usuário)

GET /user/2.json (Obter as entidades detalhadas dos amigos)
GET /user/3.json
...
GET /artist/1.json (Obter os artistas favoritos)
GET /artist/2.json
...
GET /album/1.json (Obter os álbuns do artista)
GET /album/2.json
...
GET /song/1.json (Obter as músicas do álbum)
GET /song/2.json
...

Claramente, isto não escala conforme avançamos mais a fundo no gráfico de objetos. A latência na rede irá rapidamente se tornar um gargalho de desempenho.

Com seletores, podemos especificar todos os dados que precisamos, e recuperá-los em uma única chamada. Para desenvolvimento e clientes autorizados, podemos especificar o seletor explicitamente:

GET /user/1.json?selector=friends(favoriteArtists(albums(songs)))

Para a instalação em produção das API's públicas, podemos publicar um Pseudônimo (Alias):

GET /user/1.json?selector=$friendsFavoriteMusic

Implementando o Yoga

Para adicionar o Yoga na aplicação existente é necessário pouquíssimas configurações. O Yoga integra facilmente com o Spring MVC REST, Jersey e o RESTEasy. No exemplo a seguir, iremos implementar o Yoga com uma aplicação Spring MVC REST.

Nossa aplicação REST usa a MappingJacksonJsonView do Spring para serializar as respostas:

  <property name="defaultViews">
       <list>
           <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
       </list>
   </property>

Nosso UserController usa URIs parametrizáveis para processar as requisições GET do recurso User e a anotação Spring @ResponseBody para renderizar a saída com o MappingJacksonJsonView:

  @RequestMapping("/user/{id}")
  public @ResponseBody User get( @PathVariable long id ) {
    return _userRepository.fetchUser( id );
  }

Se precisamos de um controle maior sobre a forma como renderizamos os documentos de um User, podemos migrar de uma aplicação REST para uma aplicação Yoga. Primeiro, precisamos importar nossas dependências Maven:

  <dependency>
      <groupId>org.skyscreamer</groupId>
      <artifactId>yoga-core</artifactId>
      <version>1.0.0</version>
  </dependency>
  <dependency>
      <groupId>org.skyscreamer</groupId>
      <artifactId>yoga-springmvc</artifactId>
      <version>1.0.0</version>
  </dependency>

Em seguida, substituímos o MappingJacksonJsonView do Spring pelo YogaSpringView, o qual sabe como analisar seletores:

<property name="defaultViews">
  <list>
    <bean class="org.skyscreamer.yoga.springmvc.view.YogaSpringView" p:yogaView-ref="jsonView"/>
    <!--<beanclass="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>-->
  </list>
</property>

A injeção de dependência do jsonView diz para o SpringMVC que o Yoga processará as requisições JSON e renderizará as saídas JSON. Definimos o jsonView no contexto da nossa aplicação:

  <bean name="jsonView" class="org.skyscreamer.yoga.view.JsonSelectorView" p:selectorParser-ref="selectorParser" />
  <bean id="selectorParser" class="org.skyscreamer.yoga.selector.parser.GDataSelectorParser"/>

Aqui, também informamos que a sintaxe especificada do GData usará os seletores. O Yoga apresenta um SelectorParser alternativo, LinkedlnSelectorParser, o qual pode ser usado por desenvolvedores que prefiram o formato dos seletores das APIs do Linkedln.

Por final, removemos a anotação @ResponseBody no nosso UserController. A anotação @ResponseBody é dependente do MappingJacksonJsonView, que foi substituido pelo YogaSpringView.

@RequestMapping("/user/{id}")
public User get( @PathVariable long id ) {
  return _userRepository.fetchUser( id );
}

Até aqui, o desenvolvedor pode disponibilizar uma aplicação web e associar um seletor apropriado para a requisição do recurso.

GET /user/1.json?selector=id,name

desta forma será renderizado a saída:

{
     "name": "Carter Page",
     "id": 1
}

O desenvolvedor pode adicionar os artistas favoritos ao seletor:

GET /user/1.json?selector=id,name,favoriteArtists(id,name)

e navegar para baixo no gráfico de objetos para visualizar as instâncias do recurso Artista:

{
     "id": 1,
     "name": "Carter Page",
     "favoriteArtists": [
           {
                 "id": 1,
                 "name": "Arcade Fire"
           },
           {
                 "id": 3,
                 "name": "Neutral Milk Hotel"
           }
      ]
}

Campos Core

No exemplo anterior, vamos assumir que os campos id e name são obrigatórios e devem ser retornados para todas as requisições do recurso User. Pois são baratos, pequenos, campos que integram e nomea-los explicitamente cada vez que criamos um seletor poderia começar a ficar trabalhoso.

O Yoga provê uma anotação @Core que pode ser aplicada no seu modelo de domínio serializável (ou DTO) para marcar campos que sempre serão retornados em uma requisição do Yoga. Aqui, estamos anotando o método getter no nosso objeto de domínio User:

   @Core
   public long getId() {
     return _id;
   }
   @Core
   public String getName() {
     return _name;
   }

Uma vez feito isso, já não é mais necessário ter explicitamente os campos id e name no nosso seletor. A seguinte requisição:

GET /user/1.json?selector=favoriteArtists(id,name)

retornará o id, name e qualquer outra coisa especificada no seletor:

{
      "id": 1,
      "name": "Carter Page",
      "favoriteArtists": [
            {
                  "id": 1, 
                  "name": "Arcade Fire" 
            },
            {
                  "id": 3,
                  "name": "Neutral Milk Hotel"
            }
      ]
}

Pseudônimos (Alias)

Refina iterativamente seus seletores criando assim um ciclo de desenvolvimento/debug ágil. Entretanto, quando se fala em disponibilizar sua API em produção, provavelmente não queremos que os usuários externos componham arbitrariamente os seletores para serem executados em seu ambiente. A navegação irrestrita no seu gráfico de objetos pode rapidamente se tornar problemas de segurança e desempenho.

O Yoga permite definir pseudônimos para os seletores que serão publicados, assim, somente os usuários autorizados poderão utilizar os seletores previamente definidos por pseudônimos. Podemos dizer que estamos felizes com o seguintes seletores e queremos publicá-los:

?selector=id,name,favoriteArtists(id,name)

Primeiramente, iremos desabilitar a utilização de seletores explícitos do nosso ambiente de produção, assim os usuários nesse ambiente não poderão compor seletores utilizando a sintaxe do GData (ou Linkedln)

<bean id="selectorParser" class="org.skyscreamer.yoga.selector.parser.GDataSelectorParser p:disableExplicitSelectors="true"/>

Em seguida, definimos o pseudônimo. O Yoga provê diversos mecanismos para definir pseudônimos; neste caso, iremos defini-los em um arquivo de propriedades.

<bean id="aliasSelectorResolver" class="org.skyscreamer.yoga.selector.parser.DynamicPropertyResolver" p:propertyFile="classpath:selectorAlias.properties"/>

No arquivo de propriedade, configuramos o pseudônimo e o nomeamos. Por convensão, um pseudônimo no Yoga começa com $:

$userFavoriteArtists=id,name,favoriteArtists(id,name)

Agora, no ambiente de produção, os usuários podem utilizar o pseudônimo, cujo o comportamento esta definido na documentação da API:

GET /user/1.json?selector=$userFavoriteArtists

Conclusão

Para muitos desenvolvedores, o modelo REST é suficiente por prover acesso web à API da aplicação de domínio. Porém se deseja um controle mais granulado sobre a estrutura dos documentos retornados, o Yoga se integrará como sua aplicação REST existente, e permitirá à adição de seletores nas requisições web.

A Skyscreamer Software liberou a versão 1.0 do Yoga. O Yoga integra diretamente com aplicações Java que executem implementações do Spring MVC REST, Jersey ou RESTEasy. Pode se encontrar um vídeo demonstrativo do material apresentado neste artigo neste link.

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Muito bom, mas induz ao erro by Alexandre Saudate

A questão de seletores é muito boa para reduzir o volume de tráfego, mas ao que me parece, faltou um "pequeno" detalhe: esses seletores só ajudam de fato a reduzir o tráfego em caso de entidades com muitos atributos de tipagem simples (String, int, double, etc.). Em casos de entidades referenciando outras entidades, o ideal é utilizar sempre HATEOAS com links estruturais.

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

1 Dê sua opinião

Conteúdo educacional

Feedback geral
Bugs
Publicidade
Editorial
InfoQ Brasil e todo o seu conteúdo: todos os direitos reservados. © 2006-2014 C4Media Inc.
Política de privacidade
BT