Saltar al contenido
White / wsuites

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.

Flujo de proceso Solo arquitectura conceptual. Omite identificadores, nombres de servidores y contenido de comandos.

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.

Flujo de proceso Flujo conceptual. El asterisco marca los checks de permisos del bot, que los comandos de slash aplican y el camino de texto no.

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.

Flujo de proceso Camino de datos conceptual. El estado de cooldowns y anti-raid vive en sus propias cachés Moka, no mostradas.

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.

Flujo de proceso Flujo de i18n conceptual. Los códigos de locale de la aplicación y los de Discord son tablas separadas.

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.

Flujo de proceso Flujo de detección conceptual. Los umbrales, los mapeos de acción y los identificadores de guild se omiten.

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.