APIs e Microsserviços

11 mar, 2016

Documentação HTTP com API Blueprint

Publicidade

Ao planejar minha palestra e livro sobre o desenvolvimento REST/HTTP API, acabei mencionando a documentação no final, e sem muito respeito disse: “Ah, e API Blueprint é muito bom, provavelmente, é só usar isso”. Isso é algo que eu gostaria de corrigir com uma máquina do tempo, já que atualmente eu faço a especificação da API em API Blueprint antes de eu chegar perto o código.

Documentação primeiro dá a você e à equipe a chance de brincar com o que os campos estão trazendo e enviando, e você ainda pode colocar esse arquivo de documentação em uma ferramenta de edição colaborativa, como o Dropbox Notes ou ter uma sessão do Google Hangouts com um companheiro de trabalho. Quando a equipe de API e os clientes (web, iOS, Android etc.) tiverem atingido um bom nível de concordância, os contratos podem ser “bloqueados” e colocados no Git, ou seja, as alterações são controladas e a culpa pode ser apontada para qualquer um que ferrar o contrato.

Se a documentação da API é então testada com o Dredd, você sabe que seus contratos estão se segurando como esperado ao longo do tempo.

Começando

Getting Started with API Blueprint” é bastante vago e aberto. Eles ensinam sobre rascunhar, que no início é totalmente irrelevante, exceto para verificação de sintaxe, o que o editor Apiary faz bem melhor. O getting started é muito diferente do que eles tem documentado, então aqui vai como eu o trato.

Vamos assumir por agora que você deseja utilizar a plataforma do Apiary na hospedagem deles, e eu irei ensinar como usar alternativas em artigos posteriores.

Configurando a API com o Apiary

Crie um novo diretório para trabalhar, e coloque um novo arquivo apiary.apib nele. Vamos colocar a sintaxe da API Blueprint mais básica possível:

FORMAT: 1A
# My API

## GET /places

+ Response 200 (text/plain)

        Hello World!

Nota: Normalmente eu usaria ~~~ para embrulhar o corpo da resposta e apenas um nível de indentação, mas parsers Markdown piram se você tentar colocar blocos de código fechados dentro de blocos de código fechados. Você pode usar 8 espaços, 2 abas, ou envolver em ~~~ por si só.

Agora, para enviar esse arquivo para o Apiary, temos que fazer um repositório no GitHub. Se você é descolado, e usa a ferramenta hub CLI, basta fazer isto:

$ hub init
$ hub create
$ git add apiary.apib
$ git commit -m "Added basic API Blueprint"
$ git push origin master

Agora, que a API Blueprint está em um repositório GitHub, podemos configurar o Apiary.

Em primeiro lugar, clique no menu suspenso ao lado de onde ele tem o padrão “Notes API” e selecione “Create New API”.

api-1

Opção para criar uma nova API no Apiary

Clique em Configurações e desça até a opção de conexão com o GitHub.

api-2

Associe o seu repositório no GitHub

Isso irá configurar automaticamente um gancho de serviço no Github, assim você não precisa fazer manualmente.

Agora, encontre o novo repositório que fizemos, e o conecte, clicando em Go.

api-3

Achei!

Se isso funcionar, você verá algumas opções. Se você pode, clique em “Load blueprint from GitHub” para importar, mas você pode ter um erro – o que eu não tenho certeza da causa – então substitua primeiro e, em seguida, faça um novo commit sobrepondo o anterior.

api-4

Opções iniciais de sincronização com o GitHub. Você provavelmente nunca precisará olhar aqui novamente

Agora, sempre que fizermos um push de alteração no nosso arquivo apiary.apib, ele vai atualizar o Apiary. Se atualizarmos o Apiary, ele vai salvar a alteração no apiary.apib por meio de um novo commit.

Respostas formatadas

Ter um Hello World é um bom começo, mas também temos que aprender a trabalhar com formatos de dados, como o JSON. A maneira mais fácil de dar saída ao JSON na response documentation é mudar a response mime-type e colar algum exemplo de JSON nele.

FORMAT: 1A
# My API

## GET /places

+ Response 200 (application/json)

        {
            "places" : [{
                "id" : "fRge5",
                "name" : "Battery Harris"
            }]
        }

Dê uma olhada na API Blueprint. Vá em frente e utilize o seu editor com a visualização em tempo real à medida que avançamos salvando e fazendo commit nas coisas. Você deve ver na saída:

api-5

Saída da resposta JSON no Apiary

Em seguida, vamos adicionar outro endpoint. Temos GET/places, por isso vamos adicionar GET/places/{id} para obter apenas um único lugar.

FORMAT: 1A
# My API

## GET /places

+ Response 200 (application/json)

        {
            "places" : [{
                "id" : "fRge5",
                "name" : "Battery Harris"
            }]
        }


## GET /places/{id}

+ Parameters
  + id: `fRge5` - The unique ID of the place.

+ Response 200 (application/json)

        {
            "places" : {
                "id" : "fRge5",
                "name" : "Battery Harris"
            }
        }

Em um nível muito simples, isso funciona, mas não é muito limpo. Nós estamos repetindo os dados em nossos exemplos muitas vezes, mesmo que eles compartilhem uma estrutura muito similar.

Vamos alavancar MSON ( “Markdown Syntax for Object Notation”) para facilitar nossas vidas:

FORMAT: 1A
# My API

## GET /places

+ Response 200 (application/json)
    + Attributes
        - places (array[Place])
    
## GET /places/{id}

+ Parameters
  + id: `fRge5` - The unique ID of the place.

+ Response 200 (application/json)
    + Attributes
        - places (Place)

# Data Structures

## Place (object)

- id: `fRge5` (string)
- name: `Battery Harris` (string)

Isso está ficando um pouco grande, então vamos dar uma olhada em pedaço por pedaço.

## GET /places

+ Response 200 (application/json)
    + Attributes
        - places (array[Place])

Aqui nós temos o nosso pedido GET/places. Ele tem uma resposta JSON, com atributos. Esses atributos contêm uma array de nível superior de places, que é um array de estruturas de dados Place.

Sim, nós definimos estruturas de dados! Onde? Neste pedaço:

# Data Structures

## Place (object)

- id: `fRge5` (string)
- name: `Battery Harris` (string)

Aqui definimos o nome de dois campos, demos a ele valores padrão (um movimento opcional, mas aconselhável) e, em seguida, demos o tipo de dado.

Porque ambos GET/places e GET/placess/{id} compartilham essa estrutura de dados (mesmo que um seja um array e o outro, um objeto), podemos expandir nos campos e realmente melhorar a documentação de place.

# Data Structures

## Place (object)

- id: `fRge5` (string, required) - The unique ID of the place.
- name: `Battery Harris` (string, required) - Name of the place.
- lat: `40.712017` (number, required) - Latitude as a decimal.
- lon: `-73.950995` (number, required) - Longitude as a decimal.
- status (enum[string])
  - pending - They haven't finished their public profile or whatever
  - active - Good as gold
  - closed - This place doesn't exist
- created_at: `2015-01-07T14:03:43Z` (string, required) - ISO8601 date and time of when the rider was created.

Agora temos descrições sobre os nossos campos, e acrescentamos muitos outros campos. O mais legal é o campo de status, estabelecendo um monte de valores de string em um enum, com todas as opções explicadas.

api-6

Como nossas respostas MSON se parecem em JSON

Você pode saber mais sobre a palavra-chave “Attributes” na API Blueprint Spec, e lendo mais especificamente sobre a sintaxe MSON.

Requisições

APIs são mais do que apenas obter dados, portanto, hora de olhar para um request.

Eu fiz um exemplo completo e atualizado, que usa grupos de recursos para definir uma URL e, em seguida, tem h3 com o método HTTP para definir o que acontece nas ações.

FORMAT: 1A
# My API

Bit of a description or intro, and an introduction to how to OAuth, etc.

# Group Places

## Places Collection [/places]

### GET

+ Response 200 (application/json)
    + Attributes
        - places (array[Place])

## Place Resource [/places/{id}]

+ Parameters
  + id: `fRge5` - The unique ID of the place.

### GET

+ Response 200 (application/json)
    + Attributes
        - places (Place)

### PUT

+ Request (application/json)
    + Attributes
        - places (Place)

+ Response 200 (application/json)
    + Attributes
        - places (Place)

### DELETE

+ Response 204


# Data Structures

## Place (object)

- id: `fRge5` (string, required) - The unique ID of the place.
- name: `Battery Harris` (string, required) - Name of the place.
- lat: `40.712017` (number, required) - Latitude as a decimal.
- lon: `-73.950995` (number, required) - Longitude as a decimal.
- status (enum[string])
  - pending - They haven't finished their public profile or whatever.
  - active - Good as gold.
  - closed - This place doesn't exist.
- created_at: `2015-01-07T14:03:43Z` (string, required) - ISO8601 date and time of when the rider was created

 

Parece bom, hein? Temos PUT e DELETE lá também agora, mostrando nada menos que um 204.

Bem, uma coisa ainda falta: Usar POST para criar um recurso na coleção.

A parte difícil aqui é que a maioria das solicitações POST envolve o envio de representações parciais, e deixa o servidor preencher as lacunas. Para fazer isso, a nossa estrutura de dados Place falha um pouco.

Então, podemos dividir. Dê uma olhada neste exemplo completo para ver o que mudou:

FORMAT: 1A
# My API

Bit of a description or intro, and an introduction to how to OAuth, etc.

# Group Places

## Places Collection [/places]

### GET

+ Response 200 (application/json)
    + Attributes
        - places (array[Place Full])

### POST

+ Request (application/json)
    + Attributes
        - places (Place Create)

+ Response 200 (application/json)
    + Attributes
        - places (Place Full)

## Place Resource [/places/{id}]

+ Parameters
  + id: `fRge5` - The unique ID of the place.

### GET

+ Response 200 (application/json)
    + Attributes
        - places (Place Full)

### PUT

+ Request (application/json)
    + Attributes
        - places (Place Full)

+ Response 200 (application/json)
    + Attributes
        - places (Place Full)

### DELETE

+ Response 204

# Data Structures

## Place Create (object)

- name: `Battery Harris` (string, required) - Name of the place.
- lat: `40.712017` (number, required) - Latitude as a decimal.
- lon: `-73.950995` (number, required) - Longitude as a decimal.
- status (enum[string])
  - pending - They haven't finished their public profile or whatever.
  - active - Good as gold.
  - closed - This place doesn't exist.

## Place Full (object)

- id: `fRge5` (string, required) - The unique ID of the place.
- Include Place Create
- created_at: `2015-01-07T14:03:43Z` (string, required) - ISO8601 date and time of when the rider was created

Temos agora um objeto para Place Create que possui os campos necessários para criar, e o objeto Place Full, que inclui o Place Create para aquele efeito completo, juntamente com dois campos extras que só irão aparecer depois.

API Blueprint vs. Alternativas

Existem algumas opções por aí, e as populares são Swagger e RAML. API Nordic compara esses formatos brevemente, mas é um pouco vaga.

Eu vou ser honesto, tudo o que fiz com Swagger foi horrível. Swagger-PHP é baseado em uma abordagem orientada a código muito complicada, na qual ele assume que suas classes de modelo atuais serão como você irá representar seus dados, e isso não é de todo verdade. Talvez eu pudesse encontrar uma maneira de amarrar Swagger-PHP para os serializers em vez disso, mas mesmo a Swagger UI é terrível. Swagger me parece muito errado.

RAML é um formato poderoso. Tem um monte de ferramentas, mas, para ser honesto, muito desse ferramental existe para API Blueprint também. A principal desvantagem para mim é que RAML é YAML, e eu odeio YAML.

A maioria das reclamações sobre API Blueprint são de que começar com ela é muito ruim (eu arrumei isso ^) ou que é complexa para escrever. Bom, versões recentes fizeram a sintaxe ser menos verbosa, MSON é excelente e você pode obter checagem de sintaxe para muitos editores, incluindo o meu novo herói Atom.

Teste-os e veja qual é melhor para você, mas a ideia é que você deve especificar sua API e criar a documentação antes de construí-la, quando possível.

Resumo

Há muito mais de Apiary e MSON do que isso, e eu quero mostrar como usar isso para mocar servidores, mas ter esse material construído é uma ótima maneira de fazer a equipe chegar a acordo sobre os contratos para solicitações e respostas antes de as pessoas começarem a bagunçar construindo coisas de verdade.

P.S.: Se você está interessado no desenvolvimento de API e quer conversar com outros desenvolvedores que compartilham seus interesses, dê uma passada no canal do Slack APIs You Won’t Hate. É uma comunidade de mais de 300 desenvolvedores que ajudam uns aos outros, e conversam sobre HTTP, REST e APIs.

***

Phil Sturgeon faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://philsturgeon.uk/api/2015/10/08/http-documentation-with-api-blueprint/