En DoorDash, la entrega depende en gran medida del proceso. Los "dashers" deben conocer a fondo todo el proceso de entrega para completar los pedidos con éxito y ganar dinero. La primera iteración del contenido de soporte de DoorDash hizo más por explicar cómo hacer dash, manejar problemas y escollos de entrega comunes y maximizar la experiencia de hacer dash que las iteraciones posteriores.
Nuestro reciente esfuerzo por renovar el centro de soporte de Dasher ha creado un sistema que permite a los Dasher encontrar respuestas de soporte de forma independiente, lo que reduce los costes de soporte. Sin embargo, los recursos existentes disponibles para responder a problemas comunes de Dasher eran:
- Difícil de encontrar porque los recursos están dispersos en numerosos canales y fuentes; los dashers ya deben saber exactamente dónde buscar si esperan encontrar respuestas, y
- A veces, el contenido no ayudaba a resolver el problema de un Dasher, lo que provocaba largas llamadas a un agente para llegar a una solución.
Los artículos de preguntas frecuentes de la renovación debían ser concisos y permitir cambios rápidos, como añadir capturas de pantalla de la aplicación o cambios en las próximas directrices. Crear un sistema que permitiera esta flexibilidad en cada plataforma móvil exigiría duplicar el desarrollo y actualizar cada aplicación nativa cuando cambiara la estructura de contenidos. Las aplicaciones nativas pueden tardar semanas en desplegarse, mientras que los despliegues web pueden realizarse inmediatamente. Así que decidimos crear una aplicación web común que pudiera alojarse dentro de la aplicación nativa para ofrecer una experiencia dentro de la aplicación y, al mismo tiempo, la flexibilidad necesaria para realizar cambios rápidos.
Arquitectura
Figura 1: La primera iteración de nuestra solución (MVP) seguía esta arquitectura: Móvil (iOS/Android) ⇒ WebView ⇒ Backend para Frontend (BFF) ⇒ Servicio Backend.
Como se muestra en la Figura 1, ambos clientes móviles utilizan WebView, lo que garantiza la misma experiencia en todos los clientes. Algunos menús, como el chat integrado en la aplicación, utilizan callbacks específicos del cliente móvil para tender un puente entre la aplicación nativa y la vista web.
El BFF se utiliza para transformar el formato de solicitud de HTTP a gRPC para permitir que el servicio backend lo analice. La respuesta se transforma de forma similar de gRPC a HTTP. No se maneja ninguna lógica de negocio en la capa BFF; existe únicamente para formatear los datos del servicio backend en un formato adecuado para la aplicación web.
El servicio backend almacena y sirve el contenido. El contenido de los artículos se almacena como un JSON que contiene no sólo el contenido sino también el formato de la interfaz de usuario. En MVP, todos los artículos, categorías, menús y fragmentos se almacenan como archivos JSON. Cada cambio en el artículo requiere formatearlo manualmente, actualizar el archivo JSON y desplegar el servicio backend.
Alcance del MVP
Para minimizar el alcance del MVP, mantuvimos el contenido localizado al mercado estadounidense. Un contenido localizado con el idioma y el país/región específicos habría complicado el lanzamiento inicial; aunque mantuvimos estas características en mente, no las implementamos en el MVP.
Al principio, mantuvimos el contenido almacenado en JSON fixtures en lugar de invertir en una base de datos completa. Los archivos JSON nos permitían iterar rápida y repetidamente sobre el formato de los datos. Esto tenía un inconveniente: No podíamos utilizar un sistema de búsqueda más robusto. En su lugar, simplificamos el uso de una búsqueda básica únicamente en los títulos de los artículos utilizando la distancia Levenshtein para tener en cuenta los errores ortográficos. Todas las etiquetas, categorías, menús y contenido de los artículos mostrados en las FAQ tuvieron que formatearse manualmente en JSON y cargarse.
Protocolo de comunicación
Se envían muchos mensajes para permitir transiciones fluidas entre las aplicaciones nativas y la función FAQ que se ejecuta dentro de un WebView. Por ejemplo, la WebView puede necesitar el contexto Dasher o una interacción con la interfaz de usuario puede necesitar navegar de vuelta a una vista nativa.
Tanto Android como iOS tienen métodos para enviar mensajes a y desde sus WebViews. Para enviar mensajes desde la WebView a la aplicación nativa, la parte nativa debe registrar manejadores o usar delegación. Por ejemplo, la parte nativa puede registrar un manejador llamado "WebViewModuleCallbacks". Desde el lado WebView, esto se resolverá a una propiedad en la ventana de mensajes.
// For Android
WebViewModuleCallbacks?.postMessage(...)
// For iOS
webkit?.messageHandlers?.WebViewModuleCallbacks?.postMessage({...})
Para enviar mensajes desde el lado nativo al WebView, JavaScript debe evaluarse en forma de cadena. Por ejemplo, en iOS:
WebView.evaluateJavaScript("WebViewModule.completeCall(1234, { payload: \"somePayload\" }")
Debe tenerse en cuenta que el JavaScript evaluado debe hacer referencia a algo que ya esté disponible en tiempo de ejecución dentro de la aplicación web. Por esta razón, se implementó una instancia global que pudiera responder a los mensajes para la función FAQ (es decir, "WebViewModule").
Nos enfrentamos a algunos retos con el siguiente patrón de mensajería:
- Por defecto, la parte receptora del mensaje no acusa recibo ni devuelve un resultado como respuesta.
- Tenía que haber una forma de gestionar la complejidad de los distintos tipos de mensajes a medida que evolucionaban los requisitos de las funciones.
- Las funciones y las versiones de los clientes cambian constantemente; un mensaje que puede haberse introducido no estará disponible en versiones anteriores de la aplicación.
Para resolver el primer reto identificado se implementó un patrón basado en promesas de JavaScript. Para ello, una instancia dentro de la aplicación web mantenía una colección de promesas de mensajes en curso. Se podía hacer referencia a estas promesas de mensaje en un momento posterior mediante un identificador. Cuando la aplicación web se prepara para enviar un mensaje a una aplicación nativa, crea una nueva promesa en la colección junto con un identificador. A continuación, el identificador se pasa junto con el contenido del mensaje de la web a la aplicación nativa. Para completar el mensaje, la parte nativa evalúa JavaScript y devuelve el identificador y cualquier resultado, cumpliendo la promesa.
Para resolver el segundo reto, se forma un contrato de mensajes tipificados para gestionar la complejidad de manejar múltiples tipos de mensajes:
export type ScriptMessage = {
name: string
payload?: Record<string, unknown>
}
El tipo "ScriptMessage" lleva el nombre que identifica la intención del mensaje. Este nombre permite a las aplicaciones nativas y web cambiar el tratamiento específico del mensaje. El receptor del mensaje debe saber cómo desempaquetar el contenido de la carga útil.
Seguimos trabajando para resolver el tercer reto -el objetivo móvil de las versiones cliente- mediante cambios incrementales. Teniendo en cuenta el patrón basado en promesas descrito anteriormente, si la aplicación web envía un mensaje que no es compatible con la parte nativa, podría paralizar la aplicación mientras espera el cumplimiento de la promesa. Para evitarlo, las aplicaciones nativas envían actualmente su versión en un mensaje de configuración inicial. La aplicación web mantiene versiones conocidas de los clientes nativos y las compara con ellas cuando gestiona los mensajes.
WebView
Para un ingeniero web, una WebView es simplemente otra aplicación con algunos protocolos integrados que permiten la comunicación bidireccional entre las capas móvil y web.
Para la pila de tecnología WebView, aprovechamos los marcos y bibliotecas existentes de DoorDash, que incluyen React, React Query y nuestro sistema de diseño interno. Decidimos utilizar React Query para la obtención de datos y la gestión del estado porque dispone de funciones de caché e invalidación de caché y funciona bien con nuestro BFF a través de llamadas a la API REST (transferencia de estado representacional).
Puntos de entrada y salida de WebView
Nuestra aplicación expone múltiples puntos de entrada basados en el origen del usuario dentro de la app Dasher, como la configuración de la cuenta → WebView contenido prefiltrado a las preguntas frecuentes de la cuenta. Esto ayuda a eliminar la necesidad de mirar a través de todo el contenido. El protocolo para deeplink de móvil a WebView se parece a esto:
// The entry point will be specified by the callee of the FAQ module
enum EntryPoint {
case dashing
case earnings
/* ... */
}
// Based on the above entry point the filter query param will be populated
webView?.load(
URL(string: "https://<app-base-url>?filter=\(entryPoint.rawValue)")!
)
También utilizamos un protocolo de mensajería para minimizar un artículo. De este modo, cuando un usuario decida navegar por la aplicación móvil después de ver el contenido del artículo y jugar con las funciones, no perderá el acceso al artículo que estaba viendo. Los manejadores se adjuntan a un icono; al hacer clic, envía un mensaje al cliente móvil nativo para que escuche el evento.
Definición del gestor de mensajes
const postMessageToNative = (args) => {
if (webViewHost === 'iOS') {
setTimeout(() => {
webkit?.messageHandlers?.webViewModuleCallbacks?.postMessage(args)
}, 0)
} else if (webViewHost === 'Android') {
webViewModuleCallbacks?.postMessage(JSON.stringify(args))
} else {
// Web code: do nothing
}
}
Contenido de interfaz de usuario basado en servidor
Para soportar un sistema basado en contenido más robusto, añadimos todo el contenido de nuestros artículos de FAQ en el backend. A continuación, obtenemos los datos a través de WebView. Esto nos permite tener una única fuente de verdad y facilita las actualizaciones a través de una herramienta de configuración. Además, si alguna vez decidimos exponer esta aplicación como una aplicación web independiente, seguiremos teniendo el contenido.
A partir de este proyecto, hemos desarrollado un prototipo de herramienta de configuración de contenidos que crea el contenido del artículo en lugar de añadirlo en el cliente como un accesorio. El complejo JSON describe la estructura del artículo de FAQ. Esto permite futuras ampliaciones de la colección de la base de conocimientos sin esfuerzo de desarrollo.
He aquí una muestra de la estructura del contenido. La anatomía de la página del artículo está en parte dirigida por el servidor y en parte por el cliente:
Figura 2: Diseño de página de un artículo, parcialmente dirigido por el cliente y parcialmente dirigido por el servidor
Esta herramienta nos permitió construir una biblioteca de componentes que mapea el objeto de respuesta en componentes de nodos de texto y exporta una función de utilidad de renderizado para que la WebView de Dasher la consuma.
Creemos que esta herramienta nos reportará enormes beneficios en el futuro, cuando necesitemos ampliar sus capacidades a otras iniciativas de interfaz de usuario basadas en servidores, como un creador de pantallas para diseños de páginas.
Retos de desarrollo y despliegue inicial
Hay desafíos adicionales en el desarrollo, depuración y validación cuando se crea una función en una aplicación nativa como se presenta dentro de una WebView. Para entender si algo funciona realmente - y si no lo hace, donde se rompió - requiere el conocimiento de las pilas nativas y web.
Por ejemplo, al desarrollar la función de preguntas frecuentes se produjo un defecto en el protocolo de comunicación basado en promesas. Para depurar esto correctamente, necesitábamos ejecutar la aplicación web localmente, emitir un mapa fuente para el TypeScript transpilado, permitir y dirigir la aplicación nativa a la aplicación web ejecutada localmente, marcar la WebView como inspeccionable dentro de iOS o Android, y adjuntar a la WebView en ejecución desde Safari o Chrome. Esto podría ser un reto para un desarrollador web o nativo solitario, por lo que a menudo practicamos el emparejamiento.
Resultados y trabajo futuro
El desarrollo del centro de soporte de Dasher como WebView ha aportado numerosas ventajas, entre las que destacan:
- Implantaciones rápidas: Los despliegues web son rápidos y sencillos, sin fricciones para la adopción por parte de los usuarios. En cambio, las aplicaciones móviles tardan aproximadamente una semana en desplegarse y unas dos semanas en alcanzar porcentajes de adopción significativos. Los cambios en WebView requieren menos de un día.
- Actualización de contenidos: Como el contenido se almacena en el servidor, puede actualizarse con rapidez y frecuencia, enviando la versión más reciente al cliente sin necesidad de lanzamientos móviles.
Esto también nos permitió crear proyectos adicionales que ayudarán aún más a crear herramientas de apoyo para nuestros clientes y agentes y que se ampliarán, incluyendo:
- Herramienta de configuración de contenidos: Podemos ampliar esta herramienta para configurar otros flujos web, reduciendo el esfuerzo de desarrollo para crear flujos similares en nuestros productos móviles y web.
- Biblioteca de componentes para el contenido de interfaz de usuario basada en servidor: Estos componentes se encargan de renderizar el complejo JSON, que también se puede ampliar para manejar diferentes diseños.
Conclusión / Resumen
Crear un centro de asistencia Dasher mejorado en la aplicación era crucial para ofrecer a los usuarios un mejor acceso a la información. Los usuarios necesitan contenidos claros, concisos y de fácil acceso que ofrezcan soluciones rápidas a los problemas, sin necesidad de recurrir a agentes en directo. La información también tiene que ser fácilmente actualizable, lo que requiere una solución única para evitar largos tiempos de despliegue o duplicar el trabajo en Android e iOS. La creación de una aplicación web común nos permitió sortear las dificultades que plantea la actualización de las aplicaciones nativas, reduciendo enormemente el tiempo de despliegue y ofreciendo a los usuarios de Dashers acceso a la información más actualizada disponible.
Manténgase informado con las actualizaciones semanales
Suscríbase a nuestro blog de ingeniería para estar al día de los proyectos más interesantes en los que trabaja nuestro equipo.