Définition des cas d'utilisation de l'application
Dans cette section, nous allons explorer comment définir les cas d’utilisation de votre application à l’aide de Lino CLI. Les cas d’utilisation représentent des interactions spécifiques entre les utilisateurs ou les systèmes externes et l’application, en encapsulant les règles métier et en promouvant une architecture orientée domaine. Nous aborderons la séparation des opérations de lecture et d’écriture à travers le pattern CQRS, ainsi que la standardisation des réponses en utilisant le Result Pattern.
Le pattern CQRS (Command Query Responsibility Segregation) propose de séparer les opérations qui modifient l’état de l’application (Commands) de celles qui ne font que consulter les données (Queries). Cette approche permet d’optimiser chaque type d’opération de manière indépendante, améliorant ainsi l’évolutivité, les performances et la maintenabilité du système.
Le Result Pattern est utilisé pour standardiser le retour des opérations, en encapsulant des informations sur le succès ou l’échec, les messages d’erreur et les données résultantes. Cela facilite la gestion cohérente des réponses à travers les différentes couches de l’application.
Nous verrons comment sont structurés les fichiers de Commands et de Queries, la validation des données, comment la logique métier est appliquée dans les Handlers, et comment retourner des résultats de manière standardisée. Tout cela est réalisé grâce à la génération automatique de ces composants via le CLI de Lino.
Remarque : Bien que cela ne soit pas obligatoire, Lino propose actuellement deux options pour appliquer le pattern CQRS dans la couche applicative : en utilisant le pattern Mediator via la bibliothèque MediatR
(de Jimmy Bogard) ou la bibliothèque Mediator
(de Martin Othamar).
Vue d’ensemble des cas d’utilisation
Un cas d’utilisation représente une interaction complète entre des utilisateurs ou des systèmes externes et l’application, décrivant des scénarios métier spécifiques. Dans Lino, chaque cas d’utilisation est divisé en :
- Command : représente l’intention de modifier l’état du système (créer, mettre à jour, supprimer, etc.).
- Query : représente l’intention de consulter des données sans modifier l’état du domaine.
Cette séparation favorise la clarté du code, facilite les tests, permet une scalabilité indépendante pour la lecture et l’écriture, et s’aligne sur les principes de l’architecture propre et les bonnes pratiques du Domain-Driven Design (DDD).
Commandes
Une Commande est un message immuable qui contient uniquement les données nécessaires pour exécuter une action modifiant l’état du système (par exemple, CreateInvoice
, DeactivateUser
).
Elle doit contenir uniquement les propriétés représentant les informations essentielles à l’exécution de l’opération.
Caractéristiques d’une Commande
- Immutabilité : implémentée comme un
record
ou une classe avec uniquement desget
, sans setters publics. - Nom à l’impératif : reflète l’action qui sera exécutée, par exemple
CreateOrder
,UpdateCustomerAddress
. - Données minimales : contient uniquement les champs nécessaires à l’exécution de l’opération, sans retourner de grandes quantités de données.
- Validation isolée : chaque Commande possède ses propres règles de validation, garantissant sa cohérence avant d’atteindre le gestionnaire (Handler).
Validateurs de Commande
Les Validateurs de Commande s’assurent que la Commande est bien formée et respecte les exigences métier avant d’être envoyée au gestionnaire. Dans Lino, nous utilisons la bibliothèque FluentValidation pour implémenter ces validations, car elle est largement adoptée dans les projets .NET et offre une API Fluent pour la création des règles.
Règles communes de validation
NotEmpty
,NotNull
pour les champs obligatoires.InclusiveBetween
pour les plages numériques (ex. : valeurs monétaires, quantités minimum/maximum).MaximumLength
etMinimumLength
pour la longueur des chaînes de caractères.RuleForEach
pour la validation des éléments dans les collections (List<T>
).Must
pour les règles personnalisées (ex. : format des documents, validation des dates).
Gestionnaires de Commande
Le Gestionnaire de Commande est responsable d’exécuter la logique métier associée à cette Commande. Il orchestre les repositories, les unités de travail (IUnitOfWork
), les services auxiliaires et déclenche les événements de domaine si nécessaire.
Modèle d’implémentation d’un gestionnaire
- Recevoir les dépendances (repositories, services externes, UnitOfWork) via injection de dépendances.
- Mapper et instancier les entités du domaine, garantissant la cohérence initiale.
- Appliquer les règles métier (validations supplémentaires, calcul des valeurs, déclenchement d’événements de domaine).
- Persister les modifications via
IUnitOfWork
ou des repositories directs. - Retourner un Résultat de Commande, indiquant succès ou échec (
Result<T>
).
Résultats de Commande et Result Pattern
Le Résultat de Commande doit être un DTO simple contenant uniquement les données minimales nécessaires pour que l’appelant (par exemple, une API ou un front-end) puisse continuer. Il inclut généralement l’identifiant de l’entité créée ou mise à jour (Id
) et, en cas d’échec, des informations standardisées sur l’erreur.
Pour les scénarios où la Commande peut échouer, nous utilisons le Result Pattern : une abstraction qui encapsule le résultat d’une opération, pouvant être un succès ou un échec. En .NET, il est courant d’utiliser des types comme Result<T>
(ou des bibliothèques similaires), qui :
- Permettent de définir une
Value
en cas de succès (par exemple, un DTO simple). - En cas d’échec, stockent des codes ou messages standardisés (
Error
), éventuellement avec des métadonnées supplémentaires (codes HTTP, détails de validation, etc.). - Facilitent l’unification du flux de gestion des erreurs à travers toute l’API, maintenant la cohérence entre les couches.
Créer une Commande avec le CLI
Lino simplifie la génération de tous les artefacts nécessaires pour une nouvelle Commande via la commande :
lino command new
Lors de l’exécution de cette commande, l’assistant interactif demandera :
- Service — Nom du service où la Commande sera créée.
- Module — Dans les services modulaires, définit le module cible.
- Entité — Entité de domaine liée à la Commande (optionnelle mais recommandée).
- Nom de la Commande — Nom final de la Commande (par exemple,
CreateOrder
).
Après confirmation, Lino créera automatiquement les fichiers :
CreateOrderCommand.cs
CreateOrderCommandValidator.cs
CreateOrderCommandHandler.cs
CreateOrderCommandResult.cs
Exemple de structure générée
Considérez la Commande CreatePerson
. La structure générée ressemblera à :
MyApp/ └── src/ └── Services/ └── MyService/ └── Application/ ├── MyApp.MyService.Application.csproj └── UseCases/ └── People/ ├── Commands/ │ └── CreatePerson/ │ ├── CreatePersonCommand.cs │ ├── CreatePersonCommandValidator.cs │ ├── CreatePersonCommandHandler.cs │ └── CreatePersonCommandResult.cs └── Queries/ └── ...
RequĂŞtes
Une requête représente l’intention d’obtenir des données sans modifier l’état du domaine. Les requêtes sont conçues pour être efficaces en lecture, en retournant uniquement les champs nécessaires, sans charger des entités complètes lorsqu’elles ne sont pas demandées.
Caractéristiques d’une Requête
- Immuable — comme les Commandes, une Requête ne doit pas être modifiée après sa création.
- Nom descriptif — reflète les informations recherchées, par exemple :
GetCustomerById
,ListOrdersByDateRange
. - Filtres et Pagination — peut recevoir des paramètres pour appliquer des recherches spécifiques (dates, statut, page, tri).
- Projection — doit retourner un DTO ou un modèle de vue contenant uniquement les champs nécessaires, évitant le chargement complet des objets de domaine.
Validateurs de RequĂŞte
Les validateurs de requête ont pour responsabilité de valider les paramètres d’entrée, tels que les filtres, les valeurs de pagination (page, pageSize) et les règles de sécurité (autorisations utilisateur, visibilité des données). Ils sont également implémentés avec FluentValidation.
Règles de Validation Courantes pour les Requêtes
GreaterThanOrEqualTo
ouLessThanOrEqualTo
pour les filtres par plage (ex. : dates).Length
pour les filtres textuels (ex. : recherche par nom ou e-mail).InclusiveBetween
pour les paramètres de pagination (ex. : nombre minimum/maximum d’éléments par page).
Gestionnaires de RequĂŞte
Le gestionnaire de requête est responsable d’interroger les dépôts ou le contexte de base de données et de retourner des projections optimisées, en évitant le chargement complet des entités du domaine.
Dans Lino, il est recommandé d’utiliser des projections avec des méthodes comme .Select()
pour mapper directement les données dans le format attendu du DTO, garantissant ainsi efficacité et meilleures performances.
Résultats de Requête
Dans Lino, les résultats des requêtes sont toujours représentés par des records nommés avec le suffixe QueryResult
.
Cette convention s’applique quel que soit le type de réponse — qu’il s’agisse d’une liste d’éléments (avec ou sans pagination) ou d’un élément unique détaillé.
Créer une Requête avec le CLI
Comme pour les Commandes, Lino fournit la commande suivante :
lino query new
L’assistant vous demandera :
- Service — le service dans lequel la requête sera créée.
- Module — si applicable, définit le module du domaine.
- Entité — l’entité ou l’agrégat auquel la requête est associée (facultatif).
- Nom de la Requête — par exemple :
GetOrderById
ouListProductsByCategory
.
Lino générera automatiquement :
GetOrderByIdQuery.cs
GetOrderByIdQueryValidator.cs
GetOrderByIdQueryHandler.cs
GetOrderByIdQueryResult.cs
Exemple de Structure Générée pour les Requêtes
MyApp/ └── src/ └── Services/ └── MyService/ └── Application/ ├── MyApp.MyService.Application.csproj └── UseCases/ └── Orders/ ├── Commands/ | └── ... └── Queries/ └── GetOrderById/ ├── GetOrderByIdQuery.cs ├── GetOrderByIdQueryValidator.cs ├── GetOrderByIdQueryHandler.cs └── GetOrderByIdQueryResult.cs
Conclusion
Après avoir compris comment définir et structurer les Use Cases (Commands et Queries) dans Lino, vous êtes prêt à créer des scénarios métier robustes et évolutifs, en segmentant clairement la logique d’écriture et de lecture. Utilisez le CLI pour accélérer le développement, mais pensez toujours à revoir et ajuster les implémentations selon les besoins spécifiques de votre domaine.
Ensuite, explorez comment gérer la persistance, les événements de domaine et les services d’infrastructure pour composer toute l’architecture de votre application.