Design de APIs modernas

Primeiro, o termo design aqui empregado tem o sentido de projeto (projetar).

Dependendo da dimensão da API, antes de colocar a mão na massa é fundamental efetuar um planejamento prévio, padronizar os erros, objetos, requisições, respostas e etc. Para isso é possível utilizar ferramentas de código aberto que auxiliam tanto no design, quanto desenvolvimento, testes e entrega de uma API robusta e inteligível.

PS: No âmbito organizacional, utilizar tais ferramentas exige tempo e requer atividades/tarefas extras, então dependendo do tamanho da API essa abordagem pode não fazer muito sentido (quanto utilizar uma ferramenta de documentação estática por ex. o Slate). Entretanto vale a pena conferir o artigo para conhecer o funcionamento e a proposta das ferramentas abordadas.

Acredito que já tenha deixado claro qual o intuito desse artigo, e a partir de hoje com essas ferramentas, torço para que não haja mais nenhuma API com documentação desatualizada.

A seguir são relacionados os instrumentos que darão suporte para as boas práticas de design de uma API do zero e em sequencia serão destacados exemplos de código e as vantagens ou desvantagens da sua abordagem.

Desejo uma boa leitura e aguardo seu feedback.
Caso queira pular o conteúdo e ir direto ao código fonte do projeto Clique Aqui.

Suíte de ferramentas: API Blueprint, Aglio, Drakov e Dredd

API Blueprint

https://github.com/apiaryio/api-blueprint

O API Blueprint (open source) é uma poderosa linguagem de alto nível, baseado em markdown para o design de APIs. Com sintaxe concisa e expressiva ele auxilia os desenvolvedores durante as tarefas do ciclo de vida de design de APIs.

Com o API Blueprint é possível prototipar e modelar uma API rapidamente ou até mesmo descrever APIs de missão crítica já implantadas.

Além da etapa de desenvolvimento o API Blueprint fornece ferramentas que auxiliam também na governança e deploy. Essas também serão abordadas aqui.

Aglio

https://github.com/danielgtaylor/aglio

O Aglio (open source) é um processador (renderer) do API Blueprint que fornece temas e produz um conteúdo estático de documentação pronto para ser hospedado em qualquer host do mercado.

O Aglio fornece um servidor com live-reloading para desenvolvimento e facilita o planejamento da arquitetura, fornecendo mecanismos para inclusão de arquivos e referências para objetos e recursos.

Exemplo de documentação gerada com o Aglio
Execução em servidor de desenvolvimento local (com live-reloading)

Drakov

https://github.com/Aconex/drakov

O Drakov (open source) é um servidor de simulação (mock) que implementa e suporta a especificação do API Blueprint.

Com o Drakov é possível inicializar localmente um servidor de mock para simular os recursos projetados (endpoints) e desenvolvidos com o API Blueprint. E tudo isso no mesmo projeto de software aproveitando o código da documentação existente.

Exemplo do mock server em execução

Mock server

Dredd

https://github.com/apiaryio/dredd

O Dredd é um framework para automação de testes HTTP em APIs.
Nesse cenário o Dredd atua como uma ferramenta de linha de comando que permite validar o documento de design da API com a implementação disponibilizada pelo backend.

O Dredd basicamente lê a descrição da sua API (escrita seguindo o API Blueprint), e passo a passo valida se a implementação descrita dos endpoints corresponde com as respostas recebidas do backend, parece mágica, é verboso o suficiente e ainda ajuda a aproveitar mais código já documentado.

Exemplo de execução de testes com o Dredd

Mock server

Por que utilizar o API Blueprint?

Atualmente há diversos frameworks open source apoiados por um grande ecossistema de ferramentas que auxiliam os desenvolvedores em construir, documentar e consumir serviços web RESTful.

O Swagger é um deles, também é open source e está no mercado deste 2011, mas, no entanto, em meu ponto de vista, esse framework apresenta uma curva de aprendizado mais alta em comparação com o API Blueprint. Vale a pena conferir como alternativa.

Já o API Blueprint foi iniciado em 2013 com a empresa Apiary, mas no início de 2017 foi adquirida pela Oracle com intuito de criar uma solução mais abrangente de integração com nuvem e governança. Entretanto a especificação continuou com código aberto para a comunidade.

Como dito anteriormente, a linguagem do API Blueprint é baseada em Markdown, especificamente no MSON, isso facilita o aprendizado para quem é familiar com esse tipo de linguagem de marcação.

+ Para conhecer mais sobre a suíte de ferramentas e recursos: https://apiblueprint.org/tools.html

+ PS: O API Blueprint exige um certo cuidado na descrição e formatação dos itens da API, principalmente com a indentação e espaçamentos, atente-se a esse detalhe.

O que é o MSON?

MSON ou “Sintaxe Markdown para Notação de Objetos” é outro projeto open source iniciado pela empresa Apiary como parte da sintaxe da API Blueprint, ele é um formato de descrição de texto simples, legível tanto para humanos quanto por máquina, que permite descrever estruturas de dados em formato de marcação comum, como por ex. JSON, XML ou YAML.

O objetivo principal desse formato é facilitar a discussão e, portanto, a validação das estruturas de dados com seu formato agnóstico às linguagens de marcação comuns e adequado para os cenários de “recursos e representações” e simplificação de “negociação de conteúdo”.

Tipos base suportados

O MSON define uma série de tipos primitivos e estruturais distintos, das quais todas as estruturas de dados são criadas.

  • boolean
  • string
  • number
  • array
  • enum
  • object
  • E ainda os “tipos nomeados”, que podem estender os tipos primitivos ou referenciados para construir outros tipos.

+ Detalhes, consulte a especificação: https://apiblueprint.org/documentation/mson/specification.html#21-base-types

Definição de atributos

Com o MSON também é possível definir atributos adicionais associados à implementação de tipos.

  • required – obrigatório
  • optional – a instância desse tipo é opcional (padrão)
  • fixed – instâncias desse tipo são corrigidos (esse atributo se propaga para tipos de membros aninhados)
  • fixed-type – instâncias desse tipo são corrigidas, mas o valor não (esse atributo não se propaga para tipos de membros aninhados)
  • nullable – o valor pode ser desativado
  • sample – uma maneira alternativa de indicar valores de amostra
  • default – um modo alternativo para indicar valores padrões

Exemplo completo de definição de um atributo:

+ nome_do_atributo: valorDefault (string, nullable) - Descrição do atributo

Como iniciar o desenvolvimento com o API Blueprint

O arquivo com suporte a API Blueprint deve possuir a extensão .apib e o arquivo principal ainda deve conter as informação da versão e do host da API.

FORMAT: 1A9
HOST: https://api.coderi.com.br

# Título da API
Descrição, detalhes e qualquer informação que julgar relevante para o contexto da API (em formato markdown).

<!-- include(resources/users/users.apib) -->

O exemplo acima mostra o arquivo index.apib, e um detalhe importante é a possibilidade de realizar inclusão de outros arquivos do projeto (include na última linha).

Essa é uma funcionalidade exclusiva do Aglio e auxilia muito na organização hierárquica da arquitetura.

Algum editor ou IDE pode ajudar?

Considero esse assunto de certa maneira até irrelevante, cada desenvolvedor utiliza a ferramenta que tem afinidade, no entanto, o Atom possui o pacote language-api-blueprint que auxilia muito o desenvolvimento, principalmente para iniciantes.

Atom

Estrutura de dados

Além do agrupamento de recursos (debatido logo em sequencia), esse é um dos pontos mais fortes que eu considero no API Blueprint.

Ao iniciar o desenvolvimento da API utilizando o MSON é comum chegar a conclusão da necessidade de reaproveitamento de componentes de estrutura de dados comumente usados ou até aninhados.

Isso é possível com a sessão # Data Structures.
E ainda, na sessão de descrição de atributos é possível fazer referências às estruturas já definidas na mesma ou em outras sessões do código apenas referenciando o nome da estrutura, definir valores padrões, tipagem e fornecer uma descrição detalhada de um atributo.

Assim que a especificação da estrutura do atributo é definida, o corpo das requisições onde ele é usado é automaticamente descrito com JSON Schema, descrevendo os tipos, membros obrigatórios/opcionais e outra série de recursos avançados desse esquema.

# Data Structures

## dsUsuariosRequest
+ username: `admin` (required, string) - Nome de usuário
+ password: `*****` (required, string) - Senha de usuário
+ nome_completo: `Administrador` (required, string) - Nome completo
+ data_nascimento: `12/11/1984` (optional, string) - Data de nascimento
+ sexo: `masculino`, `feminino` (required, enum) - Sexo do usuário

## dsUsuariosResponse
+ id: 1 (required, number) - ID do usuário
+ Include (dsUsuariosRequest)

Por padrão, cada estrutura de dados é um objeto, então repare no aproveitamento de código (da estrutura dsUsuariosResponse) utilizando a instrução Include e passando o objeto deseja em seguida.

Grupo de recursos, Recurso e Ações

Ao iniciar o desenvolvimento da sua API o primeiro passo é o agrupamento de recursos, ao usar a palavra chave Group no inicio do título é possível criar um grupo de recursos.

Com o agrupamento de recurso é possível reaproveitar ou não os parâmetros em comum entre as ações, minimizando a escrita de código.

# Group Usuários //--> Grupo de recursos
Recurso para manipular usuários no sistema.

## Coleção de usuários [/usuarios] //--> Recurso
Essa sessão especifica os endpoints para trabalhar com coleção de usuários.

### Lista usuários [GET] //--> Ação
Projeta uma coleção com os usuários do sistema.

+ Response 200 (application/json; charset=utf-8)
    + Attributes
        + data (array[dsUsuariosResponse], fixed-type)

### Adiciona usuário [POST] //--> Ação
Adiciona um novo usuário ao sitema.

+ Request (application/json)
    + Attributes (dsUsuariosRequest)

+ Response 201 (application/json; charset=utf-8)
    + Attributes (dsUsuariosResponse)

Trabalhando dessa maneira a exibição dos endpoints fica categorizada e organizada com base no grupo de ações. Como mostra a imagem a seguir.

Atributos

Os atributos oferecem uma maneira para descrever as estrutura das requisições e respostas também utilizando MSON, pois como já dito anteriormente o API Blueprint permite que se use textos simples para descrever as coisas em geral, em vez de formatos projetados apenas para análise por computadores.

## Usuário [/usuarios/{id}]
Essa sessão especifica o endepoint de usuário identificado pelo ID.

+ Parameters
    + id: 1 (number, required) - ID do usuário

### Exibe usuário [GET]
Projeta um usuário identificado pelo ID.

+ Response 200 (application/json; charset=utf-8)
    + Attributes (dsUsuariosResponse)

### Altera usuário [PUT]
Altera um usuário identificado pelo ID.

+ Request (application/json)
    + Attributes (dsUsuariosRequest)

+ Response 200 (application/json; charset=utf-8)
    + Attributes (dsUsuariosResponse)

Com os objetos já definidos na estrutura de dados, ao utiliza-los nos atributos tanto para requisições quanto para respostas o API Blueprint automaticamente processa e projeta JSON Schema.

Na sequencia segue um exemplo de schema do objeto dsUsuariosResponse definido apenas na estrutura de dados e gerado pelo API Blueprint.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "id": {
      "type": "number",
      "description": "ID do usuário"
    },
    "username": {
      "type": "string",
      "description": "Nome de usuário"
    },
    "password": {
      "type": "string",
      "description": "Senha de usuário"
    },
    "nome_completo": {
      "type": "string",
      "description": "Nome completo"
    },
    "data_nascimento": {
      "type": "string",
      "description": "Data de nascimento"
    },
    "sexo": {
      "type": "string",
      "enum": [
        "masculino",
        "feminino"
      ],
      "description": "Sexo do usuário"
    }
  },
  "required": [
    "id",
    "username",
    "password",
    "nome_completo",
    "sexo"
  ]
}

Isso facilita muito o trabalho para o cliente que irá consumir sua API.

E caso seja necessário é possível informar também códigos JSON, XML ou etc. diretamente nos atributos, mas de toda maneira a estrutura de dados auxilia muito, pois permite gerar o JSON Schema automaticamente com base nas definições programadas.

Relacionamentos

As ações especificas de um modelo definem um significado semântico de domínio, e a sessão de + Relation foi projetada para fazer o mesmo. Isso significa que uma ação pode ter um significado específico, independentemente do seu nome ou URI, o que permite que os clientes sejam criados com base no domínio em vez de URI ou recursos específicos.

## Usuário [/usuarios/{id}]

### Exibe usuário [GET]
+ Relation: self

### Deleta usuário [DELETE]
+ Relation: delete


## Coleção de usuários [/usuarios]

### Projeta uma coleção com os usuários do sistema [GET]
+ Relation: self

Nada mais que um approach de orientação a objetos.

Arquitetura da aplicação

Até ao definir a arquitetura o Aglio junto ao API Blueprint fornece facilidades.

Para as etapas de desenvolvimento ou deploy, o Aglio lê e processa o arquivo src/index.apib (junto com os includes) e gera um único arquivo final em dist/index.html.

Essa arquitetura em particular foi planejada para separar os recursos e estrutura de dados em uma única pasta dentro do diretório resources, mas nada impede que você defina a sua própria arquitetura de acordo com sua necessidade.

Realmente não há recomendações ou boas práticas para essa parte.

Eu pessoalmente utilizei essa mesma estratégia em um projeto (em produção) com mais de 30 agrupamentos de recursos e aproximadamente 270 endpoints ricos e funcionou muito bem. ✌️

Repositório de código

O código fonte do projeto encontra-se disponível no repositório público a seguir:
Repositório: https://github.com/kamihouse/api-design

Nesse projeto configurei alguns comandos para realizar as operações mais frequentes em uma abordagem real de produção e desenvolvimento, essas operações foram configuradas para não exigir nenhuma instalação adicional, então ao clonar o projeto os seguintes comandos estarão disponíveis:

  • npm run dev – Inicia o servidor de desenvolvimento (live-reload).
  • npm run mock – Inicia o servidor de simulação (mock).
  • npm run test – Executa testes HTTP automatizados (dredd).
  • npm run build – Constrói a aplicação p/ deploy (no diretório /dist).

O servidor de desenvolvimento executa na porta 3000, enquanto o servidor de mock escuta a porta 4000. Os demais detalhes estão no README do projeto.

Concluindo

Como diria meu amigo Salviano, se você gosta muito de uma ferramenta é porque ainda não a utilizou o suficiente. 😅

O API Blueprint é um projeto maduro e atendeu todas minhas expectativas para o desenvolvimento de recursos complexos, com as mais variadas estruturas de dados e endpoints; Entretanto no repositório do Github o projeto carece de manutenção, contendo no momento da escrita desse artigo 75 issues em aberto sem qualquer feedback para a comunidade.

Contudo isso não quer dizer que o que já foi estabelecido não te atenda, muito pelo contrário, o projeto está no ar desde 2013 e pela minha experiencia ele compreende recursos para o desenvolvimento de APIs modernas de qualquer categoria e complexidade.

Se gostou compartilhe! Até mais!

Thiago Pereira Rosa