Como melhorar a comunicação de erros em APIs HTTP — Parte 1

No mundo do desenvolvimento APIs HTTP, discutimos bastante sobre como utilizar toda a semântica dos verbos HTTP, sobre como tratar a segurança, sobre como tratar a paginação e o tamanho das resposta e em todos os benefícios de um design apoiado na OpenAPI, mas mesmo nos melhores projetos costumamos falhar em como estabelecer um design vencedor quando o tema é o tratamento de erros da camada de API. Se o alvo é facilitar as integrações, ganhar de tração nos times de design de APIs e desenvolvimento e responder aos consumidores de maneira previsível ser um elemento de maturidade crítico para todo tipo serviço, então utilizar padrões técnicos abertos e estruturantes é um excelente caminho para obtenção de melhores resultados. É neste contexto que surge a RFC 7808, Problem Details for HTTP APIs, uma especificação do IETF, que como milhares de outras definem os padrões de funcionamento da Internet.

O que é uma RFC

Antes de falarmos sobre como podemos resolver, por meio de um design simples, a comunicação de erros em APIs HTTP, vamos falar um pouco sobre o modelo de normatização e governança dos padrões que sustentam a Internet.

Para quem nunca teve contato com o termo ou mesmo não tem ainda clareza, existe uma organização chamada Internet Engineering Task Force, ou simplesmente IETF cuja missão é o progresso da Internet.

Os padrões que suportam a Internet são formalizados e mantidos através de especificações chamadas RFC’s. RFC é o acrônimo para Request for Comment. O termo foi originalmente definido entre os técnicos que atuavam na estruturação da ARPANET, o nascimento da Internet, e representava a necessidade de documentar as propostas e submetê-las a revisões juntos aos pares. Eram cópias físicas em papel.

Hoje preservadas e disponíveis no próprio site do IETF como no caso da RFC 1, de abril de 1969.

No contexto atual, a essência se mantém a mesma, todavia o acesso ao processo e aos drafts é feito de forma aberta no site do IETF. Além de abertos para consultas, também o processo de criação e revisão é aberto a qualquer voluntário, mediante forte consenso entre os pares revisores.

Uma RCF é em resumo um documento que especifica métodos, comportamentos, pesquisas ou inovações aplicáveis ao funcionamento da Internet bem como aos sistemas conectados a ela.

Mesmo a normatização das próprias RFCs é feita por uma RFC específica, a RFC 1000, de agosto de 1987. Ou ainda a declaração de missão do IETF que também existe na forma da RFC 3935.

Ao longo das décadas, o formato das RFCs provou ser um veículo eficiente para documentação e compartilhamento de pesquisas executadas pelo desenvolvedores da Internet, tornando-se o registro oficial das decisões de design, arquitetura e padrões técnicos da Internet.

Existem algumas centenas de especificações que somadas ao protocolo HTTP constituem os blocos de fundamentais de construção da Internet. O por falar em protocolo HTTP, ele também é definido por uma RFC, a RFC 2616, HTTP 1.1, originalmente criada pela Internet Society. São especificações de ampla abrangência ou mais restritas que definem sintaxe e semântica mais localizadas com a RFC 7807 que é o objeto deste texto.

A RFC 7807

Há alguns anos, outubro de 2016 para ser mais preciso, enquanto desenvolvíamos um conjunto de APIs Rest para um cliente com apoio de times diversos, percebemos que a falta de uma padrão para os retornos de erros havia se tornado uma dor para integração entre APIs e no atendimento aos aplicativos suportados pelas mesmas.

A cada momento surgia um end point novo e com ele o risco e a preocupação quanto aos retornos com erros. Os aplicativos precisavam implementar lógicas condicionais para detectar em que formato estava o retorno com erro; havia aquela quebra resultante de uma atualização na API solicitada pelo aplicativo A, mas que o aplicativo B não estava preparado para reconhecer; a complexidade das próprias APIs, que já possuíam diversas versões, se tornava ainda mais crítica pela ausência de um padrão para as respostas Rest em conformidade com a semântica HTTP.

Na busca por soluções, em uma conversa, um colega citou ter visto essa especificação RFC 7807, então recém lançada, do IETF cujo título é “Problem Details for HTTP APIs” e cujo abstract nos informa:

💡 This document defines a “problem details” as a way to carry machine readable details of errors in a HTTP response to avoid the need to define new error response for HTTP APIs

Entendemos que o objetivo principal da "Problem Details for HTTP APIs" é evitar que a necessidade de apresentar as respostas HTTP nos cenários de erros estejam sempre nos conduzindo a criar novas modelos. É também objetivo prover um meio facilitar a automação da comunicação entre máquinas, mas, como veremos a frente, também existe a preocupação em entregar informações que sejam acessíveis para a leitura humana.

O primeiro draft da RFC 7807 foi apresentado em Julho de 2012 e posteriormente aceito e publicado oficialmente como Proposed Standard (um dos estágios de evolução de uma RFC e que significa um estágio inicial de aceitação) em março de 2016. Existem atualizações em andamento que ainda não foram aprovadas que trarão evoluções, mas que não deverão provocar quebras de compatibilidade.

De volta ao contexto de análise da RFC, concluímos que seria muito positiva sua adoção nos projetos que estávamos tocando, pois conseguiríamos unir todos os times em torno de um modelo único de respostas com erro nas APIs HTTP em construção, lembrando que já era uma dor a falta de uniformidade na apresentação de erros aos consumidores das APIs.

Além de aplicar a RFC 7807 em soluções internas, um outro caso em que ela se aplica muito bem é normatização de relações com fornecedores. Cada novo fornecedor, pode apresentar os mais variados e criativos meios de apresentar erros: inclusive aqueles que infelizmente não se apoiam na semântica HTTP (RFC 7231) e ao invés de retornarem um erro 400 no status da resposta HTTP, retornam 200 e colocam no corpo da mensagem algum atributo do tipo “erro” no qual o consumidor deve se apoiar para entender qual resposta efetiva que ocorre: sucesso ou falha. Assim cabe ao desenvolvedor do client verificar a presença ou nulidade de um determinado atributo para eventualmente saber o que aconteceu.

Se olharmos para o mercado como um todo, mesmo grandes instituições da Internet não seguem nenhum padrão mais uniforme.

Uma determinada API responde assim:

{
"error": {
"message": "Message describing the error",
"type": "Some type",
"code": 17,
"error_subcode": 0,
"error_user_title": "A title",
"error_user_msg": "A message",
"trace_id": "EJplcsCHuLu"
}
}

Segundo a própria documentação desta API, o código erro 17 significa:

Um problema temporário devido à limitação. Aguarde um momento e refaça a operação ou verifique o volume de solicitações de API.

Porém, a RFC 6885 (Additional HTTP Status Codes) prevê o Status 429 (Too Many Requests). Assim, um padrão da própria internet já supre a necessidade. Neste caso, a API mantém uma solução meio que dando aquela impressão de reinvenção de roda: ainda que os desenvolvedores sejam capazes de explicar que existe um grande legado e que por isso não podem não arriscar a mudar.

Outra API responde assim:

HTTP/1.1 400 Bad Request
{
"error": {
"status": 400,
"message": "invalid id"
}
}

Aqui já existe uma melhor conformidade com a semântica do erro, pois o atributo “status” reflete o status code da resposta HTTP. Em outras palavras, respeita a semântica HTTP. Mas é um formato não padronizado, pois é a solução dada pelos desenvolvedores da API.

Um último exemplo de API nos responde assim:

{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalidParameter",
"message": "Invalid string value: 'asdf'. Allowed values: [mostpopular]",
"locationType": "parameter",
"location": "chart"
}
],
"code": 400,
"message": "Invalid string value: 'asdf'. Allowed values: [mostpopular]"
}
}

Mais uma vez, uma estrutura proprietária e completamente diferente das anteriores, embora o valor do atributo “code” pareça ser semanticamente válido.

Em resumo, organizações diferentes estabelecem padrões diferentes para sua APIs HTTP:

- Para a organização como um todo

- Por setor ou unidade de negócio

- Por projeto

Por esse caminho, cada nova integração é um novo aprendizado, são novas implementações, são novos prazos e portanto são novos custos, mais testes, maior complexidade, maiores riscos, apenas para tratar os erros retornados pelas APIs.

Ao normatizar tanto o desenvolvimento interno, quanto os serviços que iremos consumir de APIs HTTP externas por meio da RFC 7807, estamos implementando uma medida de amadurecimento das relações entre times de desenvolvimento e também relações com parceiros comerciais, pois como todo bom padrão, ela garante maior tração no desenvolvimento, melhores caminhos para as evoluções, melhor bug tracking e no final o que todos buscamos, menor tempo e menor custo para novas integrações.

Elementos Técnicos da RFC 7807

Vamos começar a entender agora a RFC 7807.

Baseado no objeto JSON abaixo, vamos conhecer cada um dos cinco atributos canônicos definidos na especificação.

{
"status": 403,
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"type": "<https://example.com/probs/out-of-credit>",
"instance": "/account/12345/msgs/abc"
}

1 — status

Repete o status code da Resposta HTTP, tem um papel de conveniência ao replicar o status code da resposta, atrelando a semântica ao corpo de cada resposta HTTP e, dessa forma habilitando, por exemplo, que quando armazenados em logs, não precisem de informações adicionais para que o status HTTP daquela resposta seja claro.

Tipo: Number

2 — title

Pode conter apenas uma informação genérica relativa ao status da resposta, como “Bad Request”. Mas pode conter uma informação negocial mais clara para o caso e preferencialmente que seja uma informação destinada a leitura humana: neste caso o atributo type deve ser informado conforme será descrito logo a frente. Também utilizado com valores que podem ser internacionalizados de acordo com a língua declarada pelo client da requisição (Accept-Language request header).

Tipo: String

3 — detail

O serviço, ou o produtor da resposta, pode fornecer uma explicação mais detalhada sobre o erro ocorrido com foco em leitura humana. Deve ser mais específico que o atributo title. Em algumas implementações, é do atributo detail que podemos extrair a informação a ser apresentada em um componente visual destinado a apresentar erros ao usuário como em componentes de Toast ou modais para erros.

Tipo: String

4 — type

Trata-se de um nível adicional de detalhes para o atributo status. Pode ser entendido como um subtipo do status, ou ainda um “Sub Status”. Porém, não é um inteiro como ocorre com o status, mas uma URI que deve apontar para um recurso que forneça maiores detalhes sobre a definição do tipo de erro ocorrido.

Tipo: String (formato URI)

5 — instance

Tem o propósito de aprofundar ainda mais os detalhes fornecidos pelo atributo type. É uma URI que referencie a ocorrência específica para o tipo do problema ocorrido.

Tipo: String (formato URI)

Considerações sobre a utilização de instance e type

Vale ressaltar a importante diferença entre os atributos instance e type. A URI de instance sempre referencia a ocorrência específica do problema devendo assim ser única para cada erro ocorrido. Já a URI do atributo type referencia a definição geral do problema. Se fizermos uma analogia com programação OO, type está para classe assim como instance está para objeto.

Porém, ambos são atributos que podem simplesmente ser omitidos. Caso estejam presentes no corpo da resposta HTTP, mas a implementação não forneça os valores, então ambos poderão ser preenchidos com o valor “about:blank”.

Quando type é utilizado com o valor “about:blank”, a RFC 7807 prevê que isso indica que não existe nenhuma semântica adicional além daquela informada pelo status. Mais ainda, caso o valor seja “about:blank”, o atributo title deve ter o mesmo valor que a descrição padrão do status code HTTP. Se imaginarmos que o erro é 404, então title deve ter como valor “Not Found”. Contudo, vale também a regra de internacionalização e o title poderá vir por exemplo em português: “Não Encontrado”.

Finalmente, caso o atributo type seja omitido pelo serviço produtor, o cliente é orientado a assumir implicitamente o valor “about:blank”. Em outras palavras, o cliente assume que não há outra semântica além daquela informada pelo status code HTTP.

Extensões

A RFC 7807 prevê os cinco atributos que acabamos de conhecer e adicionalmente prevê a possibilidade de extensões.

{
...,
"balance": 30,
"accounts": ["/account/12345",
"/account/67890"]
}

Extensões são outros atributos não canônicos e de domínio do serviços que implementa a API HTTP.

É recomendado manter o modelo o mais simples possível, embora a especificação não faça qualquer restrição à quantidade de extensões que podem ser adicionadas.

{
"status": 403,
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"type": "<https://example.com/probs/out-of-credit>",
"instance": "/account/12345/msgs/abc",
"balance": 30,
"accounts": ["/account/12345",
"/account/67890"]
}

Internet media types

Além dos atributos canônicos e das extensões, a RFC 7807 define dois media types como forma de entregar maior precisão para os clients da requisições HTTP. Além de informar o status code HTTP, com o auxílio dos media types definidos pela especificação o client pode saber de forma objetiva o formato da resposta, livrando o desenvolvedor de executar verificações no corpo da resposta com erro para confirmar o formato.

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
"status": 403,
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"type": "<https://example.com/probs/out-of-credit>",
"instance": "/account/12345/msgs/abc",
"balance": 30,
"accounts": ["/account/12345",
"/account/67890"]
}

application/problem+json

Utilizando quando responde no formato JSON.

application/problem+xml

Utilizando quando responde no formato XML.

Conclusões

  1. A tração no desenvolvimento é melhorada, pois a RFC 7807 possui um design simples, elegante e extensível. Esses atributos combinados permitem a melhora na comunicação entre times, BUs, parceiros, fornecedores e mercado em geral.
  2. Ganhamos facilidade para manutenções tanto para produtores quanto para consumidores de APIs. As implementações de bibliotecas comuns, sejam internas ou de mercado, podem ser compartilhadas garantindo maior estabilidade entre as pontas.
  3. Melhor BUG tracking e LOGs: logs contendo a própria resposta HTTP em um mesmo padrão tornam os processo de consulta, visualização e análise de logs mais simples.
  4. Mais rapidez e menor custo para novas integrações: não há nova curva de aprendizado e esforço de desenvolvimento para tratamento de erros em formatos desconhecidos.
  5. Embora não tenhamos ainda entrado em detalhes de implementação ainda, o formato de erro pode ser normalizado nos APIs Gateways a partir das respostas HTTP originais dos serviços. Por exemplo, nos casos em que esses serviços legados que não possam ser ajustados.

Próximos passos

Para avançarmos na compreensão e aplicação da RFC 7807, na segunda parte deste artigo abordaremos casos mais práticos de implementações.

Até breve.

Referências

--

--

tecnologia no Grupo Boticário
tecnologia no Grupo Boticário

Published in tecnologia no Grupo Boticário

Aqui, o time de tech do Grupo Boticário compartilha conhecimento sobre como estamos reinventando o futuro da beleza através da tecnologia.

Francisco Tarcizo Bomfim Júnior
Francisco Tarcizo Bomfim Júnior

Written by Francisco Tarcizo Bomfim Júnior

Father, Husband, Solutions Architect, Developer and Reader