Organizar los comandos en módulos
Cómo se reparten treinta comandos sin que lib.rs se convierta en un fichero de mil líneas: un módulo por dominio, la capa de frontera separada de la lógica, y el registro que sigue siendo una sola lista legible.
La estructura
src-tauri/src/├── main.rs tres líneas. Nivel 6.4├── lib.rs el builder, el setup, y el registro. Nada más├── error.rs ErrorPayload y las conversiones. Nivel 18│├── proyectos/│ ├── mod.rs re-exporta lo público del módulo│ ├── comandos.rs LA FRONTERA: los #[tauri::command]│ ├── modelo.rs los tipos que cruzan, con su serialización│ └── logica.rs lo que hace el trabajo. Sin nada de Tauri dentro│├── catalogo/│ ├── mod.rs│ ├── comandos.rs│ └── …└── ajustes/ └── …La regla que la sostiene: comandos.rs no tiene lógica y logica.rs no sabe que existe Tauri. Esa separación es lo que permite probar la lógica sin ventana, reutilizarla desde otro binario o desde móvil, y leer la superficie entera mirando solo tres ficheros.
La capa de frontera
Un comandos.rs típico se lee de un vistazo, y esa es la idea:
// proyectos/comandos.rs — solo frontera: validar, delegar, traducir el erroruse crate::error::ErrorPayload;use super::{logica, modelo::{Proyecto, ProyectoId}};#[tauri::command]pub async fn cargar_proyecto(app: tauri::AppHandle, id: ProyectoId) -> Result<Proyecto, ErrorPayload> { Ok(logica::cargar(&app.path().app_data_dir()?, id).await?)}#[tauri::command]pub async fn guardar_proyecto( app: tauri::AppHandle, id: ProyectoId, contenido: String,) -> Result<(), ErrorPayload> { Ok(logica::guardar(&app.path().app_data_dir()?, id, &contenido).await?)}#[tauri::command]pub async fn listar_proyectos(app: tauri::AppHandle) -> Result<Vec<Proyecto>, ErrorPayload> { Ok(logica::listar(&app.path().app_data_dir()?).await?)}Tres cosas de ese fichero merecen atención:
- Cada función es de tres líneas. Si una crece, la lógica se está colando en la frontera.
- El
?conOk(...)alrededor es la conversión del error interno al que cruza, gracias alFromdel nivel 18. La frontera es exactamente el sitio donde ocurre esa traducción. pub, o el registro no la ve. Es el error del nivel 14.1.
Registrar desde módulos
El macro acepta rutas completas:
// lib.rsmod ajustes;mod catalogo;mod error;mod proyectos;pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ // proyectos proyectos::comandos::cargar_proyecto, proyectos::comandos::guardar_proyecto, proyectos::comandos::listar_proyectos, // catálogo catalogo::comandos::buscar, catalogo::comandos::sincronizar, // ajustes ajustes::comandos::leer_ajustes, ajustes::comandos::escribir_ajustes, ]) .run(tauri::generate_context!()) .expect("error al arrancar la aplicación");}Con treinta comandos esa lista es larga y conviene que lo sea: es el inventario de tu superficie, y agruparla por módulo con un comentario por grupo la convierte en algo que se puede leer de arriba abajo en la auditoría del nivel 33.
La tentación es esconderla —una función por módulo que devuelva su lista, un macro propio que la componga— y merece resistirse. Una superficie que hay que reconstruir mentalmente juntando tres sitios es una superficie que nadie va a revisar.
Cuándo partir
Una regla práctica para no pasarse en ninguna dirección:
| momento | qué hacer |
|---|---|
| 1 a 5 comandos | todo en lib.rs. Partir antes de tiempo es ceremonia |
| 5 a 15 | un comandos.rs suelto, y la lógica ya fuera |
| más de 15 | módulos por dominio, con las tres capas |
| un módulo pasa de ~10 comandos | probablemente son dos dominios |
Y una señal fiable de que el reparto está bien: puedes contarle a alguien qué hace la aplicación leyendo solo los nombres de los módulos y de sus comandos. Si hace falta abrir la lógica para entenderlo, los nombres son de mecanismo.