Saltar al contenido

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

TXT
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:

Rust
// 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 ? con Ok(...) alrededor es la conversión del error interno al que cruza, gracias al From del 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:

Rust
// 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.