Créer une application .NET complète avec Lino : guide étape par étape

Cette rubrique fournit un guide pratique, étape par étape, pour utiliser Lino CLI comme outil principal dans la construction d'un projet : depuis l'installation et la configuration initiale, en passant par la génération de services, de modules et d'entités, jusqu'aux fonctionnalités avancées telles que les événements, les Background Jobs, les migrations, la création d'images Docker et le versioning.

L'objectif est de montrer, de manière intégrée, comment les Commands CLI s'intègrent dans le flux de développement réel — pas seulement les lister, mais expliquer pourquoi chaque choix est fait, ce qui est généré automatiquement et quelles sont les implications architecturales.

Même si chaque commande dispose déjà d'une documentation spécifique, vous verrez ici le processus de bout en bout — une feuille de route reproductible qui évite des heures de travail répétitif et permet de maintenir un code cohérent et testable.

Tout au long du guide, nous expliquerons les concepts techniques appliqués par Lino (par exemple : CQRS, TypedResults, Source Generators, Outbox Pattern), nous montrerons des exemples de Commands et indiquerons les bonnes pratiques pour le versioning, le déploiement et l'intégration continue.

Utilisez cette feuille de route comme séquence d'apprentissage : comprenez d'abord la structure, puis modélisez le domaine, puis générez ensuite des API, des pages, des événements et des artefacts de déploiement. Cela réduit les retouches et maintient le code généré aligné sur la conception du système.

Installation et configuration de Lino CLI

La première étape pour commencer à travailler avec le Lino CLI consiste à installer l’outil dans votre environnement de développement. Il est distribué sous forme de outil mondial dotnet, ce qui signifie qu'il sera disponible pour n'importe quel projet.NET sur votre ordinateur. Avant l'installation, confirmez que le SDK.NET requis par les modèles actuels est disponible et que le terminal peut s'exécuter dotnet et que le répertoire global des outils.NET se trouve dans le PATH.

Étape 1 : Installation

Pour installer (ou mettre à jour) Lino CLI, exécutez la commande suivante dans le terminal :

dotnet tool install --global Tolitech.Lino

Remarques importantes :

  • Si une version est déjà installée, vous pouvez utiliser dotnet tool update --global Tolitech.Lino à mettre à jour.
  • Vérifiez que le répertoire des outils globaux.NET se trouve dans le PATH du système afin que la commande lino fonctionner correctement.

Étape 2 : Configurer la langue

Après l'installation, il est recommandé de configurer la langue (ou la culture) que CLI utilisera dans les messages, les invites et les journaux :

lino preferences culture set

Il vous sera demandé de choisir parmi les langues disponibles. Ce paramètre garantit que toutes les instructions et invites apparaissent de manière cohérente dans la langue souhaitée. Cette préférence modifie les messages, invites et conseils localisés de CLI ; il ne renomme pas les entités, services, modules ou termes métier générés dans le projet.

Étape 3: Authentification et enregistrement

Pour accéder à toutes les fonctionnalités de Lino, y compris les modèles avancés, la publication d'images Docker et les intégrations avec des services externes, vous devez être authentifié.

- Si vous n'êtes pas encore inscrit, inscrivez-vous avec la commande :

lino user register

- Si vous êtes déjà inscrit, connectez-vous avec:

lino auth login

Ce qui se produit: CLI stocke localement un jeton d'authentification, vous permettant d'exécuter des Commands nécessitant un accès à des ressources protégées sans avoir à vous connecter à chaque fois que vous l'utilisez. Gardez ce jeton privé et évitez de partager le même profil utilisateur entre différents développeurs, agents CI ou machines.

Étape 4 : Vérification

Pour confirmer que l'installation et l'authentification ont réussi, exécutez :

lino --version

Si la commande renvoie la version installée, vous êtes prêt à commencer à utiliser le Lino CLI dans vos projets.

Création du projet MyApp

Dans cette étape, nous créerons la structure initiale du projet en utilisant le Lino CLI. Ce projet servira de base pour démontrer la création de services, de modules, le front-end et l'intégration de tous les événements. Un projet Lino n'est pas seulement un dossier avec une solution : il définit les conventions pour les limites des services, les bibliothèques partagées, l'hôte Aspire, le framework frontend, les tests, la gestion des packages, les analyseurs et les configurations que les Commands suivantes réutilisent.

Étape 1 : Exécuter la commande de création

Pour créer un nouveau projet, exécutez la commande ci-dessous dans le terminal :

lino project new

CLI vous guidera étape par étape, en vous demandant des informations telles que :

  • Nom du projet : nous utiliserons MyApp, mais vous pouvez choisir le nom de votre choix ;
  • Fonctionnalités supplémentaires : analyseurs de code, mise en cache distribuée, prise en charge des événements asynchrones, etc.

Étape 2 : configuration des fonctionnalités essentielles

Pour ce projet, nous vous recommandons d'activer les fonctionnalités suivantes dès le début :

  • Analyseurs de code : garantir que le code suit de bonnes pratiques et des normes cohérentes, évitant ainsi les erreurs de mise en œuvre courantes ;
  • Cache distribué : améliore les performances des applications dans des scénarios avec plusieurs services, en évitant les Queries de base de données inutiles ;
  • Communication asynchrone : permet l'utilisation d'événements et de files d'attente pour l'intégration entre les services, garantissant ainsi l'évolutivité et le découplage.

Il est important d'activer toutes ces options dans ce projet, car nous créerons plusieurs services qui communiqueront via des événements d'intégration. Cela vous permettra de comprendre comment structurer des systèmes modulaires et distribués à l'aide de Lino.

Étape 3 : Structure générée

Après avoir exécuté la commande et configuré les ressources, CLI générera la structure initiale du projet. Il comprendra:

  • Dossiers de services et de modules ;
  • Modèles frontaux (le cas échéant) ;
  • Paramètres de cache initiaux, événements et intégrations ;
  • Fichiers de solution (.slnx) et de projet (.csproj) prêts à être compilés.

Maintenant votre projet Mon application est prêt à recevoir les services, modules, entités et front-end que nous configurerons dans les prochaines étapes. Avant de continuer, ouvrez la solution générée et exécutez dotnet build pour confirmer que les références, les générateurs de sources, les modèles, les fichiers de projet et la configuration initiale sont cohérents.

Ajout de l'application web Backoffice

Un système complet nécessite généralement au moins une application Web pour faire fonctionner le domaine. Dans ce guide, l'application s'appelle Backoffice et représente une interface interne permettant aux administrateurs, aux gestionnaires ou aux utilisateurs opérationnels de surveiller les produits, les catégories, les stocks, les ventes et d'autres informations du système.

L'application Web ne remplace pas les services de domaine. Il fait office de point d'entrée visuel pour consommer des API, déclencher des Commands, consulter des Queries et afficher des écrans cohérents avec les règles déjà modélisées dans les services.

Étape 1 : Exécuter la commande de création

Pour ajouter une nouvelle application web au projet, utilisez :

lino web-app new

L'alias lino webapp new est également disponible pour faciliter la saisie. Pendant l'exécution, indiquez un nom clair pour l'application web ; dans cet exemple, nous utiliserons Backoffice.

lino web-app new --name Backoffice

Étape 2 : Comprendre la structure générée

À la fin du processus, Lino crée la structure initiale de la Web App dans src/WebApps/<WebAppName>. Pour une application Blazor, la structure peut inclure des projets server/client, des ressources partagées, des fichiers de localisation, des clients pour consommer les APIs et des conventions qui seront ensuite utilisées par lino page new.

  • Dossiers pour les pages, les composants, les mises en page, les services et les ressources d'application ;
  • Clients HTTP et contrats requis pour consommer les API exposés par les services du projet ;
  • Ressources de localisation et modèles initiaux utilisés par l'expérience Web ;
  • Projets client/serveur lorsque le type d'application nécessite cette séparation ;
  • Conventions de routage, de navigation et d'intégration qui seront réutilisées dans la génération des pages ;
  • Points d'intégration avec authentification et autorisation lorsque la fonctionnalité d'authentification est ajoutée au projet.

Étape 3 : Quand créer l’application Web dans le flux

Si vous savez déjà que le système aura une interface Blazor, créez la Web App dès le début, après la création du projet. Ainsi, les services, modules, entités, APIs et pages générés ensuite seront déjà alignés avec l'application web qui les consommera.

Ce flux est particulièrement utile parce que les services créés par la suite génèrent aussi des projets typés Api.Contracts et Api.Client, consommés par le projet Blazor. Avec la Web App présente dès le départ, il devient plus simple de valider le parcours complet : domaine, API, contrats, HttpClient et écran.

Remarques importantes

  • Le Backoffice doit consommer les données via les API générés, en maintenant la logique métier dans les services et modules appropriés.
  • Avant d'exposer les pages d'administration, vérifiez l'authentification, l'autorisation, les rôles, les autorisations et les stratégies d'accès.
  • Vous pouvez créer plusieurs applications Web, par exemple un Backoffice interne et un Site public, lorsque les audiences, les autorisations, les déploiements ou les responsabilités sont différents.
  • Évitez de mélanger les flux publics et administratifs dans une même Web App lorsque cela rend difficile la sécurité, la navigation, le déploiement ou l'appropriation des équipes.

Une fois l'application web créée, le guide peut passer aux services et modules qui fourniront les données et les règles métier consommées par cette interface.

Création de services et de modules

Dans cette étape, nous allons construire les services et modules qui composeront l’application. L'objectif est de créer une architecture modulaire et évolutive, permettant aux différents domaines du système, tels que les produits, les catégories, les stocks, les ventes et les médias, d'évoluer de manière indépendante, en maintenant la cohésion et en facilitant la maintenance. Les services définissent les limites de déploiement et de persistance ; Les modules organisent les domaines d'activité au sein d'un service modulaire. Avant de créer des fichiers, évaluez quelles parties du domaine nécessitent leur propre base de données, une version indépendante, un accord d'intégration ou une propriété distincte.

Étape 1: Définir les services

Dans un premier temps, nous créerons les services suivants, chacun avec des responsabilités bien définies:

  • Catalog (modulaire) – responsable de la gestion des produits, des catégories et des prix ;
  • Sales – responsable du traitement des ventes et des Commands ;
  • Action – responsable de la gestion des stocks et des mouvements ;
  • Security – responsable de l’authentification, de l’autorisation et de la gestion des utilisateurs.

Pour créer les services de l'exemple, exécutez une commande pour chaque service. Dans le wizard de Catalog, choisissez l'architecture modulaire ; pour les autres, choisissez l'architecture adaptée à une frontière simple.

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

Pendant l'exécution de la commande, le CLI demandera :

  • Nom du service : par exemple, Catalog ;
  • Nom d'affichage et style architectural : choisissez une architecture simple pour les frontières compactes, ou modulaire lorsque le service possède des zones cohésives indépendantes ;
  • Base de données : choisissez la technologie qui convient le mieux à votre projet (SQL Server, PostgreSQL, etc.) ;

Étape 2 : Création de modules dans les services

Tous les services n'ont pas besoin d'être modulaires. Dans notre projet, seul le service Catalog aura des modules, afin de séparer des responsabilités comme le merchandising et la tarification.

Nous définissons les modules suivants pour le service Catalog :

  • Merchandising – gestion des produits et des catégories ;
  • Pricing – gestion des prix, des promotions et de l'historique des changements.

Pour créer les modules de Catalog, exécutez :

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

Vous pouvez créer autant de modules que vous le souhaitez au sein du service modulaire Catalog. De plus, en fonction de leur complexité, certains modules pourraient devenir des services indépendants à l'avenir. La séparation présentée ici est uniquement à des fins didactiques et sert d'exemple d'organisation modulaire. Ne vous contentez pas d'utiliser des modules pour créer des dossiers ; utilisez-les lorsqu'ils protègent un domaine d'activité avec ses propres entités, cas d'utilisation, API, migrations et événements.

Structure finale du projet

Après avoir créé les services et les modules, votre solution doit avoir une structure similaire à la suivante :

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/

Explication de la structure :

  • Services/: contient tous les services système, chacun isolé avec sa propre logique métier, sa propre infrastructure et son hébergement ;
  • Modules/: dossiers au sein de services modulaires, permettant d'organiser des fonctionnalités spécifiques et de maintenir un code cohérent ;
  • WebApps/: frontends associés au système, déjà intégrés aux services ;
  • Shared/: bibliothèques et ressources partagées entre services et frontends ;
  • tests/: tests unitaires et d'intégration organisés par service et module.

Grâce à cette structure modulaire, chaque équipe ou développeur peut travailler indépendamment sur différentes parties du système, facilitant ainsi l'évolutivité, la maintenance et les tests. Après avoir créé les services et les modules, exécutez dotnet build pour confirmer que les projets sont correctement attachés à la solution, hébergez Aspire, les projets partagés et le cadre de test.

Ajout d'authentification et d'autorisation

L'authentification et l'autorisation sont des éléments essentiels de tout système moderne. L'authentification prouve qui est l'utilisateur ; l'autorisation définit ce que cet utilisateur peut effectuer. Dans Lino CLI, la feature d'auth génère la base nécessaire pour les utilisateurs, les rôles, les autorisations, les jetons, les politiques d'accès et l'intégration avec les API.

Étape 1 : Exécuter la commande d'authentification

Pour ajouter des fonctionnalités d'authentification et d'autorisation, utilisez la commande :

lino feature auth add

CLI vous guidera à travers les étapes suivantes :

  • Choix du service ou du module: vous devez indiquer où les artefacts d'authentification seront installés. Dans notre exemple de projet, nous utilisons le service Security, qui centralise toute la logique de sécurité du système ;
  • Paramètres supplémentaires : création de tableaux d'utilisateurs, de rôles, d'autorisations, de jetons et configuration des politiques d'accès ;
  • Durée de vie du jeton : définition de l'expiration du access token et du refresh token conformément à la politique de sécurité du produit ;
  • Type d'identifiant utilisateur : choisir le type utilisé par le modèle utilisateur et les contrats générés.

Étape 2 : Structure générée

Après avoir exécuté la commande, le service Security contiendra des fichiers et des dossiers tels que :

  • Domaine/Entités : agrégats, entités et règles pour les utilisateurs, les rôles, les autorisations et les jetons ;
  • Infrastructure/Persistance : configurations de bases de données, mappages Entity Framework et migrations pour les tables de sécurité ;
  • Application: Commands, Queries, Handlers, services d'authentification, génération de jetons, validation des informations d'identification et vérifications des autorisations ;
  • API/Hôte : points de terminaison pour la connexion, la déconnexion, l'enregistrement, le refresh token et les opérations protégées ;
  • Intégration avec l'application Web : prise en charge des flux authentifiés lorsqu'une application Web est présente.

Grâce à cela, votre application disposera d'une authentification robuste et d'un contrôle d'accès granulaire, prête à prendre en charge plusieurs utilisateurs et différents niveaux d'autorisations. Néanmoins, le code généré doit être traité comme une base solide et non comme l’examen de sécurité final. Avant la production, examinez la durée de vie du jeton, la conception des autorisations, la politique de mot de passe, HTTPS, les secrets, la limitation de débit, les journaux et les configurations de déploiement.

Ajout de Background Jobs

Dans les systèmes distribués et modulaires, comme celui que nous construisons Lino CLI, tous les services ne communiquent pas directement entre eux. Pour garantir la cohérence et la fiabilité des échanges d’informations, nous utilisons des événements d’intégration. Cependant, pour traiter ces événements de manière efficace et asynchrone, nous avons besoin Background Jobs.

Lino utilise la norme Outbox Pattern pour garantir que tous les messages générés par les services sont enregistrés de manière fiable avant leur envoi. Avec cela, nous avons réalisé:

  • Évitez la perte d’événements en cas de pannes ou de redémarrages de services ;
  • Assurez-vous que le même message n’est envoyé qu’une seule fois ;
  • Autoriser le retraitement des messages en cas d'échec de livraison ;
  • Séparez le traitement des événements de la logique principale de l’application, améliorant ainsi les performances et l’évolutivité.

Étape 1 : Exécuter la commande

Pour ajouter la prise en charge des Background Jobs à votre projet, exécutez la commande :

lino feature background-job add

CLI vous demandera de sélectionner le service sur lequel le travail en arrière-plan sera installé. Généralement, vous choisirez le service qui centralise la production événementielle, comme Catalog ou Sales. Dans les options actuelles, l'assistant peut également demander le module, la bibliothèque de jobs, si les événements Outbox doivent être traités, le planning et la taille du lot. Le flux de modèle actuel utilise Hangfire pour l'exécution de tâches récurrentes.

Étape 2 : Configuration de l'exécution

Lors de la configuration, vous pourrez définir:

  • Intervalle de vérification : détermine la fréquence à laquelle le travail en arrière-plan vérifiera la table Outbox pour les nouveaux messages. Un intervalle trop court peut augmenter l'utilisation des ressources, tandis qu'un intervalle trop long peut retarder la livraison des événements ;
  • Lot d'enregistrements traités à la fois : contrôle le nombre d’événements qui seront lus et envoyés par exécution. Des lots plus volumineux peuvent augmenter les performances, mais nécessitent plus de mémoire et de traitement ;
  • Politique de récupération : En cas d'échec de l'envoi de messages, vous pouvez configurer le nombre de tentatives de renvoi du travail.

Ces paramètres dépendent de la taille de votre système, de la capacité de la machine et du volume d'événements attendu.

Étape 3 : Structure générée

Après la configuration, le projet disposera d'un travail en arrière-plan prêt à traiter les messages des tables. Outbox dans chaque prestation.

  1. Le cas d'utilisation modifie le domaine et enregistre un événement de domaine ou d'intégration.
  2. L'unité de travail enregistre les données métiers et les messages de Outbox dans la même transaction.
  3. Hangfire exécute des tâches récurrentes qui lisent les messages en attente par lots.
  4. Les messages sont publiés sur le moteur d'intégration configuré tel que RabbitMQ lorsque la communication asynchrone est activée.
  5. Les messages terminés, échoués, anciens ou bloqués peuvent être traités par la logique et la configuration générées pour la tâche.

Cela garantit que tous les événements d'intégration sont traités de manière fiable et efficace, permettant à plusieurs services et modules de communiquer de manière asynchrone, sans affecter les performances du système principal. La règle opérationnelle la plus importante est de garder la frontière transactionnelle claire: les événements qui doivent être envoyés via Outbox doivent être créés dans le même flux transactionnel que le changement métier qu'ils représentent.

Création d'entités et d'énumérations

Dans cette section, nous détaillerons la conception des entités, des énumérations et des Value Objects de l'application, en montrant dans quels services et modules chaque élément sera créé.

1. Création de l'entité Category

Pour créer l'entité Category dans le service Catalog et le module Merchandising, exécutez :

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

L'entité sera créée dans le service Catalog et dans le module Merchandising avec la structure suivante:

┌────┬────┬───────────────┬────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type   │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid   │        │    x     │       x        │
├────┼────┼───────────────┼────────┼────────┼──────────┼────────────────┤
│    │    │ Name          │ string │   50   │    x     │                │
└────┴────┴───────────────┴────────┴────────┴──────────┴────────────────┘

2. Création de l'entité Product

Ensuite, nous créons l'entité Product dans le même service et le même module. Dans ce flux, le Value Object ProductDimension et l'enum ProductStatus sont configurés dans le wizard de création de l'entité lui-même, comme partie de l'agrégat Product :

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

Pendant l'exécution, ajoutez les propriétés simples, la relation avec Category, la propriété Dimensions de type Value Object et la propriété Status de type 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 Configuration du Value Object ProductDimension dans Product

Dans le scénario de ce guide, ProductDimension n'est pas créé par une commande séparée. Il est ajouté pendant le lino entity new de Product, comme propriété Dimensions, et représente les dimensions du produit :

┌───────────────┬─────────┬────────┬──────────┐
│ Property name │ Type    │ Length │ Required │
├───────────────┼─────────┼────────┼──────────┤
│ Width         │ decimal │        │    x     │
├───────────────┼─────────┼────────┼──────────┤
│ Height        │ decimal │        │    x     │
├───────────────┼─────────┼────────┼──────────┤
│ Depth         │ decimal │        │    x     │
└───────────────┴─────────┴────────┴──────────┘

Remarque : la commande lino value-object new existe pour les scénarios où le Value Object doit être créé séparément. Dans ces cas, utilisez les arguments du service et du module de destination :

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

2.2 Configuration de l'Enum ProductStatus dans Product

De la même manière, ProductStatus est configurée dans le lino entity new de Product, comme propriété Status. Elle définit le statut du produit :

┌───────┬──────────────┬──────────────┐
│ Value │ Name         │ Display Name │
├───────┼──────────────┼──────────────┤
│ 1     │ Active       │ Active       │
├───────┼──────────────┼──────────────┤
│ 2     │ Inactive     │ Inactive     │
├───────┼──────────────┼──────────────┤
│ 3     │ Discontinued │ Discontinued │
└───────┴──────────────┴──────────────┘

Remarque : la commande lino enumeration new peut aussi être utilisée lorsqu'une enum doit être créée séparément dans un autre scénario :

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

3. Ajout de nouvelles propriétés

Au fur et à mesure que le projet évolue, nous pouvons modifier les entités existantes pour ajouter de nouvelles propriétés. Par exemple, nous ajouterons une liste d'images à l'entité Product:

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

Dans ce même flux, nous créons la propriété Images de type List<ProductImage>. Comme ProductImage appartient à l'agrégat Product, sa structure est également configurée pendant le 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 Configuration de ProductImage dans Product

ProductImage n'est pas créé par une commande séparée dans ce scénario. Il est configuré comme élément de la collection Images pendant l'édition de Product et possède la structure suivante :

┌────┬────┬───────────────┬────────────────┬────────┬──────────┬────────────────┐
│ PK │ FK │ Property name │ Type           │ Length │ Required │ Auto-increment │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│ x  │    │ Id            │ Guid           │        │    x     │       x        │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │ x  │ ProductId     │ EntityId       │        │    x     │                │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │    │ UploadDate    │ DateTimeOffset │        │    x     │                │
├────┼────┼───────────────┼────────────────┼────────┼──────────┼────────────────┤
│    │    │ Image         │ File           │        │    x     │                │
└────┴────┴───────────────┴────────────────┴────────┴──────────┴────────────────┘

Remarque : la commande lino entity new existe pour créer des entités indépendantes dans d'autres scénarios. Lorsque l'entité ne fait pas partie de l'édition d'un agrégat existant, utilisez :

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

4. Création d'entités pour d'autres services

En service Sales, nous créons l'entité ProductSnapshot, qui sera alimenté par des événements d’intégration. Comme identifiant d'origine de l'entité Product vient du service Catalog, il ne peut pas être auto-incrémental ici.

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     │                │
└────┴────┴───────────────┴─────────┴────────┴──────────┴────────────────┘

Remarque : seuls les champs essentiels ont été répliqués dans ProductSnapshot pour le service Sales. Les entités complémentaires, comme Customer, Order et StockItem, ne seront pas détaillées ici afin de simplifier la documentation. ProductSnapshot fonctionne comme une shadow entity : une copie locale, minimale et contrôlée de données dont le propriétaire se trouve dans un autre service. Cela permet à Sales de consulter les données produit nécessaires sans dépendre directement de l'entité ou de la base de données de Catalog.

Ce type de structure pourrait aussi être créé par la commande lino shadow new, alias de lino shadow-entity new. Dans ce flux, Lino copie la structure d'une entité d'un autre service ou module et permet de sélectionner uniquement les propriétés pertinentes pour le contexte consommateur.

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

Dans l'exemple, l'entité source est Catalog.Merchandising.Product et la destination est le service Sales, en conservant seulement les champs nécessaires aux ventes.

Création d'événements et de leurs Handlers

Pour récapituler, dans la section précédente nous avons créé un service modulaire Catalog.Merchandising les entités Product, Category et ProductImage, pendant mon service Sales nous créons l'entité ProductSnapshot.

Créons maintenant des événements de domaine et des événements d'intégration. L'objectif est que lors de la création ou de la mise à jour de produits sur le service Catalog, ces changements sont répliqués aux services grand public, tels que Sales et Action.

1. Création d'événements de domaine

La première étape consiste à créer des événements de domaine ProductCreated et ProductUpdated avec la commande:

lino event new

Lors de la création, nous pouvons associer l'événement à un gestionnaire et, simultanément, configurer le déclenchement d'un événement d'intégration. Cela centralise la création de tous les flux nécessaires.

┌──────────────────────────────────────────────────────┬────────────────────────────────┐
│ 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 même manière, nous créons l'événement 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                   │
└──────────────────────────────────────────────────────┴────────────────────────────────┘

Avec cela, nous avons:

2. Création de Handlers d'événements d'intégration

L'étape suivante consiste à définir quels services consommeront les événements d'intégration. Pour ce faire, nous utilisons:

lino event-handler new

Le flux de création implique :

Par exemple, dans le service Sales nous avons créé les Handlers pour ProductCreated et ProductUpdated qui consommera les événements déclenchés par le Catalog.Merchandising.Product:

┌────────────────────────────────────────────┬───────────────────┐
│ 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    │
└────────────────────────────────────────────┴───────────────────┘

Avec cela, nous avons deux Handlers d'événements d'intégration dans le service Sales, en consommant uniquement les champs nécessaires des événements d'intégration du Catalog.Merchandising. Cela garantit que les tables répliquées conservent uniquement les données essentielles, optimisant ainsi le stockage et les performances. En termes d'architecture, le flux attendu est le suivant : le gestionnaire de Commands modifie l'agrégat, l'événement de domaine est déclenché, le gestionnaire prépare l'événement d'intégration, le Outbox stocke le message, le travail en arrière-plan traite le problème et le moteur de messagerie transmet le message aux consommateurs. Cela évite le couplage synchrone entre services et réduit le risque de modification de la base de données sans que l'événement correspondant soit publié.

Génération de pages Web, API, Commands et Queries

L'un des grands avantages de Lino CLI est de permettre la création intégrée de pages Web, de API, de Commands et de Queries de manière automatisée, simplifiant ainsi l'ensemble du flux de développement. Lorsque le modèle de domaine est suffisamment stable, cette commande crée un chemin complet depuis l'écran jusqu'à la persistance, rendant le travail de modélisation effectué lors des étapes précédentes visible à l'utilisateur final. Pour commencer, exécutez simplement la commande :

lino page new

Au cours du processus, vous:

Avec cette commande, vous obtenez une application fonctionnelle sans avoir à écrire manuellement la couche d'interface, les API et la logique métier, en maintenant le standard et la cohérence entre les services. Néanmoins, examinez les règles métier et les validations après la génération, car toutes les règles ne peuvent pas être déduites des seules métadonnées de propriété.

Pour ce projet, nous pouvons générer des pages intégrées pour les entités suivantes :

Après avoir généré les pages, les API et les Commands/Queries, l'application sera prête à interagir complètement entre frontend et backend, avec des validations, des routes et une persistance déjà configurées automatiquement par le Lino CLI. Courir dotnet build après génération pour identifier rapidement les références rompues, les contrats incohérents ou les impacts des modifications récentes du modèle.

Création et application de migrations

Après avoir créé ou modifié des entités, des Value Objects, des énumérations, des relations, l'authentification, la prise en charge des locataires ou la persistance des Background Jobs, générez des migrations pour maintenir la base de données et le code alignés. La migration transforme le changement de modèle en un artefact explicite, versionnable et révisable.

Lino CLI coordonne ce processus avec Entity Framework, en sélectionnant le bon service/module, en utilisant la version actuelle du service et en organisant les scripts générés pour faciliter la traçabilité.

Étape 1 : Créer une migration

Pour créer une nouvelle migration, exécutez :

lino database migrations add

La commande peut également accepter des alias comme lino database migrations new et lino database migrations create, mais la méthode préférée dans la documentation est add.

Lors de l'exécution, vous devez informer:

┌───────────────────────────────────────────┬───────────────────┐
│ 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 │
└───────────────────────────────────────────┴───────────────────┘

Lorsque vous connaissez déjà le service et le module, vous pouvez saisir directement ces données:

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

Étape 2 : ce qui est généré

Lors de la confirmation de la création, Lino prépare les Commands Entity Framework correctes pour le projet de persistance de service ou de module sélectionné. Dans un service modulaire, la migration reste isolée dans le module correspondant, évitant de mélanger les changements issus de contextes différents.

Étape 3 : Répertorier et appliquer les migrations

Avant d'appliquer des modifications à une base de données, répertoriez les migrations connues et confirmez que la migration attendue est présente :

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

Pour appliquer les migrations à l'environnement configuré :

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

En développement local, ce flux accélère la validation du modèle. Dans des environnements partagés ou de production, appliquez les migrations via le processus de déploiement défini par l'équipe, avec révision, approbation et sauvegarde du script si nécessaire.

Étape 4 : Inversion ou suppression dans un environnement contrôlé

Utiliser revert lorsqu'il faut restituer une migration appliquée dans un environnement contrôlé, sachant que l'opération peut exécuter des Commands destructrices en fonction du contenu de la migration:

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

Utiliser remove pour ignorer la dernière migration non validée, généralement avant de valider ou de publier la modification :

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

Bonnes pratiques

En suivant ce flux, la base de données reste cohérente avec le modèle de domaine défini dans Lino, et chaque modification de schéma est documentée, traçable et prête à être examinée avant le déploiement.

Étape 5 : Validation locale de l'application

Avec le projet, la Web App, les services, modules, entités, migrations, APIs, Commands et Queries prêts, validez l'application avant de générer les images Docker. Commencez par compiler la solution pour confirmer que tous les projets, contrats et clients générés restent cohérents :

dotnet build

Ensuite, exécutez l'application via l'AppHost Aspire :

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

Utilisez le dashboard Aspire pour vérifier que les APIs, la Web App, la base de données, le cache, la messagerie et les Background Jobs ont démarré correctement. Ensuite, testez les flux principaux dans le Backoffice : pages générées, appels Blazor vers les projets Api.Client, migrations appliquées, authentification lorsqu'elle est activée, et événements ou jobs lorsqu'ils font partie du scénario.

Lorsque l'application compile, s'exécute localement et que les flux principaux sont validés, le projet est prêt pour l'étape de packaging.

Génération d'images Docker

Une fois que l'application compile, s'exécute localement via l'AppHost et que les flux principaux ont été testés, vous pouvez générer les images Docker des services et applications web pour une publication ultérieure dans un registre de conteneurs. Utilisez lino build lorsque les éléments sélectionnés sont prêts à être empaquetés comme images.

Lino CLI simplifie ce processus avec la commande :

lino build

Une fois exécuté, vous verrez une liste de tous les services et applications Web disponibles dans le projet, ainsi que leurs versions actuelles :

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|

Vous pouvez sélectionner un ou plusieurs services et applications Web pour générer des images simultanément. Marquez simplement les éléments souhaités.

Il vous sera alors demandé de choisir comment vous souhaitez mettre à jour la version des images générées. Les options disponibles sont :

Après avoir sélectionné les services et défini l'incrément de version, le Lino CLI effectue:

À la fin du processus, étant donné que tous les services et applications Web ont été sélectionnés, les images générées auront la structure suivante :

De manière générale, les services simples ont tendance à générer des référentiels comme project-name/services/service-name-api:1.2.3, les services modulaires utilisent des hôtes comme project-name/services/service-name-host:1.2.3, et les applications Blazor utilisent des chemins tels que project-name/webapps/webapp-name:1.2.3.

Observation: Ce processus garantit la cohérence entre le code et la version des images Docker, facilitant le déploiement et la maintenance de plusieurs environnements, en plus de permettre à chaque service d'être isolé dans des conteneurs indépendants. Une fois l'image locale générée, publiez-la dans le registre utilisé par votre plateforme de déploiement, tel que Docker Hub, GitHub Container Registry, AWS ECR, Azure Container Registry ou un autre registre compatible OCI. N'incluez pas de secrets, de chaînes de connexion de production ou d'informations d'identification dans l'image.

Création de versions dans l'application

Lino conserve la version de travail de chaque service dans src/Services/<ServiceName>/version.txt et chaque application Web dans src/WebApps/<WebAppName>/version.txt. Cela vous permet de planifier des versions indépendantes pour chaque élément déployable.

Avant de changer de version, inspectez l'état actuel :

lino version list

Utiliser lino version show lorsque vous avez besoin de consulter un service ou une application Web spécifique.

L'incrémentation de nouvelles versions de services ou d'applications Web est un processus simple et centralisé. Lino CLI. Exécutez simplement la commande :

lino version bump

Tout comme pour générer des images Docker, lorsque vous exécutez cette commande, vous verrez une liste complète de tous les services et applications Web de votre projet. Seuls les éléments que vous sélectionnez verront leur version augmentée, tandis que le reste restera inchangé.

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|

Après avoir sélectionné les éléments souhaités, il vous sera demandé de choisir le type d'incrément de version. Les options disponibles sont :

Il est important de souligner que les versions des services web et des applications ont un impact direct sur:

Avant d'appliquer une modification, examinez les modifications de code, les migrations, les événements d'intégration, les contrats API et les modifications frontales qui font partie de la version. Une version Correctif doit représenter des correctifs compatibles, Mineure doit représenter des ajouts compatibles, et Majeur il doit être réservé aux changements qui nécessitent une adaptation des consommateurs.

Avec cela, nous concluons le guide étape par étape de toutes les Commands essentielles pour créer un projet Web à l'aide de Lino CLI, de l'installation, la création de services, d'entités, d'événements et de pages, à la génération et au versionnage d'images Docker. Le flux complet est traçable : domaine de modélisation, génération de cas d'utilisation et d'écrans, validation avec des builds et des tests, création de migrations, publication d'images avec des balises de version et publication de chaque service ou application Web avec une valeur SemVer explicite.

N'oubliez pas de suivre notre chaîne sur YouTube pour suivre des tutoriels détaillés, des démonstrations pratiques et des conseils d'utilisation de l'outil, des opérations simples aux fonctionnalités avancées.

Une erreur non gérée est survenue. Rafraîchir 🗙