Tradução – Modelo de Maturidade de Richardson

O conceituado computeiro Martin Fowler escreveu um interessante e esclarecedor artigo em seu website martinfowler.com baseado no modelo de maturidade RESTful desenvolvido por Leonard Richardson.

Este artigo foi escrito em 18 de março  de 2010 por Martin Fowler e postado como Richardson Maturity Model.

Por considerá-lo muito pertinente em relação ao desenvolvimento de aplicações RESTful, achei que poderia ser útil traduzí-lo.

Modelo de Maturidade de Richardson

Por: Martin Fowler
Traduzido por: Vanessa Schissato

 

Passos em Direção à Glória do REST

Um modelo (desenvolvido por Leonard Richardson) que quebra os principais elementos de uma funcionalidade REST em três passos. Estes introduzem recursos, verbos HTTP e controles de hipermídia.


Recentemente eu li rascunhos do Rest In Practice: um livro no qual uma dupla de colegas tem trabalhado. Seu objetivo é explicar como usar serviços web RESTful para lidar com muitos dos problemas de integração que as empresas enfrentam. No coração do livro está a noção de que a web é a prova da existência de um sistema distribuído escalável em massa que funciona muito bem, e nós podemos tirar idéias disso para construir sistemas integrados mais facilmente.

Modelo de Maturidade de Richardson

Figura 1: Passos na direção REST

Para ajudar a explicar as propriedades específicas de um sistema do estilo web, os autores usaram um modelo de maturidade RESTful que foi desenvolvido por Leonard Richardson e explicada numa palestra da QCon. O modelo é uma agradável maneira de pensar sobre o uso destas técnicas, então pensei em me aventurar na minha própria explicação disso. (Os exemplos de protocolo aqui são meramente ilustrativos, eu não achei que valia a pena implementá-los e testá-los, de forma que podem existir problemas nos detalhes.)

Nível 0

O ponto de partida para o modelo é o uso do HTTP como um sistema de transporte para interações remotas, mas sem usar qualquer dos mecanismos da web. Essencialmente o que você faz aqui é usar HTTP como um mecanismo de tunelamento para seu próprio mecanismo de interação remota, geralmente baseado em Invocação de Procedimento Remoto (Remote Procedure Invocation).

Uma interação de exemplo no Nível 0

Figura 2: Uma interação de exemplo no Nível 0

Vamos considerar que eu queira agendar uma consulta com meu médico. Meu software de agendamento primeiro precisa saber que horários livres (slots) meu médico possui em uma determinada data, então ele faz uma requisição para o sistema de agendamento do hospital para obter essa informação. No cenário do nível 0, o hospital irá expor um endpoint de serviço em alguma URI. Eu, então, faço um POST para esse endpoint com um documento contendo os detalhes da minha solicitação.

POST /appointmentService HTTP/1.1
[various other headers]

<openSlotRequest date = "2010-01-04" doctor = "mjones"/>

O servidor irá retornar então um documento me dando esta informação:

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot start = "1400" end = "1450">
    <doctor id = "mjones"/>
  </slot>
  <slot start = "1600" end = "1650">
    <doctor id = "mjones"/>
  </slot>
</openSlotList>

Eu estou usando XML aqui para o exemplo; mas o conteúdo poderia, na verdade, ser qualquer coisa: JSON, YAML, pares chave-valor, ou qualquer formato customizado.

Meu próximo passo é agendar uma consulta, o que eu posso fazer com um POST de um documento para o endpoint.

POST /appointmentService HTTP/1.1
[various other headers]

<appointmentRequest>
  <slot doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
</appointmentRequest>

Se tudo correr bem eu recebo uma resposta dizendo que minha consulta está agendada.

HTTP/1.1 200 OK
[various headers]

<appointment>
  <slot doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
</appointment>

Se houver um problema, digamos que outra pessoa agendou antes de mim, então eu irei receber algum tipo de mensagem de erro no corpo da resposta.

HTTP/1.1 200 OK
[various headers]

<appointmentRequestFailure>
  <slot doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
  <reason>Slot not available</reason>
</appointmentRequestFailure>

Até agora este é um claro sistema no estilo RPC (Remote Procedure Call). Ele é simples, uma vez que está apenas atirando plain old XML (POX) pra frente e pra trás. Se você usa SOAP ou XML-RPC, é o mesmo mecanismo, a única diferença é que você envolve as mensagens XML em algum tipo de envelope.

Nível 1 – Recursos

O primeiro passo rumo à Glória do REST no Modelo de Maturidade de Richardson é introduzir recursos. Então, agora, ao invés de fazer todas as nossas requisições para um único endpoint de serviço, nós agora começamos a falar com recursos individuais.

Nível 1 adiciona recursos

Figura 3: Nível 1 adiciona recursos

Então, com nossa consulta inicial, nós poderíamos ter um recurso para um dado médico.

POST /doctors/mjones HTTP/1.1
[various other headers]

<openSlotRequest date = "2010-01-04"/>

A resposta carrega a mesma informação básica, mas cada horário livre é agora um recurso que pode ser endereçado individualmente.

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

Com recursos específicos, agendar uma consulta significa fazer um POST para um horário livre particular.

POST /slots/1234 HTTP/1.1
[various other headers]

<appointmentRequest>
  <patient id = "jsmith"/>
</appointmentRequest>

Se tudo correr bem, eu recebo uma resposta similar a anterior.

HTTP/1.1 200 OK
[various headers]

<appointment>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
</appointment>

A diferença agora é que se alguém precisa fazer qualquer coisa relacionada a agendamento, como agendar alguns exames, ele primeiro obtém o recurso do agendamento e faz um POST para este recurso.

Para um cara de objetos como eu, isto é como a noção da identidade do objeto. Ao invés de ter que chamar alguma função no limbo e passar argumentos, nós chamamos um método em um objeto particular provendo argumentos para a outra parte.

Nível 2 – Verbos HTTP

Eu usei verbos POST do HTTP para todas as minhas interações aqui em nível 0 e 1, mas algumas pessoas usam GET ao invés disso, ou adicionalmente. Nestes níveis, isto não faz muita diferença, eles estão ambos sendo usados como mecanismos de tunelamento que permitem a você transportar suas interações através do HTTP. O nível 2 vai além disso, usando os verbos HTTP tão próximos quanto possível de como eles são usados no próprio HTTP.

Nível 2 adiciona verbos HTTP

Figura 4: Nível 2 adiciona verbos HTTP

Para nossa lista de horários livres, queremos usar o GET.

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk

A resposta é a mesma que teria sido com o POST.

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

No nível 2, o uso do GET para uma requisiçãp como essa é crucial. HTTP define GET como uma operação segura, isto é, que não faz nenhuma mudança significativa em nenhum estado. Isto nos permite invocar GETs de maneira segura qualquer número de vezes em qualquer ordem e obter a os mesmos resultados toda vez. Uma consequência importante é que isto permite a qualquer participante na rota de requisições usar cache, que é o elemento chave que faz a web performar tão bem quanto performa. HTTP inclui várias medidas para suportar cache, que podem ser usadas por todos os participantes na comunicação. Ao seguir as regras do HTTP nos tornamos aptos a tirar vantagem da sua capacidade.

Para agendar uma consulta nós precisamos de um verbo HTTP que faça mudanças no estado, um POST ou um PUT. Eu irei usar o mesmo POST que eu usei antes.

POST /slots/1234 HTTP/1.1
[various other headers]

<appointmentRequest>
  <patient id = "jsmith"/>
</appointmentRequest>

A escolha entre usar POST e PUT vai além de até onde eu gostaria de ir aqui, talvez eu faça um artigo separado sobre isso algum dia. Mas eu gostaria de assinalar que algumas pessoas incorretamente fazem uma correspondência entre POST/PUT e criar/atualizar. A escolha entre eles é bastante diferente.

Mesmo que eu usasse o mesmo POST como no nível 1, existe outra diferença significante em como o serviço remoto responde. Se tudo der certo, o serviço responde com um código de resposta 201 para indicar que existe um novo recurso no mundo.

HTTP/1.1 201 Created
Location: slots/1234/appointment
[various headers]

<appointment>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
</appointment>

A resposta 201 inclui um atributo localização com uma URI que o cliente pode usar para obter (GET) o estado atual daquele recurso no futuro. A resposta aqui também inclui uma representação daquele recurso para economizar uma chamada extra do cliente logo em seguida.

Existe outra diferença se algo der errado, como alguma outra pessoa agendando a sessão.

HTTP/1.1 409 Conflict
[various headers]

<openSlotList>
  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

A parte importante desta resposta é o uso de um código de resposta HTTP para indicar que alguma coisa deu errado. Neste caso o 409 parece uma boa escolha para indicar que outra pessoa já atualizou o recurso de uma forma incompatível. Ao invés de usar um código de retorno 200 e incluir um erro na resposta, no nível 2 nós explicitamente usamos algum tipo de resposta de erro como esta. Cabe ao desenvolvedor do protocolo decidir que códigos usar, mas não deveria ser uma resposta 2xx quando um erro ocorresse. Nível 2 introduz o uso dos verbos HTTP e códigos de resposta HTTP.

Existe uma inconsistência horrenda aqui. REST prega o uso de todos os verbos HTTP. Eles também justificam sua técnica dizendo que REST está tentando aprender do sucesso da web na prática. Mas a web não usa muito PUT ou DELETE na prática. Existem razões sensíveis para usar mais PUT e DELETE, mas a prova existente da web não é uma delas.

Os elementos chave que são embasados pela existência da web são a separação forte entre operações seguras (p.e. GET) e não-seguras, aliadas com o uso de códigos de estado para ajudar a comunicar os tipos de erros.

Nível 3 – Controles Hipermídia

O nível final introduz algo que eu costumo ouvir ser referenciado sob o acrônimo feio de HATEOAS (Hypertext As The Engine Of Application State – Hipertexto como Mecanismo de Estado da Aplicação). Ele aborda a questão de como obter a partir de uma lista os horários livres já sabendo o que fazer para agendar uma consulta.

 Nível 3 adiciona controles hipermídia

Figura 5: Nível 3 adiciona controles hipermídia

Nos começamos com o mesmo GET inicial que enviamos no nível 2.

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk

Mas a resposta tem um novo elemento.

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450">
     <link rel = "/linkrels/slot/book" 
           uri = "/slots/1234"/>
  </slot>
  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650">
     <link rel = "/linkrels/slot/book" 
           uri = "/slots/5678"/>
  </slot>
</openSlotList>

Cada horário livre tem agora um elemento link que contém uma URI para nos dizer como agendar uma consulta.

A questão com os controles de hipermídia é que eles nos contam o que podemos fazer em seguida, e a URI do recurso que nós precisamos manipular para isto. Ao invés de termos que saber onde postar nossa requisição de agendamento, os controles de hipermídia na resposta nos contam como fazer isto.

O POST poderia de novo ser copiado do nível 2.

POST /slots/1234 HTTP/1.1
[various other headers]

<appointmentRequest>
  <patient id = "jsmith"/>
</appointmentRequest>

E a resposta contém um número de controles de hipermídia para diferentes coisas a serem feitas em seguida.

HTTP/1.1 201 Created
Location: http://royalhope.nhs.uk/slots/1234/appointment
[various headers]

<appointment>
  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
  <link rel = "/linkrels/appointment/cancel"
        uri = "/slots/1234/appointment"/>
  <link rel = "/linkrels/appointment/addTest"
        uri = "/slots/1234/appointment/tests"/>
  <link rel = "self"
        uri = "/slots/1234/appointment"/>
  <link rel = "/linkrels/appointment/changeTime"
        uri = "/doctors/mjones/slots?date=20100104@status=open"/>
  <link rel = "/linkrels/appointment/updateContactInfo"
        uri = "/patients/jsmith/contactInfo"/>
  <link rel = "/linkrels/help"
        uri = "/help/appointment"/>
</appointment>

Um benefício óbvio dos controles hipermídia é que eles permitem que o servidor altere seus esquemas de URI sem quebrar os clientes. Contanto que os clientes busquem a URI do link “addTest”, então o time do servidor pode alterar todas as URIs para outras que não os valores iniciais.

Similarmente, permitem ao time de servidor informar novas capacidades ao adicionar novos links na resposta. Se os desenvolvedores clientes estão mantendo-se atentos para links desconhecidos, estes links podem ser um gatilho para exploração adicional.

Não existe padrão absoluto de como representar controles hipermídia. O que eu fiz aqui foi usar as recomendações atuais do time REST na Prática (REST in Practice), que é seguir a ATOM (RFC 4287). Eu usei um elemento <link> com um atributo uri para a URI de destino e um atributo rel para descrever o tipo de relacionamento. Um relacionamento bem-conhecido (como um self para uma referência ao próprio elemento) é simples, já qualquer relacionamento específico àquele servidor é uma URI totalmente qualificada. ATOM prega que a definição de linkrels bem-conhecidos é o Registry of Link Relations (Registro de Relações de Links). Enquanto escrevo isto, eles são restritos ao que é feito pelo ATOM, que é geralmente visto como um líder no nível 3 do REST.

O Significado dos níveis

Eu deveria reforçar que o MMR, embora seja uma boa forma de pensar sobre os elementos do REST, não é uma definição de níveis do próprio REST. Roy Fielding tornou claro que o nível 3 do MMR é uma pre-condição do REST. Como muitos termos de software, o REST ganhou muitas definições, mas desde que Roy Fielding cunhou o termo, sua definição deveria ter um peso maior.

O que eu acho útil sobre este MMR é que ele provê um bom passo-a-passo para entender as ideias básicas por trás do pensamento REST. Como tal, eu o vejo como uma ferramenta para nos ajudar a aprender sobre os conceitos e não algo que deveria ser usado em algum tipo de mecanismo de validação. Eu não acho que nós tenhamos exemplos suficientes ainda para assegurar que a técnica REST é o jeito certo de integrar sistemas, eu acho que é uma técnica muito atrativa e a que eu recomendaria na maioria das situações.

Falando sobre isso com Ian Robinson, ele reforçou que o que ele achou atrativo sobre este modelo quando Leonard Richardson o apresentou inicialmente foi seu relacionamento com técnicas de projeto comuns.

  • Nível 1 aborda a questão de lidar com complexidade usando dividir e conquistar, quebrando um endpoint de serviço grande em recursos múltiplos.
  • Nível 2 introduz um conjunto padrão de verbos de forma a tratar situações similares do mesmo jeito, removendo variações desnecessárias.
  • Nível 3 introduz explorabilidade, provendo uma maneira de fazer um protocolo mais auto-documentado.

O resultado é um modelo que nos ajuda a pensar no tipo de serviço HTTP que nós queremos fornecer e moldar as expectativas das pessoas que buscam interagir com ele.

Referências

Artigo “Richardson Maturity Model” – Martin Fowler 

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>