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 🗙