Soluções fáceis para problemas difíceis REST

Para todo problema complexo, existe sempre uma solução simples, elegante e completamente errada.
H. L. Mencken

Implementar soluções RESTful nem sempre é tarefa trivial. Neste apêndice, apresentamos algumas soluções para desafios comuns.

Suportando operações com longa duração

HTTP é um protocolo síncrono e stateless. Sempre que uma requisição é submetida a um “componente servidor”,  é normal que o “componente cliente” espere por uma resposta indicando falha ou sucesso. Entretanto, nem sempre é possível executar todo o processamento em tempo razoável.

Operações de longa duração iniciadas por HTTP POST

Problema

Eventualmente, operações iniciadas com verbos HTTP POST demoram para serem completadas. Logo, se implementadas ingenuamente, “seguram” o componentes “cliente” por muito tempo, eventualmente com timeout.

Solução

Ao executar operações de longa duração, iniciadas por verbos HTTP POST, o componente “servidor” deve retornar rapidamente uma response marcada com código HTTP 202 (Accepted) e com uma representação de um recurso específico para indicar o andamento.

HTTP/1.1 202 Accepted
Content-Type: application/json; charset=UTF-8
Content-Location: /tasks/1
Date: ...

{
  status: "accepted",
  message: "Your request has been accepted for processing.",
  ping-after: ... 
  links: [
  {
    rel: "self",
    href: "/tasks/1"
  }
  ]
}

É importante que este recurso possua, além de um indicador de status uma “previsão” de quanto tempo a operação deverá consumir.

Solicitações HTTP GET futuras para o recurso indicado na URL de acompanhamento devem ser ajustadas conforme estado de processamento:

  • se o processamento ainda não tiver sido completado, o recurso deve indicar status “still Processing”, retornando 200 (OK). Além disso, é recomendável ajustar o caching considerando tempo de processamento, aliviando pressão sobre o componente “servidor”
  • se o processamento foi completado com êxito, retornar 303 (Ser Other) com Location indicando URL do recurso gerado.
  • se o processamento não foi completado com êxito, retornar 200 (OK) com documento de status indicando a falha que ocorreu.

HTTP não foi pensado para ser assíncrono

Um detalhe que pode chamar a atenção na solução proposta é o retorno de 200 (OK), para recuperar o recurso de estado quando há falha no processamento. O motivo para isso é que a recuperação do recurso com o status, em si, não falhou.

Operações de longa duração iniciadas por HTTP POST e DELETE

Problema

Eventualmente, operações iniciadas com verbos HTTP PUT e DELETE demoram para serem completadas. Logo, se implementadas ingenuamente, “seguram” o componentes “cliente” por muito tempo, eventualmente com timeout.

Solução

Ao executar operações de longa duração, iniciadas por verbos HTTP DELETE, o componente “servidor” deve retornar rapidamente uma response marcada com código HTTP 202 (Accepted) e com uma representação de um recurso específico para indicar o andamento, de forma quase idêntica a abordagem para suportar operações HTTP POST – a mudança é não fazer redirecionamento ao concluir a operação (opcionalmente, para operações PUT)

Requisições condicionais (caching)

Operações HTTP GET não suportadas por caching podem comprometer a performance de uma API em produção. A utilização inadequada de caching pode causar problemas sérios de consistência em operações PUT, POST e DELETE (não seguras).

Toda vez que um componente “cliente” utilizar mecanismos de caching local, afim de reduzir a quantidade de requisições custosas para um componente “servidor” para melhorar a performance, deverá armazenar, também, no cache as informações Last-Modified e ETag.

Gestão de concorrência

Assuma o seguinte fluxo:

  1. Dois usuários, em duas máquinas separadas, acessam o “cadastro” de um mesmo cliente (HTTP GET)
  2. Os dois usuários alteram em “campos” diferentes
  3. Um dos usuários, submete atualização dos dados do cliente (por exemplo, usando HTTP PUT), mudando uma representação do recurso, pedindo atualização de estado no componente “servidor”
  4. O segundo usuário, logo depois, também submete atualização de dados.

Como impedir, no fluxo, que o segundo usuário não “mate” as alterações do primeiro? Este problema é reconhecido, em TI, como gestão de concorrência. Há duas abordagens comuns:

  1. concorrência pessimista – onde um usuário, ao obter dados para alteração, “bloqueia” o acesso ao recurso para outros fluxos de alteração concorrentes até que as modificações estejam efetivadas.
  2. concorrência otimista – onde sempre que um componente “cliente” recupera o estado de um recurso, obtém junto um token de versionamento (que é modificado sempre que o recurso tem estado atualizado no componente “servidor”). Dessa forma, sempre que uma tentativa de atualização é realizada, o token de versionamento que foi obtido no GET é enviado junto no request (operações PUT, POST ou DELETE), sendo que, se já não for mais o token vigente, a operação falha.

Por causa da restrição de que serviços REST devem ser stateless, apenas a abordagem otimista é considerada válida.

Estabelecendo tokens de versionamento

Em HTTP há duas abordagens comuns para associação de tokens de versionamento de recursos:

  1. Atributo de cabeçalho Last-Modified – explicitando quando aconteceu a última modificação (em horário do servidor)
  2. Atributo de cabeçalho ETag – com uma “chave de conteúdo”, geralmente hash ou versão.

Essas tokens podem ser determinadas seguindo uma das seguintes abordagens:

  1. Caso seja possível armazenar, junto as informações de recurso, a data da última modificação ou um número de versão (por exemplo, em uma coluna no banco de dados), estes valores poderão ser utilizados, também, para Last-Modified ETag.
  2. Se o recurso não for grande ou custoso de ser obtido, poderá ter uma hash (por exemplo MD5) computada on-the-fly sempre que necessário.
  3. Se o recurso for grande ou custoso de ser obtido, a hash poderá ser gerada sempre que o estado do recurso for modificado e armazenada

Operações com GET ou HEAD condicional

Requisições para a URL de um recurso armazenado no cache devem conter os valores de If-Modified-Since e If-None-Match ajustados com, respectivamente, os valores de Last-Modified ETag da última requisição.

Sempre que um componente “servidor” receber uma requisição HTTP GET com valores em If-Modified-Since If-None-Match deverá compará-los com os valores vigentes e, se permanecerem iguais, retornar 304 (Not Modified), sinalizando para o “cliente” que os valores em cache permanecem válidos. Caso contrário, uma nova representação do recurso deverá ser retornada com o código 200 (Ok)

Operações com PUT ou DELETE  condicional

Atualizações em recursos em que é necessário manter gestão de concorrência otimista devem conter os valores de If-Unmodified-Since e If-Match ajustados com, respectivamente, os valores de Last-Modified ETag da última requisição.

Requisições PUT ou DELETE  para recursos que tem gestão de concorrência otimista:

  • que não tenham valores definidos para If-Unmodified-Since e If-Match, devem ser “negadas” pelo servidor retornando 403 (Forbidden).
  • que tenham tokens de versionamento desatualizados, devem ser “negadas” pelo servidor retornando 412 (Pre condition failed)
  • que tenham tokens de versionamento atualizados, devem ser “aceitas” pelo servidor, retornando 200 (OK) ou 204 (No Content), junto com, para requisições PUT, atualizações para Last-Modified ETag

Sempre que um componente “servidor” receber uma requisição HTTP GET com valores em If-Modified-Since If-None-Match deverá compará-los com os valores vigentes e, se permanecerem iguais, retornar 304 (Not Modified), sinalizando para o “cliente” que os valores em cache permanecem válidos. Caso contrário, uma nova representação do recurso deverá ser retornada com o código 200 (Ok).

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
0 Comentários
Feedbacks interativos
Ver todos os comentários

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentoria

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais, com ênfase em systems design.

Consultoria e Assessoria em

Arquitetura de Software

EximiaCo oferece a alocação de um Arquiteto de Software em sua empresa para orientar seu time no uso das melhores práticas de arquitetura para projetar a evolução consistente de suas aplicações.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

+55 51 99942-0609 |  contato@eximia.co

+55 51 99942-0609  contato@eximia.co

0
Quero saber a sua opinião, deixe seu comentáriox
()
x