Modelando el dominio

En el corazΓ³n de cualquier aplicaciΓ³n orientada al dominio estΓ‘ el modelo que representa el conocimiento central y las reglas de negocio del sistema. Modelar bien el dominio significa traducir conceptos del mundo real en estructuras de software expresivas, cohesivas y consistentes.

Entidades

Una entidad es un objeto definido principalmente por su identidad y no solo por sus atributos. Aunque los atributos cambien con el tiempo, la identidad de una entidad permanece igual.

CaracterΓ­sticas principales:

  • Tiene una identidad ΓΊnica (generalmente un Id).
  • Lo que importa es quiΓ©n es la entidad, no solo quΓ© contiene.
  • Sus atributos pueden cambiar con el tiempo.

Creando una entidad con Lino

Para crear una nueva entidad utilizando Lino, ejecute:

lino entity new

El asistente del CLI solicitarΓ‘:

  • Servicio – Servicio en el que se crearΓ‘ la entidad.
  • MΓ³dulo – MΓ³dulo donde se crearΓ‘ la entidad (solo en servicios modulares).
  • Nombre de la entidad – Nombre usado en el dominio y en la tabla de la base de datos.

Luego, definirΓ‘s los campos que componen la entidad configurando cada uno de ellos.

Tipos de campos disponibles

Tipo DescripciΓ³n Rango / Observaciones
shortEntero de 16 bits-32 768 β†’ 32 767
intEntero de 32 bits-2 147 483 648 β†’ 2 147 483 647
longEntero de 64 bits-9 223 372 036 854 775 808 β†’ 9 223 372 036 854 775 807
stringTextoHasta ~2 mil millones de caracteres
boolValor booleanotrue o false
GuidIdentificador global ΓΊnicoUnicidad distribuida
decimalNΓΊmero decimal de alta precisiΓ³nIdeal para valores monetarios
floatPunto flotante (32 bits)β‰ˆ 6–9 dΓ­gitos de precisiΓ³n
doublePunto flotante (64 bits)β‰ˆ 15–17 dΓ­gitos de precisiΓ³n
DateTimeFecha y horaIncluye zona horaria
DateOnlySΓ³lo fecha (C# 10+)–
TimeOnlySΓ³lo hora (C# 10+)–
EntityReferencia a otra entidad1:1 o 1:N
Value ObjectValue Object inmutableEj.: DirecciΓ³n, CPF
EnumEnumeraciΓ³nConjunto fijo de valores
List<Entity>Lista de entidades1:N
ManyToManyMuchos a muchosRequiere tabla de uniΓ³n

Ejemplo

Creando la entidad Person:

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

Estructura generada por Lino:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ Errors/
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            └── Person/
                                β”œβ”€β”€ PersonResources.resx
                                β”œβ”€β”€ PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

DespuΓ©s de definir tus entidades, usa Lino para gestionar las Migrations y mantener la base de datos sincronizada. Este proceso se abordarΓ‘ en detalle en la secciΓ³n Capa de Persistencia.

Flujo actual de evoluciΓ³n

AdemΓ‘s del flujo interactivo con lino entity new, la CLI tambiΓ©n permite dejar explΓ­cita la intenciΓ³n en el comando cuando el servicio, el mΓ³dulo y el nombre ya se conocen:

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

Use entity list antes de crear nuevos conceptos en mΓ³dulos grandes. Durante la ediciΓ³n, revise identificador, propiedades, obligatoriedad, longitud, relaciones, Γ­ndices, ownership, tenant e impacto en migrations.

Strongly Typed IDs, ownership e invariantes

Cuando Strongly Typed IDs estΓ‘n habilitados, el identificador pasa a ser un tipo dedicado, como ProductId, reduciendo intercambios accidentales entre IDs de entidades diferentes. Las relaciones deben reflejar el ownership real del dominio: no toda referencia necesita convertirse en navegaciΓ³n directa; en muchos casos, un identificador, shadow entity, evento de integraciΓ³n o consulta explΓ­cita protege mejor el lΓ­mite entre mΓ³dulos.

Coloque las invariantes en el dominio, no solo en la UI, en la base de datos o en validators. DespuΓ©s de cambiar entidades, ejecute un build, revise el diff y genere o actualice migrations.

Value Objects

Un Value Object representa un concepto del dominio definido ΓΊnicamente por sus atributos – no posee identidad propia. Dos Value Objects se consideran iguales si todos sus valores son iguales.

CaracterΓ­sticas principales:

  • Inmutables despuΓ©s de la creaciΓ³n.
  • No poseen Id.

Creando un Value Object con Lino

Ejecute:

lino value-object new

El CLI solicitarΓ‘:

  • Servicio – Servicio en el que se crearΓ‘ el objeto.
  • MΓ³dulo – MΓ³dulo en el que se crearΓ‘ el objeto (solo en servicios modulares).
  • UbicaciΓ³n – RaΓ­z del dominio o agregado especΓ­fico.
  • Nombre del Value Object.

Luego, defina los campos que componen el objeto.

Tipos de campos disponibles

TipoDescripciΓ³nObservaciones
shortEntero de 16 bits-32.768 β†’ 32.767
intEntero de 32 bits-2.147.483.648 β†’ 2.147.483.647
longEntero de 64 bits-9.223.372.036.854.775.808 β†’ 9.223.372.036.854.775.807
stringTextoHasta ~2 mil millones de caracteres
boolBooleanotrue/false
decimalDecimal precisoValores monetarios
floatPunto flotante (32 bits)β‰ˆ 6–9 dΓ­gitos
doublePunto flotante (64 bits)β‰ˆ 15–17 dΓ­gitos
DateTimeFecha/horaIncluye zona horaria
DateOnlySΓ³lo fechaC# 10+
TimeOnlySΓ³lo horaC# 10+

Ejemplo

Value Object Address:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Property name β”‚ Type   β”‚ Length β”‚ Required  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Street        β”‚ string β”‚  100   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Number        β”‚ string β”‚   10   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Neighborhood  β”‚ string β”‚   50   β”‚           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ City          β”‚ string β”‚  100   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ State         β”‚ string β”‚   2    β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PostalCode    β”‚ string β”‚   20   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Country       β”‚ string β”‚  100   β”‚     x     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Estructura de archivos generada (agregado Person):

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ ValueObjects/
                        β”‚   └── Address.cs
                        β”œβ”€β”€ Errors/
                        β”‚   β”œβ”€β”€ AddressErrors.cs
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            β”œβ”€β”€ Address/
                            β”‚   β”œβ”€β”€ AddressResources.resx
                            β”‚   β”œβ”€β”€ AddressResources.en.resx
                            β”‚   └── AddressResources.pt-BR.resx
                            └── Person/
                                β”œβ”€β”€ PersonResources.resx
                                β”œβ”€β”€ PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

AsΓ­ como en las entidades, las Migrations pueden ser gestionadas por Lino para mantener el modelo de datos sincronizado.

Flujo actual de evoluciΓ³n

AdemΓ‘s del comando interactivo, use parΓ‘metros cuando ya sepa dΓ³nde debe nacer el concepto:

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

Cuando una propiedad de entidad se declara como ValueObject, Lino puede reutilizar un Value Object existente o crear uno nuevo durante el modelado de la entidad. Prefiera esta opciΓ³n cuando el conjunto de campos represente un ΓΊnico concepto, como dinero, direcciΓ³n, dimensiones, perΓ­odo o documento.

Persistencia, UI y localizaciΓ³n

Lino mapea propiedades de Value Objects mediante la capa de persistencia del mΓ³dulo y lleva metadatos de visualizaciΓ³n a resources. Esto permite labels y mensajes localizados para valores anidados. El Value Object pertenece al mΓ³dulo donde fue generado; no se convierte en un contrato compartido entre mΓ³dulos sin una integraciΓ³n explΓ­cita.

Proteja las invariantes en el propio tipo: valores negativos, moneda invΓ‘lida, perΓ­odos invertidos o documentos mal formados no deben nacer vΓ‘lidos.

Enumeraciones

Las enumeraciones en DDD pueden ir mΓ‘s allΓ‘ de los enum tradicionales de C#. Pueden ser objetos ricos que representan estados fijos, conteniendo validaciones, mΓ©todos auxiliares e incluso comportamiento.

MotivaciΓ³n:

  • Los enum de C# estΓ‘n limitados a un valor entero o cadena.
  • Modelar una EnumeraciΓ³n como clase ofrece mayor flexibilidad y expresividad.

CaracterΓ­sticas principales:

  • Son clases que heredan de una base comΓΊn y encapsulan Id y Nombre.
  • Permiten agregar validaciones, mΓ©todos auxiliares y comportamiento.

Creando una enumeraciΓ³n con Lino

Ejecute:

lino enumeration new

El asistente solicitarΓ‘:

  • Servicio.
  • MΓ³dulo (si aplica).
  • UbicaciΓ³n – raΓ­z del dominio o agregado.
  • Nombre de la enumeraciΓ³n.
  • Tipo – enum tradicional o Smart Enum (class).
  • Almacenamiento – int o string en la base de datos.

Ejemplo

EnumeraciΓ³n PersonStatus:

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

Estructura generada:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ Enums/
                        β”‚   └── PersonStatus.cs
                        β”œβ”€β”€ ValueObjects/
                        β”‚   └── Address.cs
                        β”œβ”€β”€ Errors/
                        β”‚   β”œβ”€β”€ AddressErrors.cs
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            β”œβ”€β”€ Address/
                            β”‚   β”œβ”€β”€ AddressResources.resx
                            β”‚   β”œβ”€β”€ AddressResources.en.resx
                            β”‚   └── AddressResources.pt-BR.resx
                            β”œβ”€β”€ Person/
                            β”‚   β”œβ”€β”€ PersonResources.resx
                            β”‚   β”œβ”€β”€ PersonResources.en.resx
                            β”‚   └── PersonResources.pt-BR.resx
                            └── PersonStatus/
                                β”œβ”€β”€ PersonStatusResources.resx
                                β”œβ”€β”€ PersonStatusResources.en.resx
                                └── PersonStatusResources.pt-BR.resx

Almacenar el valor de una enumeraciΓ³n como string es vΓ‘lido y puede mejorar la legibilidad, pero suele ser menos eficiente en tΓ©rminos de rendimiento y almacenamiento. Por eso recomendamos almacenar el valor como int, y para mantener la integridad referencial y facilitar el mantenimiento, crear una entidad (tabla) auxiliar donde la clave primaria corresponda al valor de la enumeraciΓ³n.

DespuΓ©s de definir las enumeraciones, use Lino para generar y aplicar las Migrations, asegurando que la base de datos refleje el modelo de dominio. Vea detalles en la secciΓ³n Capa de Persistencia.

Flujo actual de evoluciΓ³n

AdemΓ‘s del comando interactivo, use parΓ‘metros cuando la ubicaciΓ³n y el nombre ya estΓ©n definidos:

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

Enumeration, entidad o configuraciΓ³n

Use enumerationUse entidad o configuraciΓ³n
Estado de pedido, estado de publicaciΓ³n, tipo tΓ©cnico de integraciΓ³nCategorΓ­a administrable por usuario, motivo configurable por tenant, proveedor gestionado en backoffice
Valores versionados junto con el cΓ³digoValores modificados en runtime o controlados por permisos
Regla simple o pequeΓ±o comportamiento por valorCiclo de vida, auditorΓ­a, traducciΓ³n dinΓ‘mica o relaciΓ³n propia

Si la enumeration expone un nombre de visualizaciΓ³n, Lino puede generar resources para labels localizados. Si se persiste o se usa como seed data, revise migrations despuΓ©s de agregar, eliminar o renombrar valores. La consistencia entre cΓ³digo, base de datos y UI forma parte del cambio de dominio.

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