Executando verificação de segurança...
42

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.

RuimBom
GET /querycarts/123GET /carts/123
GET /getAllCustomersGET /customers
POST /criateNewCustomerPOST /customers
DELETE /deleteCustomerDELETE /customers/1

Endpoints com múltiplos substantivos devem ser separados por "-":

RuimBom
GET /salesOrderGET /sales-order
GET /userManagement/usersGET /user-management/users
GET /userManagement/users/1GET /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.

RuimBom
GET /cart/123GET /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.
RuimBom
GET /getAllCustomersGET /customers
GET /getCustomer/1GET /customers/1
POST /criateNewCustomerPOST /customers {"name": "João Tabajara", "address": "Rua Tabajara"}
DELETE /deleteCustomerDELETE /customers/1
PUT /updateCustomerPUT /customers/1 {"name": "João Tabajara Queiroz", "address": "Rua Triste Feliz"}
PATCH /patchCustomerPATCH /customers/1 {"name": "João Tabajara Queiroz Juazeiro"}

7 - Utilizar subrecursos para relacionamentos

Quando há hierarquia de objetos e recursos, use subrecursos.

RuimBom
GET /carts/1?item=1GET /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.

RuimBom
GET /carts/v1/123GET /v1/carts/123
GET /carts/123GET /carts/123?version=1
GET /carts/123GET /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:

RuimBom
GET /cartsGET /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).
RuimBom
GET /itemsGET /items?sorteBy=time&order=asc
GET /itemsGET /items?sorteBy=price&order=desc
GET /itemsGET /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.
RuimBom
GET /productsGET /products?filter=corlor:eq:red
GET /productsGET /products?filter=price:gt:50
GET /productsGET /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:

  1. 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)
  2. 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.

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.

  1. 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.
  2. 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"
              }
          }
      }
      
  3. 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.

2

Excelente dica, apenas senti um pouco falta de alguma orientação de como deixar o json de resposta menos "prolixo". Ou como garantir objetividade do retorno da api com o mais necessário.

2

@hbm obrigado pelo feedback! Sobre o que comentou, acredito que a melhor forma de evitar isso é dividir sua API em entidades menores, evitar entidades que trazem listas de entidades filhas. Por exemplo: Post e Comentário, veja a abaixo.

Bom:

GET /posts [{"id":1,"title":"Meu title", "body":"texto do post"}]
GET /posts/1/comments [{"author":"ze ning", "text":"texto do comment"}...]

Ruim:

GET /postsAndComments {"title":"Meu title", "body":"texto do post", "comments": [{"author":"ze ning", "text":"texto do comment"}...]}
2
0

Com certeza! Por isso é importante avaliar se o join que vc vai fazer pra trazer os recursos é mais onerante que fazer outra request.
As vezes o join é mais onerante, então é interessante usar o poder computacional do frontend para juntar essas informações.

Outro detalhe que quando vc ta trazendo payloads complexos (parent-childs) isso aumenta o tamanho de dados trafegados, o seu parse json demora mais e consequentemente mais esforço pro seu backend receber/enviar dados assim. Não existe uma receita de bolo falando que vc não pode fazer isso, tem casos que fazem sentido vc trazer retornos complexos, por exemplo: Telas de formulários onde diversas regras de negócio precisam ser ocultadas do frontend, retornos para gráficos... A questão aqui é saber decidir quando convém usá-lo.

2

Poderia compartilhar onde viu que a recomendação é manter a nomenclatura dos atributos seguindo o padrão Camel Case? Obviamente no mercado cada empresa faz de uma forma, mas no geral vejo mais no estilo snake case do que o próprio pascal case. Aliás excelentes dicas!

1

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 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!

1
1