Lo sé, llevo más de tres semanas sin publicar un update sobre el estado de Jade. Se me han juntado vacaciones con trabajo atrasado de Calisteniapp y la verdad es que no he dado abasto.
Esta semana por fin pudimos juntarnos un rato para empezar a picar código. Como siempre pasa en todos los proyectos, a medida que vas bajando de mayor nivel de abstracción o de la fase idea, a niveles más bajos y concretos, empiezan a surgir problemas que inicialmente no se habían tenido en cuenta o escenarios que pueden no resultar tan interesantes como se plantearon. En definitiva, se comienza a vislumbrar la complejidad real de tu solución. Lo que parecía un pis-pas, ya no lo es tanto.
Siempre digo que, en tecnología, prácticamente todo lo que se te ocurra hacer es posible. La cuestión está en que posible no equivale a sencillo.
Lo primero que replanteamos fue el punto de partida. En lugar de empezar por la paleta de comandos como inicialmente habíamos pensado, finalmente hemos decidido comenzar con la parte de la ingesta. Principalmente porque creemos que, junto con el procesamiento, es la parte en la que más valor podemos aportar.
Además, tras investigar un poco, vimos que hay una gran cantidad de plugins de Obsidian que actualmente hacen cosas muy parecidas a lo que planteamos (ej : obsidian-textgenerator-plugin). Por tanto, tenemos buenos puntos de partida para rápidamente tener una paleta de comandos funcional cuando la necesitemos.
Manos a la obra
Nos tiramos a la piscina y nos ponemos a picar código, pero, ¿por donde empezamos? Pues bien, toca tomar desiciones técnicas concretas, analizar los requisitos nuevamente y valorar posibles stacks con sus pros y sus contras.
Ingesta
Aunque inicialmente contemplamos recibir los datos en Obsidian y enviarlos al backend de procesamiento mediante un plugin, finalmente decidimos que la ingesta no estaría en Obsidian.
Cada una de las partes de Jade debe ser un sistema independiente para garantizar máximo control y flexibilidad. Por esto, hemos elegido como punto de entrada un bot de Telegram:
Es sencillo, accesible multiplataforma y gratuito.
Nos permite enviar todo tipo de ficheros, enlaces o mensajes.
La forma de enviar contenido es natural y universal.
Nos queda un historial de todo que lo hemos enviado a Jade, ordenado cronológicamente y en crudo, out-of-the-box.
De esta forma, la principal (aunque en un futuro, no única) forma de enviar contenido al sistema de procesamiento será compartiendo contenido como si se lo enviases a un amigo. Podrás enviar un enlace, audio, notas de voz, imágenes, documentos, todo lo que Telegram te permita (que no es poco).
Cosas que valoramos pero descartamos (al menos de momento):
Desarrollar una app ad hoc para la ingesta (mucho curro).
Extensión de navegador (útil, pero no universal).
PWA (no vale por sus limitaciones).
Web-app (UX/UI maluchas e incómodas para el caso).
Procesamiento
La modularización es uno de lo requisitos más importantes. Dado que la idea es poder enchufar diferentes componentes en cada parte del flujo, el procesamiento tampoco puede estar en Obsidian.
Partiendo de este punto, hemos optado por implementar un backend con NestJS que se encargará de centralizar el procesamiento. Adicionalmente, servirá como un segundo sistema de ingesta mediante una api REST, puesto que es natural (y muy sencillo) exponer algunos endpoints que abran una segunda opción para la entrada de datos.
Con esto conseguimos dejar el sistema listo para en un futuro poder tener varios puntos de ingesta adicionales (apps ad hoc, extensiones, otros sistemas, zappier, etc).
En cuanto a porqué elegir NestJS como framework, es evidente:
Me encanta.
Puedes hacer prácticamente lo que quieras con él (desde apis hasta scheduled tasks o CLIs).
Aunque está biased, desde mi punto de vista, lo está en el buen sentido. Te fuerza a llevar a cabo buenas prácticas de arquitectura y testing.
Es expressjs con esteroides, todo tipado y bien nombrado con sus decoradores.
Escribes mucho menos boilerplate cuando haces las cosas bien.
En cuanto a la arquitectura e infraestructura, aunque nest permite orientar el proyecto a microservicios, no creemos que sea necesario dado el alcance del proyecto y que solo seremos dos personas picando código a tiempo parcial (muy parcial). Implementaremos un monolito con sistema de colas con Bull (elección de nest). La idea es procesar ligeramente todos los eventos de entrada de datos, e ir despachando el heavy processing en distintas colas.
También tuvimos que tomar otro montón de pequeñas decisiones. Sin detenernos mucho, les comento algunas de las más relevantes:
El Redis que necesita Bull lo montaremos en Upstash (ya dije que quería probarlo).
Necesitaremos un almacenamiento para todos los ficheros de entrada (crudos) y los outputs. Casi seguro que optaremos por S3 de AWS por el precio y porque funciona estupendamente.
Usaremos una base de datos en PlanetScale con Prisma para gestionar las ubicaciones de los ficheros y otros datos persistentes.
Aún no sabemos donde lo desplegaremos, pero tampoco importa a estas alturas. Probablemente lo tiremos en Railway (también quería probarlo) pero estamos valorando AWS Fargate o, en el peor de los casos, alguna maquina básica de gcp, aws o el digital ocean de turno.
Próximos pasos
Y… Hasta aquí puedo contar. Este finde planteamos la configuración básica del proyecto y empezamos a juguetear con una implementación sencilla del bot y la api de chatgpt. ¡Al fin picamos algo de código!
Empieza a tener sentido dejar todo algo más planificado y documentado. Para ello, comenzaremos a usar Linear (lo amo) con la idea de planificar pequeños proyectos semanales que vayan atacando los distintos casos de uso.
Objetivos a dos semanas vista
Poder ingerir texto y almacenarlo en markdown.
Poder ingerir notas de voz, transcribirlas y almacenarlas en markdown.
Investigar en profundidad el funcionamiento de las nuevas propiedades de Obsidian de cara a usarlas para metadatos.
¡Nos vemos en el update #5!