Modélisation du domaine
Au cœur de toute application orientée domaine se trouve le modèle qui représente la connaissance centrale et les règles métier du système. Bien modéliser le domaine signifie traduire les concepts du monde réel en structures logicielles expressives, cohérentes et consistantes.
Entités
Une entité est un objet défini principalement par son identité et non uniquement par ses attributs. Même si les attributs changent au fil du temps, l'identité d'une entité reste la même.
Caractéristiques principales :
- Possède une identité unique (généralement un
Id). - Ce qui importe, c'est qui est l'entité, pas seulement ce qu'elle contient.
- Ses attributs peuvent changer au fil du temps.
Créer une entité avec Lino
Pour créer une nouvelle entité en utilisant Lino, exécutez :
lino entity new
L'assistant CLI vous demandera :
- Service – Le service dans lequel l'entité sera créée.
- Module – Le module dans lequel l'entité sera créée (uniquement dans les services modulaires).
- Nom de l'entité – Le nom utilisé dans le domaine et dans la table de la base de données.
Ensuite, vous définirez les champs qui composent l'entité en configurant chacun d'eux.
Types de champs disponibles
| Type | Description | Plage / Observations |
|---|---|---|
short | Entier 16 bits | -32 768 → 32 767 |
int | Entier 32 bits | -2 147 483 648 → 2 147 483 647 |
long | Entier 64 bits | -9 223 372 036 854 775 808 → 9 223 372 036 854 775 807 |
string | Texte | Jusqu'à environ 2 milliards de caractères |
bool | Valeur booléenne | true ou false |
Guid | Identifiant global unique | Unicité distribuée |
decimal | Nombre décimal haute précision | Idéal pour valeurs monétaires |
float | Nombre flottant (32 bits) | ≈ 6–9 chiffres de précision |
double | Nombre flottant (64 bits) | ≈ 15–17 chiffres de précision |
DateTime | Date et heure | Inclut le fuseau horaire |
DateOnly | Seulement la date (C# 10+) | – |
TimeOnly | Seulement l'heure (C# 10+) | – |
Entity | Référence à une autre entité | 1:1 ou 1:N |
Value Object | Value Object immuable | Ex.: Adresse, CPF |
Enum | Énumération | Ensemble fixe de valeurs |
List<Entity> | Liste d'entités | 1:N |
ManyToMany | Plusieurs-à-plusieurs | Nécessite une table de jointure |
Exemple
Création de l'entité Person :
┌────┬────┬───────────────┬────────┬────────┬───────────┬────────────────┐ │ PK │ FK │ Property name │ Type │ Length │ Required │ Auto-increment │ ├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤ │ x │ │ Id │ int │ │ x │ x │ ├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤ │ │ │ Name │ string │ 100 │ x │ │ └────┴────┴───────────────┴────────┴────────┴───────────┴────────────────┘
Structure générée par 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
Après avoir défini vos entités, utilisez Lino lui-même pour gérer les Migrations et garder la base de données synchronisée. Ce processus sera détaillé dans la section Couche de Persistance.
Flux actuel d'évolution
En plus du flux interactif avec lino entity new, la CLI permet aussi d'exprimer explicitement l'intention dans la commande lorsque le service, le module et le nom sont déjà connus :
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>
Utilisez entity list avant de créer de nouveaux concepts dans de grands modules. Pendant l'édition, vérifiez l'identifiant, les propriétés, l'obligation, la longueur, les relations, les index, l'ownership, le tenant et l'impact sur les migrations.
Strongly Typed IDs, ownership et invariants
Lorsque les Strongly Typed IDs sont activés, l'identifiant devient un type dédié, comme ProductId, ce qui réduit les confusions accidentelles entre IDs d'entités différentes. Les relations doivent refléter l'ownership réel du domaine : toute référence n'a pas besoin de devenir une navigation directe ; souvent, un identifiant, une shadow entity, un événement d'intégration ou une requête explicite protège mieux les limites entre modules.
Placez les invariants dans le domaine, pas seulement dans l'UI, la base de données ou les validators. Après avoir modifié des entités, exécutez un build, vérifiez le diff et générez ou mettez à jour les migrations.
Value Objects
Un Value Object représente un concept du domaine défini uniquement par ses attributs – il ne possède pas d’identité propre. Deux Value Objects sont considérés comme égaux si toutes leurs valeurs sont identiques.
Principales caractéristiques :
- Immuables après création.
- Ne possèdent pas de
Id.
Créer un Value Object avec Lino
Exécutez :
lino value-object new
Le CLI demandera :
- Service – Service dans lequel l’objet sera créé.
- Module – Module dans lequel l’objet sera créé (uniquement dans les services modulaires).
- Emplacement – Racine du domaine ou agrégat spécifique.
- Nom de l’Value Object.
Puis définissez les champs qui composent l’objet.
Types de champs disponibles
| Type | Description | Remarques |
|---|---|---|
short | Entier 16 bits | -32 768 → 32 767 |
int | Entier 32 bits | -2 147 483 648 → 2 147 483 647 |
long | Entier 64 bits | -9 223 372 036 854 775 808 → 9 223 372 036 854 775 807 |
string | Texte | Jusqu’à ~2 milliards de caractères |
bool | Booléen | true/false |
decimal | Décimal précis | Valeurs monétaires |
float | Nombre à virgule flottante (32 bits) | ≈ 6–9 chiffres |
double | Nombre à virgule flottante (64 bits) | ≈ 15–17 chiffres |
DateTime | Date/heure | Inclut le fuseau horaire |
DateOnly | Seulement la date | C# 10+ |
TimeOnly | Seulement l’heure | C# 10+ |
Exemple
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 │ └───────────────┴────────┴────────┴───────────┘
Structure des fichiers générée (agrégat 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
Comme pour les entités, les Migrations peuvent être gérées par Lino pour maintenir la synchronisation du modèle de données.
Flux actuel d'évolution
En plus de la commande interactive, utilisez des paramètres lorsque vous savez déjà où le concept doit être créé :
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>
Lorsqu'une propriété d'entité est déclarée comme ValueObject, Lino peut réutiliser un Value Object existant ou en créer un nouveau pendant la modélisation de l'entité. Préférez cette option lorsque l'ensemble de champs représente un seul concept, comme l'argent, l'adresse, les dimensions, la période ou le document.
Persistance, UI et localisation
Lino mappe les propriétés des Value Objects via la couche de persistance du module et transporte les métadonnées d'affichage vers les resources. Cela permet des labels et messages localisés pour les valeurs imbriquées. Le Value Object appartient au module où il a été généré ; il ne devient pas un contrat partagé entre modules sans intégration explicite.
Protégez les invariants dans le type lui-même : les valeurs négatives, une devise invalide, des périodes inversées ou des documents mal formés ne doivent pas naître valides.
Énumérations
Les énumérations en DDD peuvent aller au-delà des enum traditionnels de C#. Elles peuvent être des objets riches
représentant des états fixes, contenant des validations, des méthodes auxiliaires et même des comportements.
Motivation :
- Les
enumde C# sont limités à une valeur entière ou chaîne. - Modéliser une Enumeration comme une classe offre plus de flexibilité et d’expressivité.
Caractéristiques principales :
- Ce sont des classes héritant d’une base commune et encapsulant
IdetNom. - Elles permettent d’ajouter des validations, des méthodes auxiliaires et des comportements.
Créer une énumération avec Lino
Exécutez :
lino enumeration new
L’assistant vous demandera :
- Service.
- Module (si applicable).
- Emplacement – racine du domaine ou agrégat.
- Nom de l’énumération.
- Type –
enumtraditionnel ou Smart Enum (class). - Stockage –
intoustringen base de données.
Exemple
Énumération PersonStatus :
┌───────┬───────────┬──────────────┐ │ Value │ Name │ Display Name │ ├───────┼───────────┼──────────────┤ │ 1 │ Active │ Active │ ├───────┼───────────┼──────────────┤ │ 2 │ Inactive │ Inactive │ ├───────┼───────────┼──────────────┤ │ 3 │ Suspended │ Suspended │ ├───────┼───────────┼──────────────┤ │ 4 │ Deleted │ Deleted │ └───────┴───────────┴──────────────┘
Structure générée :
<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
Stocker la valeur d’une énumération comme string est valide et peut améliorer la lisibilité, mais tend à être moins efficace en termes de performances et de stockage.
C’est pourquoi nous recommandons de stocker la valeur comme int, et pour maintenir l’intégrité référentielle et faciliter la maintenance, de créer une entité (table) auxiliaire dont la clé primaire correspond à la valeur de l’énumération.
Après avoir défini les énumérations, utilisez Lino pour générer et appliquer les Migrations, garantissant que la base de données reflète le modèle de domaine. Voir les détails dans la section Couche de Persistance.
Flux actuel d'évolution
En plus de la commande interactive, utilisez des paramètres lorsque l'emplacement et le nom sont déjà définis :
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é ou configuration
| Utiliser une enumeration | Utiliser une entité ou une configuration |
|---|---|
| Statut de commande, état de publication, type technique d'intégration | Catégorie administrable par l'utilisateur, motif configurable par tenant, fournisseur géré dans le backoffice |
| Valeurs versionnées avec le code | Valeurs modifiées au runtime ou contrôlées par des permissions |
| Règle simple ou petit comportement par valeur | Cycle de vie, audit, traduction dynamique ou relation propre |
Si l'enumeration expose un nom d'affichage, Lino peut générer des resources pour des labels localisés. Si elle est persistée ou utilisée comme seed data, vérifiez les migrations après l'ajout, la suppression ou le renommage de valeurs. La cohérence entre le code, la base de données et l'UI fait partie du changement de domaine.
