Design da API REST para páginas da Web com assistentes

11

Eu tenho uma página da web com formato de assistente. O botão de envio para a API estará na quarta etapa do assistente. No entanto, quero que os dados inseridos sejam armazenados no banco de dados antes de passar para a próxima etapa do assistente. Também quero que a API REST esteja funcionando para as páginas com uma única guia.

Por isso, projetei a API para executar um parâmetro de consulta action = draft ou submit. Se a ação for rascunhada, apenas certos campos serão obrigatórios. Se a ação for enviada, todos os campos são obrigatórios. As validações na camada de serviço da API REST serão feitas com base no parâmetro de consulta. Parece que eu tenho que especificar explicitamente as cláusulas if / else na documentação. Essa é uma forma aceitável de design RESTful? Qual seria o melhor design com esses requisitos?

TechCrunch
fonte
3
Por que os dados provisórios precisam ser armazenados no banco de dados?
Dan1701
2
@ Dan1701: para que você possa retomar o assistente de outra máquina. Ao preencher formulários longos e complexos, pode levar alguns dias para concluir todos os dados necessários, pois o usuário pode não ter todos os dados necessários em mãos ou pode precisar reunir arquivos adicionais para fazer upload de locais diferentes. Se você pode retomar a partir de um dispositivo diferente, você pode carregar o assistente para fazer upload de uma foto do telefone móvel, e continuar a digitar uma longa descrição / discussão com um reais teclados na área de trabalho, etc.
Lie Ryan
Nesse caso, acho que a resposta de @ guillaume31 faz sentido.
Dan1701

Respostas:

7

Como você deseja manter as coisas no servidor entre as etapas do assistente, parece perfeitamente aceitável considerar cada etapa como um recurso separado. Algo nesse sentido:

POST /wizard/123/step1
POST /wizard/123/step2
POST /wizard/123/step3

Ao incluir links hipermídia na resposta, você pode informar o cliente sobre o que ele pode fazer após esta etapa - avançar ou voltar para as etapas intermediárias e nada para a etapa final. Você pode ver um exemplo disso na Figura 5 aqui .

guillaume31
fonte
Estou usando o Angular para a interface do usuário. Portanto, não tenho certeza do quanto a máquina de estado é útil. Mas acho que o recurso baseado em etapas parece ser mais significativo do que gerenciar outra tabela. Além disso, devo poder enviar tudo em uma única etapa. Dar-lhe-á um tiro neste design hoje. Obrigado pela ajuda.
TechCrunch
De nada. A propósito, a abordagem "duas mesas" não é mutuamente exclusiva. Ter um recurso HTTP por etapa não determina seu modelo de objeto no servidor de aplicativos, muito menos o esquema do banco de dados. É apenas uma representação da Web.
precisa
1
@TechCrunch Basicamente, Guillaume significa que o objeto / tabela que representa o formulário pode ser dividido em partes, onde parte do modelo é salva em cada etapa. De fato, você pode apenas fazer com que cada "etapa" seja um formulário para parte de todo o modelo . E se você adotar essa abordagem, ela realmente torna a arquitetura incrivelmente simples. Cada POST para o servidor (criará) atualizará o mesmo modelo, e cada GET carregará o mesmo modelo, e cada etapa será um formulário para preencher conjuntos de campos que são semanticamente significativos (pertencem um ao outro). E simplesmente tenha um booleano no modelo para in_progressor draft.
22816 Chris Cirefice
3

Eu precisava fazer algo semelhante há algum tempo, e o seguinte descreve o que terminamos.

Temos duas tabelas, Item e UnfinishedItem. Quando o usuário preenche os dados com o assistente, os dados são armazenados na tabela UnfinishedItem. Em cada etapa do assistente, o servidor valida os dados inseridos durante essa etapa. Quando o usuário termina o assistente, ele cria um formulário oculto / somente leitura em uma página de confirmação que mostra todos os dados a serem enviados. O usuário pode revisar esta página e voltar à etapa relevante para corrigir erros. Quando o usuário estiver satisfeito com suas entradas, o usuário clica em enviar e o assistente envia todos os dados nos campos de formulário oculto / somente leitura para o servidor da API. Quando o servidor da API processa essa solicitação, ele executa novamente todas as validações realizadas durante cada etapa do assistente e executa validações adicionais que não se encaixam nas etapas individuais (por exemplo, validações globais, validações caras).

As vantagens da abordagem de duas tabelas:

  • no banco de dados, você pode ter restrições mais rígidas na tabela Item do que na tabela UnfinishedItem; você não precisa ter colunas opcionais que serão realmente necessárias quando o assistente for concluído.

  • As consultas agregadas nos itens concluídos para geração de relatórios são mais fáceis, pois você não precisa se lembrar de excluir os UnfinishedItems. No nosso caso, nunca precisamos fazer consultas agregadas entre Item e UnfinishedItems, portanto, isso não é um problema.

A desvantagem:

  • É propenso a duplicação da lógica de validação. A estrutura da web que usamos, Django, torna isso um pouco mais suportável, pois usamos a herança de modelo com um pouco de meta mágica para alterar as restrições que precisamos ser diferentes em Item e UnfinishedItem. O Django gera a maior parte do banco de dados e a validação de formulário a partir do modelo, e precisamos apenas de algumas validações adicionais sobre ele.

Outras possibilidades que considerei e por que não as acompanhamos:

  • salvar os dados em cookies ou armazenamento local: o usuário não pode continuar enviando a partir de um dispositivo diferente ou se excluir o histórico do navegador
  • armazene o UnfinishedItem como dados não estruturados (por exemplo, JSON) no banco de dados ou no armazenamento de dados secundário: vou ter que definir a lógica de análise e não posso usar a validação automática de modelo / formulário do Django.
  • faça a validação por etapa no lado do cliente: terei que duplicar a lógica de validação entre Python / Django e JavaScript.
Lie Ryan
fonte
1
+1 para apontar validações em modelos do tipo 'rascunho' e modelos 'finalizados'; Eu não pensei nisso, e é um ponto importante que deve ser levado em consideração. Caso contrário, você provavelmente terá um monte de ifinstruções verificando o status de rascunho durante as validações, o que não seria bom. Embora algumas estruturas muito sofisticadas, como o Ruby on Rails, possam simplificar significativamente esse problema se implementadas corretamente.
22616 Chris Cirefice
1

Eu implementei isso de maneira semelhante a uma mistura de soluções @ guillauma31 e @Lie Ryan.

Aqui estão os principais conceitos:

  1. Há um assistente de 3 etapas que pode ser parcialmente persistido até a conclusão.
  2. Cada etapa tem o seu próprio recurso (por exemplo .: /users/:id_user/profile/step_1, .../step_2etc.)
  3. Em cada etapa, os dados e o status de conclusão podem ser recuperados por meio de solicitações GET e persistidos por meio de solicitações PATCH.
  4. Cada recurso possui suas próprias regras de validação para os dados inseridos.
  5. Cada etapa retorna uma chave que deve ser usada na entrada da próxima etapa para garantir a sequência. Uma vez usado ou um novo é gerado, esse token expira.
  6. Na etapa final, temos todos os dados necessários no banco de dados e uma tela de confirmação é exibida. Esta confirmação chama outro recurso para marcar os dados como completos (por exemplo:) .../profile/confirm. Este recurso não precisa receber todos os dados novamente. Apenas marca os dados como corretos e completos.
  7. Há uma rotina agendada que limpa essas entradas incompletas que têm mais de alguns dias.

O pessoal do front-end precisa cuidar dos tokens para que o fluxo de entrada e saída do assistente funcione.

A API é sem estado e atômica.

Para fazer com que um "assistente de uma etapa" funcione com essa configuração, é necessário alterar algumas coisas, como remover o fluxo de token ou criar um recurso para retornar tokens com base no tipo de assistente ou até mesmo criar um novo recurso apenas para preencher esse único assistente de etapas (como PUT /users/:id_user/profile/).

Ricardo Souza
fonte