AssetFlow Core est une application d'exemple conçue avec les principes de la Clean Architecture, du Domain-Driven Design (DDD) et du pattern CQRS. Elle gère des assets (matériels), des équipes techniques et des tickets de maintenance, avec un moteur d'assignation automatique basé sur le pattern Strategy.
- Clean Architecture : séparation claire des couches
WebApi,Application,DomainetInfrastructure. - DDD : les entités du domaine (
Asset,Team,MaintenanceTicket) encapsulent la logique métier et les invariants. - CQRS / Vertical Slice : chaque cas d'usage implémente un Handler unique (Create, Update, Get, ...).
- Pattern Strategy : moteur d'assignation de tickets qui sélectionne une stratégie basée sur le type d'actif et la criticité du ticket.
- Gestion des Actifs (Assets) : Enregistrement, suivi d'inventaire unique par numéro de série, et déclassement des équipements obsolètes.
- Cycle de vie des Tickets : Création de tickets de maintenance assignés à des équipements spécifiques avec gestion fine des niveaux de criticité (
Low,Medium,High). - Affectation Intelligente : Attribution automatique ou manuelle des demandes d'intervention à des équipes techniques dédiées.
- Résolution collaborative : Processus de clôture des incidents avec traçabilité et rapports de résolution détaillés.
- Haute Performance : Couche de mise en cache avancée éliminant la fragmentation mémoire, validée par des tests rigoureux de benchmarking.
- Extensibilité : Architecture modulaire facilitant l'ajout de nouvelles fonctionnalités, types d'actifs ou stratégies d'assignation sans impact sur les composants existants.
La couche Infrastructure inclut désormais une intégration RAG (Retrieval‑Augmented Generation) pour générer des notes d'assistance et des résumés de résolution à l'aide d'un modèle local (Ollama).
Principales caractéristiques :
- Utilise Microsoft Semantic Kernel pour le pipeline de chat completion.
- Génération d'embeddings pour recherche vectorielle locale (LocalVectorStore).
- Services principaux situés dans :
AssetFlowCore.Infrastructure/RAG(ex.AIAssistanceGenerator,OllamaConnectivityService,OllamaSemanticKernelSetup). - File d'attente et worker pour traitement asynchrone des demandes IA :
AIAssistanceQueue,AIAssistanceWorker.
Configuration minimale (appsettings.json / variables d'environnement) :
"Ollama": {
"BaseUrl": "http://localhost:11434",
"ChatModel": "mistral",
"EmbeddingModel": "nomic-embed-text"
}Activation :
- Le module RAG est enregistré automatiquement par
services.AddInfrastructure(configuration)(voirAssetFlowCore.Infrastructure/DependencyInjection.cs).
Conseils :
- Vérifier que le démon Ollama est démarré et que l'endpoint
/api/tagsretourne la liste des modèles. - Les logs d'infrastructure exposent l'état de la connexion et les erreurs de génération IA.
Si vous souhaitez désactiver l'IA, retirez l'appel AddOllamaRagServices(configuration) de l'enregistrement des services.
- Assets
RegisterAsset: enregistrer un nouvel assetGetAllAssets: lister les assetsDecommissionAsset: mettre un asset au rebut
- Tickets
CreateMaintenanceTicket: ouvrir un ticketAssignTicketToTechnician: assigner un ticket à un technicienCloseTicket: clôturer un ticketGetTicket: récupérer un ticketRequestTicketTransfer: demander un transfert
- Teams
CreateTeam: créer une équipe d'astreinteGetTeam: récupérer une équipeUpdateTeam: mettre à jour une équipe
Le moteur TicketAssignmentEngine résout dynamiquement la meilleure IAssignmentStrategy pour un couple (AssetType, TicketCriticality).
Stratégies implémentées :
LaptopHighCriticalityStrategy: match siAssetType == LaptopetTicketCriticality == HighLaptopStandardStrategy: match siAssetType == LaptopetTicketCriticality != HighNetworkAssignmentStrategy: match siAssetType == NetworkDeviceServerAssignmentStrategy: match siAssetType == Server
Algorithme résumé :
- DI injecte
IEnumerable<IAssignmentStrategy>dansTicketAssignmentEngine. ResolveTeamIdAsyncsélectionne la première stratégie dontIsMatch(assetType, criticality)retournetrue.- La stratégie appelle
TeamRepository.GetByAssetTypeAndCriticalityAsync(assetType, criticality)pour récupérer l'équipe en base et renvoyer sonName. - Si aucune équipe n'est trouvée,
AssignmentStrategyBase.GetTeamNameAsynclève uneDomainException— d'où la nécessité de pré-seeder les équipes. - Si aucune stratégie ne matche, fallback explicite vers
LaptopStandardStrategy.
Avant de créer des assets et tickets, assurez-vous d'avoir au minimum ces 4 équipes en base :
LaptopHighCriticality(AssetType =Laptop, TicketCriticality =High)LaptopStandard(AssetType =Laptop, TicketCriticality !=High)NetworkAssignment(AssetType =NetworkDevice)ServerAssignment(AssetType =Server)
Sans ces équipes, l'assignation automatique lèvera une DomainException et l'opération échouera.
- Seed / Create Teams (4 équipes minimum).
RegisterAsset: persiste un nouvel asset.CreateMaintenanceTicket:- Récupère l'asset et valide les invariants.
- Convertit la criticité en énumération.
- Appelle
TicketAssignmentEngine.ResolveTeamIdAsync(asset.Type, criticality). - La stratégie appropriée est choisie et renvoie le
team.Name. - Le ticket est créé (assigné à
assignedTeamId) et l'asset est marquéDown. - Persistance via
UnitOfWork.SaveChangesAsync().
AssignTicketToTechnician: mutation du ticket,asset.MarkInMaintenance()si nécessaire, puisSaveChangesAsync().
- Implémentez
IAssignmentStrategy(ou héritez deAssignmentStrategyBase). - Enregistrez la stratégie dans DI (injection scoped dans
Program.cs). - Seed/ajoutez l'équipe correspondante en base via migration/seed ou API
CreateTeam.
- L'ordre d'enregistrement des stratégies (DI) peut influer sur la priorité si plusieurs stratégies sont compatibles ;
TicketAssignmentEngineprend la première match. AssignmentStrategyBasecentralise la résolution d'équipe et lève uneDomainExceptionsi la recherche échoue.- Utiliser les DTOs exposés par l'API pour les réponses (ex :
TeamResponseDtone contient pas toutes les propriétés internes).
flowchart LR
A["CreateTicket (Request)"] --> B["Engine (TicketAssignmentEngine)"]
B --> C{"Strategy Selection"}
C -->|Match| D["LaptopHighCriticality\nLaptopStandard\nNetworkAssignment\nServerAssignment"]
D --> E[("Team")]
- Unit tests et Integration tests fournis pour la plupart des UseCases (handlers, repositories, controllers).
- .NET 8 requis
- Lancer les tests via Visual Studio Test Explorer ou
dotnet test.
Les diagrammes suivants représentent les principaux scénarios applicatifs et flux utilisateur/système. Ils couvrent la création d'équipes, la gestion des assets, la création/assignation/clôture de tickets, le moteur d'assignation (Strategy) et la gestion des erreurs/notifications.
flowchart TD
A["Client/API"] -->|"POST /api/assets"| B["AssetsController.Register"]
B --> C["RegisterAssetCommandHandler"]
C --> D["Validate + ExistsWithSerialNumberAsync"]
D -->|"ok"| E["TeamRepository.AddAsync / AssetRepository.AddAsync"]
E --> F["UnitOfWork.SaveChangesAsync"]
F --> G["Return AssetResponseDto"]
%% Decommission path
H["Client/API"] -->|"POST /api/assets/{id}/decommission"| I["DecommissionAssetHandler"]
I --> J["MaintenanceTicketRepository.CountActiveTicketsByAssetIdAsync"]
J -->|"> 0"| K["Throw DomainException"]
J -->|"== 0"| L["Asset.Decommission()"]
L --> F
flowchart TD
%% Create Ticket Path
A["Client/API"] -->|"POST /api/tickets"| B["CreateMaintenanceTicketHandler"]
B --> C["AssetRepository.GetByIdAsync"]
C --> D["Validate asset state"]
D --> E["TicketAssignmentEngine.ResolveTeamIdAsync"]
E --> F["Choose Strategy (IsMatch)"]
F --> G["AssignmentStrategy.GetTeamNameAsync"]
G --> H["TeamRepository.GetByAssetTypeAndCriticalityAsync"]
H --> I["Create MaintenanceTicket entity"]
I --> J["Asset.MarkAsDown()"]
J --> K["MaintenanceTicketRepository.AddAsync"]
K --> L["UnitOfWork.SaveChangesAsync"]
L --> N["Return TicketResponseDto"]
%% Assign Path
O["Client/API"] -->|"POST /api/tickets/{id}/assign"| P["AssignTicketToTechnicianHandler"]
P --> Q["MaintenanceTicketRepository.GetByIdAsync"]
Q --> R["AssetRepository.GetByIdAsync"]
R --> S["Ticket.AssignToTechnician()"]
S --> T["Asset.MarkInMaintenance()"]
T --> L
%% Close Path
U["Client/API"] -->|"POST /api/tickets/{id}/close"| V["CloseTicketHandler"]
V --> Q
%% Re-use of Asset retrieval through the same flow
V --> W["Ticket.Close()"]
W --> X["MaintenanceTicketRepository.CountActiveTicketsByAssetIdAsync"]
X -->|"<= 1"| Y["Asset.RestoreToService()"]
X -->|"> 1"| Z["No restore"]
Y --> L
Z --> L
%% Final notification after any SaveChanges
L --> M["NotificationService.NotifyTeamNewTicketAsync"]
flowchart LR
A["CreateTicket (Workflow)"] --> B["Engine (TicketAssignmentEngine)"]
B -->|"IEnumerable<IAssignmentStrategy>"| C[("Strategies List\n(Server, Network, LaptopHigh, LaptopStandard)")]
C -->|"First IsMatch(assetType, criticality)"| D["SelectedStrategy"]
D -->|"GetTeamNameAsync"| E["TeamRepository.GetByAssetTypeAndCriticalityAsync"]
E --> F["Team Entity"]
F -->|"Assigns Name"| A
flowchart TD
%% Core Entry Point
A["Client/API"] -->|"POST /api/teams"| B["CreateTeamCommandHandler"]
B --> C["TeamRepository.AddAsync"]
C --> D["UnitOfWork.SaveChangesAsync"]
D --> E["Return TeamResponseDto"]
%% Queries (Read)
A -->|"GET /api/teams/{id}"| G["GetTeamHandler"]
G --> H["TeamRepository.GetByIdAsync"]
H --> I["Return TeamResponseDto"]
%% Commands (Write/Update)
A -->|"PUT /api/teams/{id}"| K["UpdateTeamCommandHandler"]
K --> L["TeamRepository.GetByIdAsync"]
L --> M["Team.Update(...)"]
M --> D
flowchart LR
%% global Error Handling Pipeline
A["AnyController"] --> B["ExceptionHandlingMiddleware"]
B -->|"DomainException"| C["ProblemDetails 400 Bad Request"]
B -->|"DbUpdateConcurrencyException"| D["ProblemDetails 409 Conflict"]
B -->|"Other Exception"| E["ProblemDetails 500 Internal Error"]
%% Notification Provider Strategy
F["NotificationService (Factory/Abstraction)"]
F -->|"Benchmarks Environment"| G["NoOpNotificationService"]
F -->|"Production Environment"| H["SignalRNotificationService"]
Ces diagrammes représentent les principaux chemins d'interaction utilisateur et les flux internes du système.