- Em engenharia de software, APIs são uma ferramenta central, e uma característica desejável de uma boa API é ser familiar e simples a ponto de parecer entediante
- Como uma API, depois de publicada, é difícil de mudar, o princípio de não quebrar o ambiente do usuário (WE DO NOT BREAK USERSPACE) é importante
- Quando a mudança for inevitável, é necessário versionamento (versioning), mas isso é um mal necessário que aumenta bastante a complexidade e o custo de manutenção
- A qualidade da API acaba dependendo do valor do próprio produto, e um produto mal projetado dificulta a criação de uma boa API
- Para estabilidade e escalabilidade, vale considerar autenticação baseada em chave de API, idempotência, rate limiting e paginação baseada em cursor
Introdução: a importância e o contexto do design de APIs
- Uma das principais atividades de engenheiros de software modernos é interagir com APIs
- O autor também tem experiência em projetar/implementar/utilizar APIs públicas e internas em várias formas, como REST, GraphQL e ferramentas de linha de comando
- Os conselhos existentes sobre design de APIs tendem a se apegar a conceitos complexos (definição de REST, HATEOAS etc.)
- Este texto organiza princípios práticos de design de APIs com base em experiência real
Equilíbrio entre familiaridade e flexibilidade: a primeira condição de uma boa API
- Uma boa API é uma API “comum e entediante”, ou seja, seu modo de uso deve ser parecido com o de APIs que as pessoas já encontraram antes
- Como o usuário quer se concentrar em atingir o próprio objetivo, e não na API em si, é preciso um design com baixa barreira de entrada
- Uma vez publicada, uma API é muito difícil de alterar, então é preciso cuidado desde a fase inicial de projeto
- Desenvolvedores querem APIs o mais simples possível, mas sempre existe a preocupação de preservar flexibilidade no longo prazo
- No fim, a questão central é o equilíbrio entre familiaridade e flexibilidade de longo prazo
Nunca quebrar o espaço do usuário (WE DO NOT BREAK USERSPACE)
- Em geral, mudanças que adicionam campos à estrutura de resposta não causam problema
- Já remover campos ou mudar tipos e estruturas acaba quebrando o código de todos os consumidores
- Quem mantém a API tem a responsabilidade de não estragar deliberadamente o software dos usuários existentes
- Até o erro de grafia no header HTTP "referer" não é corrigido por causa dessa cultura de preservar o espaço do usuário
Como mudar sem quebrar a API: estratégia de versionamento
- Mudanças quebrando compatibilidade na API só devem ser permitidas quando forem realmente necessárias, e nesse caso a resposta é versionamento
- É preciso operar a versão antiga e a nova ao mesmo tempo, induzindo uma transição gradual
- Identificadores de versão podem ser usados de várias formas, como URL (
/v1/) ou headers, e os usuários podem migrar no próprio ritmo
- O versionamento tem desvantagens como enorme custo de manutenção (mais endpoints, testes, suporte) e confusão para o usuário
- Mesmo usando uma camada interna de tradução como a Stripe, a complexidade fundamental não pode ser evitada
- Introduzir versionamento na API deve ser o último recurso
O sucesso da API depende totalmente do valor do produto
- Uma API é, em essência, apenas a interface de um produto de negócio real
- Mesmo APIs como OpenAI e Twilio são, no fim, casos em que o usuário queria a funcionalidade em si oferecida pela API
- Se o produto tiver valor, as pessoas o usarão mesmo que a API seja incômoda
- A qualidade da API é uma característica “marginal”: só vira fator de escolha quando a competitividade essencial é parecida
- Por outro lado, um produto sem API é uma grande barreira para usuários técnicos
Se o design do produto for ruim, a API também não pode ser boa
- Mesmo que exista uma API tecnicamente muito bem feita, isso não significa muito se o produto não tiver apelo de mercado
- Mais importante ainda: se a estrutura básica dos recursos for ilógica ou ineficiente, isso aparece na API também
- Por exemplo, um sistema que armazena comentários como lista encadeada dificulta até mesmo um design RESTful natural
- Problemas técnicos que podem ficar escondidos na UI ficam todos expostos na API, forçando desnecessariamente o usuário a entender o sistema interno
Autenticação (Authenticaton) e diversidade de usuários
- É preciso oferecer suporte obrigatório a autenticação baseada em chaves de API de longa duração
- Mesmo que se dê suporte adicional a métodos mais seguros como OAuth, a barreira de entrada da chave de API é muito menor
- Consumidores de API não são apenas engenheiros; há também não desenvolvedores (vendas, planejamento, estudantes, hobbyistas etc.)
- Exigências de autenticação difíceis ou complexas (como OAuth) viram barreira para usuários não especialistas
Idempotência e tratamento de retries
- Em requisições de ação (por exemplo, pagamento, mudança de estado etc.), é importante garantir segurança para retry em caso de falha
- Idempotência significa garantir que, mesmo enviando a mesma requisição várias vezes, o resultado seja processado apenas uma vez
- O método padrão é passar uma "chave de idempotência" em parâmetro ou header para evitar processamento duplicado
- Para armazenar a chave de idempotência, um armazenamento simples de chave/valor como Redis é suficiente, e na maioria dos casos dá para aplicar expiração periódica sem problema
- Em geral, isso não é necessário para requisições de leitura/remoção (no estilo REST)
Segurança da API e rate limiting
- Requisições de API por código podem acontecer muito mais rápido do que interações manuais de usuários
- Uma única API publicada sem muita atenção pode acabar sendo usada de forma inesperada (por exemplo, em um sistema de chat em larga escala)
- Rate limiting é indispensável, e deve ser aplicado de forma mais rígida em operações com custo elevado
- Uma desativação temporária da API para um cliente específico (killswitch) também deve ser considerada como opção
- É preciso informar dados de rate limiting por meio de headers de resposta como
X-Limit-Remaining e Retry-After
Estratégia de paginação
- Para retornar grandes conjuntos de dados com eficiência (por exemplo, milhões de tickets), paginação é essencial
- Paginação baseada em offset é simples, mas vai ficando lenta em grandes volumes de dados
- Paginação baseada em cursor funciona bem até em datasets muito grandes, sem degradação da performance da query
- A abordagem com cursor é um pouco mais difícil de implementar e usar, mas no longo prazo tem grande chance de se tornar uma mudança obrigatória
- É sensato incluir no response um campo como
next_page, orientando claramente qual cursor usar na próxima requisição
Campos opcionais e opinião sobre GraphQL
- Campos custosos ou lentos devem ficar fora da resposta padrão e ser adicionados opcionalmente só quando necessário
- É possível incluir dados relacionados com um parâmetro como
includes
- GraphQL tem a vantagem da flexibilidade na estrutura de dados, mas traz problemas como menor acessibilidade para não desenvolvedores, maior complexidade de cache/casos de borda e maior dificuldade de implementação no backend
- Pela experiência prática, adotar GraphQL é apropriado apenas quando realmente necessário
Características de APIs internas
- APIs internas têm várias condições diferentes das APIs externas (públicas)
- Como os consumidores são em sua maioria engenheiros de software profissionais, autenticação mais complexa e mudanças incompatíveis podem ser aceitáveis
- Ainda assim, os princípios de projeto voltados a idempotência, prevenção de incidentes e redução da carga operacional continuam válidos
Resumo
- APIs têm a característica de serem difíceis de mudar e fáceis de usar
- Não quebrar o espaço do usuário é o dever mais importante de quem mantém uma API
- Versionamento de API tem custo alto, então deve ser usado apenas como último recurso
- No fim, a qualidade da API é determinada pelo valor essencial do produto
- Um produto mal projetado tem limites grandes, mesmo que se tente compensar isso no nível da API
- É importante oferecer métodos simples de autenticação, garantir idempotência em requisições de ação indispensáveis e adotar medidas de estabilidade como rate limiting e paginação
- APIs internas têm estratégias diferentes conforme o uso e o público, mas ainda exigem cuidado no design
- REST, JSON, OpenAPI e formatos semelhantes não são o ponto essencial. Uma documentação clara é mais importante
Ainda não há comentários.