Design: Desfazendo Mal-entendidos - REST

William Santos - Mar 6 '23 - - Dev Community

Olá!

Este é mais um post da seção Design, dentro da série Desfazendo Mal-entendidos, e, desta vez, falaremos um pouco sobre REST e aplicações RESTful.

Como já é habitual nesta série, vou começar com algumas afirmações, das quais tratarei ao longo do post:

  1. REST não é sobre verbos e códigos de status do HTTP;
  2. REST limita a semântica de aplicações complexas;
  3. Provavelmente você nunca implementou uma aplicação RESTful.

Vamos começar contextualizando, entendendo basicamente o que é REST, qual sua motivação e principais características e, em seguida, exploramos as três provocações.

O que é REST?

REST é um acrônimo para Representational State Transfer (ou Transferência de Estado por Representação, em tradução livre), e foi um padrão idealizado por Roy T. Fielding nos anos '90, e formalizado em sua tese de doutorado (em inglês) em 2000.

Sua principal motivação foi definir um conjunto de restrições para a Web, visto que ela era flexível o bastante para tornar-se complexa e confusa. Fielding entendeu que, definindo um estilo arquitetural baseado nessas restrições, seria possível criar implementações observáveis, escaláveis, confiáveis e de fácil evolução.

Nota: Fielding cunhou um termo muito interessante para o cenário no qual definiu o REST, o Null Style. Este termo define a ausência de fronteiras claras entre os componentes de uma arquitetura, bem como um vazio (nulidade) de restrições a estes aplicados. Grosso modo, todo sistema livre destas delimitações é um sistema Null Style.

Vamos falar sobre as restrições do REST que se aplicam ao escopo deste post. Para as demais, recomendo este ótimo post do amigo Elemar JR, em português, que traduz muito bem o capítulo sobre REST da tese de Fielding.

Não Guardar Estado (Stateless)

Esta é uma restrição importantíssima, porque determina que toda requisição deve conter todas as informações necessárias ao servidor para atendê-la, e o mesmo para resposta, possibilitando ao cliente interpretá-la.
Ou seja, uma requisição jamais fará referência a um estado armazenado no servidor a partir do atendimento a uma requisição anterior, e o servidor não servirá qualquer dado que não seja demandado pela requisição.

Além disso, todo controle de sessão deve ser, necessariamente, implementado no cliente, de modo a não manter vínculo entre este e o servidor, permitindo assim que o escopo da observação das relações entre o cliente e o servidor se restrinjam a uma requisição, sem que um contexto presente no servidor precise ser examinado em conjunto.

Nota: é importante destacar que, quando a tese foi escrita (2000), o protocolo HTTP estava em sua versão 1.1. Ou seja, as características de conexão persistente e multiplexação do HTTP/2 ainda não estavam disponíveis.

Suporte a Cache

Esta restrição tem por principal motivação a redução de round trips entre o cliente e o servidor. Ou seja, que o cliente precise, necessariamente, se conectar ao servidor para obter um dado já solicitado. Por conta deste mecanismo, o desempenho percebido pelo cliente é melhor, e evita-se a latência da rede. Cabe ao servidor determinar se um dado pode, ou não, ser cacheado e, para isso, caso deseje explicitar esta possibilidade, adiciona metadados à resposta das requisições (como Etag ou Cache-Control).

Nota: à época da tese de Fielding, havia um mecanismo de cache implícito, que determinava que um dado recebido do servidor poderia ser reutilizado pelo cliente por um período calculado a partir de seu header Last-Modified. O uso de cache com base neste critério, sempre implementado no cliente, é chamado de Heuristic Cache (ou "Cache Baseada em Heurística" em tradução livre). Este modelo acabou caindo em desuso por força da adoção em escala do header Cache-Control.

Interface Uniforme

Aqui falamos sobre a padronização do formato de requisições e respostas. Este formato existe para permitir que cliente e servidor consigam se entender adequadamente, sem que o cliente precise conhecer especificidades do servidor e vice-versa. Basicamente, falamos sobre quatro restrições que formam a Interface Uniforme: identificação do recurso (presente em sua URL), manipulação dos recursos por meio de representações (grosso modo, o corpo da requisição), mensagens auto-descritivas (stateless) e hipermídia como formato de estado.

HATEOAS

Este é um acrônimo para Hypermidia as the Engine of Application State, que costumo traduzir como "Hipermídia como Formato do Estado da Aplicação". A razão pela qual troco Engine por "Formato" é apenas didática.

Parte da hipermídia como formato de estado é uma informação adicional ao recurso servido ao cliente, que deve indicar quais são os vínculos deste com outros recursos e, geralmente, o faz por meio da oferta de links. Ou seja, se solicitamos o dado de um pedido em uma loja virtual, uma das respostas possíveis seria a seguinte:

{
    "id":1,
    "status": "payment pending",
    ...
    "links":
    [
        {
            "rel": "self",
            "method": "GET",
            "href": "/orders/1
        },
        {
            "rel": "items",
            "method": "GE",
            "href": "/orders/1/items"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Agora, que entendemos basicamente o que é REST, para fins de compreensão das afirmações, vamos explorá-las.

Afirmação 1: REST não é sobre verbos e códigos de status do HTTP

Aqui já temos uma boa ideia razão pela qual esta afirmação é verdadeira. Não há nenhuma menção aos verbos e status na definição de REST, o que deixa a implementação do servidor livre para definir sua semântica. Ou seja, uma vez respeitadas as restrições do estilo, verbos e status, são pouco relevantes.

É claro que isso não significa que deve-se implementar uma ação com metodo GET para alterar um recurso. Mas respeitar a semântica dos verbos HTTP, bem como a dos códigos de status, não torna sua aplicação RESTful.

Este é um grande mal-entendido quando se trata de REST, porque muito se discute qual verbo utilizar para uma dada ação, e qual status retornar em uma dada situação. Mas a verdade é que essa não é uma discussão sobre REST. É, apenas, uma discussão sobre a semântica do protocolo HTTP.

Portanto, este é apenas um detalhe de implementação, que não faz parte do escopo das restrições do estilo.

Afirmação 2: REST limita a semântica de aplicações complexas

Esta afirmação não é minha, a emprestei de Greg Young, criador do padrão arquitetural CQRS.

O problema em questão é semântico. Como vimos acima, REST é um estilo de transferência de estado por representação. Isto é, qualquer que seja o recurso existente e um servidor (um documento, página HTML, imagem, ou resultado de um processamento) é a representação de um estado sendo transferido entre cliente e servidor. Isso faz de REST um estilo focado em dados (data centric).

Isso significa que os recursos estão sujeitos, apenas, às ações disponíveis por meio da combinação entre um verbo HTTP e o identificador do recurso. Do ponto de vista da semântica do modelo de domínio, isso cria uma limitação, uma vez que as ações possíveis sobre um recurso não necessariamente representam um processo, como é comum a aplicações complexas.

Caso eu queira representar o cancelamento do pedido ilustrado como exemplo acima, não faz sentido utilizar um verbo como DELETE, ou mesmo POST, para um identificador como, por exemplo /orders/1/cancelling, pura e simplesmente porque cancelamento não é um recurso sendo acessado, mas sim uma operação com significado no domínio atendido pela aplicação.

Foi ao perceber esta limitação semântica que Greg Young propôs o que, a época, ele chamou de Task-Based UI ou "Interface de usuário baseada em tarefas", em tradução livre, para que a semântica da aplicação fosse respeitada, em vez de tratada, sempre, basicamente como um CRUD.

A fim de resolver esta questão em uma WebAPI, por exemplo, uma solução simples seria abandonar a ideia de resource locator das URLs, e adotar um modelo semântico de endereçamento de entidades e operações.
No caso do pedido de exemplo acima, poder-se-ia usar /order/cancel/1, onde order representa um modelo de domínio, cancel a operação a ser realizada e 1 seria parte da lista de parâmetros necessários à operação, neste caso o identificador do pedido a ser cancelado.
Ou seja, grosso modo, caso estivéssemos falando sobre um código C#, teríamos algo próximo do seguinte:

... 
var order = _repository.Get(1);
order.cancel();
... 
Enter fullscreen mode Exit fullscreen mode

Há uma frase que costumo usar para sustentar este ponto, que é "a infraestrutura existe para servir ao domínio, e não o contrário". Ou seja, subverter o formato de URLs para tornar a semântica da aplicação mais clara, não é um problema do meu modesto ponto de vista.

Entendo que puristas do HTTP não vão gostar desta proposta. Mas, do ponto de vista das equipes envolvidas no desenvolvimento de aplicações e integrações, me parece muito mais intuitivo que tentar fazer o modelo de domínio se adequar às convenções do protocolo HTTP.

Afirmação 3: Provavelmente você nunca implementou uma aplicação RESTful

Imagino que, a esta altura, esta afirmação já faça sentido. Mas, vamos explorar um pouco mais.

Como dito acima, segundo a proposta de Fielding, há um conjunto de restrições que definem REST. Vamos observar duas delas no detalhe:

  1. Não Guardar Estado: se você já implementou aplicações, por exemplo, em ASP.NET e se utilizou de recursos como Application e Session para guardar informações na memória do servidor, ou mesmo o objeto Cache, você já violou REST.
    Lembre-se de que os dados de sessão devem estar armazenados no cliente, e todos os dados necessários ao atendimento de uma requisição devem estar nela presentes. Ou seja, qualquer mecanismo que preserve dados na memória do servidor durante uma sessão, ou diretamente da aplicação, para o atendimento de requisições, faz de uma aplicação não RESTful. O mesmo é válido, obviamente, para qualquer outro framework e linguagem usados para a construção de aplicações Web.

  2. HATEOAS

Aqui vou deixar o próprio Fielding falar por mim, em tradução livre: "HATEOAS é uma restrição do REST. Não é uma opção. Não é um ideal. Hipermídia é uma restrição. Assim sendo, ou você implementa, ou não está implementando REST".

Me parece difícil contestar uma afirmação tão forte do próprio propositor do estilo. Não?

Pois bem. Muito provavelmente você já caiu em um dos dois casos, ou até mesmo em ambos. E, por isso, é bem provável que você nunca tenha implementado uma aplicação RESTful. Eu mesmo nunca implementei (e não há qualquer problema quanto a isso)!

Conclusão

Muitas das discussões que parecem ser sobre REST na verdade não são sobre REST, mas sobre nuances da semântica do HTTP. REST é um compêndio muito mais amplo, e tem por objetivo padronizar a arquitetura da própria Web.

Infelizmente, não apenas é comum que desconheçamos a proposta de REST como, também, subvertamos seu sentido ao tratar de aplicações Web, sobretudo Web APIs, como se todas fossem REST por definição quando, na imensa maioria dos casos, não são.

Gostou? Peço que me deixe saber pelos indicadores. Caso queira fique à vontade para deixar um comentário, ou me procurar em minhas redes sociais.

Até a próxima!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .