API's - Uma Comparação Com Websites

Novamente aqui para falar sobre HTTP e API’s. Cada vez mais tenho a certeza de que apesar da necessidade, desenvolver serviços web envolve problemas arquiteturais e gerenciais muito complexos. Dessa vez viveremos um comparativo entre um serviço REST e websites.

O que é um serviço web?

Primeiro vamos dar nomes aos bois;

“Um website é um conjunto de hipertextos acessíveis geralmente pelo protocolo HTTP na internet.”

Ou seja, por uma página web, temos hipertexos que se ligam a outros hipertextos através de links, que via navegador são clicáveis.

Um serviço web é uma aplicação sob o http, criada com o intuito de prover informações a outras aplicações. Assim, se a prefeitura da sua cidade detêm informações do posicionamento dos ônibus da cidade em tempo real, ela possui, poderia/deveria ter um serviço web. Se o facebook consegue fornecer dados de usuários para que criemos aplicações, ele também fornece uma api, que seria uma maneira de se comunicar ao webservice deles.

Em um serviço web apenas dados são importantes. Esses dados podem ser dividios em estruturas que são representações de algo. Uma representação de um usuário (e não usuário em si), uma representação de um lugar (e não o lugar em si), uma representação de uma notícia (e não a notícia em si). Essa representação é chamada de recurso .

Você também pode (e deve) desenvolver serviços dentro do domínio de sua aplicação, afinal, hoje o mesmo dado utilizado num website é requerido por exemplo num aplicativo mobile.

REST e SOAP

Ao desenvolver uma API, podemos seguir um de dois padrões bem conhecidos. O primeiro é o SOAP, um padrão mais rígido que utiliza de um xml descritivo (wsdl) para definir as rotas, tipos de mensagens e retornos. Isso o torna mais flexível que o segundo. O REST usa o próprio HTTP como padrão de transporte de mensagens, ou seja, o corpo e o header das mensagens são os mesmos de uma request HTTP comum. Veja abaixo uma request que pede um recurso notícia de id 5.

1
2
GET /news/5
Host: api.meusite.com

Response:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-type: application/json; charset=UTF-8

{
 id: 5,
 title: "Manifestantes protestam contra aumento da passagem em SP",
 summary: "Em ato pacífico, grupo percorreu vias da Zona Oeste da capital...",
 access: [
 "2015-01-27 22:00:00", "2015-01-27 22:31:00"
 ]
}

A resposta da requisição retornou em formato json o recurso que contém, alem de id, title e resume, um array com todas as datas de acesso dessa notícia.

Content negotiation

Como vimos no post sobre negociação de conteúdo, é uma boa prática que serviços web sejam flexíveis quanto ao tipo de retorno, graças a uma negociação feita via cabeçalho HTTP. Pense agora em requisitar o mesmo recurso notícia de id 5 em formato HTML.

1
2
3
GET /news/5
Host: api.meusite.com
Accept: text/html; q=1.0 , application/json; 0.5

Teremos a seguinte resposta (vista pelo navegador):

g1 mobile site

Visibilidade de serviços web

Uma API pode ser definida como pública ou privada, e os conceitos são simples.
Uma API privada não está disponível pra toda a internet, geralmente ela é acessível apenas dentro de uma rede ou para um grupo restrito de clientes (ex: parceiros de uma empresa). Podemos comparar isso à uma página administrativa que lista todas as vendas de um ecommerce no mês. Essas informações sensíveis nunca poderão ser utilizadas por usuários comuns na web.
Ao contrário, uma web pública está na web e pode ser consumida por qualquer pessoa. São casos de API’s públicas o graph API do facebook, API do twitter ou mesma a API do github. Assim como sites oscilam em partes públicas e privadas, uma arquitetura orientada a serviços também pode (e deve) conter essa característica.

Linkando recursos

Até agora vimos que é bem provável que pra toda página web é possível ter uma API relacionada que expõe parte dos conteúdos que antes eram encontrados só no HTML.Mas também vimos que o princípio básico da web e do HTTP é a navegação através de hiperlinks. Podemos utilizar do padrão HAL para definir novos mime-types de retorno e assim linkarmos recursos.

Vamos voltar àquela request que busca um recurso notícia :

1
2
3
GET /news/5
Host: api.meusite.com
Accept: text/html; q=1.0 , application/json; 0.5

Teríamos como HTML

Podemos ver na imagem que na seção “Saiba mais” há links para mais 3 notícias. Falando em API’s podemos agir com algumas estratégias (ver atriuto similar):

* Notícias relacionadas embedadas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200 OK
Content-type: application/json; charset=UTF-8

{
 id: 5,
 title: "Manifestantes protestam contra aumento da passagem em SP",
 summary: "Em ato pacífico, grupo percorreu vias da Zona Oeste da capital...",
 access: [
 "2015-01-27 22:00:00", "2015-01-27 22:31:00"
 ], 
 similar: [
      { id: 6 , title: "FOTOS: quarto ato contra aumento da tarifa do transporte em SP", }, 
      { id: 8, title: "MPL faz caminhada contra tarifa na Zona Leste de São Paulo", },
      { id: 20, title: "Bombas, depredação e detidos marcam 2º ato contra tarifa em SP" }
 ]
}

* Caminho para os outros recursos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200 OK
Content-type: application/json; charset=UTF-8

{
 id: 5,
 title: "Manifestantes protestam contra aumento da passagem em SP",
 summary: "Em ato pacífico, grupo percorreu vias da Zona Oeste da capital...",
 access: [
 "2015-01-27 22:00:00", "2015-01-27 22:31:00"
 ], 
 similar: [
      "/news/6", 
      "/news/8",
      "/news/20"
 ]
}

* Considerando que notíciasRelacionadas é outro recurso isolado

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Content-type: application/json; charset=UTF-8

{
 id: 5,
 title: "Manifestantes protestam contra aumento da passagem em SP",
 summary: "Em ato pacífico, grupo percorreu vias da Zona Oeste da capital...",
 access: [
 "2015-01-27 22:00:00", "2015-01-27 22:31:00"
 ], 
 similar: "/news/5/similars"
}

A que eu acho mais interessante e a que o padrão HAL defende é a segunda. Dessa forma você consegue fazer com que seu recurso seja mais leve, entregando só o essencial e ele aponta caminhos para outros recursos relacionados, caso seja necessário pelo usuário. Mas para utilizar o padrão você ainda precisa usar atributos no seu recurso que seriam metainformações, como podemos ver abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
HTTP/1.1 200 OK
Content-type: application/hal+json; charset=UTF-8

{
 _links: {
    self:  {  
        href : "/" 
    }, 
    curies: [
        {
            "name" : "cl", 
            "href" : "api.meusite.com", 
            "templated" : true
        }
   ]
 },
 id: 5,
 title: "Manifestantes protestam contra aumento da passagem em SP",
 summary: "Em ato pacífico, grupo percorreu vias da Zona Oeste da capital...",
 access: [
 "2015-01-27 22:00:00", "2015-01-27 22:31:00"
 ], 
 cl:news: [
      { "href": "/news/6"} , 
      { "href": "/news/8"},
      { "href": "/news/20}"
 ]
}

Aqui sim temos definições de link self (assim como temos quando estamos usando o navegador web), definições da uri base e um alias para tal (“cl” é na verdade api.meusite.com) para aí sim dizer que cl:news são links para outros recursos.
Como podemos perceber no cabeçalho dessa resposta, utilizamos o mime-type application/hal+json que não deixa de ser um próprio json com alguns metadados. Na minha opinião poderíamos utilizar a próŕia negociação de conteúdo pra decidir entre hal+json ou json puro.

Conclusão

Websites e webservices podem ter muito em comum, e é um bom caminho para perceber como nossas aplicações sempre puderam ser feitas com o pensamento API first. Isso pode nos levar a um novo assunto que está em alta, os microservices. Porém deixaremos isso para uma próxima conversa! FalouValeu!

Fontes:

Definição de domínio: Domain driven design
Lista de API’s públicas
HAL – especificação
Notícia do G1 – printscreen
API first
Microservices

Comments