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 record oder Klasse nur mit get, 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, NotNull fĂĽr Pflichtfelder.
  • InclusiveBetween fĂĽr Zahlenbereiche (z. B. Geldwerte, Mindest-/Höchstmenge).
  • MaximumLength und MinimumLength fĂĽr String-Längen.
  • RuleForEach zur Validierung von Elementen in Sammlungen (List<T>).
  • Must fĂĽ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 IUnitOfWork oder 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 Value zu 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.cs
  • CreateOrderCommandValidator.cs
  • CreateOrderCommandHandler.cs
  • CreateOrderCommandResult.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

  • GreaterThanOrEqualTo oder LessThanOrEqualTo fĂĽr Bereichsfilter (z. B. Daten).
  • Length fĂĽr Textfilter (z. B. Suche nach Name, E-Mail).
  • InclusiveBetween fĂĽ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 GetOrderById oder ListProductsByCategory.

Lino generiert automatisch:

  • GetOrderByIdQuery.cs
  • GetOrderByIdQueryValidator.cs
  • GetOrderByIdQueryHandler.cs
  • GetOrderByIdQueryResult.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.

Ein unbehandelter Fehler ist aufgetreten. Aktualisieren đź—™