APIs e Microsserviços

11 jul, 2017

Definindo APIs com o API Blueprint

Publicidade

Uma das melhores decisões que tomamos na Coderockr foi adotarmos a abordagem “API First” para todos os projetos que iniciamos, desde 2010. Mas nos últimos meses, percebemos uma necessidade: melhorar o processo de definição e documentação das APIs.

Já usávamos outras abordagens, mas a maioria delas envolvia documentar a API no próprio código, usando annotations. Esta abordagem funciona, mas tem alguns problemas, principalmente quando a documentação precisa ser alterada por alguém de negócios. E gerar “mocks” e testes destas anotações também é um desafio complexo.

Com isso em mente, fizemos algumas pesquisas e chegamos a duas alternativas: Swagger e API Blueprint. Ambos são padrões de documentação de APIs e tem suas vantagens e desvantagens:

  • Swagger: é o padrão de mercado e vem sendo adotado por várias empresas como a Amazon. Para descrever a API, é necessário criar arquivos JSON, o que facilita bastante para os programadores, mas é um pouco complexo para visualizar e alterar seu conteúdo por pessoas não tão envolvidas com código. Existe uma série de ferramentas que podem ajudar neste processo, mas isso se tornou uma pequena barreira para nós (bom, pelo menos não é YML… Já comentei que odeio YML?);
  • API Blueprint: é uma especificação mais recente e foi criada por uma empresa chamada apiary, comprada pela Oracle. A grande vantagem do API Blueprint é ser descrita em Markdown, o que facilita bastante a edição dos documentos, mesmo por quem não tem familiaridade com código. Além disso, existe uma série de ferramentas disponíveis que permitem gerar documentos no padrão Swagger, “mock servers” e testes.

Optamos pelo API Blueprint pela facilidade de uso e agilidade que isso nos trouxe. Vou demonstrar com um pequeno exemplo.

Definindo uma API

A definição é escrita em um arquivo no formato Markdown, que pode ser nomeado como “api.md” ou “api.apib”. Ambos funcionam, mas se usarmos a extensão .apib, podemos aproveitar plugins para editores como o SublimeText que auxiliam na escrita. Os plugins podem ser encontrados no site oficial da especificação.

Nosso exemplo:

FORMAT: 1A
HOST: http://api.sample.com.br/v1

# Sample da API

Descrição da Sample.

# Group API

## Sobre [/]

Aqui podemos descrever detalhes que são comuns a todos os serviços como formatos, headers, tipos de erros, etc

# Group Mensagem

## Mensagens [/message]

### Criar mensagens [POST]

+ Request Criar uma mensagem

    + Headers

            Accept: application/json
            Content-Type: application/json
    + Attributes (Message)

+ Response 201 (application/json)
    + Attributes (Created)

### Listar mensagens [GET]

+ Response 200 (application/json)
    + Attributes (array[Message])

+ Response 404 (application/json)
    + Attributes (Error)


## Mensagem [/message/{id_message}]

+ Parameters
    + id_message: 1 (number, required) - ID da mensagem

### Ver mensagem [GET]

+ Response 200 (application/json)
    + Attributes (Message)

+ Response 404 (application/json)
    + Attributes (Error)


### Excluir mensagem [DELETE]

+ Response 200 (application/json)
    + Attributes (Id)

+ Response 404 (application/json)
    + Attributes (Error)


### Alterar mensagem [POST]

+ Request Alterar uma mensagem

    + Headers

            Accept: application/json
            Content-Type: application/json
    + Attributes (Message)

+ Response 200 (application/json)
    + Attributes (Created)

+ Response 400 (application/json)
    + Attributes (Error)


## Anexos [/message/{id_message}/file]

+ Parameters
    + id_message: 1 (number) - ID da mensagem

### Listar anexos [GET]

+ Response 200 (application/json)
    + Attributes (array[File])

+ Response 404 (application/json)
    + Attributes (Error)

## Anexos [/message/{id_message}/file/{file_id}]

+ Parameters
    + id_message: 1 (number) - ID da mensagem
    + file_id: 1 (number) - ID do arquivo

### Ver anexo [GET]

+ Response 200 (application/json)
    + Attributes (File)

+ Response 404 (application/json)
    + Attributes (Error)

### Remover anexo [DELETE]

+ Response 200 (application/json)
    + Attributes (Id)

+ Response 400 (application/json)
    + Attributes (Error)

# Data Structures

## Message (object)
+ subject (string) - Assunto da mensagem
+ body (string) - Corpo da mensagem


## MessageUpdate (Message)
+ id_message (number) - Id da mensagem

## File (object)
+ id_file (number) - Id do arquivo
+ name (string) - Nome do arquivo
+ url (string) - Url do arquivo

## Created (object)
+ id (number) - Id gerado

## Id (object)
+ id (number) - Id a ser processado

## Error (object)
+ code: 400 (number) - Status code
+ message (string) - Mensagem do status
+ description (string) - Descrição do status


## Multi (object)
+ id (number) - Código da entidade
+ code: 200 (number) - Status code
+ message (string) - Descrição do status


## User (object)
+ id (number) - Código do usuário
+ name (string) - Nome do usuário
+ token (string) - Token do usuário conectado

No site da especificação, é possível ver os detalhes, mas basicamente o que fazemos é definir as URLs, o formato das requisições e das respostas. Podemos definir estruturas de dados simples e complexas e usá-las para descrever o que a API espera de entradas e o que deve gerar de saída. O documento é relativamente simples de entender e alterar, o que foi um dos pontos de maior peso para nossa escolha. Mesmo assim, podemos melhorar a apresentação.

Gerando documentação

Dentre as ferramentas disponíveis no site oficial, a Aglio é uma das mais interessantes para geração de uma apresentação HTML da nossa definição. Ele pode ser instalado via:

npm install -g aglio

Para gerar a documentação, podemos usar o comando:

aglio -i api.apib --theme-full-width --no-theme-condense -o index.html

No site da ferramenta é possível ver todas as opções de customização de temas e apresentação. Outro comando útil é o:

aglio -i api.apib --theme-full-width --no-theme-condense -s

Ele gera um servidor local na porta 3000, que fica observando alterações no arquivo .apib e atualiza automaticamente a página da documentação. Isso facilita bastante a manutenção do documento. Um exemplo da documentação gerada:

A documentação ajuda muito no processo de desenvolvimento dos clientes da API, mas podemos ir além.

Gerando um mock server

Com a API definida, as equipes de frontend (web, mobile etc) e backend (quem vai desenvolver a API) podem trabalhar em paralelo. Para facilitar ainda mais, podemos criar um “mock server” que vai gerar dados falsos baseados na definição da API. Assim, a equipe de frontend pode trabalhar sem precisar esperar a equipe de backend terminar a implementação. Para isso, vamos usar outra ferramenta, a drakov.

Para instalar a ferramenta, basta executar:

npm install -g drakov

E para gerar o servidor:

drakov -f api.apib -p 4000

Desta forma, temos uma API funcional que pode ser usada para testes e desenvolvimento.

O passo final é definirmos uma forma de validarmos nossa API.

Testando

Podemos usar uma ferramenta chamada apib2swagger para gerar um arquivo Swagger da nossa API e realizarmos testes usando algum recurso do Swagger. Optamos por usar o dredd que automatiza os testes, tanto usando API Blueprint quanto Swagger.

Para instalá-lo:

npm install -g dredd

E para executar os testes:

dredd api.apib http://localhost:4000

Neste exemplo, estou usando o dredd para testar nosso “mock server”, por isso o resultado deve ser positivo. Podemos colocar o dredd na execução do nosso servidor de integração contínua para garantir que a implementação da API sempre esteja de acordo com a documentação, evitando surpresas e documentos abandonados.

Com o conjunto API Blueprint + aglio + drakov + dredd conseguimos mapear todo o ciclo de vida de uma API: definição, documentação, desenvolvimento e testes. Os resultados estão sendo bem positivos e devemos adotar essa solução em todos os novos projetos.