Definition von Anwendungsfällen
In diesem Abschnitt werden wir untersuchen, wie man die Anwendungsszenarien Ihrer Anwendung mit dem Lino CLI definiert. Anwendungsszenarien stellen spezifische Interaktionen zwischen Benutzern oder externen Systemen und der Anwendung dar, kapseln GeschĂ€ftsregeln ein und fördern eine domĂ€nenorientierte Architektur. Wir behandeln die Trennung von Lese- und Schreiboperationen durch das CQRS-Muster sowie die Standardisierung der RĂŒckgaben mit dem Result Pattern.
Das CQRS-Muster (Command Query Responsibility Segregation) schlÀgt vor, Operationen, die den Zustand der Anwendung Àndern (Commands), von denen zu trennen, die nur Daten abfragen (Queries). Dieser Ansatz ermöglicht es, jeden Operationstyp unabhÀngig zu optimieren, was die Skalierbarkeit, Leistung und Wartbarkeit des Systems verbessert.
Das Result Pattern wird verwendet, um die RĂŒckgabe von Operationen zu standardisieren, indem Informationen ĂŒber Erfolg oder Misserfolg, Fehlermeldungen und Ergebnisdaten gekapselt werden. Dies erleichtert die konsistente Behandlung von Antworten in verschiedenen Schichten der Anwendung.
Wir werden uns ansehen, wie die Dateien fĂŒr Commands und Queries strukturiert sind, wie die Datenvalidierung erfolgt, wie die GeschĂ€ftslogik in Handlers angewendet wird und wie Ergebnisse standardisiert zurĂŒckgegeben werden. All dies geschieht durch die automatische Erstellung dieser Komponenten mittels des Lino CLI.
Hinweis: Obwohl es nicht zwingend erforderlich ist, bietet Lino derzeit zwei Optionen zur Anwendung des CQRS-Musters auf Anwendungsebene an: Die Verwendung des Mediator-Musters ĂŒber die Bibliothek MediatR (von Jimmy Bogard) oder die Bibliothek Mediator (von Martin Othamar).
Ăberblick ĂŒber AnwendungsfĂ€lle
Ein Anwendungsfall stellt eine vollstÀndige Interaktion zwischen Benutzern oder externen Systemen und der Anwendung dar und beschreibt spezifische GeschÀftsszenarien. In Lino wird jeder Anwendungsfall unterteilt in:
- Command: stellt die Absicht dar, den Systemzustand zu Àndern (erstellen, aktualisieren, löschen usw.).
- Query: stellt die Absicht dar, Daten abzufragen, ohne den DomÀnenzustand zu Àndern.
Diese Trennung fördert die Klarheit des Codes, erleichtert Tests, ermöglicht unabhĂ€ngige Skalierbarkeit fĂŒr Lese- und SchreibvorgĂ€nge und entspricht den Prinzipien der Clean Architecture sowie bewĂ€hrten Praktiken des Domain-Driven Design (DDD).
Commands
Ein Command ist eine unverĂ€nderliche Nachricht, die nur die Daten enthĂ€lt, die erforderlich sind, um eine Aktion auszufĂŒhren, die den Systemzustand Ă€ndert (z. B. CreateInvoice, DeactivateUser).
Es sollte nur die Eigenschaften enthalten, die die wesentlichen Informationen zur DurchfĂŒhrung der Operation darstellen.
Merkmale eines Commands
- UnverÀnderlichkeit: Implementiert als
recordoder Klasse nur mitget, ohne öffentliche Setter. - Imperativer Name: Reflektiert die auszufĂŒhrende Aktion, z. B.
CreateOrder,UpdateCustomerAddress. - Minimale Daten: EnthĂ€lt nur die Felder, die fĂŒr die AusfĂŒhrung der Operation notwendig sind, ohne groĂe Datenmengen zurĂŒckzugeben.
- Isolierte Validierung: Jeder Command hat eigene Validierungsregeln, die sicherstellen, dass er konsistent ist, bevor er den Handler erreicht.
Command Validators
Die Command Validators stellen sicher, dass der Command wohlgeformt ist und die GeschĂ€ftsanforderungen erfĂŒllt, bevor er an den Handler gesendet wird. Im Lino verwenden wir die Bibliothek FluentValidation zur Implementierung dieser Validierungen, da sie in .NET-Projekten weit verbreitet ist und eine Fluent API zur Regeldefinition bietet.
GĂ€ngige Validierungsregeln
NotEmpty,NotNullfĂŒr Pflichtfelder.InclusiveBetweenfĂŒr Zahlenbereiche (z. B. Geldwerte, Mindest-/Höchstmenge).MaximumLengthundMinimumLengthfĂŒr String-LĂ€ngen.RuleForEachzur Validierung von Elementen in Sammlungen (List<T>).MustfĂŒr benutzerdefinierte Regeln (z. B. Dokumentenformate, Datumsvalidierung).
Command Handlers
Der Command Handler ist verantwortlich fĂŒr die AusfĂŒhrung der domĂ€nenspezifischen Logik, die mit diesem Command verbunden ist. Er orchestriert Repositories, Unit of Work (IUnitOfWork), Hilfsdienste und löst bei Bedarf Domain Events aus.
Implementierungsmuster eines Handlers
- Empfang von AbhĂ€ngigkeiten (Repositories, externe Dienste, UnitOfWork) ĂŒber Dependency Injection.
- Mapping und Instanziierung von DomÀnenentitÀten zur GewÀhrleistung der Konsistenz.
- Anwendung von GeschÀftsregeln (zusÀtzliche Validierungen, Wertberechnungen, Auslösen von Domain Events).
- Persistieren der Ănderungen ĂŒber
IUnitOfWorkoder direkte Repositories. - RĂŒckgabe eines Command Result, das Erfolg oder Fehler anzeigt (
Result<T>).
Command Results und Result Pattern
Das Command Result sollte ein einfaches DTO sein, das nur die minimal notwendigen Daten enthĂ€lt, damit der Aufrufer (z. B. eine API oder ein Frontend) weiterarbeiten kann. Es enthĂ€lt ĂŒblicherweise die ID der erstellten oder aktualisierten EntitĂ€t (Id) und im Fehlerfall standardisierte Fehlerinformationen.
FĂŒr Szenarien, in denen der Command fehlschlagen kann, verwenden wir das Result Pattern: eine Abstraktion, die das Ergebnis einer Operation kapselt und Erfolg oder Fehler darstellen kann. In .NET werden hĂ€ufig Typen wie Result<T> (oder Ă€hnliche Bibliotheken) verwendet, die:
- Ermöglichen, im Erfolgsfall einen
Valuezu definieren (z. B. ein einfaches DTO). - Im Fehlerfall standardisierte Codes oder Nachrichten speichern (
Error), eventuell mit zusĂ€tzlichen Metadaten (HTTP-Codes, Validierungsdetails, etc.). - Die Vereinheitlichung des Fehlerhandlings ĂŒber die gesamte API erleichtern und Konsistenz zwischen Schichten sicherstellen.
Erstellen eines Commands mit dem CLI
Lino vereinfacht die Generierung aller notwendigen Artefakte fĂŒr einen neuen Command durch den Befehl:
lino command new
Beim AusfĂŒhren dieses Befehls fragt der interaktive Assistent:
- Service â Name des Dienstes, in dem der Command erstellt wird.
- Modul â In modularisierten Diensten das Zielmodul.
- EntitĂ€t â Die DomĂ€nenentitĂ€t, die mit dem Command verbunden ist (optional, aber empfohlen).
- Name des Commands â Der endgĂŒltige Name des Commands (z. B.
CreateOrder).
Nach BestÀtigung erstellt Lino automatisch folgende Dateien:
CreateOrderCommand.csCreateOrderCommandValidator.csCreateOrderCommandHandler.csCreateOrderCommandResult.cs
Beispielhafte generierte Struktur
Betrachten Sie den Command CreatePerson. Die generierte Struktur sieht etwa so aus:
MyApp/
âââ src/
âââ Services/
âââ MyService/
âââ Application/
âââ MyApp.MyService.Application.csproj
âââ UseCases/
âââ People/
âââ Commands/
â âââ CreatePerson/
â âââ CreatePersonCommand.cs
â âââ CreatePersonCommandValidator.cs
â âââ CreatePersonCommandHandler.cs
â âââ CreatePersonCommandResult.cs
âââ Queries/
âââ ...
Abfragen
Eine Abfrage stellt die Absicht dar, Daten zu erhalten, ohne den Zustand der DomĂ€ne zu verĂ€ndern. Abfragen sind fĂŒr eine effiziente Lesbarkeit konzipiert und geben nur die erforderlichen Felder zurĂŒck, ohne ganze EntitĂ€ten zu laden, wenn dies nicht erforderlich ist.
Eigenschaften einer Abfrage
- UnverĂ€nderlich â wie Befehle (Commands) sollte eine Abfrage nach ihrer Erstellung nicht mehr verĂ€ndert werden.
- Beschreibender Name â spiegelt die gesuchten Informationen wider, z.âŻB.
GetCustomerById,ListOrdersByDateRange. - Filter und Paginierung â kann Parameter fĂŒr spezifische Suchen enthalten (Daten, Status, Seite, Sortierung).
- Projektion â sollte ein DTO oder View Model mit nur den benötigten Feldern zurĂŒckgeben, um das Laden kompletter DomĂ€nenobjekte zu vermeiden.
Abfrage-Validatoren
Abfrage-Validatoren sind dafĂŒr verantwortlich, Eingabeparameter wie Filter, Paginierungswerte (page, pageSize) und Sicherheitsregeln (Benutzerberechtigungen, Datenzugriffsrechte) zu validieren. Sie werden ebenfalls mit FluentValidation implementiert.
GĂ€ngige Validierungsregeln fĂŒr Abfragen
GreaterThanOrEqualTooderLessThanOrEqualTofĂŒr Bereichsfilter (z.âŻB. Daten).LengthfĂŒr Textfilter (z.âŻB. Suche nach Name, E-Mail).InclusiveBetweenfĂŒr Paginierungsparameter (z.âŻB. minimale/maximale Anzahl von Elementen pro Seite).
Abfrage-Handler
Der Abfrage-Handler ist dafĂŒr zustĂ€ndig, Repositories oder den Datenbankkontext abzufragen und optimierte Projektionen zurĂŒckzugeben, ohne vollstĂ€ndige DomĂ€nenentitĂ€ten zu laden.
In Lino wird empfohlen, Projektionen mit Methoden wie .Select() zu verwenden, um Daten direkt im erwarteten DTO-Format abzubilden, was Effizienz und Leistung verbessert.
Abfrageergebnisse
In Lino werden Abfrageergebnisse stets durch Records mit dem Suffix QueryResult dargestellt.
Diese Konvention gilt unabhĂ€ngig vom Antworttyp â sei es eine Liste von Elementen (mit oder ohne Paginierung) oder ein einzelnes detailliertes Element.
Erstellen einer Abfrage mit dem CLI
Ăhnlich wie bei Commands stellt Lino folgenden Befehl zur VerfĂŒgung:
lino query new
Der Assistent fragt nach:
- Service â Der Dienst, in dem die Abfrage erstellt wird.
- Modul â Falls zutreffend, das zugehörige DomĂ€nenmodul.
- EntitĂ€t â Die EntitĂ€t oder das Aggregat, dem die Abfrage zugeordnet ist (optional).
- Abfragename â Zum Beispiel
GetOrderByIdoderListProductsByCategory.
Lino generiert automatisch:
GetOrderByIdQuery.csGetOrderByIdQueryValidator.csGetOrderByIdQueryHandler.csGetOrderByIdQueryResult.cs
Beispiel fĂŒr eine generierte Struktur fĂŒr Abfragen
MyApp/
âââ src/
âââ Services/
âââ MyService/
âââ Application/
âââ MyApp.MyService.Application.csproj
âââ UseCases/
âââ Orders/
âââ Commands/
| âââ ...
âââ Queries/
âââ GetOrderById/
âââ GetOrderByIdQuery.cs
âââ GetOrderByIdQueryValidator.cs
âââ GetOrderByIdQueryHandler.cs
âââ GetOrderByIdQueryResult.cs
Fazit
Nachdem Sie verstanden haben, wie man Use Cases (Commands und Queries) in Lino definiert und strukturiert, sind Sie bereit, robuste und skalierbare GeschĂ€ftsszenarien zu erstellen, indem Sie die Schreib- und Leselogik klar trennen. Nutzen Sie das CLI, um die Entwicklung zu beschleunigen, ĂŒberprĂŒfen und passen Sie jedoch die Implementierungen stets an die spezifischen Anforderungen Ihrer DomĂ€ne an.
Im nÀchsten Schritt erkunden Sie, wie Sie Persistenz, DomÀnenereignisse und Infrastrukturdienste handhaben, um die gesamte Architektur Ihrer Anwendung zu gestalten.
