Decisiones arquitecturales antes de generar código
Lino genera mucho código rápidamente, pero la calidad del resultado depende de las decisiones tomadas antes de la generación. Elegir límites, tipo de proyecto, estrategia de comunicación y nivel aceptable de deuda técnica evita que la productividad inicial se convierta en retrabajo.
Usa esta página como checklist de arquitectura para alinear equipo, dominio y operación antes de crear servicios, módulos y recursos avanzados.
Tipo de proyecto
Antes de ejecutar lino project new, define el objetivo del sistema. Un proyecto administrativo interno, un SaaS multi-tenant, una API pública y una plataforma distribuida tienen necesidades diferentes.
- Aplicación simple: pocos contextos, deploy único y baja necesidad de independencia operativa.
- Monolito modular: varios módulos en el mismo proceso, con límites explícitos y menor costo operativo.
- Microservicios: servicios separados, deploy independiente, comunicación asíncrona y mayor complejidad operativa.
- SaaS: exige aislamiento de tenant, seguridad sólida, secrets, rate limiting, observabilidad y decisiones claras de facturación/operación.
Esta decisión también define qué preocupaciones deben existir desde el primer día. Un SaaS no puede tratar tenant como un detalle tardío; una API pública no puede nacer sin rate limiting y contratos claros; una solución con eventos debe pensar en Outbox, idempotencia y observabilidad; una aplicación con múltiples culturas debe evitar strings dispersas desde el inicio.
Al crear la base, registra las decisiones principales: culturas soportadas, caché distribuida, comunicación asíncrona, analizadores de código, tipo de base de datos, presencia de web app, autenticación, tenant y worker. Estas opciones afectan paquetes, templates, AppHost, parámetros, secrets, proyectos generados y el esfuerzo necesario para cambiar la arquitectura después.
Monolito modular vs. microservicios
El monolito modular permite organizar el dominio en módulos con contratos internos claros, manteniendo build, debug y deploy más simples. Los microservicios solo compensan cuando existen motivos fuertes: escala independiente, equipos separados, fronteras de negocio estables o requisitos operativos distintos.
Empezar con un monolito modular no significa ignorar la arquitectura. Significa conservar un deploy simple mientras el dominio todavía se descubre, pero ya crear límites internos que impiden que todo se convierta en una masa única de entidades, servicios y tablas compartidas. Esa es la diferencia entre “empezar simple” y “empezar sin estructura”.
| Criterio | Monolito modular | Microservicios |
|---|---|---|
| Deploy | Más simple, normalmente único | Independiente por servicio |
| Consistencia | Más fácil mantener transacciones locales | Exige eventos, Outbox y consistencia eventual |
| Operación | Menor costo de infraestructura | Más observabilidad, colas, retries y automatización |
| Refactorización | Más barata al inicio | Más difícil cuando los contratos ya fueron publicados |
Lino soporta ambos caminos. La decisión debe seguir el dominio y la capacidad operativa del equipo, no solo la preferencia técnica. Los microservicios exigen automatización de deploy, observabilidad distribuida, versionado de contratos, tolerancia a fallas de red, mensajería, reprocesamiento y gobernanza de datos. Si el equipo aún no puede operar eso con seguridad, distribuir demasiado pronto suele aumentar el riesgo.
Usa monolito modular cuando la prioridad sea aprender el dominio, validar el producto y mantener la operación controlable. Usa microservicios cuando la independencia operativa ya sea una necesidad concreta: escala separada, equipos con ownership real, ciclo de release independiente o requisitos de infraestructura incompatibles entre partes del sistema.
Límites de servicios y módulos
Un buen límite reduce el acoplamiento y deja claro quién es dueño de cada regla. Evita crear servicios por tabla o por pantalla. Servicios y módulos deben representar capacidades de negocio.
- Catalog: productos, categorías, información comercial y datos consultados por ventas.
- Sales: pedidos, carrito, pricing aplicado, snapshots y flujo comercial.
- Stock: disponibilidad, reservas y movimientos.
- Security: usuarios, roles, permisos, autenticación y tokens.
Un buen límite tiene lenguaje propio, reglas que cambian juntas, datos bajo ownership claro y contratos explícitos para comunicarse con otros contextos. Un límite malo suele aparecer como módulo “Common”, entidad compartida por varias áreas, tabla consultada por todos o servicio creado solo porque había una nueva pantalla.
Cuando un módulo necesita datos de otro, elige entre API, evento de integración, shadow entity o remodelación del límite. El acceso directo a la base de datos de otro módulo es señal de acoplamiento estructural. Incluso cuando dos módulos se ejecutan en el mismo proceso y usan la misma base física, el schema, los proyectos de persistencia y los contratos deben proteger el límite de negocio.
- Señal de buen límite: el módulo puede evolucionar sus entidades y migrations sin obligar a otro módulo a recompilar por un detalle interno.
- Señal de límite débil: un cambio simple en una entidad exige revisar pantallas, queries y handlers de módulos que no deberían conocer esa entidad.
- Señal de dependencia escondida: el consumidor necesita “saber demasiado” sobre tablas, campos técnicos o reglas internas del productor.
Deuda técnica consciente
La deuda técnica no es cualquier imperfección. Es una decisión de postergar calidad conociendo el costo futuro. El problema es crear deuda sin nombre, sin dueño y sin plan de pago.
En un SaaS o sistema modular, la deuda técnica más peligrosa no es solo un método largo. Es la deuda que limita la evolución: módulos que no pueden cambiar por separado, datos de tenant sin aislamiento confiable, secrets dentro del repositorio, endpoints sensibles sin límite, eventos sin reprocesamiento o integraciones sin contrato claro.
- Documenta atajos tomados durante la generación y la customización.
- Ejecuta build y pruebas antes de avanzar a la siguiente etapa de generación.
- Evita corregir problemas generados con hacks locales repetidos; ajusta el modelado o el template cuando el patrón esté equivocado.
Trata cada decisión arquitectural como algo auditable. Si eliges no habilitar mensajería, registra que los flujos actuales no dependen de procesamiento asíncrono. Si eliges columna por tenant, garantiza filtros y pruebas. Si optas por integración síncrona, asume timeout, indisponibilidad y error de contrato como parte del diseño.
Estrategias de comunicación
Elige la comunicación según la necesidad real de consistencia, latencia, trazabilidad y acoplamiento. La pregunta principal no es “¿qué tecnología usar?”, sino “¿el productor necesita la respuesta ahora, la falla del consumidor debe cancelar la operación y quién es dueño del dato?”.
En sistemas modulares, el error más común es convertir la comunicación en una dependencia escondida: un módulo consulta una tabla de otro, reutiliza una entidad interna, inyecta un repositorio externo o crea una llamada síncrona sin tratar fallas. Esto parece productivo al comienzo, pero vuelve cada cambio posterior más riesgoso porque el límite deja de ser explícito.
- Llamada síncrona: buena para consulta inmediata y dependencia aceptable. Evítala en cascadas largas.
- Evento de dominio: reacción interna dentro del mismo límite de aplicación.
- Evento de integración: comunicación entre módulos, servicios o sistemas, normalmente con Outbox.
- Shadow Entity: copia local mínima para consulta y validación sin depender directamente del productor.
- Integración HTTP externa: apropiada para servicios de terceros, con timeout, retry, logs y tratamiento de fallas.
Si la regla exige actualización inmediata, usa un flujo síncrono o revisa el límite. Si acepta consistencia eventual, prefiere eventos para reducir el acoplamiento. Cuando el consumidor solo necesita leer un pequeño subconjunto de datos de otro contexto, una shadow entity puede ser más segura que exponer la entidad original completa.
| Pregunta | Dirección recomendada |
|---|---|
| ¿La respuesta es obligatoria para concluir el caso de uso? | Integración síncrona explícita, con timeout, error previsible y contrato claro. |
| ¿El productor debe confirmar la transacción aunque el consumidor esté fuera de línea? | Evento de integración con Outbox y procesamiento asíncrono. |
| ¿El consumidor solo necesita validar o consultar datos mínimos? | Shadow Entity alimentada por evento o integración, con ownership local del modelo de lectura. |
| ¿El cambio ocurre dentro del mismo agregado o bounded context? | Evento de dominio o lógica de dominio local, sin publicar un contrato externo innecesario. |
