Como uma API REST se encaixa em um domínio baseado em comando / ação?

24

Neste artigo, o autor afirma que

Às vezes, é necessário expor uma operação na API que inerentemente não seja RESTful.

e essa

Se uma API tiver muitas ações, isso indica que ela foi projetada com um ponto de vista RPC em vez de usar os princípios RESTful ou que a API em questão é naturalmente mais adequada para um modelo de tipo RPC.

Isso reflete o que li e ouvi em outros lugares também.

No entanto, acho isso bastante confuso e gostaria de entender melhor o assunto.

Exemplo I: Desligando uma VM por meio de uma Interface REST

Acho que existem duas maneiras fundamentalmente diferentes de modelar o desligamento de uma VM. Cada caminho pode ter algumas variações, mas vamos nos concentrar nas diferenças mais fundamentais por enquanto.

1. Corrija a propriedade de estado do recurso

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(Como alternativa, PUTno sub-recurso /api/virtualmachines/42/state.)

A VM será desligada em segundo plano e, em algum momento posterior, dependendo de o desligamento ter êxito ou não, o estado poderá ser atualizado internamente com "desligamento".

2. PUT ou POST na propriedade de ações do recurso

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

O resultado é exatamente o mesmo que no primeiro exemplo. O estado será atualizado para "desligar" imediatamente e, eventualmente, para "desligar".

Os dois projetos são RESTful?

Qual design é melhor?

Exemplo II: CQRS

E se tivermos um domínio CQRS com muitas dessas "ações" (também conhecidas como comandos) que podem levar a atualizações de vários agregados ou não podem ser mapeadas para operações CRUD em recursos e sub-recursos concretos?

Devemos tentar modelar tantos comandos quanto concreto criar ou atualizar recursos concretos, sempre que possível (seguindo a primeira abordagem do exemplo I) e usar "pontos finais de ação" para o resto?

Ou devemos mapear todos os comandos para os pontos de extremidade de ação (como na segunda abordagem do exemplo I)?

Onde devemos traçar a linha? Quando o design se torna menos RESTful?

Um modelo CQRS é mais adequado para uma API RPC como a API?

De acordo com o texto citado acima, é, como eu o entendo.

Como você pode ver nas minhas muitas perguntas, estou um pouco confuso sobre esse tópico. Você pode me ajudar a entender melhor?

leifbattermann
fonte
"criar uma ação" não parece RESTful, exceto se a ação executada tiver seu próprio identificador de recurso posteriormente. Caso contrário, alterar a propriedade "state" via PATCH ou PUT faz mais sentido. Para a parte do CQRS, ainda não tenho uma boa resposta.
Fabian Schmengler
3
@ Laiv Nada de errado com isso. É uma questão acadêmica, eu gostaria de ter uma compreensão mais profunda do assunto.
Leifbattermann

Respostas:

19

No primeiro caso (desligamento de VMs), eu consideraria nenhuma das alternativas de OP RESTful. É verdade que, se você usar o modelo de maturidade de Richardson como parâmetro, ambos são APIs de nível 2 porque usam recursos e verbos.

Porém, nenhum deles usa controles hipermídia e, na minha opinião, esse é o único tipo de REST que diferencia o design da API RESTful do RPC. Em outras palavras, permaneça nos níveis 1 e 2 e, na maioria dos casos, você terá uma API no estilo RPC.

Para modelar duas maneiras diferentes de desligar uma VM, eu exporia a própria VM como um recurso que (entre outras coisas) contém links:

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

Se um cliente desejar desligar o Ploeh VM, deve seguir o link com o shut-downtipo de relacionamento. (Normalmente, conforme descrito no RESTful Web Services Cookbook , você usaria um IRI ou um esquema de identificação mais elaborado para tipos de relacionamento, mas optei por manter o exemplo o mais simples possível.)

Nesse caso, há poucas outras informações a serem fornecidas com a ação, portanto, o cliente deve simplesmente fazer um POST vazio em relação à URL no href:

POST /vms/1234/fdaIX HTTP/1.1

(Como essa solicitação não tem corpo, seria tentador modelá-la como uma solicitação GET, mas as solicitações GET não devem ter efeitos colaterais observáveis; portanto, o POST é mais correto.)

Da mesma forma, se um cliente quiser desligar a VM, seguirá o power-offlink.

Em outras palavras, os tipos de relacionamento dos links fornecem recursos que indicam a intenção. Cada tipo de relacionamento tem um significado semântico específico. É por isso que às vezes falamos sobre a web semântica .

Para manter o exemplo o mais claro possível, ocultei intencionalmente os URLs em cada link. Quando o servidor de hospedagem recebe a solicitação de entrada, ele saberá que isso fdaIXsignifica desligar e CHTY91significa de desligamento .

Normalmente, eu codificaria a ação no próprio URL, para que os URLs fossem /vms/1234/shut-down e /vms/1234/power-off, mas ao ensinar, isso atrapalha a distinção entre tipos de relacionamento (semântica) e URLs (detalhes de implementação).

Dependendo de quais clientes você possui, considere tornar os URLs RESTful não hackáveis .

CQRS

Quando se trata de CQRS, uma das poucas coisas que Greg Young e Udi Dahan concordam é que o CQRS não é uma arquitetura de nível superior . Portanto, eu seria cauteloso ao criar uma API RESTful muito semelhante ao CQRS, porque isso significaria que os clientes se tornariam parte de sua arquitetura.

Freqüentemente, a força motriz por trás de uma API RESTful real (nível 3) é que você deseja evoluir sua API sem interromper os clientes e sem ter o controle dos clientes. Se essa é a sua motivação, o CQRS não seria minha primeira escolha.

Mark Seemann
fonte
Você quer dizer que os primeiros exemplos não são RESTful porque não usam controles hipermídia? Mas eu nem postei nenhuma resposta, apenas os URLs e corpos da solicitação.
Leifbattermann
4
@leifbattermann Eles não são RESTful porque usam o corpo da mensagem para comunicar a intenção; isso é claramente RPC. Se você usou links para chegar a esses recursos, por que precisaria comunicar a intenção através do corpo?
precisa
Isso faz sentido. Por que você sugere um POST? A ação não é idempotente? De qualquer forma, como você diz ao seu cliente qual método HTTP usar?
Leifbattermann
2
@ guillaume31 DELETEme parece estranho, porque depois de desligar o vm ainda existirá, apenas no estado "power off" (ou sth. assim).
Leifbattermann
1
O recurso não precisa refletir uma VM atemporalmente, pode representar uma instância de execução dela.
precisa
6

Desligando uma VM por meio de uma interface REST

Este é realmente um exemplo um pouco famoso, apresentado por Tim Bray em 2009 .

Roy Fielding, discutindo o problema, compartilhou esta observação :

Pessoalmente, prefiro sistemas que tratam o estado monitorado (como status de energia) como não editável.

Em resumo, você tem um recurso de informações que retorna uma representação atual do estado monitorado; essa representação pode incluir um link hipermídia para um formulário que solicite uma alteração nesse estado, e o formulário possui outro link para um recurso para manipular (cada) solicitação de alteração.

Seth Ladd teve as principais informações sobre o problema

Passamos da Running de um estado simples de uma pessoa para um Substantivo verdadeiro, que pode ser criado, atualizado e comentado.

Levando isso de volta para a reinicialização de máquinas. Eu diria que você faria o POST para / vdc / 434 / cluster / 4894 / server / 4343 / reboots Depois de postar, você terá um URI que representa isso reboot e poderá obtê-lo para atualizações de status. Através da mágica do hiperlink, a representação da reinicialização é vinculada ao servidor que é reinicializado.

Eu acho que cunhar o espaço do URI é barato, e o URI é ainda mais barato. Crie uma coleção de atividades, modeladas como Substantivos, e POST, PUT e DELETE away!

A programação RESTful é a burocracia Vogon em escala web. Como você faz algo RESTful? Invente uma nova documentação e digitalize a documentação.

Em uma linguagem um pouco mais sofisticada, o que você está fazendo é definir o protocolo de aplicativo de domínio para "desligar uma VM" e identificar os recursos necessários para expor / implementar esse protocolo

Olhando para seus próprios exemplos

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

Isso está ok; você realmente não está tratando a solicitação em si como seu próprio recurso de informação separado, mas ainda pode gerenciar.

Você perdeu um pouco de sua representação da mudança.

Com PATCH, no entanto, a entidade incluída contém um conjunto de instruções que descrevem como um recurso que atualmente reside no servidor de origem deve ser modificado para produzir uma nova versão.

Por exemplo, o tipo de mídia JSON Patch formata instruções como se você estivesse modificando diretamente um documento JSON

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

Em sua alternativa, a ideia está próxima, mas não obviamente correta. PUTé uma substituição completa do estado do recurso no URL de destino ; portanto, você provavelmente não escolheria uma ortografia que se pareça com uma coleção como destino de uma representação de uma única entidade.

POST /api/virtualmachines/42/actions

É consistente com a ficção de que estamos anexando uma ação a uma fila

PUT /api/virtualmachines/42/latestAction

É consistente com a ficção de que estamos fazendo uma atualização no item de cauda na fila; é um pouco estranho fazer dessa maneira. O princípio da menor surpresa recomenda atribuir a cada PUT seu próprio identificador único, em vez de colocá-las em um único local e modificar vários recursos ao mesmo tempo.

Observe que, na medida em que estamos discutindo, a ortografia do URI-REST não se importa; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8bé um URI perfeitamente cromulento no que diz respeito ao REST. A legibilidade, como nos nomes de variáveis, é uma preocupação separada. Usar grafias consistentes com a RFC 3986 tornará as pessoas muito mais felizes.

CQRS

E se tivermos um domínio CQRS com muitas dessas "ações" (também conhecidas como comandos) que podem levar a atualizações de vários agregados ou não podem ser mapeadas para operações CRUD em recursos e sub-recursos concretos?

Greg Young no CQRS

O CQRS é um padrão muito simples que permite muitas oportunidades de arquitetura que, de outra forma, não existiriam. O CQRS não é uma consistência eventual, não está ocorrendo, não está enviando mensagens, não está tendo modelos separados para leitura e gravação, nem está usando fonte de eventos.

Quando a maioria das pessoas fala sobre CQRS, está realmente falando sobre a aplicação do padrão CQRS ao objeto que representa o limite de serviço do aplicativo.

Dado que você está falando sobre CQRS no contexto de HTTP / REST, parece razoável supor que você esteja trabalhando neste último contexto, então vamos com isso.

Surpreendentemente, este é ainda mais fácil que o seu exemplo anterior. A razão para isso é simples: comandos são mensagens .

Jim Webber descreve o HTTP como o protocolo de aplicação de um escritório da década de 1950; o trabalho é realizado recebendo mensagens e colocando-as nas caixas de entrada. A mesma idéia é válida - obtemos uma cópia em branco de um formulário, preenchemos com as especificidades que conhecemos, entregamos. Ta da

Devemos tentar modelar tantos comandos quanto concreto criar ou atualizar recursos concretos, sempre que possível (seguindo a primeira abordagem do exemplo I) e usar "pontos finais de ação" para o resto?

Sim, na medida em que os "recursos concretos" são mensagens, e não entidades no modelo de domínio.

Ideia-chave: sua API REST ainda é uma interface ; você poderá alterar o modelo subjacente sem que os clientes precisem alterar as mensagens. Ao liberar um novo modelo, você libera uma nova versão dos pontos de extremidade da Web que sabem como usar o protocolo de domínio e aplicá-lo ao novo modelo.

Um modelo CQRS é mais adequado para uma API RPC como a API?

Na verdade, não - em particular, os caches da web são um ótimo exemplo de um "modelo de leitura eventualmente consistente". Tornar cada uma das suas visualizações endereçáveis ​​de forma independente, cada uma com suas próprias regras de cache, oferece um monte de dimensionamento gratuitamente. Há relativamente pouco apelo a uma abordagem exclusivamente de RPC para leituras.

Para gravações, é uma pergunta mais complicada: enviar todos os comandos para um único manipulador em um único endpoint ou em uma única família de endpoints é certamente mais fácil . O REST é realmente mais sobre como você encontra a comunicação onde o ponto final está para o cliente.

Tratar uma mensagem como seu próprio recurso exclusivo tem a vantagem de poder usar PUT, alertando os componentes intermediários para o fato de que o tratamento da mensagem é idempotente, para que eles possam participar de certos casos de tratamento de erros. . (Nota: que, do ponto de vista dos clientes, se os recursos tiverem URI diferente, eles serão recursos diferentes; o fato de que todos eles podem ter o mesmo código de manipulador de solicitação no servidor de origem é um detalhe de implementação oculto pelo uniforme interface).

Fielding (2008)

Devo também observar que o exposto acima ainda não é totalmente RESTful, pelo menos como eu uso o termo. Tudo o que fiz foi descrito nas interfaces de serviço, que não são mais do que qualquer RPC. Para torná-lo RESTful, eu precisaria adicionar hipertexto para introduzir e definir o serviço, descrever como executar o mapeamento usando formulários e / ou modelos de link e fornecer código para combinar as visualizações de maneiras úteis.

VoiceOfUnreason
fonte
2

Você pode aproveitar 5 níveis de tipo de mídia para especificar o comando no campo de cabeçalho do tipo de conteúdo da solicitação.

No exemplo da VM, seria algo nesse sentido

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

Então

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

Ou

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

Consulte https://www.infoq.com/articles/rest-api-on-cqrs

guillaume31
fonte
Esteja ciente de que o 5LMT era uma solução proposta e não é suportada por padrões . Eu me deparei com o artigo do CQRS antes e aprendi muito com ele.
Peter L
1

O exemplo no artigo vinculado baseia-se na idéia de que a inicialização e o desligamento da máquina devem ser direcionados por comandos, e não por alterações no estado dos recursos modelados. O último é praticamente o que o REST vive e respira. Uma melhor modelagem da VM exige uma olhada em como funciona a sua contraparte do mundo real e como você, como humano, interagia com ela. Isso é longo, mas acho que dá uma boa visão do tipo de pensamento necessário para fazer uma boa modelagem.

Existem duas maneiras de controlar o estado de energia do computador na minha mesa:

  • Interruptor de energia: corta imediatamente o fluxo de eletricidade para a fonte de alimentação, interrompendo repentinamente o computador inteiro.
  • Botão liga / desliga: Diz ao hardware para notificar o software que algo do lado de fora quer que tudo seja encerrado. O software faz um desligamento ordenado, notifica o hardware concluído e sinaliza à fonte de alimentação que ele pode entrar no estado de espera. Se o interruptor estiver ligado, a máquina estiver funcionando e o software estiver em um estado em que não possa responder ao sinal de desligamento, o sistema não será desligado a menos que eu desligue o interruptor. (Uma VM se comportará exatamente da mesma maneira; se o sinal de desligamento for ignorado pelo software, a máquina continuará funcionando, preciso forçá-la a desligar.) Se quiser poder reiniciar a máquina, preciso ligue o interruptor novamente e pressione o botão liga / desliga. (Muitos computadores têm a opção de pressionar longamente o botão liga / desliga para ir diretamente para o estado de espera, mas esse modelo realmente não precisa disso.) Esse botão pode ser tratado como uma chave de alternância, pois pressioná-lo resulta em um comportamento diferente, dependendo do estado em que é pressionado. Se o interruptor estiver desligado, pressionar este botão não fará absolutamente nada.

Para uma VM, ambos podem ser modelados como valores booleanos de leitura / gravação:

  • power- Quando alterado para true, nada acontece além de uma anotação de que a chave foi colocada nesse estado. Quando alterada para false, a VM é comandada para um estado de desligamento imediato. Por uma questão de integridade, se o valor for inalterado após uma gravação, nada acontece.

  • onoff- Quando alterado para true, nada acontece se powerestiver false, caso contrário, a VM deve iniciar. Quando alterado para false, nada acontece se powerfor false, caso contrário, a VM é comandado para notificar o software para fazer um desligamento ordenado, o que ele vai fazer e, em seguida, notificar o VM que ele pode ir para o poder-fora do estado. Novamente, para ser completo, uma gravação sem alteração não faz nada.

Com tudo isso, percebe-se que há uma situação em que o estado da máquina não reflete o estado dos comutadores, e é durante o desligamento. powerainda será truee onoffserá false, mas o processador ainda está sendo desligado e, para isso, precisamos adicionar outro recurso para que possamos saber o que a máquina está realmente fazendo:

  • running- Um valor somente leitura que é truequando a VM está em execução e falsequando não está, determinado solicitando o estado do hypervisor.

O resultado disso é que, se você deseja que uma VM seja inicializada, verifique se os recursos powere onoffforam definidos true. (Você pode permitir que a poweretapa seja ignorada, redefinindo-a automaticamente, de modo que, se definida como false, trueocorrerá após a parada verificável da VM. Se isso é uma coisa RESTfully pura a fazer é forragem para outra discussão.) Se você quer fazer um desligamento ordenado, você define onoffa falsee voltar mais tarde para ver se a máquina realmente parou, definindo powera falsese não o fizesse.

Como o mundo real, você ainda tem o problema de ser direcionado para iniciar a VM depois que ela foi onoffalterada para, falsemas ainda é runningporque está no meio do desligamento. Como você lida com isso é uma decisão política.

Blrfl
fonte
0

Os dois projetos são RESTful?

Então, se você quiser pensar tranqüilamente, esqueça os comandos. O cliente não diz ao servidor para desligar a VM. O cliente "encerra" (metaforicamente falando) sua cópia da representação de recurso, atualizando seu estado e, em seguida, coloca essa representação novamente no servidor. O servidor aceita essa nova representação de estado e, como efeito colateral disso, na verdade, desliga a VM. O aspecto do efeito colateral é deixado para o servidor.

É menos

Ei servidor, cliente aqui, você se importaria de desligar a VM

e mais de

Ei, servidor, cliente aqui, atualizei o estado do recurso VM 42 para o estado de desligamento, atualize sua cópia desse recurso e faça o que achar adequado

Como efeito colateral do servidor que aceita esse novo estado, ele pode verificar quais ações ele realmente deve executar (como desligar fisicamente a VM 42), mas isso é transparente para o cliente. O cliente não está preocupado com as ações que o servidor precisa executar para se tornar consistente com esse novo estado

Então esqueça os comandos. Os únicos comandos são os verbos no HTTP para transferência de estado. O cliente não sabe, nem se importa, como o servidor colocará a VM física no estado de desligamento. O cliente não está emitindo comandos para o servidor para conseguir isso, está apenas dizendo: Este é o novo estado, descubra .

O poder disso é que ele desacopla o cliente do servidor em termos de controle de fluxo. Se, posteriormente, o servidor alterar a forma como funciona com VMs, o cliente não se importará. Não há pontos de extremidade de comando para atualizar. No RPC, se você alterar o ponto final da API de shutdownpara, shut-downvocê quebrou todos os seus clientes, pois agora eles não sabem o comando para chamar o servidor.

O REST é semelhante à programação declarativa, onde, em vez de listar um conjunto de instruções para alterar algo, você apenas indica como deseja que seja e permite que o ambiente de programação descubra.

Cormac Mulhall
fonte
Obrigado pela sua resposta. A segunda parte sobre a dissociação de cliente e servidor está muito alinhada com o meu próprio entendimento. Você tem um recurso / link que faz backup da primeira parte da sua resposta? Exatamente qual restrição REST está quebrada se eu usar recursos, métodos HTTP, hipermídia, mensagens auto-descritivas etc.?
Leifbattermann #
Não há problema com o uso de recursos, métodos HTTP etc. O HTTP é um protocolo RESTful, afinal. Quando houver um problema, use o que você chama de "pontos finais de ação". No REST, existem recursos que representam conceitos ou coisas (como a máquina virtual 42 ou minha conta bancária) e os verbos HTTP usam para transferir o estado desses recursos entre clientes e servidores. É isso. O que você não deve fazer é tentar criar novos comandos combinando pontos de extremidade que não são de recursos com verbos HTTP. Portanto, 'máquinas virtuais / 42 / actions' não é um recurso e não deveria existir em um sistema RESTful.
Cormac Mulhall
Ou, dito de outra forma, o cliente não deve tentar executar comandos no servidor (além dos verbos HTTP limitados relacionados apenas à transferência de recursos por estado). O cliente deve estar atualizando sua cópia do recurso e simplesmente pedindo ao servidor que aceite esse novo estado. A aceitação desse novo estado pode ter efeitos colaterais (a VM 42 é fisicamente desligada), mas isso está além da preocupação do cliente. Se o cliente não estiver tentando executar comandos no servidor, não haverá acoplamento por esses comandos entre o cliente e o servidor.
Cormac Mulhall
Você pode executar o comando no recurso ... Como você diria "depositar" e "retirar" em uma conta bancária? Seria usar CRUD para algo que não é CRUD.
Konrad
É melhor usar em POST /api/virtualmachines/42/shutdownvez de ter qualquer "efeito colateral". A API deve ser compreensível para o usuário, como vou saber que, por exemplo DELETE /api/virtualmachines/42, desligará a VM? Um efeito colateral para mim é um bug. Devemos projetar nossas APIs para serem compreensíveis e auto-descritivas
Konrad