Guia para Projeto de API HTTP

Guia para Projeto de API HTTP

Introdução

Este guia descreve uma série de boas práticas para o projeto de APIs HTTP+JSON, originalmente extraído de um trabalho na API da Plataforma Heroku.

Este guia inclui adições a essa API e serve de guia para novas APIs Internas no Heroku. Esperamos que seja de interesse de outros que projetam APIs que não são do Heroku.

Nosso objetivo aqui são consistência e foco nas regras de negócios evitando coisas supérfluas. Procuramos um jeito bom, consistente e bem documentado de projetar APIs, não necessariamente o método único/ideal.

Assumimos que você já esteja familiarizado com o básico de APIs HTTP+JSON APIs e não cobriremos todos os fundamentos disto nesse guia.

Agradecemos contribuições a esse guia.

Conteúdo

Fundamentos

Separe as Responsabilidades

Mantenha as coisas simples quando for projetar separando as responsabilidades entre partes diferentes do ciclo de requisição e resposta. Mantendo regras simples aqui permite um foco maior em problemas maiores e mais complexos.

Requisições e respostas serão feitas para se dirigir a um recurso em particular ou coleção. Use o caminho para indicar a identidade, o corpo para transferir o conteúdo e os cabeçalhos para indicar meta dados. Parâmetros podem ser usado como um meio de passar informações de cabeçalhos em casos extremos, mas cabeçalhos são preferidos já que são mais flexiveis e podem transmitir informações mais diversas.

Exija Conexões Seguras

Exija conexões seguras com TLS para acessar a API, sem excessões. Não vale a pena ficar imaginando ou explicando quando se deve usar TLS e quando não. Simplesmente exija TLS para tudo.

O ideal é simplesmente rejeitar qualquer requisição não TLS não respondendo a requisição http ou na porta 80 para evitar troca de dados inseguros. Em ambientes que isto não é possível, responda com 403 Forbidden.

Redirecionamentos são desencorajados uma vez que eles permitem um comportamento incorreto do cliente sem oferecer nenhum ganho. Clientes que dependem de redirecionamentos dobram o tráfego do servidor e fazem com que o TLS seja inútil uma vez que dados sensíveis já terão sido expostos na primeira requisição.

Exija Versionamento no Cabeçalho Accepts

Versionamento e a transição entre versões podem ser um dos aspectos mais desafiadores de projetar e manter uma API. Por isso, é melhor empregar mecanismos para facilitar isto desde o começo.

Para prevenir surpresas, indisponibilidade para o usuário, é melhor exigir que a versão seja especificada com todas as requisições. Versões padrões devem ser evitadas já que, no melhor dos casos, são muito difíceis de mudar no futuro.

É melhor enviar especificação da versão no cabeçalho, com outros meta dados, usando o cabeçalho Accept com um content type personalizado, por exemplo:

Accept: application/vnd.heroku+json; version=3

Suporte ETags para Cacheamento

Inclua um cabeçalho ETag em todas as respostas, identificando a versão específica do recurso retornado. Isto permite aos usuários cachear os recursos e usar requisições com este valor no cabeçalho If-None-Match para determinar se o cache deve ser atualizado.

Forneça Request-Ids para Introspecção

Inclua um cabeçalho Request-Id em cada resposta da API, populada com um valor UUID. Registrando esses valores no cliente, servidores e qualquer serviço adicional, oferece um mecanismo para rastrear, diagnosticar e depurar requisições.

Divida Respostas Longas Entre Requisições com Ranges

Respostas grandes devem ser quebradas entre múltiplas requisições usando o cabeçalho Range para especificar quando mais dados estão disponíveis e como obter eles. Veja Heroku Platform API discussion of Ranges para os detalhes da requisição e dos cabeçalhos de respostas, códigos de estado, limites, organização e iteração.

Requisições

Aceite JSON serializado no corpo das requisições

Aceite JSON serializado no corpo das requisições PUT/PATCH/POST além de ou em conjunto a dados form-encoded. Isto cria uma simetria com o corpo das requisições JSON serializado, p.e.:

$ curl -X POST https://service.com/apps \
    -H "Content-Type: application/json" \
    -d '{"name": "demoapp"}'

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "name": "demoapp",
  "owner": {
    "email": "username@example.com",
    "id": "01234567-89ab-cdef-0123-456789abcdef"
  },
  ...
}

Use formatos de rotas consistentes

Nomes de recursos

Use a versão pluralizada de um nome de recurso a menos que o recurso em questão seja único no sistema (por exemplo, na maioria dos sistemas, um dado usuário deve ter somente uma conta). Isto permite consistência na forma que você se refere a um recurso em particular.

Ações

Prefira endpoint que não precisem de quaisquer ações especiais para recursos individuais. Em casos onde ações especiais são necessárias, coloque-as sobre um prefixo actions padrão, para diferencia-los com clareza:

/resources/:resource/actions/:action

p.e.

/runs/{run_id}/actions/stop

Rotas e atributos em letras minúsculas

Use rotas com letras minúsculas e separadas por hífen, para que seja igual a nomes de domínio, p.e.:

service-api.com/users
service-api.com/app-setups

Também utilize letras minúsculas para os atributos, mas use sublinhado como separador pois assim nomes de atributos podem ser digitados sem aspas em JavaScript, p.e.:

service_class: "first"

Suporte referencia com atributos que não sejam ID por conveniência

Em alguns casos pode ser inconveniente para o usuário final oferecer IDs para identificar um recurso. Por exemplo, um usuário pode pensar no nome de Apps no Heroku, mas este app pode ser identificado por um UUID. Nestes casos você poderia aceitar ambos, p.e.:

$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod

Não aceite somente nomes omitindo IDs.

Minimize a profundidade da rota

Em alguns modelos de dados com relacionamento de recursos pai/filho, as rotas podem acabar ficando muito profundas, p.e.:

/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}

Limite a profundidade excessiva preferindo localizar recursos na raiz da rota. Use aninhamento para indicar coleções. Por exemplo, para o caso acima em que um dyno pertence a um app, que pertence a uma organização:

/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}

Respostas

Retorne o código de estado apropriado

Retorne o código de estado HTTP adequado com cada resposta. Respostas com êxito devem retornar códigos de acordo com este guia:

Preste atenção ao uso de códigos de erros para autenticação e autorização:

Retorne códigos adequadospara prover informações adicionais quando há erros:

Consulte a Especificação de códigos de respostas HTTP par um guia sobre código de estado para erros de usuário e servidor.

Prover recursos completos quando disponível

Disponibilize a representação completa do recurso (p.e. o objeto com todos os atributos) quando possível na resposta. Sempre disponibilize o recurso completo em respostas 200 e 201, incluindo requisições PUT/PATCH e DELETE p.e.:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/domains/0fd4

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
  "created_at": "2012-01-01T12:00:00Z",
  "hostname": "subdomain.example.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "updated_at": "2012-01-01T12:00:00Z"
}

Respostas 202 não incluirão a representação total do recurso, p.e.:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/dynos/05bd

HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}

Prover os (UU)IDs dos recursos

Dê a cada recurso um atributo id por padrão. Use UUIDs a menos que você tenham uma razão muito boa para não fazê-lo. Não use IDs que não são únicos globalmente entre instancias de serviços ou outros recursos no serviço, especialmente IDs com auto incremento.

Mostre os UUIDs em letras minúsculas no formato 8-4-4-4-12, p.e.:

"id": "01234567-89ab-cdef-0123-456789abcdef"

Prover timestamps padrões

Disponibilize timestamps created_at e updated_at para recursos por padrão, p.e.:

{
  // ...
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T13:00:00Z",
  // ...
}

Estes timestamps talvez não façam sentido para alguns recursos, neste caso, podem ser omitidos.

Use horários UTC em formato ISO8601

Aceite e retorne horários somente em UTC. Mostre horários em formato ISO8601, p.e.:

"finished_at": "2012-01-01T12:00:00Z"

Aninhe as relações de chaves estrangeiras

Serialize as referencias a chaves estrangeiras com um objeto aninhado, p.e.:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0..."
  },
  // ...
}

Ao invés de p.e.:

{
  "name": "service-production",
  "owner_id": "5d8201b0...",
  // ...
}

Este método possibilita mostrar mais informações sobre um recurso sem ter de mudar a estrutura de resposta ou introduzir mais campos de de resposta de alto nível p.e.:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0...",
    "name": "Alice",
    "email": "alice@heroku.com"
  },
  // ...
}

Gere erros estruturados

Gere corpos de respostas de erros estruturados e consistentes. Inclua um id legível para máquinas, uma message de erro legível para humanos e opcionalmente uma url que aponte para mais informações sobre o erro e como resolver isto, p.e.:

HTTP/1.1 429 Too Many Requests
{
  "id":      "rate_limit",
  "message": "Account reached its API rate limit.",
  "url":     "https://docs.service.com/rate-limits"
}

Documente seus formatos de erros e possíveis ids de erros que os clientes possam encontrar.

Mostre o estado limite de requisições

Limite as requisições dos clientes para proteger o seu serviço e manter um serviço de alta qualidade para outros clientes. Voc6e pode usar um algoritimo de token bucket para calcular o limite de respostas.

Retorne o número restante de requisições no cabeçalho de resposta RateLimit-Remaining.

Manter o JSON minimizado em todas as respostas

Espaços extras adicionam tamanho extra as respostas, e muitos clientes que mostram os dados para humanos “embelezam” a saída em JSON. É melhor manter as respostas JSON mininizadas, p.e.:

{"beta":false,"email":"alice@heroku.com","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z","created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}

Ao invés de p.e.:

{
  "beta": false,
  "email": "alice@heroku.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "last_login": "2012-01-01T12:00:00Z",
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T12:00:00Z"
}

Você pode, opcionalmente, prover um jeito para os clientes de receber uma respostas mais verbosa, tanto via um parâmetro query (p.e. ?pretty=true) como via um parâmetro do cabeçalho Accept (e.g. Accept: application/vnd.heroku+json; version=3; indent=4;).

Artefatos

Prover um esquema JSON processável

Disponibilize um esquema processável para especificar sua API. Use prmd para gerenciar o seu esquema e assegure que está validado com prmd verify.

Prover documentação para leitura

Disponibilize uma documentação para que os desenvolvedores possam entender a sua API.

Se você criar um esquema com prmd, como descrito acima, você pode facilmente gerar documentação em Markdown para todos os endpoints com prmd doc`.

Em adição aos detalhes do endpoint, disponibilize um resumo da API com informações sobre:

Prover exemplos executáveis

Disponibilize exemplos executáveis que os usuários possam utilizar direto em seus terminais para ver como a API trabalha. Sempre que possível, estes exemplos devem ser palavra por palavras, para minimizar o trabalho que um usuário precisa fazer para testar a API, p.e.:

$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users

Se você usa prmd para gerar documentação em Markdown, você terá exemplos para cada endpoint inclusos.

Descreva a estabilidade

Descreva a estabilidade da sua API ou seus vários endpoints de acordo com a maturidade e estabilidade, p.e. com sinalização de protótipo/desenvolvimento/produção.

Veja a politica de compatibilidade da API do Heroku para um exemplo de estabilidade e gerenciamento de mudanças.

Uma vez que sua API está declarada como estável e pronta para produção, não faça mudanças incompatíveis com a mesma versão de API. Se você precisar mudanças incompatíveis, crie uma nova API com numero de versão superior.

Traduções

Voltar