Meu guia para Design de APIs
Material também estará disponível no meu substack: https://andredemattosferraz.substack.com/
O Design de API é uma etapa crucial no desenvolvimento de sistemas, pois define como os componentes se comunicam entre si. Uma API bem projetada facilita a integração, a escalabilidade e a manutenção do sistema.
Neste contexto, exploraremos os princípios e as melhores práticas para criar APIs eficientes, intuitivas e robustas. Para elucidar o material os exemplos a seguir usam o contexto de uma Loja Virtual.
1 - Documentação
A documentação desempenha um papel crucial como um canal de comunicação entre os desenvolvedores que utilizam a API e os criadores da própria API. Ela oferece informações claras sobre como utilizar a API, quais endpoints estão disponíveis, quais parâmetros são aceitos e como interpretar as respostas. Uma documentação bem elaborada é essencial para evitar erros comuns e garantir que os desenvolvedores possam utilizar a API de forma eficaz.
Além disso, uma API bem documentada demonstra transparência e profissionalismo por parte dos criadores. Quando a API passa por evoluções (como a adição de novos recursos ou alterações), é fundamental atualizar a documentação para refletir essas mudanças. Uma boa prática é sempre disponibilizar a especificação OpenAPI (O famoso swagger 😅) da API, que oferece uma descrição detalhada dos endpoints, parâmetros e respostas esperadas. Isso facilita a vida dos desenvolvedores e contribui para um ambiente de desenvolvimento mais eficiente e confiável. Para mais informações acesse: https://swagger.io/specification/.
Curiosidade ⭐
Qual a diferença entre Swagger e OpenAPISpec?
OpenAPI = Especificação
Swagger = Ferramentas para implementar a especificação
2 - Suporte JSON
Utilize JSON como o formato padrão para transferência de dados. Isso garante compatibilidade com várias tecnologias e simplifica a manipulação dos dados tanto no lado do cliente quanto no servidor.
Em casos críticos de performance é aconselhável o uso de payloads baseados no protocolo gRPC que trafega binário.
A recomendação é manter a nomenclatura dos atributos seguindo o padrão Camel Case.
Essa minha recomendação, pois nas empresas que trabalhei sempre seguiam esse padrão. Mas não tem uma regra explícita dizendo qual é melhor usar. O que existe é um senso baseado nas grandes empresas como Google, Facebook, Twitter (X) que o padrão snake-case é o mais recomendável.
No fim a regra é: abrace um padrão e siga ele em todas suas API. O que não pode é ficar mudando de padrão a cada API que vc constrói!
Exemplo:
{
"name": "Lucas",
"lastName": "Paixão",
"city": "Tabajara"
}
- Camel Case:
- Começa com a primeira letra minúscula e a primeira letra de cada nova palavra subsequente em maiúscula.
- Exemplo: coisasParaFazer, idadeDoAmigo, valorFinal.
- Pascal Case (também conhecido como “upper camel case” ou “capital case”):
- Todas as palavras começam com letra maiúscula.
- Exemplo: CoisasParaFazer, IdadeDoAmigo, ValorFinal.
- Snake Case (também conhecido como “underscore case”):
- Utiliza underline no lugar de espaço para separar as palavras.
- Exemplo: coisas_para_fazer, idade_do_amigo, valor_final.
3 - Use substantivos nos caminhos dos endpoints
Em vez de verbos, escolha nomes descritivos para os recursos.
Ruim | Bom |
---|---|
GET /querycarts/123 | GET /carts/123 |
GET /getAllCustomers | GET /customers |
POST /criateNewCustomer | POST /customers |
DELETE /deleteCustomer | DELETE /customers/1 |
Endpoints com múltiplos substantivos devem ser separados por "-":
Ruim | Bom |
---|---|
GET /salesOrder | GET /sales-order |
GET /userManagement/users | GET /user-management/users |
GET /userManagement/users/1 | GET /user-management/users/{id} |
4 - Use substantivos no plural
A questão de usar nomes no singular ou plural gera discussões frequentes. Em geral, é preferível nomes no plural, pois indicam um conjunto de características.
Ruim | Bom |
---|---|
GET /cart/123 | GET /carts/123 |
5 - Idempotência
A idempotência refere-se à propriedade de uma operação que pode ser aplicada várias vezes, produzindo o mesmo resultado e sem alterar o estado do sistema além da primeira execução. Em termos de APIs REST, isso significa que uma chamada de API idempotente, independentemente do número de repetições, terá o mesmo efeito.
Em resumo, a idempotência é uma boa prática em serviços REST, garantindo que operações possam ser repetidas sem efeitos colaterais indesejados!
Verbos Idempotentes em APIs REST:
- Os seguintes verbos HTTP são considerados idempotentes:
- GET: Pode retornar a mesma resposta da primeira chamada N vezes.
- PUT: Altera o estado de uma aplicação, mas sempre retorna a mesma resposta após a primeira chamada.
- PATCH: Altera o estado de uma aplicação, mas sempre retorna a mesma resposta após a primeira chamada.
- DELETE: Quando chamado, deleta um objeto e mantém o mesmo estado da aplicação para chamadas subsequentes.
- HEAD, TRACE e OPTIONS também são idempotentes.
- POST não é idempotente. Sua principal funcionalidade é criar um recurso, alterando o estado da aplicação a cada requisição.
Exemplo Prático:
- Suponha um endpoint "/user" com o verbo POST que recebe o seguinte payload:
{ "name": "Lucas", "lastName": "Paixão", "city": "Tabajara" }
- Cada chamada a esse endpoint criará um novo usuário no banco de dados, alterando o estado da aplicação.
- Por outro lado, o GET, PATCH e o PUT são idempotentes, retornando a mesma resposta independentemente do número de chamadas.
6 - Utilizar corretamente os métodos HTTP
- Separe seu API em recursos lógicos.
- Manipule esses recursos usando os métodos HTTP apropriados:
- GET: Recuperar informações de um recurso.
- POST: Criar um novo recurso.
- PUT: Atualizar um recurso existente.
- PATCH: Atualizar parte de um recurso existente. Por exemplo, atualizar somente o nome do produto
- DELETE: Excluir um recurso.
- Por exemplo:
- Solicitar /clientes/563 com o método GET recupera um cliente específico.
- Solicitar a mesma URL com o método DELETE exclui o cliente com código 563.
Ruim | Bom |
---|---|
GET /getAllCustomers | GET /customers |
GET /getCustomer/1 | GET /customers/1 |
POST /criateNewCustomer | POST /customers {"name": "João Tabajara", "address": "Rua Tabajara"} |
DELETE /deleteCustomer | DELETE /customers/1 |
PUT /updateCustomer | PUT /customers/1 {"name": "João Tabajara Queiroz", "address": "Rua Triste Feliz"} |
PATCH /patchCustomer | PATCH /customers/1 {"name": "João Tabajara Queiroz Juazeiro"} |
7 - Utilizar subrecursos para relacionamentos
Quando há hierarquia de objetos e recursos, use subrecursos.
Ruim | Bom |
---|---|
GET /carts/1?item=1 | GET /carts/1/items/1 |
8 - Versionamento de API
O versionamento de APIs garante a estabilidade e a confiabilidade das suas interfaces. Quando uma API evolui, é importante comunicar de forma transparente as mudanças para os consumidores. O versionamento de APIs envolve gerenciar as alterações em uma API de maneira controlada e transparente. Ele garante que as versões antigas coexistam com as novas, sem quebrar aplicativos existentes. Afinal, você está entregando dados para o público, e eles precisam saber quando a maneira como esses dados são entregues muda.
Ruim | Bom |
---|---|
GET /carts/v1/123 | GET /v1/carts/123 |
GET /carts/123 | GET /carts/123?version=1 |
GET /carts/123 | GET /carts/123 {"Header": {"Accept-version": "v1"}} |
Existem várias razões para versionar sua API
- Compatibilidade: À medida que a API evolui, você pode precisar fazer alterações incompatíveis com a versão anterior. O versionamento permite que as versões coexistam sem impactar aplicativos existentes.
- Comunicação: O versionamento é uma forma de comunicação com os desenvolvedores. Ele informa quais mudanças ocorreram e como elas afetam a API.
- Transparência: Versionar sua API demonstra transparência e responsabilidade. Os consumidores sabem que você está gerenciando as mudanças de forma controlada.
Aqui estão algumas estratégias comuns para versionar APIs
- Prefixo de URL: Adicione um prefixo à URL da API para indicar a versão. Por exemplo:
- Versão 1: /v1/products
- Versão 2: /v2/products
- Header de versão: Use um cabeçalho HTTP para especificar a versão desejada.
- Parâmetro de versão: Query parameter que informa qual versão da API está sendo realizada a requisição
9 - Paginação
A paginação é uma técnica essencial quando se trata de projetar APIs que retornam grandes conjuntos de dados. Ela permite dividir os resultados em páginas menores, facilitando o manuseio e a exibição dos dados para os consumidores da API. Aqui estão algumas considerações importantes:
Ruim | Bom |
---|---|
GET /carts | GET /carts?pageSize=XX&pageToken=xx |
Motivos de usar paginação
- Quando uma API retorna muitos resultados, como uma lista de itens, é impraticável retornar todos os dados de uma só vez. A paginação permite que os resultados sejam divididos em partes gerenciáveis.
- A paginação é especialmente útil para melhorar o desempenho e a eficiência da API, reduzindo a carga no servidor e a quantidade de dados transferidos pela rede.
Considerações adicionais
- Certifique-se de que a documentação da sua API explique claramente como usar a paginação.
- Lide com casos em que não há mais páginas (por exemplo, a última página) e forneça feedback adequado aos consumidores.
10 - Ordenação
Quando se lida com conjuntos de dados que precisam ser apresentados de maneira organizada é importante oferecer uma forma de ordená-los. Aqui estão algumas considerações relevantes:
- Ao projetar uma API, é fundamental considerar como os recursos serão ordenados quando solicitados pelos clientes.
- Por exemplo, se você está criando uma API para listar produtos, os clientes podem querer ordená-los por preço, nome ou data de lançamento.
- Considere definir uma ordem padrão para os resultados quando nenhum critério de ordenação é especificado.
- Certifique-se de que os nomes dos critérios sejam intuitivos e fáceis de entender.
- Além do critério de ordenação, permita que os clientes especifiquem a direção da ordenação (ascendente ou descendente).
Ruim | Bom |
---|---|
GET /items | GET /items?sorteBy=time&order=asc |
GET /items | GET /items?sorteBy=price&order=desc |
GET /items | GET /items?sorteBy=name&order=asc |
Considerações adicionais
- Lide com cenários em que os clientes fornecem critérios de ordenação inválidos.
- Retorne mensagens de erro claras e instruções sobre como corrigir a solicitação.
11 - Filtros
Quando projetamos APIs, a capacidade de filtrar e buscar dados específicos é importante para atender às necessidades dos consumidores. Aqui estão algumas práticas importantes relacionadas aos filtros:
- Os parâmetros de consulta são uma maneira comum de permitir que os clientes filtrem os resultados de uma API.
- Por exemplo, ao buscar produtos, os clientes podem querer filtrar por categoria, preço, data de criação etc.
- Documente quais campos podem ser usados como filtros na sua API.
- Considere fornecer sugestões de valores para os campos de filtro, se aplicável.
- Ofereça suporte a diferentes operadores de filtro, como igual, diferente, maior que, menor que etc.
- Permita que os clientes combinem vários filtros para refinar ainda mais os resultados.
- Valide os parâmetros de consulta para garantir que os valores sejam válidos e dentro dos limites aceitáveis.
- Retorne mensagens de erro claras para solicitações inválidas.
Ruim | Bom |
---|---|
GET /products | GET /products?filter=corlor:eq:red |
GET /products | GET /products?filter=price:gt:50 |
GET /products | GET /products?filter=name:neq:Tabajara and price:gt:50 |
12 - Limite de tráfego
O Rate Limiting é uma estratégia para restringir o tráfego de rede recebido por sua aplicação, limitando a quantidade de requisições realizadas em um certo período de tempo. Geralmente, isso é feito rastreando os endereços IP que realizam as requisições e definindo um limite para a quantidade de requisições permitidas dentro de um intervalo de tempo específico. Quando a quantidade de requisições excede o limite, novas requisições não são completadas por um período determinado. Essa abordagem é essencial para proteger contra atividades maliciosas, como ataques de bots, força bruta e DDoS.
Benefícios do Rate Limiting
- Segurança: Protege sua aplicação contra sobrecarga de tráfego e ataques.
- Estabilidade: Evita que recursos sejam consumidos excessivamente, mantendo a estabilidade do servidor.
- Controle de Carga: Ajuda a manter um controle adequado da carga nos serviços web publicados.
13 - Circuit Breakers
APIs bem arquitetadas levam em consideração que o sistema final pode estar indisponível e criam mecanismos de proteção. Entre esses mecanismos, o circuit breaker desempenha um papel fundamental. Ele promove robustez, evita falhas em cascata e mantém a estabilidade do seu sistema
- Resiliência e Estabilidade:
- Quando uma API externa (ou qualquer serviço) está instável, o circuit breaker atua como um guardião.
- Se a API começar a retornar erros repetidamente ou ficar indisponível, o circuit breaker intervém temporariamente, interrompendo as chamadas para essa API.
- Isso evita sobrecarregar a API com chamadas desnecessárias e preserva a estabilidade do seu próprio serviço.
- Prevenção de Overhead:
- Sem um circuit breaker, sua aplicação continuaria tentando chamar a API problemática, resultando em overhead desnecessário.
- O circuit breaker evita essas chamadas repetitivas, melhorando a eficiência e performance.
- Transparência e Controle:
- O circuit breaker permite que você defina regras para quando ele deve abrir ou fechar.
- Ele registra os estados (aberto, fechado ou meio aberto) e permite ajustes com base nas condições da API.
- Recuperação Automática:
- Quando o circuit breaker está no estado meio aberto, ele faz chamadas de teste para verificar se a API está estável novamente.
- Se a API se recuperar, o circuit breaker reabre a conexão automaticamente.
14 - Segurança
A segurança é um aspecto fundamental para proteger os dados, garantir a integridade das transações e evitar ameaças.
- SSL/TLS (Transport Layer Security): Toda API deve usar TLS para criptografar as mensagens em trânsito. Isso protege as informações enviadas pela API e pelos usuários.
- OAuth2: Para autenticação e autorização, o OAuth2 é amplamente utilizado. Ele oferece fluxos específicos para aplicativos da web, aplicativos móveis e outros dispositivos.
- O OAuth2 com OpenID Connect é uma ótima opção para SSO, permitindo que os usuários façam login uma vez e acessem várias APIs.
- API-KEY: As API keys são tokens de segurança que permitem que um usuário ou aplicativo acesse e utilize os recursos de uma API.
- Elas são frequentemente incluídas nos cabeçalhos das solicitações para autenticar e autorizar o acesso.
- As API keys não são consideradas totalmente seguras, pois geralmente são acessíveis aos clientes.
- Se uma API key for roubada, ela pode ser usada indefinidamente, a menos que o proprietário do projeto a revogue ou a regenere.
Em resumo, o OAuth2 oferece maior segurança, flexibilidade e controle em comparação com as API keys. Desta forma, o uso do OAuth2 é recomendável sempre quando disponível.
15 - Controle de Acesso
Às vezes, é necessário garantir acessos específicos a recursos da sua API, e isso pode ser alcançado por meio do controle de acesso. Atualmente, existem dois tipos de controle de acesso comumente usados:
- RBAC (Role-Based Access Control)
- O RBAC concede ou nega acesso com base nos papéis do usuário dentro de uma organização.
- Os papéis em RBAC geralmente se referem a grupos de pessoas que compartilham certas características, como:
- Departamentos
- Localizações
- Níveis de senioridade
- Funções de trabalho
- Com um papel definido, você pode atribuir permissões, como:
- Acesso (o que o usuário pode ver)
- Operações (leitura, gravação, criação ou exclusão de arquivos)
- Sessões (duração do login)
- ABAC (Attribute-Based Access Control)
- O ABAC responde à pergunta “O que essa pessoa pode fazer?” com base em:
- Usuário: Características do usuário, como título do cargo, tarefas típicas ou nível de senioridade.
- Atributos do recurso: Tipo de arquivo, criador do arquivo, sensibilidade do documento etc.
- Ambiente: Local de acesso, horário do dia, data no calendário etc.
- O ABAC responde à pergunta “O que essa pessoa pode fazer?” com base em:
A escolha entre ABAC e RBAC dependerá das complexidades e requisitos específicos da sua API. Em alguns casos, combinar os dois métodos pode ser a melhor solução.
Em resumo:
- RBAC controla acesso amplo em toda a organização com base em papéis.
- ABAC adota uma abordagem mais detalhada, considerando atributos específicos do usuário, ambiente e recurso.
16 - HATEOAS
O HATEOAS (Hypermedia as the Engine of Application State) é um conceito que pode transformar a maneira como as APIs são consumidas e interagidas.
- Auto-Descrição e Navegação:
- O HATEOAS permite que uma API se torne autoexplicativa.
- Os recursos da API incluem links de hipermídia nas respostas, permitindo que os clientes naveguem dinamicamente entre os recursos.
- Isso melhora a descoberta e a usabilidade da API.
- Exemplo Prático:
- Imagine uma API de leitura de um cliente da loja virtual.
- Sem HATEOAS: A resposta é um JSON com o ID e o Nome do cliente, somente.
{ "customerId": "10A", "customerName": "Jane" }
- Com HATEOAS: A resposta possui um atribruto _links que permitem que os clientes naveguem dinamicamente entre os recursos, descobrindo as ações disponíveis e melhorando a usabilidade da API. Inclusive levando em consideração controle de acesso!
{ "customerId": "10A", "customerName": "Jane", "_links": { "self": { "href": "/customers/10A" }, "orders": { "href": "/customers/10A/orders" } } }
- Comparação com Hipertexto:
- O HATEOAS segue o mesmo princípio do hipertexto na web.
- Assim como os usuários da Internet navegam por links para encontrar informações, os clientes de APIs podem fazer o mesmo com recursos HATEOAS.
Em resumo, o HATEOAS torna as APIs mais flexíveis, autoexplicativas e dinâmicas. Ele reduz o acoplamento entre clientes e servidores, permitindo que os clientes descubram as ações disponíveis sem conhecimento prévio da estrutura da API.
A recomendação é avaliar se o uso de HATEOAS irá trazer benefícios reais aos usuários finais. Em APIs simples, por exemplo, o uso de HATEOAS pode trazer uma complexidade desnecessária para o backend sem ganhos reais para o usários finais.