Cordis y arquitectura de plugins en TypeScript sin quebraderos de cabeza
Si alguna vez has escrito una aplicación extensible en Node.js o TypeScript, probablemente te hayas encontrado con los mismos problemas. Un usuario o sistema conecta un plugin. El plugin cuelga cinco listeners de eventos, inicia un par de temporizadores, registra sus rutas e inyecta un servicio. Y entonces el plugin se desactiva o actualiza sobre la marcha.
¿Qué pasa después? Exacto, fugas de memoria. Los listeners siguen colgados, los temporizadores siguen marcando en segundo plano, las referencias de contexto impiden que el recolector de basura limpie la memoria. En Node.js, gestionar el ciclo de vida de las dependencias a menudo se convierte en trabajo manual tedioso.
Hace algún tiempo, los desarrolladores del framework de chatbot Koishi se enfrentaron exactamente a este problema. Necesitaban crear un núcleo donde cientos de plugins de terceros pudieran iniciarse, aislarse, reemplazarse entre sí y descargarse sin reiniciar el proceso. Así nació el framework Cordis.
Qué es exactamente Cordis
Los creadores llaman a su proyecto un "meta-framework de composabilidad espacio-temporal". Suena inteligente y pretencioso, pero la esencia es realmente práctica.
Cordis combina un contenedor de inyección de dependencias (IoC), un bus de eventos y un árbol de contextos jerárquico. Cada plugin o servicio vive dentro de su propio contexto. Si ese contexto se destruye, Cordis limpia automáticamente absolutamente todos los recursos para él: elimina los manejadores de eventos, mata los temporizadores y elimina los servicios creados.
No hay magia aquí, pero sí una disciplina clara: si un plugin usa los métodos [object Object], [object Object] o [object Object], el framework se encarga de limpiar los efectos secundarios por sí mismo.
Cómo funciona el modelo de contexto
El concepto central de la biblioteca es [object Object]. No es solo un objeto plano con configuraciones, sino un árbol ramificado.
Cuando llamas a [object Object], el framework genera un contexto hijo (bifurcación). El contexto hijo hereda los servicios del padre pero almacena sus propias referencias a los recursos registrados.
Si desactivas el Plugin A, su contexto hijo colapsa. El manejador [object Object] se elimina del bus de eventos compartido, mientras que el Servicio de Base de Datos y el Plugin B continúan funcionando tranquilamente.
Servicios y tipado en TypeScript
Los servicios en Cordis se declaran a través de la herencia de la clase base [object Object]. Esto los hace accesibles directamente a través de las propiedades del contexto mientras se mantiene un tipado estricto:
La construcción [object Object] resuelve el problema del orden de carga de módulos. Si el servicio de base de datos se inicializa de forma asíncrona o se conecta más tarde, el plugin dependiente esperará a que esté listo y se activará.
Ajuste fino de la visibilidad del ámbito
En programas reales, los módulos a menudo no deberían reaccionar a todo. Por ejemplo, un manejador es necesario solo para mensajes de un canal específico o solicitudes con una cabecera particular.
Cordis introduce el concepto de filtros a través de la llamada [object Object] y las propiedades del contexto. Puedes limitar la visibilidad de un servicio a una rama específica del árbol o establecer un predicado que filtre los eventos no deseados:
Para qué tareas es bueno este enfoque
La biblioteca fue creada para una clase específica de aplicaciones. No deberías arrastrarla a una API CRUD normal en Fastify o Express, donde crearía una capa extra de abstracción.
Pero Cordis encaja perfectamente en los siguientes escenarios:
- Utilidades CLI modulares y generadores. Cuando los usuarios pueden entregar paquetes npm que extienden comandos o pipelines de construcción.
- Aplicaciones de escritorio en Electron/Tauri. Para organizar un sistema de addons y temas que pueden habilitarse y deshabilitarse sobre la marcha sin recargar la ventana.
- Bots y hubs de integración. Si un servicio se comunica con una docena de plataformas diferentes (Telegram, Discord, Slack), y cada adaptador necesita vivir una vida aislada.
- Herramientas de automatización. Donde los procesos se configuran dinámicamente por los usuarios a través de una interfaz web o archivos YAML.
Piedras en el camino y desventajas
No existen herramientas perfectas, y Cordis tiene muchas sutilezas específicas:
- Curva de aprendizaje pronunciada. La documentación está escrita en un lenguaje seco con una abundancia de términos específicos. Para superar los conceptos de fusión de ámbitos y efectos secundarios, necesitarás leer cuidadosamente el código fuente.
- API desconocida. Vincular servicios al objeto de contexto a través de la fusión de tipos de TypeScript puede resultar confuso al principio para quienes están acostumbrados a NestJS clásico o InversifyJS con sus decoradores.
- Ligado a un modelo mental. Si la arquitectura de tu proyecto no implica una descarga frecuente de código dinámico, los beneficios del gestor de efectos integrado se ven negados por la complejidad del código.
¿Vale la pena probar
Cordis es un proyecto de ingeniería interesante enfocado en la gestión del ciclo de vida de componentes. Resuelve elegantemente el problema de las fugas de recursos al crear sistemas extensibles.
Si estás diseñando un sistema con un ecosistema desarrollado de plugins conectables y quieres un mecanismo fiable para rastrear efectos secundarios listo para usar, haz un fork del repositorio [object Object] y estudia los ejemplos en las pruebas. Es un gran ejemplo de cómo puedes construir una arquitectura de microkernel en TypeScript puro.
Proyectos relacionados