Introducción
No fue difícil, pero tampoco diría que funciona sin configuración. Me tomó unas semanas dejar la base funcionando correctamente, y la mayor dificultad fue la falta de ejemplos y tutoriales. En comparación con Powersync + Supabase o simplemente usar Firebase, esto definitivamente requiere algo de configuración.
Implementación
Había una publicación oficial en el blog con un ejemplo de git sobre cómo lograr Appwrite + RxDB. Sin embargo, cuando lo probé, tenía bastantes errores y no hacía sincronización en tiempo real. Así que terminé construyendo el mío desde cero leyendo la documentación de Appwrite y de RxDB.
Principales obstáculos
Manejo de la gestión de estado con RxDB
Estoy usando LegendState, y hay ocasiones en las que intento hacer .set() sobre la instancia de mi colección de rxdb porque provoca un bucle en mi vista. Por eso tengo que asegurarme de usar .get() para la colección de rxdb, y usar LegendState para leer y monitorear las actualizaciones. Por otro lado, en mi viewModel tengo todas las funciones que manipulan rxdb directamente. La modificación de la réplica de Appwrite ocurre en las funciones del viewModel.
La sincronización en tiempo real actualiza la vista en bucle
Relacionado con el punto anterior. No estoy familiarizado con LegendState, pero ahora voy mejorando. Añadí debounce para resolver el problema.
El retraso de la sincronización desordena el estado local
Como la replicación de RxDB con Appwrite solo puede realizar una escritura a la vez, cada vez que realizaba varias acciones en poco tiempo, los estados locales tenían un problema de condiciones de carrera. Esto se debe a que me he suscrito a la colección en tiempo real, pero los datos modificados localmente y los recibidos no están sincronizados. Por lo tanto, creé un updatedAt para manejar los conflictos.
Campos eliminados
Puedes establecer un campo deleted para la replicación. Se indica que para realizar la eliminación se use .remove(). Sin embargo, este método de eliminación implica que no hay «deshacer eliminación». Esto se debe a que RxDB oculta todo lo que se elimina de forma lógica mediante su método .remove(), y el cliente no puede acceder al elemento simplemente comprobando !deleted. Hay un método get/findone para recuperar un archivo eliminado, algo engorroso, pero entiendo que rxdb necesita su propio mecanismo de eliminación lógica. Por lo tanto, añadí un campo deleted extra para mí. Así que, en total, hay 3 campos deleted: rxDeleted, isDeleted y deleted (el predeterminado de Appwrite).
Ahora, cuando un usuario elimina un elemento, solo alterno isDeleted, para que pueda ir a la papelera y recuperarse. En el lado del cliente, configuro una comprobación de 30 días sobre isDeleted para realizar la eliminación lógica de rxdb que alterna rxDeleted. Luego, en el lado del servidor, realizo otra comprobación, digamos de 30 días de rxDeleted, para eliminarlos de verdad.
La eliminación lógica mediante rxDeleted es necesaria para que RxDB sincronice el campo deleted entre dispositivos. Pero también quiero que el usuario pueda recuperar el elemento fácilmente, así que añadí isDeleted además de eso.
Conclusión
No fue demasiado fácil para mí, me tomó semanas resolver todo esto, ya que empecé con SwiftUI, donde Apple CoreData se encargaba de todos los procedimientos de sincronización por mí. Esta es la primera vez que realmente necesito hacer más solo para sincronizar. Pero me alegro de haberlo hecho.
¡Ahora puedo trabajar en lo siguiente!