Diseño de API REST para páginas web con asistentes

11

Tengo una página web con formato de asistente. El botón de envío a la API estará en el cuarto paso del asistente. Sin embargo, quiero que los datos ingresados ​​se almacenen en la base de datos antes de pasar al siguiente paso en el asistente. También quiero que la API REST funcione para las páginas que tienen una sola pestaña.

Así que diseñé la API para tomar una acción de parámetro de consulta = borrador o enviar. Si la acción es borrador, solo ciertos campos son obligatorios. Si se envía una acción, todos los campos son obligatorios. Las validaciones en la capa de servicio de REST API se realizarán en función del parámetro de consulta. Parece que tengo que especificar explícitamente las cláusulas if / else en la documentación. ¿Es esta una forma aceptable de diseño RESTful? ¿Cuál sería el mejor diseño con estos requisitos?

TechCrunch
fuente
3
¿Por qué los datos provisionales deben almacenarse en la base de datos?
Dan1701
2
@ Dan1701: para que pueda reanudar el asistente desde otra máquina. Al completar formularios largos y complejos, puede llevar algunos días completar todos los datos requeridos, ya que el usuario puede no tener todos los datos necesarios a la mano, o el usuario puede necesitar reunir archivos adicionales para cargar desde diferentes lugares. Si puede reanudar desde un dispositivo diferente, puede cargar el asistente para cargar una foto desde el teléfono móvil y continuar escribiendo una larga descripción / argumento con teclados reales en el escritorio, etc.
Lie Ryan
En ese caso, creo que la respuesta de @ guillaume31 tiene sentido.
Dan1701

Respuestas:

7

Dado que desea conservar las cosas en el servidor entre los pasos del asistente, parece perfectamente aceptable considerar cada paso como un recurso separado. Algo en este sentido:

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

Al incluir enlaces hipermedia en la respuesta, puede informar al cliente sobre lo que puede hacer después de este paso: avanzar o retroceder para los pasos intermedios, y nada para el paso final. Puede ver un ejemplo de esto en la Figura 5 aquí .

guillaume31
fuente
Estoy usando Angular para la interfaz de usuario. Así que no estoy seguro de lo útil que es la máquina de estado. Pero creo que el recurso basado en pasos parece ser más significativo que administrar otra tabla. Además, debería poder enviar todo en un solo paso. Le dará una oportunidad a este diseño hoy. Gracias por la ayuda.
TechCrunch
De nada. Por cierto, el enfoque de "dos tablas" no es mutuamente exclusivo con esto. Tener un recurso HTTP por paso no dicta su modelo de objeto en el servidor de aplicaciones, y mucho menos el esquema de la base de datos. Es solo una representación web.
guillaume31
1
@TechCrunch Básicamente, Guillaume significa que el objeto / tabla que representa el formulario se puede dividir en partes, donde parte del modelo se guarda en cada paso. De hecho, puede hacer que cada "paso" sea una forma de parte del modelo completo . Y si adopta este enfoque, en realidad hace que la arquitectura sea increíblemente simple. Cada POST al servidor (creará o) actualizará el mismo modelo, y cada GET cargará el mismo modelo, y cada paso será un formulario para completar conjuntos de campos que son semánticamente significativos (pertenecen juntos). Y simplemente tenga un booleano en el modelo para in_progresso draft.
Chris Cirefice
3

Había necesitado hacer algo similar hace algún tiempo, y lo siguiente describe con qué terminamos.

Tenemos dos tablas, Item y UnfinishedItem. Cuando el usuario completa los datos con el asistente, los datos se almacenan en la tabla UnfinishedItem. En cada paso del asistente, el servidor valida los datos ingresados ​​durante ese paso. Cuando el usuario termina con el asistente, el asistente presenta un formulario oculto / de solo lectura en una página de confirmación que muestra todos los datos que se deben enviar. El usuario puede revisar esta página y volver al paso correspondiente para corregir errores. Una vez que el usuario está satisfecho con sus entradas, el usuario hace clic en enviar y el asistente luego envía todos los datos en los campos de formulario ocultos / de solo lectura al servidor API. Cuando el servidor API procesa esta solicitud, vuelve a ejecutar todas las validaciones que realizó durante cada paso del asistente y realiza validaciones adicionales que no se ajustan a los pasos individuales (por ejemplo, validaciones globales, validaciones costosas).

Las ventajas del enfoque de dos tablas:

  • en la base de datos, puede tener restricciones más estrictas en la tabla Elemento que en la tabla UnfinishedItem; no tiene que tener columnas opcionales que realmente serán necesarias cuando el asistente haya finalizado.

  • Las consultas agregadas en los elementos terminados para generar informes son más fáciles, ya que no tiene que acordarse de excluir los UnfinishedItems. En nuestro caso, nunca tuvimos que hacer consultas agregadas entre Item y UnfinishedItems, por lo que esto no es un problema.

La desventaja:

  • Es propenso a la duplicación de la lógica de validación. El marco web que utilizamos, Django, lo hace un poco más soportable ya que utilizamos la herencia de modelos con un poco de meta magia para cambiar las restricciones que necesitamos para ser diferentes en Item y UnfinishedItem. Django genera la mayor parte de la base de datos y la validación de formularios a partir del modelo, y solo necesitamos hackear algunas validaciones adicionales además.

Otras posibilidades que he considerado y por qué no fuimos con ellas:

  • guardar los datos en cookies o almacenamiento local: el usuario no puede continuar su envío desde un dispositivo diferente o si elimina el historial de su navegador
  • almacenar el UnfinishedItem como datos no estructurados (por ejemplo, JSON) en la base de datos o en el almacén de datos secundario: tendré que definir la lógica de análisis y no puedo usar la validación automática de modelo / formulario de Django.
  • haga la validación por paso en el lado del cliente: tendré que duplicar la lógica de validación entre Python / Django y JavaScript.
Lie Ryan
fuente
1
+1 para señalar validaciones en modelos de tipo 'borrador' y modelos 'terminados'; No pensé en eso, y es un punto importante que debe tenerse en cuenta. De lo contrario, probablemente tenga un montón de ifdeclaraciones que verifiquen el estado del borrador a lo largo de sus validaciones, lo que simplemente no sería bueno. Aunque algunos marcos muy sofisticados como Ruby on Rails podrían simplificar significativamente ese problema si se implementan correctamente.
Chris Cirefice
1

He implementado esto de manera similar a una combinación de las soluciones @ guillauma31 y @Lie Ryan.

Aquí están los conceptos clave:

  1. Hay un asistente de 3 pasos que puede persistir parcialmente hasta que se complete.
  2. Cada paso tiene su propio recurso (por ejemplo .: /users/:id_user/profile/step_1, .../step_2, etc.)
  3. En cada paso, los datos y el estado de finalización se pueden recuperar a través de solicitudes GET y persistir a través de solicitudes PATCH.
  4. Cada recurso tiene sus propias reglas de validación para los datos ingresados.
  5. Cada paso devuelve una clave que debe usarse en la entrada del siguiente paso para garantizar la secuencia. Una vez utilizado o se genera uno nuevo, este token caduca.
  6. En el paso final, tenemos todos los datos necesarios en la base de datos y se muestra una pantalla de confirmación. Esta confirmación llama a otro recurso para marcar los datos como completos (p. Ej .:) .../profile/confirm. Este recurso no necesita recibir todos los datos nuevamente. Solo marca los datos como correctos y completos.
  7. Hay una rutina programada que borra estas entradas incompletas que tienen más de unos pocos días.

Los chicos de front-end deben cuidar las fichas para que funcione el flujo de ida y vuelta del asistente.

La API no tiene estado y es atómica.

Para hacer que un "asistente de un paso" funcione con esta configuración, tendría que cambiar algunas cosas, como eliminar el flujo de tokens o crear un recurso para devolver tokens en función del tipo de asistente o incluso crear un nuevo recurso solo para llenar este single específico asistente de pasos (como PUT /users/:id_user/profile/).

Ricardo Souza
fuente