Creando una aplicación .NET completa con Lino: guía paso a paso

Este tema presenta una guΓ­a prΓ‘ctica paso a paso para usar Lino CLI como herramienta principal en la construcciΓ³n de un proyecto: desde la instalaciΓ³n y configuraciΓ³n inicial, pasando por la generaciΓ³n de servicios, mΓ³dulos y entidades, hasta recursos avanzados como eventos, Background Jobs, migrations, build de imΓ‘genes Docker y versionado.

El objetivo es mostrar, de forma integrada, cΓ³mo los comandos del CLI encajan en el flujo de desarrollo real; no solo enumerarlos, sino explicar por quΓ© se hace cada elecciΓ³n, quΓ© se genera automΓ‘ticamente y cuΓ‘les son las implicaciones arquitectΓ³nicas.

Aunque cada comando ya tiene documentaciΓ³n especΓ­fica, aquΓ­ verΓ‘ el proceso de extremo a extremo β€” una guΓ­a reproducible que ahorra horas de trabajo repetitivo y ayuda a mantener un cΓ³digo consistente y testeable.

A lo largo de la guΓ­a, explicaremos conceptos tΓ©cnicos aplicados por Lino (por ejemplo: CQRS, TypedResults, Source Generators, Outbox Pattern), mostraremos comandos de ejemplo e indicaremos buenas prΓ‘cticas para el versionado, el deploy y la integraciΓ³n continua.

Use esta guΓ­a como una secuencia de aprendizaje: primero comprenda la estructura, luego modele el dominio y solo entonces genere APIs, pΓ‘ginas, eventos y artefactos de deploy. Esto reduce el retrabajo y mantiene el cΓ³digo generado alineado con el diseΓ±o del sistema.

InstalaciΓ³n y configuraciΓ³n de Lino CLI

El primer paso para empezar a trabajar con el Lino CLI es instalar la herramienta en su entorno de desarrollo. Se distribuye como una dotnet global tool, lo que significa que estarΓ‘ disponible para cualquier proyecto .NET en su computadora. Antes de instalar, confirme que el SDK de .NET requerido por los templates actuales estΓ© disponible, que la terminal pueda ejecutar dotnet y que el directorio de herramientas globales de .NET estΓ© en el PATH.

Paso 1: instalaciΓ³n

Para instalar (o actualizar) Lino CLI, ejecute el siguiente comando en la terminal:

dotnet tool install --global Tolitech.Lino

Notas importantes:

  • Si ya hay una versiΓ³n instalada, puede usar dotnet tool update --global Tolitech.Lino para actualizar.
  • Verifique que el directorio de herramientas globales de .NET estΓ© en el PATH del sistema para que el comando lino funcione correctamente.

Paso 2: configurar el idioma

DespuΓ©s de la instalaciΓ³n, se recomienda configurar el idioma (o cultura) que CLI utilizarΓ‘ en mensajes, prompts y logs:

lino preferences culture set

Se le pedirΓ‘ que elija entre los idiomas disponibles. Esta configuraciΓ³n garantiza que todas las instrucciones y mensajes aparezcan de forma coherente en el idioma deseado. Esta preferencia cambia los mensajes, prompts y orientaciones localizadas del CLI; no renombra entidades, servicios, mΓ³dulos o tΓ©rminos de negocio generados en el proyecto.

Paso 3: AutenticaciΓ³n y registro

Para acceder a todos los recursos de Lino, incluidos templates avanzados, la publicaciΓ³n de imΓ‘genes Docker y las integraciones con servicios externos, es necesario estar autenticado.

- Si aΓΊn no posee registro, regΓ­strese con el comando:

lino user register

- Si ya tiene registro, haga login con:

lino auth login

Lo que sucede: el CLI almacena un token de autenticaciΓ³n localmente, lo que le permite ejecutar comandos que requieren acceso a recursos protegidos sin tener que iniciar sesiΓ³n en cada uso. Mantenga este token privado y evite compartir el mismo perfil de usuario entre diferentes desarrolladores, agentes de CI o mΓ‘quinas.

Paso 4: VerificaciΓ³n

Para confirmar que la instalaciΓ³n y la autenticaciΓ³n fueron exitosas, ejecute:

lino --version

Si el comando devuelve la versiΓ³n instalada, estΓ‘ listo para comenzar a usar el Lino CLI en sus proyectos.

Crear el proyecto MyApp

En este paso, crearemos la estructura inicial del proyecto utilizando el Lino CLI. Este proyecto servirΓ‘ como base para demostrar la creaciΓ³n de servicios, mΓ³dulos, front-end y toda la integraciΓ³n de eventos. Un proyecto Lino no es solo una carpeta con una soluciΓ³n: define convenciones para lΓ­mites de servicios, bibliotecas compartidas, host Aspire, marco de interfaz, pruebas, administraciΓ³n de paquetes, analizadores y configuraciones que los siguientes Commands reutilizan.

Paso 1: ejecutar el comando crear

Para crear un nuevo proyecto, ejecute el siguiente comando en la terminal:

lino project new

CLI lo guiarΓ‘ paso a paso, solicitΓ‘ndole informaciΓ³n como:

  • Nombre del proyecto: usaremos MyApp, pero puedes elegir el nombre que quieras;
  • CaracterΓ­sticas adicionales: analizadores de cΓ³digo, almacenamiento en cachΓ© distribuido, soporte de eventos asincrΓ³nicos, etc.

Paso 2: configurar funciones esenciales

Para este proyecto, recomendamos habilitar las siguientes funciones desde el principio:

  • Analizadores de cΓ³digo: garantizar que el cΓ³digo siga buenas prΓ‘cticas y estΓ‘ndares consistentes, evitando errores comunes de implementaciΓ³n;
  • CachΓ© distribuido: mejora el rendimiento de la aplicaciΓ³n en escenarios con mΓΊltiples servicios, evitando Queries innecesarias a la base de datos;
  • ComunicaciΓ³n asincrΓ³nica: Permite el uso de eventos y colas para la integraciΓ³n entre servicios, asegurando escalabilidad y desacoplamiento.

Es importante habilitar todas estas opciones en este proyecto, ya que crearemos mΓΊltiples servicios que se comunicarΓ‘n a travΓ©s de eventos de integraciΓ³n. Esto le permitirΓ‘ comprender cΓ³mo estructurar sistemas modulares y distribuidos utilizando Lino.

Paso 3: estructura generada

DespuΓ©s de ejecutar el comando y configurar los recursos, CLI generarΓ‘ la estructura inicial del proyecto. IncluirΓ‘:

  • Carpetas de servicios y mΓ³dulos;
  • Templates para front-end (si corresponde);
  • Configuraciones iniciales de cachΓ©, eventos e integraciones;
  • Archivos de soluciΓ³n (.slnx) y proyecto (.csproj) listos para su compilaciΓ³n.

Ahora tu proyecto Mi aplicaciΓ³n estΓ‘ listo para recibir los servicios, mΓ³dulos, entidades y front-end que configuraremos en los prΓ³ximos pasos. Antes de continuar, abra la soluciΓ³n generada y ejecute dotnet build para confirmar que las referencias, los source generators, los templates, los archivos de proyecto y la configuraciΓ³n inicial son coherentes.

Agregar la aplicaciΓ³n web Backoffice

Un sistema completo normalmente necesita al menos una aplicaciΓ³n web para operar el dominio. En esta guΓ­a, la aplicaciΓ³n se llama Backoffice y representa una interfaz interna para administradores, gerentes o usuarios operativos para monitorear productos, categorΓ­as, existencias, ventas y otra informaciΓ³n del sistema.

La aplicaciΓ³n web no reemplaza los servicios de dominio. ActΓΊa como un punto de entrada visual para consumir APIs, activar Commands, consultar Queries y exponer pantallas coherentes con las reglas ya modeladas en los servicios.

Paso 1: ejecutar el comando crear

Para agregar una nueva aplicaciΓ³n web al proyecto, use:

lino web-app new

El alias lino webapp new tambiΓ©n estΓ‘ disponible para facilitar la escritura. Durante la ejecuciΓ³n, indique un nombre claro para la aplicaciΓ³n web; en este ejemplo, usaremos Backoffice.

lino web-app new --name Backoffice

Paso 2: comprender la estructura generada

Al final del proceso, Lino crea la estructura inicial de la Web App en src/WebApps/<WebAppName>. Para una aplicaciΓ³n Blazor, la estructura puede incluir proyectos server/client, recursos compartidos, archivos de localizaciΓ³n, clients para consumir APIs y convenciones que serΓ‘n utilizadas posteriormente por lino page new.

  • Carpetas para pΓ‘ginas, componentes, diseΓ±os, servicios y recursos de aplicaciones;
  • Clientes HTTP y contratos requeridos para consumir API expuestos por los servicios del proyecto;
  • Resources de localizaciΓ³n y templates iniciales usados por la experiencia web;
  • Proyectos cliente/servidor cuando el tipo de aplicaciΓ³n requiera esta separaciΓ³n;
  • Convenciones de ruta, navegaciΓ³n e integraciΓ³n que serΓ‘n reutilizadas en la generaciΓ³n de pΓ‘ginas;
  • Puntos de integraciΓ³n con autenticaciΓ³n y autorizaciΓ³n cuando se agrega la funcionalidad de auth al proyecto.

Paso 3: CuΓ‘ndo crear la aplicaciΓ³n web en el flujo

Si ya sabe que el sistema tendrΓ‘ una interfaz Blazor, cree la Web App al principio, despuΓ©s de crear el proyecto. AsΓ­, los servicios, mΓ³dulos, entidades, APIs y pΓ‘ginas generados despuΓ©s ya quedan alineados con la aplicaciΓ³n web que los consumirΓ‘.

Este flujo es especialmente ΓΊtil porque los servicios creados posteriormente tambiΓ©n generan proyectos tipados de Api.Contracts y Api.Client, consumidos por el proyecto Blazor. Con la Web App presente desde el inicio, resulta mΓ‘s simple validar el camino completo: dominio, API, contratos, HttpClient y pantalla.

Notas importantes

  • Backoffice debe consumir los datos por medio de las APIs generadas, manteniendo la lΓ³gica de negocio en los servicios y mΓ³dulos correctos.
  • Antes de exponer pΓ‘ginas administrativas, revise la autenticaciΓ³n, autorizaciΓ³n, roles, permisos y polΓ­ticas de acceso.
  • Puede crear mΓΊltiples aplicaciones web, por ejemplo un Backoffice interno y un Site pΓΊblico, cuando los pΓΊblicos, permisos, deploys o responsabilidades sean diferentes.
  • Evite mezclar flujos pΓΊblicos y administrativos en la misma aplicaciΓ³n web cuando esto dificulte la seguridad, la navegaciΓ³n, la implementaciΓ³n o la propiedad del equipo.

Con la aplicaciΓ³n web creada, la guΓ­a puede pasar a los servicios y mΓ³dulos que proporcionarΓ‘n los datos y reglas de negocio que consume esta interfaz.

CreaciΓ³n de servicios y mΓ³dulos.

En este paso, construiremos los servicios y mΓ³dulos que conformarΓ‘n la aplicaciΓ³n. El objetivo es crear una arquitectura modular y escalable, permitiendo que diferentes Γ‘reas del sistema, como productos, categorΓ­as, inventario, ventas y medios, evolucionen de forma independiente, manteniendo la cohesiΓ³n y facilitando el mantenimiento. Los servicios definen los lΓ­mites de deploy y persistencia; los mΓ³dulos organizan Γ‘reas de negocio dentro de un servicio modular. Antes de crear archivos, evalΓΊe quΓ© partes del dominio necesitan base de datos propia, release independiente, contrato de integraciΓ³n u ownership separado.

Paso 1: Definir los servicios

Inicialmente, crearemos los siguientes servicios, cada uno con responsabilidades bien definidas:

  • Catalog (modular) – responsable de gestionar productos, categorΓ­as y precios;
  • Sales – responsable de procesar ventas y pedidos;
  • Stock – responsable de la gestiΓ³n de stocks y movimientos;
  • Security – responsable de la autenticaciΓ³n, autorizaciΓ³n y gestiΓ³n de usuarios.

Para crear los servicios del ejemplo, ejecute un comando por cada servicio. En el wizard de Catalog, elija arquitectura modular; en los demΓ‘s, elija la arquitectura adecuada para una frontera simple.

lino service new --name Catalog
lino service new --name Sales
lino service new --name Stock
lino service new --name Security

Durante la ejecuciΓ³n del comando, el CLI solicitarΓ‘:

  • Nombre del servicio: por ejemplo, Catalog;
  • Nombre para mostrar y estilo arquitectΓ³nico: elija una arquitectura simple para fronteras compactas o modular cuando el servicio tenga Γ‘reas cohesivas independientes;
  • Base de datos: elija la tecnologΓ­a que mejor se adapte a su proyecto (SQL Server, PostgreSQL, etc.);

Paso 2: Creando mΓ³dulos dentro de servicios

No todos los servicios necesitan ser modulares. En nuestro proyecto, solo el servicio Catalog tendrΓ‘ mΓ³dulos, para separar responsabilidades como merchandising y precios.

Definimos los siguientes mΓ³dulos para el servicio Catalog:

  • Merchandising – gestiΓ³n de productos y categorΓ­as;
  • Pricing – gestiΓ³n de precios, promociones e historial de cambios.

Para crear los mΓ³dulos de Catalog, ejecute:

lino module new --service Catalog --name Merchandising
lino module new --service Catalog --name Pricing

Puedes crear tantos mΓ³dulos como quieras dentro del servicio modular Catalog. AdemΓ‘s, dependiendo de la complejidad, algunos mΓ³dulos podrΓ­an convertirse en servicios independientes en el futuro. La separaciΓ³n que aquΓ­ se presenta es sΓ³lo para fines didΓ‘cticos y sirve como ejemplo de organizaciΓ³n modular. No use mΓ³dulos solo para crear carpetas; ΓΊselos cuando protejan un Γ‘rea de negocio con entidades propias, casos de uso, APIs, migrations y eventos.

Estructura final del proyecto

DespuΓ©s de crear los servicios y mΓ³dulos, su soluciΓ³n deberΓ­a tener una estructura similar a la siguiente:

MyApp/
└── src/
    β”œβ”€β”€ Aspire/
    β”œβ”€β”€ Integrations/
    β”œβ”€β”€ Services/
    β”‚   β”œβ”€β”€ Catalog/
    β”‚   β”‚   β”œβ”€β”€ Modules/
    β”‚   β”‚   β”‚   β”œβ”€β”€ Merchandising/
    β”‚   β”‚   β”‚   └── Pricing/
    β”‚   β”‚   β”œβ”€β”€ MyApp.Catalog.Host
    β”‚   β”‚   └── MyApp.Catalog.Infrastructure
    β”‚   β”œβ”€β”€ Sales/
    β”‚   β”œβ”€β”€ Security/
    β”‚   β”œβ”€β”€ Shared/
    β”‚   └── Stock/
    └── WebApps/
        β”œβ”€β”€ Backoffice/
        β”‚   β”œβ”€β”€ Services/
        β”‚   β”‚   β”œβ”€β”€ Catalog/
        β”‚   β”‚   β”œβ”€β”€ Sales/
        β”‚   β”‚   β”œβ”€β”€ Security/
        β”‚   β”‚   └── Stock/
        β”‚   β”œβ”€β”€ MyApp.WebApp.Backoffice
        β”‚   └── MyApp.WebApp.Backoffice.Client
        └── Shared/
            └── MyApp.WebApp.Shared
└── tests/
    └── Services/
        β”œβ”€β”€ Catalog/
        β”‚   β”œβ”€β”€ Merchandising/
        β”‚   └── Pricing/
        β”œβ”€β”€ Sales/
        β”œβ”€β”€ Security/
        β”œβ”€β”€ Shared/
        └── Stock/

ExplicaciΓ³n de la estructura:

  • Services/: contiene todos los servicios del sistema, cada uno aislado con su propia lΓ³gica empresarial, infraestructura y alojamiento;
  • Modules/: carpetas dentro de servicios modulares, que le permiten organizar funcionalidades especΓ­ficas y mantener un cΓ³digo coherente;
  • WebApps/: frontends asociados al sistema, ya integrados con los servicios;
  • Shared/: bibliotecas y recursos compartidos entre servicios y frontends;
  • tests/: pruebas unitarias y de integraciΓ³n organizadas por servicio y mΓ³dulo.

Con esta estructura modular, cada equipo o desarrollador puede trabajar de forma independiente en diferentes partes del sistema, facilitando la escalabilidad, el mantenimiento y las pruebas. DespuΓ©s de crear servicios y mΓ³dulos, ejecute dotnet build para confirmar que los proyectos estΓ‘n conectados correctamente a la soluciΓ³n, el host Aspire, los proyectos compartidos y la estructura de pruebas.

Agregar autenticaciΓ³n y autorizaciΓ³n

La autenticaciΓ³n y la autorizaciΓ³n son elementos esenciales de cualquier sistema moderno. La autenticaciΓ³n comprueba quiΓ©n es el usuario; la autorizaciΓ³n define quΓ© puede ejecutar ese usuario. En Lino CLI, la funcionalidad de auth genera la base necesaria para usuarios, roles, permisos, tokens, polΓ­ticas de acceso e integraciΓ³n con APIs.

Paso 1: ejecutar el comando de autenticaciΓ³n

Para agregar recursos de autenticaciΓ³n y autorizaciΓ³n, use el comando:

lino feature auth add

CLI lo guiarΓ‘ a travΓ©s de los siguientes pasos:

  • ElecciΓ³n de servicio o mΓ³dulo: debe indicar dΓ³nde se instalarΓ‘n los artefactos de autenticaciΓ³n. En nuestro proyecto de ejemplo, utilizamos el servicio Security, que centraliza toda la lΓ³gica de seguridad del sistema;
  • Configuraciones adicionales: creaciΓ³n de tablas de usuarios, roles, permisos, tokens y configuraciΓ³n de polΓ­ticas de acceso;
  • Vida ΓΊtil del token: definiciΓ³n de vencimiento para el access token y el refresh token de acuerdo con la polΓ­tica de seguridad del producto;
  • Tipo de identificador de usuario: elegir el tipo utilizado por el modelo de usuario y los contratos generados.

Paso 2: estructura generada

DespuΓ©s de ejecutar el comando, el servicio Security contendrΓ‘ archivos y carpetas como:

  • Domain/Entities: agregados, entidades y reglas para usuarios, roles, permisos y tokens;
  • Infrastructure/Persistence: configuraciones de base de datos, mapeos de Entity Framework y migrations para tablas de seguridad;
  • Application: Commands, Queries, Handlers, servicios de autenticaciΓ³n, generaciΓ³n de tokens, validaciΓ³n de credenciales y comprobaciones de permisos;
  • API/Host: endpoints para login, logout, registro, refresh token y operaciones protegidas;
  • IntegraciΓ³n con la aplicaciΓ³n web: soporte para flujos autenticados cuando una aplicaciΓ³n web estΓ‘ presente.

Con esto, su aplicaciΓ³n tendrΓ‘ una autenticaciΓ³n sΓ³lida y un control de acceso granular, lista para admitir mΓΊltiples usuarios y diferentes niveles de permisos. Aun asΓ­, el cΓ³digo generado debe tratarse como una base sΓ³lida, no como la revisiΓ³n de seguridad final. Antes de producciΓ³n, revise la vida ΓΊtil de los tokens, el diseΓ±o de permisos, la polΓ­tica de contraseΓ±as, HTTPS, secrets, rate limiting, logs y las configuraciones de deploy.

Agregar Background Jobs

En sistemas distribuidos y modulares, como el que estamos construyendo con Lino CLI, no todos los servicios se comunican directamente entre sΓ­. Para garantizar la coherencia y confiabilidad en el intercambio de informaciΓ³n, utilizamos eventos de integraciΓ³n. Sin embargo, para procesar estos eventos de manera eficiente y asincrΓ³nica, necesitamos Background Jobs.

Lino utiliza el estΓ‘ndar Outbox Pattern para garantizar que todos los mensajes generados por los servicios se registren de manera confiable antes de ser enviados. Con esto logramos:

  • Evitar pΓ©rdida de eventos en caso de fallas o reinicios del servicio;
  • Garantizar que el mismo mensaje se envΓ­e solo una vez;
  • Permitir el reprocesamiento de mensajes en caso de falla en la entrega;
  • Separar el procesamiento de eventos de la lΓ³gica principal de la aplicaciΓ³n, mejorando el rendimiento y la escalabilidad.

Paso 1: ejecutar el comando

Para agregar soporte para Background Jobs a su proyecto, ejecute el comando:

lino feature background-job add

CLI le pedirΓ‘ que seleccione el servicio donde se instalarΓ‘ el Background Job. Generalmente elegirΓ‘s el servicio que centralice la producciΓ³n del evento, como por ejemplo Catalog o Sales. En las opciones actuales, el asistente tambiΓ©n puede solicitar el mΓ³dulo, la biblioteca de jobs, si se deben procesar los eventos de Outbox, la programaciΓ³n y el tamaΓ±o del lote. El flujo de los templates actuales usa Hangfire para la ejecuciΓ³n recurrente de los jobs.

Paso 2: Configurar la ejecuciΓ³n

Durante la configuraciΓ³n, podrΓ‘ definir:

  • Intervalo de control: determina la frecuencia con la que el Background Job comprobarΓ‘ la tabla Outbox para mensajes nuevos. Un intervalo demasiado corto puede aumentar el uso de recursos, mientras que un intervalo demasiado largo puede retrasar la entrega de eventos;
  • Lote de registros procesados ​​a la vez: controla cuΓ‘ntos eventos se leerΓ‘n y enviarΓ‘n por ejecuciΓ³n. Los lotes mΓ‘s grandes pueden aumentar el rendimiento, pero requieren mΓ‘s memoria y procesamiento;
  • PolΓ­tica de retries: en caso de error al enviar mensajes, puede configurar cuΓ‘ntas veces el job intentarΓ‘ reenviarlos.

Estos parΓ‘metros dependen del tamaΓ±o de su sistema, la capacidad de la mΓ‘quina y el volumen esperado de eventos.

Paso 3: estructura generada

DespuΓ©s de la configuraciΓ³n, el proyecto tendrΓ‘ un Background Job listo para procesar mensajes de las tablas Outbox en cada servicio.

  1. El caso de uso cambia el dominio y registra un dominio o evento de integraciΓ³n.
  2. La unidad de trabajo guarda los datos de negocio y los mensajes de Outbox en la misma transacciΓ³n.
  3. Hangfire ejecuta jobs recurrentes que leen mensajes pendientes en lotes.
  4. Los mensajes se publican en el motor de integraciΓ³n configurado, como RabbitMQ, cuando la comunicaciΓ³n asincrΓ³nica estΓ‘ habilitada.
  5. Los mensajes completados, fallidos, antiguos o atascados pueden manejarse mediante la lΓ³gica y la configuraciΓ³n generadas para el job.

Esto garantiza que todos los eventos de integraciΓ³n se procesen de manera confiable y eficiente, lo que permite que mΓΊltiples servicios y mΓ³dulos se comuniquen de forma asincrΓ³nica, sin afectar el rendimiento del sistema principal. La regla operativa mΓ‘s importante es mantener claros los lΓ­mites transaccionales: los eventos que deben enviarse a travΓ©s de Outbox deben crearse en el mismo flujo transaccional que el cambio de negocio que representan.

Crear entidades y enumeraciones

En esta secciΓ³n, detallaremos el diseΓ±o de las entidades, enumeraciones y Value Objects de la aplicaciΓ³n, mostrando en quΓ© servicios y mΓ³dulos se crearΓ‘ cada elemento.

1. Crear la entidad Category

Para crear la entidad Category en el servicio Catalog y en el mΓ³dulo Merchandising, ejecute:

lino entity new --service Catalog --module Merchandising --name Category

La entidad se crearΓ‘ en el servicio Catalog y en el modulo Merchandising con la siguiente estructura:

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type   β”‚ Length β”‚ Required β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ Guid   β”‚        β”‚    x     β”‚       x        β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Name          β”‚ string β”‚   50   β”‚    x     β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Crear la entidad Product

A continuaciΓ³n, creamos la entidad Product en el mismo servicio y mΓ³dulo. En este flujo, el Value Object ProductDimension y la enum ProductStatus se configuran dentro del propio wizard de creaciΓ³n de la entidad, como parte del agregado Product:

lino entity new --service Catalog --module Merchandising --name Product

Durante la ejecuciΓ³n, agregue las propiedades simples, la relaciΓ³n con Category, la propiedad Dimensions de tipo Value Object y la propiedad Status de tipo Enum.

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type        β”‚ Length β”‚ Required β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ Guid        β”‚        β”‚    x     β”‚       x        β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Name          β”‚ string      β”‚  100   β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Description   β”‚ string      β”‚  500   β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Price         β”‚ decimal     β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚ x  β”‚ CategoryId    β”‚ Category    β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Dimensions    β”‚ ValueObject β”‚        β”‚          β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Status        β”‚ Enum        β”‚        β”‚    x     β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.1 Configurando el Value Object ProductDimension dentro de Product

En el escenario de esta guΓ­a, ProductDimension no se crea mediante un comando separado. Se agrega durante el lino entity new de Product, como la propiedad Dimensions, y representa las dimensiones del producto:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Property name β”‚ Type    β”‚ Length β”‚ Required β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Width         β”‚ decimal β”‚        β”‚    x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Height        β”‚ decimal β”‚        β”‚    x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Depth         β”‚ decimal β”‚        β”‚    x     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Nota: el comando lino value-object new existe para escenarios en los que el Value Object debe crearse por separado. En esos casos, use los argumentos del servicio y del mΓ³dulo de destino:

lino value-object new --service <ServiceName> --module <ModuleName> --name <ValueObjectName>

2.2 Configurando la Enum ProductStatus dentro de Product

Del mismo modo, ProductStatus se configura dentro del lino entity new de Product, como la propiedad Status. Define el estado del producto:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Value β”‚ Name         β”‚ Display Name β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1     β”‚ Active       β”‚ Active       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 2     β”‚ Inactive     β”‚ Inactive     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 3     β”‚ Discontinued β”‚ Discontinued β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Nota: el comando lino enumeration new tambiΓ©n puede usarse cuando una enum debe crearse por separado en otro escenario:

lino enumeration new --service <ServiceName> --module <ModuleName> --name <EnumerationName>

3. Agregar nuevas propiedades

A medida que el proyecto evoluciona, podemos editar entidades existentes para agregar nuevas propiedades. Por ejemplo, agregaremos una lista de imΓ‘genes a la entidad Product:

lino entity edit --service Catalog --module Merchandising --entity Product

Dentro de este mismo flujo, creamos la propiedad Images de tipo List<ProductImage>. Como ProductImage pertenece al agregado Product, su estructura tambiΓ©n se configura durante el lino entity edit de Product:

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type               β”‚ Length β”‚ Required β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ Guid               β”‚        β”‚    x     β”‚       x        β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Name          β”‚ string             β”‚  100   β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Description   β”‚ string             β”‚  500   β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Price         β”‚ decimal            β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚ x  β”‚ CategoryId    β”‚ EntityId           β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Dimensions    β”‚ ValueObject        β”‚        β”‚          β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Status        β”‚ Enum               β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚ x  β”‚ Images        β”‚ List β”‚        β”‚          β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.1 Configurando ProductImage dentro de Product

ProductImage no se crea mediante un comando separado en este escenario. Se configura como elemento de la colecciΓ³n Images durante la ediciΓ³n de Product y tiene la siguiente estructura:

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type           β”‚ Length β”‚ Required β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ Guid           β”‚        β”‚    x     β”‚       x        β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚ x  β”‚ ProductId     β”‚ EntityId       β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ UploadDate    β”‚ DateTimeOffset β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Image         β”‚ File           β”‚        β”‚    x     β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Nota: el comando lino entity new existe para crear entidades independientes en otros escenarios. Cuando la entidad no forma parte de la ediciΓ³n de un agregado existente, use:

lino entity new --service <ServiceName> --module <ModuleName> --name <EntityName>

4. CreaciΓ³n de entidades para otros servicios

en servicio Sales, creamos la entidad ProductSnapshot, que se verΓ‘ alimentado por eventos de integraciΓ³n. Como el ID original de la entidad Product viene del servicio Catalog, aquΓ­ no puede ser autoincremental.

lino entity new --service Sales --name ProductSnapshot
β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type    β”‚ Length β”‚ Required β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ Guid    β”‚        β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Name          β”‚ string  β”‚  100   β”‚    x     β”‚                β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Price         β”‚ decimal β”‚        β”‚    x     β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ObservaciΓ³n: solo los campos esenciales se replicaron en ProductSnapshot para el servicio Sales. Las entidades complementarias, como Customer, Order y StockItem, no se detallarΓ‘n aquΓ­ para simplificar la documentaciΓ³n. ProductSnapshot funciona como una shadow entity: una copia local, mΓ­nima y controlada de datos cuyo dueΓ±o estΓ‘ en otro servicio. Esto permite que Sales consulte los datos necesarios del producto sin depender directamente de la entidad o la base de datos de Catalog.

Este tipo de estructura tambiΓ©n podrΓ­a crearse con el comando lino shadow new, alias de lino shadow-entity new. En este flujo, Lino copia la estructura de una entidad de otro servicio o mΓ³dulo y permite seleccionar solo las propiedades que tienen sentido para el contexto consumidor.

lino shadow new --service <ServiceName> --module <ModuleName> --name <ShadowEntityName>

En el ejemplo, la entidad de origen es Catalog.Merchandising.Product y el destino es el servicio Sales, manteniendo solo los campos necesarios para ventas.

Crear eventos y sus controladores.

En resumen, en el tema anterior creamos un servicio modular. Catalog.Merchandising las entidades Product, Category y ProductImage, mientras estΓ‘ de servicio Sales creamos la entidad ProductSnapshot.

Ahora, creemos eventos de dominio y eventos de integraciΓ³n. El objetivo es que, al crear o actualizar productos en el servicio Catalog, estos cambios se repliquen en los servicios consumidores, como Sales y Stock.

1. Crear eventos de dominio

El primer paso es crear eventos de dominio. ProductCreated y ProductUpdated con el comando:

lino event new

Durante la creaciΓ³n podemos asociar el evento a un handler y, simultΓ‘neamente, configurar la activaciΓ³n de un evento de integraciΓ³n. Esto centraliza la creaciΓ³n de todo el flujo necesario.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Question                                             β”‚ Answer                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a service:                                    β”‚ Catalog                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a module:                                     β”‚ Merchandising                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a entity:                                     β”‚ Product                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event type:                               β”‚ Domain Event                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event:                         β”‚ ProductCreated                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Do you want to create an associated event handler?   β”‚ Yes                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Trigger a integration event?                         β”‚ Yes                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Choose the integration event to be triggered:        β”‚ (Create new integration event) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event:                         β”‚ ProductCreated                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Which model will be used for this integration event? β”‚ Creation model                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

De la misma forma creamos el evento. ProductUpdated:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Question                                             β”‚ Answer                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a service:                                    β”‚ Catalog                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a module:                                     β”‚ Merchandising                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a entity:                                     β”‚ Product                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event type:                               β”‚ Domain Event                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event:                         β”‚ ProductUpdated                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Do you want to create an associated event handler?   β”‚ Yes                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Trigger a integration event?                         β”‚ Yes                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Choose the integration event to be triggered:        β”‚ (Create new integration event) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event:                         β”‚ ProductUpdated                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Which model will be used for this integration event? β”‚ Update model                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Con esto tenemos:

2. Crear controladores de eventos de integraciΓ³n

El siguiente paso es definir quΓ© servicios consumirΓ‘n los eventos de integraciΓ³n. Para hacer esto, usamos:

lino event-handler new

El flujo de creaciΓ³n implica:

Por ejemplo, en el servicio Sales Creamos los controladores para ProductCreated y ProductUpdated que consumirΓ‘ los eventos desencadenados por el Catalog.Merchandising.Producto:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Question                                   β”‚ Answer            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a service:                          β”‚ Sales             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a entity:                           β”‚ ProductSnapshot   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event type:                     β”‚ Integration Event β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's service to be consumed: β”‚ Catalog           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's module to be consumed:  β”‚ Merchandising     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's entity to be consumed:  β”‚ Product           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Choose the event to be consumed:           β”‚ ProductCreated    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event handler:       β”‚ ProductCreated    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Question                                   β”‚ Answer            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a service:                          β”‚ Sales             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a entity:                           β”‚ ProductSnapshot   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event type:                     β”‚ Integration Event β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's service to be consumed: β”‚ Catalog           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's module to be consumed:  β”‚ Merchandising     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select the event's entity to be consumed:  β”‚ Product           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Choose the event to be consumed:           β”‚ ProductUpdated    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Enter the name of the event handler:       β”‚ ProductUpdated    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Con esto, tenemos dos controladores de eventos de integraciΓ³n en el servicio Sales, consumiendo sΓ³lo los campos necesarios de los eventos de integraciΓ³n del Catalog.Merchandising. Esto garantiza que las tablas replicadas conserven solo los datos esenciales, optimizando el almacenamiento y el rendimiento. En tΓ©rminos de arquitectura, el flujo esperado es: el controlador de Commands cambia el agregado, se genera el evento de dominio, el controlador prepara el evento de integraciΓ³n, Outbox almacena el mensaje, el Background Job procesa el problema y el motor de mensajerΓ­a entrega el mensaje a los consumidores. Esto evita el acoplamiento sincrΓ³nico entre servicios y reduce el riesgo de que se cambie la base de datos sin que se publique el evento correspondiente.

Generar pΓ‘ginas web, APIs, Commands y Queries

Una de las grandes ventajas de Lino CLI es permitir la creaciΓ³n integrada de pΓ‘ginas web, APIs, Commands y Queries de forma automatizada, simplificando todo el flujo de desarrollo. Cuando el modelo de dominio es lo suficientemente estable, este comando crea una ruta completa desde la pantalla hasta la persistencia, haciendo que el trabajo de modelado realizado en los pasos anteriores sea visible para el usuario final. Para comenzar, simplemente ejecute el comando:

lino page new

Durante el proceso, usted:

Con este comando, obtiene una aplicaciΓ³n funcional sin tener que escribir manualmente la capa de interfaz, APIs y lΓ³gica de negocio, manteniendo el estΓ‘ndar y la coherencia entre los servicios. Aun asΓ­, revise las reglas de negocio y las validaciones despuΓ©s de la generaciΓ³n, porque no todas las reglas se pueden inferir ΓΊnicamente a partir de los metadatos de las propiedades.

Para este proyecto, podemos generar pΓ‘ginas integradas para las siguientes entidades:

DespuΓ©s de generar las pΓ‘ginas, APIs y Commands/Queries, la aplicaciΓ³n estarΓ‘ lista para interactuar de forma completa entre frontend y backend, con validaciones, rutas y persistencia ya configuradas automΓ‘ticamente por Lino CLI. Ejecute dotnet build despuΓ©s de la generaciΓ³n para identificar tempranamente referencias rotas, contratos inconsistentes o impactos de cambios recientes en el modelo.

Crear y aplicar migrations

DespuΓ©s de crear o cambiar entidades, Value Objects, enumeraciones, relaciones, autenticaciΓ³n, soporte a Tenant o persistencia de Background Jobs, genere migrations para mantener la base de datos y el cΓ³digo alineados. La migration transforma el cambio de modelo en un artefacto explΓ­cito, versionable y revisable.

Lino CLI coordina este proceso con Entity Framework, seleccionando el servicio/mΓ³dulo correcto, utilizando la versiΓ³n actual del servicio y organizando los scripts generados para facilitar la trazabilidad.

Paso 1: crear una migration

Para crear una nueva migration, ejecute:

lino database migrations add

El comando tambiΓ©n puede aceptar alias como lino database migrations new y lino database migrations create, pero la forma preferida en la documentaciΓ³n es add.

Durante la ejecuciΓ³n deberΓ‘ informar:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Question                                  β”‚ Answer            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a service:                         β”‚ Catalog           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Select a module:                          β”‚ Merchandising     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Current version of the service:           β”‚ 0.1.0             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Provide a description for this migration: β”‚ Initial migration β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Cuando ya conozcas el servicio y el mΓ³dulo, puedes introducir estos datos directamente:

lino database migrations add --service <ServiceName> --module <ModuleName>

Paso 2: QuΓ© se genera

Al confirmar la creaciΓ³n, Lino prepara los comandos correctos de Entity Framework para el proyecto de persistencia del servicio o mΓ³dulo seleccionado. En un servicio modular, la migration queda aislada en el mΓ³dulo correspondiente, evitando mezclar cambios de contextos diferentes.

Paso 3: Listar y aplicar migrations

Antes de aplicar cambios a una base de datos, enumere las migrations conocidas y confirme que la migration esperada estΓ© presente:

lino database migrations list --service <ServiceName> --module <ModuleName>

Para aplicar migrations al entorno configurado:

lino database migrations apply --service <ServiceName> --module <ModuleName>

En desarrollo local, este flujo acelera la validaciΓ³n del modelo. En entornos compartidos o de producciΓ³n, aplique migrations mediante el proceso de deploy definido por el equipo, con revisiΓ³n del script, aprobaciΓ³n y backup cuando sea necesario.

Paso 4: invertir o retirar en un entorno controlado

Use revert cuando necesite revertir una migration aplicada en un entorno controlado, entendiendo que la operaciΓ³n puede ejecutar comandos destructivos segΓΊn el contenido de la migration:

lino database migrations revert --service <ServiceName> --module <ModuleName>

Use remove para descartar la ΓΊltima migration aΓΊn no consolidada, normalmente antes de commitear o publicar el cambio:

lino database migrations remove --service <ServiceName> --module <ModuleName>

Buenas practicas

Al seguir este flujo, la base de datos permanece coherente con el modelo de dominio definido en Lino, y cada cambio de schema queda documentado, rastreable y listo para ser revisado antes del deploy.

Paso 5: Validando la aplicaciΓ³n localmente

Con el proyecto, Web App, servicios, mΓ³dulos, entidades, migrations, APIs, Commands y Queries listos, valide la aplicaciΓ³n antes de generar imΓ‘genes Docker. Primero, compile la soluciΓ³n para confirmar que todos los proyectos, contratos y clients generados siguen siendo coherentes:

dotnet build

A continuaciΓ³n, ejecute la aplicaciΓ³n mediante el AppHost de Aspire:

dotnet run --project src/Aspire/AppHost/<ProjectName>.AppHost.csproj

Use el dashboard de Aspire para comprobar si APIs, Web App, base de datos, cachΓ©, mensajerΓ­a y Background Jobs se iniciaron correctamente. DespuΓ©s, pruebe los flujos principales en el Backoffice: pΓ‘ginas generadas, llamadas de Blazor a los proyectos Api.Client, migrations aplicadas, autenticaciΓ³n cuando estΓ© habilitada, y eventos o jobs cuando formen parte del escenario.

Cuando la aplicaciΓ³n compile, se ejecute localmente y los flujos principales estΓ©n validados, el proyecto estarΓ‘ listo para la etapa de empaquetado.

Generar imΓ‘genes Docker

DespuΓ©s de que la aplicaciΓ³n compile, se ejecute localmente mediante el AppHost y los flujos principales hayan sido probados, puede generar imΓ‘genes Docker de los servicios y aplicaciones web para publicarlas posteriormente en un registro de contenedores. Use lino build cuando los elementos seleccionados estΓ©n listos para empaquetarse como imΓ‘genes.

Lino CLI simplifica este proceso con el comando:

lino build

Cuando lo ejecute, verΓ‘ una lista de todos los servicios y aplicaciones web disponibles en el proyecto, junto con sus versiones actuales:

Select the services or web applications you want to include in the build:

> [ ] Services
    [ ] Catalog |0.1.0|
    [ ] Sales |0.1.0|
    [ ] Security |0.1.0|
    [ ] Stock |0.1.0|
  [ ] Web applications
    [ ] Backoffice |0.1.0|

Puede seleccionar uno o mΓ‘s servicios y aplicaciones web para generar imΓ‘genes simultΓ‘neamente. Simplemente marque los elementos deseados.

A continuaciΓ³n, se le pedirΓ‘ que elija cΓ³mo desea actualizar la versiΓ³n de las imΓ‘genes generadas. Las opciones disponibles son:

DespuΓ©s de seleccionar los servicios y definir el incremento de versiΓ³n, el Lino CLI realiza:

Al final del proceso, considerando que se han seleccionado todos los servicios y aplicaciones web, las imΓ‘genes generadas tendrΓ‘n la siguiente estructura:

En tΓ©rminos generales, los servicios simples tienden a generar repositorios como project-name/services/service-name-api:1.2.3, los servicios modulares utilizan hosts como project-name/services/service-name-host:1.2.3, y las aplicaciones Blazor usan rutas como project-name/webapps/webapp-name:1.2.3.

ObservaciΓ³n: Este proceso garantiza la coherencia entre el cΓ³digo y la versiΓ³n de las imΓ‘genes Docker, facilitando el despliegue y mantenimiento de mΓΊltiples entornos, ademΓ‘s de permitir aislar cada servicio en contenedores independientes. Una vez generada la imagen local, publΓ­quela en el registro usado por su plataforma de deploy, como Docker Hub, GitHub Container Registry, AWS ECR, Azure Container Registry u otro registro compatible con OCI. No incluya secrets, connection strings de producciΓ³n ni credenciales dentro de la imagen.

Crear versiones en la aplicaciΓ³n

Lino mantiene la versiΓ³n operativa de cada servicio en src/Services/<ServiceName>/version.txt y cada aplicaciΓ³n web en src/WebApps/<WebAppName>/version.txt. Esto le permite planificar releases independientes para cada elemento implementable.

Antes de cambiar de versiΓ³n, inspeccione el estado actual:

lino version list

Use lino version show cuando necesite consultar un servicio o una aplicaciΓ³n web especΓ­fica.

Incrementar versiones de servicios o aplicaciones web es un proceso simple y centralizado en Lino CLI. Simplemente ejecute el comando:

lino version bump

Al igual que generar imΓ‘genes Docker, cuando ejecute este comando verΓ‘ una lista completa de todos los servicios y aplicaciones web en su proyecto. Solo los elementos que seleccione tendrΓ‘n su versiΓ³n incrementada, mientras que los demΓ‘s permanecerΓ‘n sin cambios.

Select the services or web applications that will have version changes:

> [ ] Services
    [ ] Catalog |0.1.0|
    [ ] Sales |0.1.0|
    [ ] Security |0.1.0|
    [ ] Stock |0.1.0|
  [ ] Web applications
    [ ] Backoffice |0.1.0|

DespuΓ©s de seleccionar los elementos deseados, se le pedirΓ‘ que elija el tipo de incremento de versiΓ³n. Las opciones disponibles son:

Es importante resaltar que las versiones de los servicios y aplicaciones web tienen un impacto directo en:

Antes de aplicar un bump, revise los cambios de cΓ³digo, las migrations, los eventos de integraciΓ³n, los contratos de API y los cambios en el frontend que forman parte del release. Una versiΓ³n Patch debe representar correcciones compatibles, Minor debe representar adiciones compatibles, y Major debe reservarse para cambios que requieran adaptaciΓ³n de los consumidores.

Con esto, concluimos la guΓ­a paso a paso de todos los comandos esenciales para construir un proyecto web usando Lino CLI, desde la instalaciΓ³n, la creaciΓ³n de servicios, entidades, eventos y pΓ‘ginas, hasta la generaciΓ³n y el versionado de imΓ‘genes Docker. El flujo completo es rastreable: modelado de dominio, generaciΓ³n de casos de uso y pantallas, validaciΓ³n con compilaciones y pruebas, creaciΓ³n de migrations, publicaciΓ³n de imΓ‘genes con tags de versiΓ³n y release de cada servicio o aplicaciΓ³n web con un valor SemVer explΓ­cito.

No olvides seguir nuestro canal en YouTube para seguir tutoriales detallados, demostraciones prΓ‘cticas y consejos para usar la herramienta, desde operaciones simples hasta recursos avanzados.

Se ha producido un error no controlado. Recargar πŸ—™