39 pontos por GN⁺ 2025-08-25 | 1 comentários | Compartilhar no WhatsApp
  • 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
  • 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
Publicidade

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)
Publicidade

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

1 comentários

 
GN⁺ 2025-08-25
Opinião no Hacker News
  • O conselho “nunca quebre o userspace” é famoso, mas o texto também destaca bem o lado oposto disso. Ou seja, “a API do kernel pode quebrar sem aviso”. O ponto importante não é “nunca quebre nenhuma API de jeito nenhum”, mas sim o equilíbrio sutil de “nunca quebre apenas as partes cuja estabilidade foi declarada”

    • Mesmo que o kernel do Linux não quebre o userspace, a GNU libc quebra a compatibilidade de userspace com bastante frequência. Então, no fim das contas, o espaço de usuário do Linux quebra com frequência, por mais que os desenvolvedores do kernel se esforcem. Programas e bibliotecas compilados com versões novas da libc às vezes não rodam direito em versões antigas da libc, então na prática é preciso atualizar todos os componentes de uma vez. De forma meio irônica, o Windows já resolveu esse problema há décadas com o modelo de redistributables

    • É bem conhecido que o Linux não tem uma API pública estável para drivers, e já ouvi dizer que esse foi justamente um dos motivos para o Google desenvolver o Fuschia OS. O Linux acaba tendo direções diferentes para o espaço de usuário e para o hardware

  • O autor parece não gostar muito de APIs baseadas em versão, mas eu sempre recomendo adotar versionamento desde o início ao criar um app. Como não dá para prever o futuro, em algum momento mudanças incompatíveis por fatores externos inevitavelmente vão acontecer com você também

    • Na verdade, acho que o próprio autor recomendou versionamento. No texto ele diz que “versões são uma forma responsável de mudar uma API”, então no fim está incentivando o versionamento em si. Só que a migração para uma nova versão deve ser o último recurso

    • Concordo com a opinião de que não se deve colocar v1 no endpoint sem necessidade. O que normalmente acontece conforme a API cresce é que primeiro se tenta preservar compatibilidade adicionando campos ou opções aos endpoints existentes. E, quando surge a necessidade de algo realmente incompatível, geralmente se dá um nome novo ao próprio endpoint e se cria um endpoint totalmente novo (não /v2). Se for preciso mudar a API inteira, o serviço antigo costuma ser descontinuado e um serviço completamente diferente é lançado, com outro nome desde o início. Em 25 anos de trabalho, vi um serviço usando /v1 e /v2 lado a lado exatamente uma vez

    • Não acho que a intenção do autor seja dizer que nunca se deve colocar /v1 no endpoint desde o começo. O ponto é que você deve fazer o máximo possível para evitar que surja um novo /v2. Quando aparece um /v2, cada correção de bug exige alterações de código nos dois lados, os condicionais crescem exponencialmente e a base de código vira um espaguete. No fim, o projeto original de /v1 que passou a suportar múltiplas versões foi pouco cuidadoso com compatibilidade futura

    • Acho que não há problema nenhum em adicionar versionamento depois. Por exemplo, começar com /api/posts e depois acrescentar a próxima versão como /api/v2/posts já é suficiente

    • Não concordo com a abordagem de embutir a versão desde o começo. Isso faz com que o uso de múltiplas versões se torne realmente comum, e eu acho que isso é pior, não melhor

  • Este texto foi muito útil. Eu acrescentaria mais um conselho: a qualidade de uma API é inversamente proporcional à dificuldade de obter sua documentação. Se você só consegue a documentação depois de assinar um contrato, pode assumir com segurança que a qualidade dessa API será péssima

  • O autor disse para salvar a chave de idempotência em um armazenamento key/value como Redis em vez de colocá-la separadamente na tabela de comentários, mas fico em dúvida se isso garante idempotência de forma confiável em todos os casos de falha. Por exemplo, se o servidor faz uma escrita condicional como SET key 1 NX e encontra a chave já existente, ele deveria simplesmente pular a criação do comentário, mas nesse ponto talvez a requisição anterior ainda nem tenha sido refletida de fato no banco. O armazenamento da chave de idempotência precisa ser confirmado junto com a operação real na mesma transação e, se necessário, também revertido. No fim, a essência da chave de idempotência é virar “o identificador único desta operação ou requisição”. Por exemplo, ela deveria ser um identificador por recurso adequado a cada caso, como “criação de comentário” ou “atualização de comentário”

    • Deve-se evitar adicionar um componente separado para idempotência (por exemplo, Redis etc.). Isso pode gerar erros por abstrações quebradas ou comportamento estranho, ou por falta de entendimento sobre garantias de entrega. Em vez disso, é muito melhor salvar rótulos ou metadados junto com as operações de escrita, para que o próprio usuário acompanhe o progresso e isso fique armazenado junto com os dados existentes
  • A vantagem da paginação baseada em cursor é que, do ponto de vista do usuário, mesmo que novos itens sejam adicionados entre carregar uma página e clicar em “próximo”, ele não precisa ver de novo os itens que já viu. O método com cursor guarda o ID do último objeto da página anterior e entrega os itens seguintes, por isso é especialmente útil em scroll infinito. Por outro lado, a desvantagem é que fica difícil implementar a função de “pular para a página N” com paginação por cursor

    • O cursor deve ser necessariamente opaco para não expor coisas como o tamanho do banco de dados para o exterior. E também é possível codificar no cursor informações de estado (parâmetros de busca, estado de cache, informações de roteamento etc.) para implementar funcionalidades mais variadas
  • Hoje em dia, quando se fala em “API”, a maioria pensa em enviar uma requisição para um webapp, definir parâmetros e headers e buscar dados, mas originalmente API significa “Application Programming Interface”, isto é, “interface de programação de aplicações”. O termo começou a ser usado nos anos 1940 e, até os anos 1990, era usado quase sem outro significado. A história das APIs já passa de 80 anos, e existe muito material antigo excelente. Refletir sobre quais problemas os programadores da época enfrentavam e como os resolviam provavelmente pode ajudar bastante também hoje

  • Não concordo com a ideia de tratar usuários internos apenas como “usuários”. Embora sejam pessoas mais técnicas e com maior probabilidade de serem programadores, elas também vivem ocupadas e muitas vezes não têm tempo nem folga para reagir a mudanças na API porque estão focadas nos próprios projetos. Se possível, é importante fazer bastante teste de dogfooding dentro da equipe antes de abrir para fora. Depois que algo é exposto externamente, a promessa de “não quebrar o userspace” precisa ser cumprida sem falta

    • No caso de usuários internos, normalmente já existem ferramentas de instrumentação implementadas para contatá-los diretamente e conduzir a migração. Graças a isso, também dá para descontinuar versões de API, então adotar versionamento de forma estratégica é bastante atraente. Já participei de versionamento real de APIs e vi claramente os benefícios em comparação com organizações que basicamente não usam isso

    • Acho que uma estratégia de versionamento ajuda a resolver esse problema. Uma das melhores formas de considerar os usuários internos é colaborar na especificação e compartilhar com os interessados até mesmo a versão em andamento dessa especificação. Mesmo que a documentação esteja sempre mudando, ter um ponto de referência facilita muito o feedback interno e externo, e isso pode ser extremamente útil desde que se evitem apenas riscos de conflitos de política

  • Em vez de salvar a chave de idempotência no Redis, acho mais confiável, sempre que possível, salvá-la junto na mesma transação em que os dados reais são gravados

  • O alerta “nunca quebre o userspace” é realmente importante. Foi triste ver Spotify, Reddit e Twitter ignorarem esse princípio recentemente

  • Como referência, recomendo também o link https://jcs.org/2023/07/12/api, que reúne boas recomendações sobre APIs