Modellare il dominio

Al cuore di qualsiasi applicazione guidata dal dominio c’è il modello che rappresenta la conoscenza centrale e le regole di business del sistema. Modellare bene il dominio significa tradurre i concetti del mondo reale in strutture software espressive, coese e consistenti.

EntitΓ 

Un'entitΓ  Γ¨ un oggetto definito principalmente dalla sua identitΓ  e non solo dai suoi attributi. Anche se gli attributi cambiano nel tempo, l'identitΓ  di un'entitΓ  rimane la stessa.

Caratteristiche principali:

  • Possiede un'identitΓ  unica (di solito un Id).
  • CiΓ² che conta Γ¨ chi Γ¨ l'entitΓ , non solo cosa contiene.
  • I suoi attributi possono cambiare nel tempo.

Creare un'entitΓ  con Lino

Per creare una nuova entitΓ  usando Lino, esegui:

lino entity new

Il wizard CLI ti chiederΓ :

  • Servizio – Il servizio in cui l'entitΓ  sarΓ  creata.
  • Modulo – Il modulo in cui sarΓ  creata l'entitΓ  (solo per servizi modulari).
  • Nome dell'entitΓ  – Il nome usato nel dominio e nella tabella del database.

Successivamente, definirai i campi che compongono l'entitΓ  configurando ciascuno di essi.

Tipi di campi disponibili

Tipo Descrizione Intervallo / Note
shortIntero a 16 bit-32.768 β†’ 32.767
intIntero a 32 bit-2.147.483.648 β†’ 2.147.483.647
longIntero a 64 bit-9.223.372.036.854.775.808 β†’ 9.223.372.036.854.775.807
stringTestoFino a circa 2 miliardi di caratteri
boolValore booleanotrue o false
GuidIdentificatore globale univocoUnicitΓ  distribuita
decimalNumero decimale ad alta precisioneIdeale per valori monetari
floatPunto flottante (32 bit)β‰ˆ 6–9 cifre di precisione
doublePunto flottante (64 bit)β‰ˆ 15–17 cifre di precisione
DateTimeData e oraInclude il fuso orario
DateOnlySolo data (C# 10+)–
TimeOnlySolo ora (C# 10+)–
EntityRiferimento a un'altra entitΓ 1:1 o 1:N
Value ObjectValue Object immutabileEs.: Indirizzo, CPF
EnumEnumerazioneInsieme fisso di valori
List<Entity>Lista di entitΓ 1:N
ManyToManyMolti-a-moltiRichiede tabella di join

Esempio

Creazione dell'entitΓ  Person:

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

Struttura generata da 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

Dopo aver definito le tue entitΓ , usa Lino stesso per gestire le Migrations e mantenere sincronizzato il database. Questo processo sarΓ  trattato in dettaglio nella sezione Layer di Persistenza.

Flusso attuale di evoluzione

Oltre al flusso interattivo con lino entity new, la CLI consente anche di rendere esplicita l'intenzione nel comando quando servizio, modulo e nome sono giΓ  noti:

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>

Usare entity list prima di creare nuovi concetti in moduli grandi. Durante le modifiche, verificare identificatore, proprietΓ , obbligatorietΓ , lunghezza, relazioni, indici, ownership, tenant e impatto sulle migrations.

Strongly Typed IDs, ownership e invarianti

Quando gli Strongly Typed IDs sono abilitati, l'identificatore diventa un tipo dedicato, come ProductId, riducendo scambi accidentali tra IDs di entitΓ  diverse. Le relazioni devono riflettere il reale ownership del dominio: non ogni riferimento deve diventare una navigazione diretta; spesso un identificatore, una shadow entity, un evento di integrazione o una query esplicita protegge meglio i confini tra moduli.

Collocare le invarianti nel dominio, non solo nella UI, nel database o nei validators. Dopo aver modificato le entitΓ , eseguire una build, rivedere il diff e generare o aggiornare le migrations.

Value Objects

Un Value Object rappresenta un concetto di dominio definito solo dai suoi attributi – non possiede un’identitΓ  propria. Due Value Objects sono considerati uguali se tutti i loro valori sono uguali.

Caratteristiche principali:

  • Immutabili dopo la creazione.
  • Non possiedono Id.

Creare un Value Object con Lino

Esegui:

lino value-object new

Il CLI chiederΓ :

  • Servizio – Servizio in cui sarΓ  creato l’oggetto.
  • Modulo – Modulo in cui sarΓ  creato l’oggetto (solo nei servizi modulari).
  • Posizione – Radice del dominio o specifico aggregato.
  • Nome dell’Value Object.

Quindi definisci i campi che compongono l’oggetto.

Tipi di campo disponibili

TipoDescrizioneNote
shortIntero a 16 bit-32.768 β†’ 32.767
intIntero a 32 bit-2.147.483.648 β†’ 2.147.483.647
longIntero a 64 bit-9.223.372.036.854.775.808 β†’ 9.223.372.036.854.775.807
stringTestoFino a ~2 miliardi di caratteri
boolBooleanotrue/false
decimalDecimale precisoValori monetari
floatVirgola mobile (32 bit)β‰ˆ 6–9 cifre
doubleVirgola mobile (64 bit)β‰ˆ 15–17 cifre
DateTimeData/oraInclude fuso orario
DateOnlySolo dataC# 10+
TimeOnlySolo oraC# 10+

Esempio

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     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Struttura dei file generata (aggregato 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

Come per le entitΓ , le Migrations possono essere gestite da Lino per mantenere il modello dati sincronizzato.

Flusso attuale di evoluzione

Oltre al comando interattivo, usare i parametri quando si sa giΓ  dove il concetto deve nascere:

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>

Quando una proprietΓ  di entitΓ  Γ¨ dichiarata come ValueObject, Lino puΓ² riutilizzare un Value Object esistente o crearne uno nuovo durante la modellazione dell'entitΓ . Preferire questa opzione quando l'insieme di campi rappresenta un unico concetto, come denaro, indirizzo, dimensioni, periodo o documento.

Persistenza, UI e localizzazione

Lino mappa le proprietΓ  dei Value Objects tramite il layer di persistenza del modulo e porta i metadati di visualizzazione nei resources. Questo abilita labels e messaggi localizzati per valori annidati. Il Value Object appartiene al modulo in cui Γ¨ stato generato; non diventa un contratto condiviso tra moduli senza un'integrazione esplicita.

Proteggere le invarianti nel tipo stesso: valori negativi, valuta non valida, periodi invertiti o documenti malformati non devono nascere validi.

Enumerazioni

Le enumerazioni nel DDD possono andare oltre i tradizionali enum di C#. Possono essere oggetti ricchi che rappresentano stati fissi, contenenti validazioni, metodi ausiliari e persino comportamenti.

Motivazione:

  • I enum di C# sono limitati a un valore intero o stringa.
  • Modellare un'Enumeration come classe offre maggiore flessibilitΓ  ed espressivitΓ .

Caratteristiche principali:

  • Sono classi che ereditano da una base comune e incapsulano Id e Nome.
  • Permettono di aggiungere validazioni, metodi ausiliari e comportamenti.

Creare un'enumerazione con Lino

Esegui:

lino enumeration new

L'assistente richiederΓ :

  • Servizio.
  • Modulo (se applicabile).
  • Posizione – radice del dominio o aggregato.
  • Nome dell'enumerazione.
  • Tipo – enum tradizionale o Smart Enum (class).
  • Archiviazione – int o string nel database.

Esempio

Enumerazione PersonStatus:

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

Struttura generata:

<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

Archiviare il valore di un'enumerazione come string Γ¨ valido e puΓ² migliorare la leggibilitΓ , ma tende ad essere meno efficiente in termini di prestazioni e spazio di archiviazione. Per questo, consigliamo di archiviare il valore come int e, per mantenere l'integritΓ  referenziale e facilitare la manutenzione, creare un'entitΓ  (tabella) ausiliaria dove la chiave primaria corrisponda al valore dell'enumerazione.

Dopo aver definito le enumerazioni, usa Lino per generare e applicare le Migrations, garantendo che il database rifletta il modello di dominio. Vedi i dettagli nella sezione Layer di Persistenza.

Flusso attuale di evoluzione

Oltre al comando interattivo, usare i parametri quando posizione e nome sono giΓ  definiti:

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, entitΓ  o configurazione

Usare enumerationUsare entitΓ  o configurazione
Stato dell'ordine, stato di pubblicazione, tipo tecnico di integrazioneCategoria gestibile dall'utente, motivo configurabile per tenant, fornitore gestito nel backoffice
Valori versionati insieme al codiceValori modificati a runtime o controllati da permessi
Regola semplice o piccolo comportamento per valoreCiclo di vita, audit, traduzione dinamica o relazione propria

Se la enumeration espone un nome visualizzato, Lino puΓ² generare resources per labels localizzate. Se viene persistita o usata come seed data, rivedere le migrations dopo aver aggiunto, rimosso o rinominato valori. La coerenza tra codice, database e UI fa parte della modifica di dominio.

Si è verificato un errore non gestito. Ricarica πŸ—™