Structurer le projet

Le Lino structure les solutions .NET afin que les décisions architecturales soient explicites dÚs le premier commit. Un projet généré démarre avec l'orchestration Aspire, des blocs partagés, des tests, une configuration de qualité du code et un emplacement clair pour chaque service, module, contrat d'API, préoccupation de persistance et limite d'intégration.


Cette section explique comment lino project new, lino service new et lino module new façonnent la solution. L'objectif n'est pas seulement de créer des dossiers, mais de définir des limites de runtime, l'ownership de la base de données, l'isolation des modules et un chemin d'évolution d'un service simple vers un monolithe modulaire ou un systÚme distribué.

Créer la fondation de la solution

La commande lino project new crée la fondation technique d'une nouvelle solution .NET. Exécutez-la dans un répertoire vide aprÚs avoir installé et authentifié la CLI.

lino project new --name 

L'argument représente le nom réel de la solution. Ce nom entre dans la composition des namespaces, assemblies, chemins, configurations, artefacts et références entre composants ; choisissez donc un nom court, stable et représentatif.

L'assistant interactif demande des décisions qui affectent la structure et le comportement de runtime de toute la solution :

  • Namespace du projet : identitĂ© technique racine utilisĂ©e par les projets gĂ©nĂ©rĂ©s.
  • Nom d'affichage : nom convivial utilisĂ© dans les mĂ©tadonnĂ©es gĂ©nĂ©rĂ©es et les points visibles par l'utilisateur.
  • Langage et stack : actuellement C# avec .NET 10 et Aspire.
  • Analyseurs de code : activent des packages et rĂšgles partagĂ©s pour maintenir cohĂ©rence et qualitĂ© dĂšs le bootstrap.
  • CQRS : prĂ©pare la couche d'application Ă  sĂ©parer commands et queries, avec orchestration par la bibliothĂšque de mediator sĂ©lectionnĂ©e.
  • Classes de base dans la solution : contrĂŽle si des abstractions communes seront gĂ©nĂ©rĂ©es localement dans la solution.
  • Cache distribuĂ© : dĂ©finit si Microsoft.Extensions.Caching.Hybrid utilisera seulement la mĂ©moire locale de l'instance ou aussi une couche distribuĂ©e avec Redis configurĂ©e par Aspire.
  • Communication asynchrone : active RabbitMQ avec MassTransit et les blocs de messagerie/outbox utilisĂ©s par les Ă©vĂ©nements d'intĂ©gration.
  • Langue des donnĂ©es : langue utilisĂ©e pour dĂ©crire les mĂ©tadonnĂ©es de domaine pendant la gĂ©nĂ©ration.
  • Cultures prises en charge par l'application : ressources de localisation gĂ©nĂ©rĂ©es pour les textes d'UI, validations, erreurs et rĂ©ponses d'API.
  • Culture par dĂ©faut : langue principale utilisĂ©e lorsque l'application a besoin d'un fallback.

AprÚs confirmation, Lino génÚre une solution organisée pour la croissance. Un projet minimal démarre avec Aspire, des couches partagées et les tests de la zone Shared :

/
├── .slnx
├── Directory.Build.props
├── Directory.Packages.props
├── src/
│   ├── Aspire/
│   │   ├── AppHost/
│   │   │   └── .AppHost.csproj
│   │   └── ServiceDefaults/
│   │       └── .ServiceDefaults.csproj
│   └── Services/
│       └── Shared/
│           ├── Api/
│           │   └── .Shared.Api.csproj
│           ├── Application/
│           │   └── .Shared.Application.csproj
│           ├── Domain/
│           │   └── .Shared.Domain.csproj
│           ├── Infrastructure/
│           │   └── .Shared.Infrastructure.csproj
│           ├── Infrastructure.Persistence/
│           │   └── .Shared.Infrastructure.Persistence.csproj
│           └── Integration.Events/          (quand la messagerie existe)
│               └── .Shared.Integration.Events.csproj
└── tests/
    └── Services/
        └── Shared/
            └── UnitTests/
                ├── Domain/
                │   └── .Shared.Domain.UnitTests.csproj
                └── Application/
                    └── .Shared.Application.UnitTests.csproj

Le rĂŽle des projets Shared

Shared contient le code de plateforme partagé par la solution. Ce n'est pas un service métier. Utilisez cet espace pour les abstractions transversales, les erreurs communes, l'infrastructure de localisation, les contrats de base de l'application, les helpers de persistance, les extensions d'API, l'intégration avec l'host, l'observabilité et les utilitaires techniques réutilisables.

Évitez de placer des rĂšgles mĂ©tier dans Shared. Si une rĂšgle appartient Ă  une capacitĂ© spĂ©cifique de l'application, elle doit rester dans le service ou le module qui possĂšde cette responsabilitĂ©. L'objectif de Shared est de rĂ©duire la rĂ©pĂ©tition technique, pas de devenir un raccourci pour coupler diffĂ©rents domaines.

Aspire et décisions d'infrastructure

AppHost compose la solution et ses ressources de runtime. Lorsque Redis, RabbitMQ, SQL Server, PostgreSQL, Redis Insight, des services, WebApps ou workers sont ajoutés, Aspire devient le point d'orchestration local. Cela simplifie l'exécution, la découverte de services, les logs, métriques, traces et la visualisation des ressources pendant le développement.

ServiceDefaults centralise des patterns d'hosting tels que service discovery, health checks, résilience, logging, métriques, tracing et intégration OpenTelemetry. Au lieu que chaque service configure cela isolément, la solution démarre avec un point commun de composition.

Analyseurs de code

Les analyseurs de code statique inspectent le code pendant le développement et rendent les problÚmes visibles avant l'exécution : incohérences de style, patterns fragiles, opportunités de refactoring, bugs possibles et alertes de sécurité.

Lorsque vous activez les analyseurs dans lino project new, la solution démarre déjà avec des packages comme StyleCop.Analyzers, SonarAnalyzer.CSharp et Roslynator.Analyzers configurés de maniÚre centralisée. Cela évite que chaque projet doive décider seul des rÚgles à suivre.

  • AmĂ©lioration de la qualitĂ© : garde le code lisible, cohĂ©rent et alignĂ© avec les standards de la solution.
  • PrĂ©vention des erreurs : signale les problĂšmes tĂŽt, avant qu'ils n'atteignent les tests manuels ou la production.
  • Standardisation : rĂ©duit les diffĂ©rences de style entre services, modules et Ă©quipes.
  • Refactoring assistĂ© : met en Ă©vidence des simplifications et modernisations possibles dans le code C#.

Cache distribué, cache hybride et Redis

Lino prĂ©pare la solution Ă  utiliser Microsoft.Extensions.Caching.Hybrid, la bibliothĂšque Microsoft qui centralise les opĂ©rations de cache via HybridCache. Cette abstraction permet aux handlers, services d'application et composants d'infrastructure de stocker des rĂ©sultats de requĂȘtes, permissions, configurations ou donnĂ©es de support sans disperser les dĂ©tails d'implĂ©mentation du cache dans le code.

Lorsque vous n'activez pas le cache distribué à la création du projet, HybridCache reste disponible, mais fonctionne uniquement avec la mémoire locale de l'instance en cours d'exécution. Ce mode est simple et suffisant pour les scénarios locaux, les petits environnements ou les applications avec une seule réplique, mais chaque processus conserve son propre cache et les données ne sont pas partagées entre instances.

Lorsque vous activez le cache distribuĂ©, Lino ajoute Redis aux ressources Aspire et configure l'infrastructure pour utiliser une couche de cache partagĂ©e. Avec Redis, plusieurs instances du mĂȘme service peuvent interroger la mĂȘme couche, ce qui rĂ©duit les lectures rĂ©pĂ©tĂ©es en base de donnĂ©es, APIs internes ou intĂ©grations et prĂ©pare le projet Ă  la mise Ă  l'Ă©chelle horizontale.

  • Performance : rĂ©duit le temps de rĂ©ponse pour les lectures rĂ©pĂ©tĂ©es et les opĂ©rations de support.
  • ScalabilitĂ© : permet Ă  diffĂ©rentes instances de partager des donnĂ©es en cache.
  • DisponibilitĂ© : dĂ©couple une partie de la charge de lecture de la base de donnĂ©es principale.
  • CoĂ»t opĂ©rationnel : diminue le traitement rĂ©pĂ©titif dans les requĂȘtes et intĂ©grations Ă  fort volume.

Cette décision est prise dans lino project new parce qu'elle modifie la fondation de l'environnement : ressources de l'AppHost, secrets locaux, packages, configuration d'infrastructure et topologie d'exécution affichée dans le dashboard Aspire.

Communication asynchrone

La communication asynchrone permet aux services, modules et composants de réagir à des faits du systÚme sans bloquer le flux principal de l'opération. Elle est particuliÚrement utile lorsque le producteur ne doit pas dépendre de la disponibilité immédiate du consommateur ou lorsqu'une action doit déclencher des effets ultérieurs, comme des notifications, projections, intégrations externes ou synchronisations entre contextes.

Lorsque vous activez la communication asynchrone, Lino ajoute RabbitMQ Ă  Aspire et configure MassTransit avec les blocs de messagerie et outbox utilisĂ©s par les Ă©vĂ©nements d'intĂ©gration. Ainsi, les Ă©vĂ©nements publiĂ©s par des services ou modules sont reprĂ©sentĂ©s par des contrats explicites dans Integration.Events et peuvent ĂȘtre traitĂ©s avec plus de rĂ©silience.

  • Performance : permet au cas d'utilisation principal de continuer sans attendre que tous les consommateurs aient terminĂ©.
  • ScalabilitĂ© : distribue le traitement entre consommateurs et files, en absorbant les pics avec plus de contrĂŽle.
  • RĂ©silience : permet le retraitement et rĂ©duit la perte de messages lorsqu'elle est combinĂ©e avec outbox.
  • DĂ©couplage : Ă©vite la dĂ©pendance directe entre producteur et consommateur lorsqu'une rĂ©ponse immĂ©diate n'est pas nĂ©cessaire.

Utilisez des événements d'intégration lorsqu'un échec du consommateur ne doit pas annuler la transaction du producteur. Lorsque le consommateur doit répondre immédiatement pour compléter le cas d'utilisation, préférez une intégration synchrone explicite et traitez disponibilité, timeout et fallback comme faisant partie du contrat.

Localisation et cultures

Lorsque des cultures sont sĂ©lectionnĂ©es, Lino gĂ©nĂšre des ressources afin que messages, validations, labels, erreurs et textes d'UI puissent ĂȘtre localisĂ©s dĂšs le dĂ©but. Cela Ă©vite de traiter l'internationalisation comme un correctif tardif, une fois les chaĂźnes dĂ©jĂ  dispersĂ©es dans les endpoints, handlers et composants.

Valider la fondation et passer aux services

AprÚs la génération, restaurez les dépendances, compilez la solution et exécutez l'host Aspire :

dotnet restore .slnx
dotnet build .slnx
dotnet run --project src/Aspire/AppHost/.AppHost.csproj

À ce stade, la solution reste une fondation. Ouvrez le projet dans l'Ă©diteur, rĂ©visez les ressources créées par Aspire, confirmez les dĂ©cisions de cache, messagerie, localisation et qualitĂ© du code, puis passez aux services qui reprĂ©senteront les capacitĂ©s mĂ©tier de l'application.

Créer et gérer des services

Un service est une limite de runtime et d'ownership. Dans Lino, les services peuvent représenter des APIs indépendantes dans un systÚme distribué ou des zones métier plus larges dans une solution qui peut évoluer progressivement.

AprÚs avoir créé la fondation du projet, ajoutez des services avec :

lino service new

L'assistant de service demande :

  • Namespace du service : nom technique utilisĂ© dans les dossiers, projets et namespaces.
  • Nom d'affichage : nom convivial du service.
  • Type de service : choisissez entre simple et modulaire.
  • Provider de base de donnĂ©es : choisissez SQL Server ou PostgreSQL pour la base de donnĂ©es du service.
  • Style d'architecture : actuellement Clean Architecture pour les services simples.
  • Strongly Typed IDs : dans les services simples, dĂ©finit si les identifiants seront gĂ©nĂ©rĂ©s comme types dĂ©diĂ©s.

Types de service

Service simple : structure plus directe pour une capacité métier avec une frontiÚre claire. C'est un bon choix pour une API ciblée, un microservice ou une zone assez petite pour évoluer comme une unité.

Service modulaire : structure indiquée pour les systÚmes plus grands, les monolithes modulaires ou les runtimes qui doivent héberger plusieurs capacités indépendantes. Les modules améliorent l'organisation et la scalabilité du modÚle, mais exigent plus de discipline de dépendance.

Quel que soit le type, chaque service possÚde sa propre base de données. Dans les services simples, la décision sur Strongly Typed IDs se prend au niveau du service ; dans les services modulaires, elle se prend par module.

Service simple

Un service simple place ses couches directement dans src/Services/. Utilisez cette structure lorsque la capacitĂ© mĂ©tier possĂšde une limite claire et n'a pas besoin de plusieurs modules isolĂ©s dans le mĂȘme runtime. Elle fonctionne bien pour une API ciblĂ©e, un microservice ou une zone de domaine assez petite pour Ă©voluer comme une unitĂ©.

src/Services//
├── Domain/
│   └── ..Domain.csproj
├── Application/
│   └── ..Application.csproj
├── Infrastructure.Persistence/
│   └── ..Infrastructure.Persistence.csproj
├── Infrastructure/
│   └── ..Infrastructure.csproj
├── Api/
│   └── ..Api.csproj
├── Integration.Events/              (quand la messagerie existe)
│   └── ..Integration.Events.csproj
├── Api.Contracts/                   (quand la consommation HTTP typĂ©e existe)
│   └── ..Api.Contracts.csproj
└── Api.Client/                      (quand la consommation HTTP typĂ©e existe)
    └── ..Api.Client.csproj
tests/Services//
├── UnitTests/
│   ├── Domain/
│   │   └── ..Domain.UnitTests.csproj
│   └── Application/
│       └── ..Application.UnitTests.csproj
└── IntegrationTests/
    └── ..IntegrationTests.csproj

Les couches ont des responsabilités distinctes : Domain protÚge les rÚgles métier et invariants ; Application orchestre les cas d'utilisation ; Infrastructure.Persistence contient Entity Framework Core, repositories, Unit of Work et migrations ; Infrastructure contient les intégrations techniques ; et Api adapte HTTP aux cas d'utilisation.

Les projets Api.Contracts et Api.Client apparaissent lorsqu'il existe une consommation HTTP typée, surtout dans les solutions avec Web App Blazor. Le premier concentre requests, responses, DTOs, types publics et interfaces de client partagées ; le second fournit l'implémentation HTTP de ces interfaces, avec HttpClient, afin que Blazor consomme les APIs générées sans dupliquer les contrats.

Le projet Integration.Events apparaßt lorsque la solution a la messagerie activée. Il contient les événements d'intégration publiés et consommés par d'autres modules, services ou systÚmes, généralement avec l'infrastructure de messagerie et outbox.

Service modulaire

Un service modulaire est indiquĂ© pour les monolithes modulaires ou pour un runtime qui hĂ©berge plusieurs bounded contexts. Le service lui-mĂȘme possĂšde l'host, l'infrastructure commune et le provider de base de donnĂ©es ; les rĂšgles mĂ©tier vivent dans les modules.

src/Services//
├── Host/
│   └── ..Host.csproj
├── Infrastructure/
│   └── ..Infrastructure.csproj
└── Modules/
tests/Services//
└── Modules/

Le Host compose les modules, configurations, endpoints et l'infrastructure partagée de ce service. Il ne doit pas contenir de rÚgles métier. Le projet Infrastructure au niveau du service fournit le support technique de composition partagé par les modules.

Lorsqu'un module est ajoutĂ©, les projets Domain, Application, Infrastructure.Persistence, Infrastructure et Api apparaissent dans Modules/, en prĂ©servant la frontiĂšre interne. Les projets Api.Contracts, Api.Client et Integration.Events suivent les mĂȘmes conditions : consommation HTTP typĂ©e pour Blazor/API clients et messagerie activĂ©e pour les Ă©vĂ©nements d'intĂ©gration.

Ownership de base de données

Chaque service possÚde sa propre base de données. Dans une solution avec plusieurs services, un service peut utiliser PostgreSQL tandis qu'un autre utilise SQL Server. Cette décision peut varier selon les besoins du domaine, la performance, l'exploitation, la maturité de l'équipe ou l'intégration avec l'infrastructure existante.

Dans un service modulaire, la base appartient au service en tant que runtime, mais les modules sont isolĂ©s par schema, projets de persistance et migrations propres. Un module ne doit pas consulter directement les tables d'un autre module, mĂȘme lorsque les tables se trouvent dans la mĂȘme base physique.

Choisir entre simple et modulaire

Choisissez la plus petite structure qui protÚge la limite. Un service simple suffit lorsqu'il existe une seule frontiÚre de domaine. Un service modulaire a du sens lorsque plusieurs capacités doivent partager runtime, deploy ou transaction, tout en conservant modÚles, persistance, APIs et tests séparés.

ChoixQuand l'utiliserCoût assumé
Service simpleUn domaine ciblé, peu de frontiÚres internes, API isolée ou microservice direct.Moins de structure initiale, mais moins d'isolation interne si le domaine grandit trop.
Service modulairePlusieurs sous-domaines dans le mĂȘme runtime, monolithe modulaire, SaaS avec zones indĂ©pendantes ou besoin de schemas par module.Plus de projets, plus de discipline de dĂ©pendance et davantage d'attention aux contrats internes.

Style d'architecture

Les services générés par Lino suivent Clean Architecture afin de séparer les rÚgles métier des détails techniques. La couche de domaine n'a pas besoin de connaßtre HTTP, Entity Framework Core, la messagerie, l'UI ou les providers externes ; ces préoccupations restent aux bords de l'application.

  • DĂ©couplage : les rĂšgles centrales ne dĂ©pendent pas de frameworks ni de mĂ©canismes de livraison.
  • MaintenabilitĂ© : les changements d'infrastructure tendent Ă  rester isolĂ©s des rĂšgles mĂ©tier.
  • TestabilitĂ© : les cas d'utilisation et le domaine peuvent ĂȘtre testĂ©s avec moins de dĂ©pendances externes.
  • Évolution : les dĂ©tails techniques peuvent ĂȘtre remplacĂ©s avec moins d'impact sur le noyau du service.

Clean Architecture et Strongly Typed IDs

Les services générés suivent Clean Architecture afin que le code métier ne dépende pas de HTTP, EF Core, la messagerie, l'UI ou les détails d'infrastructure. Cette séparation rend les tests plus directs, réduit le couplage et permet de remplacer les détails techniques sans réécrire les rÚgles centrales.

Strongly Typed IDs augmente la sĂ©curitĂ© en empĂȘchant les mĂ©langes accidentels d'identifiants. Au lieu d'accepter n'importe quel Guid, long ou int, le domaine peut travailler avec un type propre Ă  chaque entitĂ© ou agrĂ©gat, gĂ©nĂ©ralement au format Id. Cela Ă©vite de passer l'identifiant d'une entitĂ© lĂ  oĂč un autre Ă©tait attendu et rend les signatures plus expressives dans commands, queries, entitĂ©s, handlers et mappings.

  • SĂ©curitĂ© de type : empĂȘche de mĂ©langer accidentellement les identifiants d'entitĂ©s diffĂ©rentes.
  • ClartĂ© : rend les signatures et contrats plus expressifs que des types primitifs isolĂ©s.
  • Refactoring : concentre les changements de format ou de sĂ©rialisation dans le type d'ID correspondant.
  • RĂ©duction des erreurs : rend les usages incorrects visibles Ă  la compilation chaque fois que possible.

Intégration entre services

Lorsqu'un service doit réagir à un autre, préférez les événements d'intégration, les intégrations HTTP explicites ou les données répliquées consciemment. L'accÚs direct à la base d'un autre service crée un couplage structurel, complique les migrations et rend les deploys indépendants plus risqués.

Prochaines étapes pour les services modulaires

AprÚs avoir créé un service modulaire, l'étape suivante consiste à ajouter des modules avec lino module new. Chaque module doit représenter sa propre capacité métier, avec domaine, application, persistance, API, tests et intégrations qui préservent la limite interne du service.

Créer et gérer des modules

Les modules existent uniquement Ă  l'intĂ©rieur de services modulaires. Un module reprĂ©sente une limite mĂ©tier dans le mĂȘme runtime : il possĂšde son propre modĂšle de domaine, ses cas d'utilisation, sa persistance, sa surface d'API, ses Ă©vĂ©nements d'intĂ©gration, ses tests et son schema de base de donnĂ©es.

Utilisez des modules lorsque le service doit hĂ©berger plus d'une capacitĂ© mĂ©tier sans mĂ©langer entitĂ©s, rĂšgles, migrations et contrats. Dans un monolithe modulaire, tout peut s'exĂ©cuter dans le mĂȘme processus, mais les limites internes restent importantes : l'isolation rĂ©elle relĂšve du contrĂŽle des dĂ©pendances, pas seulement de la sĂ©paration des dossiers.

lino module new --service 

Pendant la création, l'assistant demande :

  • Service : le service modulaire qui hĂ©bergera le module. Lino n'autorise pas la crĂ©ation de modules dans des services simples, car ils ne possĂšdent pas la structure nĂ©cessaire Ă  l'isolation interne.
  • Namespace du module : le nom technique utilisĂ© dans les dossiers, namespaces, assemblies et projets.
  • Nom d'affichage : le nom convivial du module dans les emplacements destinĂ©s Ă  la lecture humaine.
  • Strongly Typed IDs : dĂ©finit si les identifiants gĂ©nĂ©rĂ©s dans le module utiliseront des types dĂ©diĂ©s, gĂ©nĂ©ralement au format Id.

AprĂšs confirmation, Lino ajoute le module dans le service sans faire disparaĂźtre la limite architecturale :

src/Services//
├── Host/
├── Infrastructure/
└── Modules/
    └── /
        ├── Domain/
        │   └── ...Domain.csproj
        ├── Application/
        │   └── ...Application.csproj
        ├── Infrastructure.Persistence/
        │   └── ...Infrastructure.Persistence.csproj
        ├── Infrastructure/
        │   └── ...Infrastructure.csproj
        ├── Api/
        │   └── ...Api.csproj
        ├── Integration.Events/              (quand la messagerie existe)
        │   └── ...Integration.Events.csproj
        ├── Api.Contracts/                   (quand la consommation HTTP typĂ©e existe)
        │   └── ...Api.Contracts.csproj
        └── Api.Client/                      (quand la consommation HTTP typĂ©e existe)
            └── ...Api.Client.csproj
tests/Services//Modules//
├── UnitTests/
│   ├── Domain/
│   │   └── ...Domain.UnitTests.csproj
│   └── Application/
│       └── ...Application.UnitTests.csproj
└── IntegrationTests/
    └── ...IntegrationTests.csproj

Responsabilités des artefacts générés

ArtefactResponsabilité dans le module
DomainEntités, agrégats, Value Objects, enumerations, événements de domaine, contrats de repository et invariants du module.
ApplicationCas d'utilisation, commands, queries, handlers, validations, contrats internes d'entrée et de sortie, et orchestration des rÚgles du module.
Infrastructure.PersistenceDbContext, configurations Entity Framework Core, repositories concrets, Unit of Work et migrations du module.
InfrastructureImplémentations techniques propres au module, adaptateurs, providers et composition des dépendances.
ApiEndpoints HTTP, versioning, filtres, autorisation et adaptation entre les requĂȘtes externes et les cas d'utilisation.
Api.ContractsGénéré lorsqu'il existe une consommation HTTP typée, généralement par des Web Apps Blazor. Contient requests, responses, DTOs, types publics et interfaces de client partagées entre l'API et le consommateur.
Api.ClientGénéré avec les contrats lorsqu'il existe une consommation HTTP typée. Contient l'implémentation HTTP des interfaces, avec HttpClient, providers, options et helpers afin que les projets Blazor consomment les APIs générées de maniÚre cohérente et fortement typée.
Integration.EventsGénéré lorsque le projet possÚde de la messagerie. Contient les événements d'intégration émis et consommés par d'autres modules, services ou systÚmes, en conservant le payload comme contrat explicite.

Structure de base de données

La base de données reste liée au service, pas au module isolément. Dans un service modulaire, chaque module est représenté par son propre schema dans la base associée, avec son propre projet de persistance et ses propres migrations. Cela offre isolation et organisation sans exiger plusieurs bases physiques pour chaque module.

Isolation et indépendance entre modules

Un module ne doit pas accéder directement au DbContext, aux entités, aux repositories ou aux services internes d'un autre module. Chaque module possÚde son propre modÚle et sa propre persistance. Lorsqu'un autre module a besoin de données, utilisez une intégration explicite au lieu de traverser la frontiÚre par des détails internes d'implémentation.

Cela évite qu'un changement apparemment local casse une autre zone du systÚme. Si un module consommateur doit consulter des données qui appartiennent à un autre module, il ne doit pas dépendre de l'entité complÚte du module source. Il peut conserver une shadow entity avec les données minimales nécessaires à son propre cas d'utilisation, alimentée par intégration ou événement.

Avantages de ce découplage :

  • Isolation : chaque module peut faire Ă©voluer rĂšgles, persistance et tests sans traverser les dĂ©tails internes d'un autre module.
  • Organisation : l'application respecte les bounded contexts et rend l'ownership explicite.
  • FlexibilitĂ© : les modules peuvent ĂȘtre ajoutĂ©s, supprimĂ©s ou refactorisĂ©s avec moins d'impact sur le reste du service.
  • FacilitĂ© de test : chaque module peut ĂȘtre validĂ© de maniĂšre plus indĂ©pendante, ce qui augmente la confiance dans les changements.

Communication entre modules

Utilisez des contrats explicites pour la communication. Pour les appels dans le mĂȘme runtime, une intĂ©gration interne peut exposer des contrats dans Integration.Contracts et une implĂ©mentation in-process lorsqu'elle est gĂ©nĂ©rĂ©e. Pour les appels entre runtimes, utilisez une intĂ©gration HTTP. Pour la propagation asynchrone, publiez des Ă©vĂ©nements d'intĂ©gration dans Integration.Events et utilisez l'infrastructure de messagerie/outbox lorsque la publication fait partie d'une opĂ©ration transactionnelle.

Schemas, migrations et base de données

Dans un service modulaire, les modules partagent le provider de base de données du service, mais chaque module est représenté par son propre schema et par son propre projet Infrastructure.Persistence. Les migrations sont générées par module, ce qui maintient l'évolution de la base alignée avec la limite métier.

Cette sĂ©paration permet Ă  chaque module de faire Ă©voluer ses tables, seeds, index et foreign keys sans transformer la base du service en un modĂšle unique partagĂ© par tous. Le schema est une limite technique qui renforce la limite mĂ©tier, mĂȘme lorsque les modules s'exĂ©cutent dans le mĂȘme processus et utilisent la mĂȘme base physique.

Bonnes limites de module

CrĂ©ez les modules autour de capacitĂ©s mĂ©tier, pas autour de couches techniques. Un bon nom de module doit dĂ©crire une responsabilitĂ© reconnaissable par le domaine, comme une zone, un processus ou une capacitĂ© qui possĂšde ses propres rĂšgles. Évitez les noms gĂ©nĂ©riques comme Common, Core ou Utilities pour les rĂšgles mĂ©tier, car ils masquent l'ownership et tendent Ă  devenir des dĂ©pendances trop partagĂ©es.

  • CohĂ©sion : le module doit avoir un langage, des rĂšgles et des donnĂ©es qui changent ensemble.
  • Autonomie : il doit ĂȘtre possible de tester et de faire Ă©voluer le module sans accĂ©der aux entitĂ©s internes d'un autre module.
  • Contrats clairs : les donnĂ©es nĂ©cessaires hors du module doivent ĂȘtre exposĂ©es par API, intĂ©gration, Ă©vĂ©nement ou shadow entity, pas par accĂšs direct Ă  la base.
  • Faible couplage : si deux modules doivent modifier les mĂȘmes entitĂ©s en permanence, la limite doit probablement ĂȘtre réévaluĂ©e.

Une fois les modules créés, les prochains sujets de la documentation montrent comment modéliser entités, Value Objects, enumerations, commands, queries, APIs, événements, intégrations et migrations à l'intérieur de ces limites.

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