Caso de estudio
Asuka RS
2025 - alpha Único desarrollador
Resumen
Asuka RS es un proyecto personal en Rust, en fase alpha, para un bot de Discord orientado a eventos. Construí la arquitectura alrededor de tareas de evento independientes, dos registros de comando separados, búsquedas cache-first, respuestas localizadas y soporte para 31 códigos de idioma. Es un estudio sobre darle a cada responsabilidad su propio límite en lugar de concentrarlo todo en un único handler.
Contexto
El proyecto explora una arquitectura de bot capaz de enrutar comandos de texto y de interacción, gestionar preferencias de usuario y ofrecer controles de moderación configurables sin que un único handler sea dueño de todo. Rust hace los límites explícitos: tareas independientes, contextos de comando tipados y cachés cuya expulsión es parte del tipo, no una idea de última hora.
Rol
Único desarrollador, responsable de la arquitectura, la implementación en Rust y la internacionalización.
Runtime y flujo de eventos
Un solo shard del gateway de Twilight recibe los eventos. Cada evento se añade a la caché de mensajes de Twilight y luego se entrega a su propia tarea Tokio, de modo que un error al manejar un evento no puede detener a los que vienen detrás. Los eventos relevantes para raids pasan primero por el pre-filtro anti-raid; después main_handler enruta por tipo de evento hacia uno de dos caminos de dispatch (comandos de texto e interacciones) que mantienen registros y validación separados.
Runtime / del gateway al dispatch
Cada evento corre en su propia tarea, sobre dos registros
Los componentes (botones y menús de selección) despachan directamente a su handler registrado y se saltan a propósito la escalera de validación de comandos, porque ya solo eran alcanzables a través de un mensaje que el propio bot envió.
La escalera de protección de comandos
Ambos tipos de comando suben una escalera de checks antes de que corra su handler, pero no la misma. La admisión y los permisos de usuario se comprueban en ambos caminos; un cooldown configurado se reclama de forma atómica antes de que se pruebe siquiera el rate limit; y solo entonces corre el handler. Los comandos de slash además verifican los permisos del bot y pueden cargar el usuario de la base de datos cuando un límite por tiers necesita el tier del usuario; el camino de texto hoy no hace ninguna de las dos.
Protección de comandos / la escalera de checks
Los comandos de texto y de slash suben escaleras distintas
Acceso a datos cache-first
El acceso a usuarios es cache-first a través de una caché Moka en memoria. En un acierto, la petición nunca toca la base de datos; en un fallo, UserService hace upsert del usuario mediante Prisma, cachea el modelo resultante y lo devuelve. Las cachés de usuario y de servidor están limitadas a 100 MiB cada una, con expiración por inactividad de una hora, y la caché de usuario pesa sus entradas a partir de la clave y la longitud del idioma.
Acceso a datos / cache-first
Un fallo de caché hace upsert en SQLite y repuebla la caché
Los cooldowns, los contadores de rate limit y las ventanas de detección anti-raid viven cada uno en su propia caché Moka asíncrona, así que ninguno añade presión a la base de datos. Ese es el tema: el estado que es barato de reconstruir vive en memoria con un peso acotado y un TTL, y solo los hechos durables llegan a SQLite.
Internacionalización
Las cadenas de cara al usuario pasan por fluent-templates, con un archivo .ftl por locale bajo locales/. El idioma se resuelve desde el locale de interacción de Discord, normalizado contra los 31 códigos de idioma de la aplicación; una preferencia cambiada se escribe en la base de datos y la caché a la vez. Cada búsqueda lleva un fallback colocado junto a la llamada, y el idioma de respaldo de Fluent es inglés, de modo que una clave que falta degrada a texto legible en vez de a un error.
Internacionalización / Fluent
El locale de Discord, la preferencia guardada, luego inglés
31 códigos de idioma
Soportados en un solo sistema
La cobertura de traducción no se asume completa: los archivos de locale pueden llevar aún marcadores TODO: translate generados, y un binario locales dedicado escanea el código en busca de llamadas de búsqueda y sincroniza los archivos .ftl, añadiendo las claves que faltan y quitando las muertas.
Anti-raid
La protección de guild corre como pre-filtro sobre los eventos relevantes para raids. Las uniones masivas, los mensajes repetidos, la saturación de mensajes y los picos destructivos de roles o canales se rastrean en ventanas deslizantes en memoria, así que la detección nunca consulta la base de datos en el camino caliente; la configuración por guild se cachea y se carga bajo demanda. Cuando una ventana cruza su umbral, una escalera de acción configurable decide la respuesta, desde el registro silencioso hasta un bloqueo total, con un guild de desarrollo siempre tratado como modo de prueba.
Anti-raid / de la detección a la respuesta
Ventanas deslizantes en memoria, una escalera de acción por guild
Tradeoffs
Las tareas de evento independientes fueron el modelo de concurrencia adecuado para el MVP, y dejaron obvia la necesidad de la siguiente etapa: todavía no hay API ni panel, así que la configuración es código y datos en vez de una superficie de producto. Aislar el estado de cooldowns, rate limits y detección en cachés en memoria acotadas mantiene SQLite para los hechos durables, a costa de que ese estado sea por proceso y se pierda al reiniciar.
Fiabilidad y seguridad
La implementación incluye permisos multicapa, cooldowns por usuario, límites de tasa opcionales y por tiers, consultas Prisma parametrizadas, errores localizados y respuestas anti-raid configurables. Un comando que falla queda aislado en su propia tarea y se convierte en una respuesta localizada de Discord en lugar de tumbar el bot. Son controles de implementación, no garantías a escala de producción.
Qué mejoraría
Añadiría una API y un panel antes de un lanzamiento más amplio, para que la configuración de guild y los ajustes de moderación pasen a ser una superficie de producto en vez de filas en la base de datos.
¿Estás construyendo una integración orientada a eventos que necesita límites claros y un núcleo mantenible? Hablemos de tu proyecto.