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”.
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.
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.
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.
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:
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.
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/