Architecture-center: Was ist der Unterschied zwischen ProductsCommandHandler (CQRS) und ProductRepository (traditioneller Bogen)?

Erstellt am 15. Nov. 2019  ·  5Kommentare  ·  Quelle: MicrosoftDocs/architecture-center

Hallo,
Vielen Dank, dass Sie diesen Artikel über CQRS geschrieben haben. Ich versuche zu verstehen, wie die Implementierung aussieht, und habe die Beispiele durchgearbeitet. Der im Beispiel bereitgestellte Beispiel-Repository-Code sieht sehr ähnlich aus, wie normalerweise ein Repo mit Ausnahme der Namenskonvention geschrieben wird. Zum Beispiel denke ich, dass das unten angegebene Repository dasselbe ist wie ProductsCommandHandler und wahrscheinlich wäre ein Unterschied, dass es hier kein GetProduct gibt, das wir normalerweise hinzufügen würden. Könnten Sie bitte erklären, was ich hier nicht bekomme?

public class ProductRepository {
  void AddNewProduct(Product newProduct) {
    ...
  }
  void RateProduct(int productId, int userId, int rating) {
    var product = repository.Find(productId);
    if (product != null)
    {
      product.RateProduct(userId, rating);
      repository.Save(product);
    }
  }
}

Dokumentdetails

Bearbeiten Sie diesen Abschnitt nicht.

Pri1 architecture-centesvc assigned-to-author cloud-fundamentalsubsvc product-question triaged

Hilfreichster Kommentar

@martinmthomas Tatsächlich ist der Befehlshandler kein Repository. Es verwendet ein Repository.

Der Befehlshandler soll "gegebenenfalls die eigentlichen Befehle verarbeiten". Im Beispiel ist die ProductsCommandHandler erhält Klasse ein IRepository<Product> in seinem Konstruktor.

Angenommen, der Benutzer bewertet das Produkt 5555 mit 4 Sternen.

  • Der Benutzer sieht eine Schnittstelle. Nehmen wir eine Webseite an (könnte ein lokales EXE-Programm sein oder was auch immer, stellen wir uns eine Website vor).
  • Der Benutzer klickt auf eine Schaltfläche. Angenommen, dies löst einen AJAX-POST aus, der zu einer Route / Rate mit Daten {"product":"5555","stars":4}
  • Die POST-Route verfügt über einen Controller. Dieser Controller ist einer "Schreib" -Operation zugeordnet, da er von einem POST-Aufruf stammt.
  • Der Controller hier in einem klassischen Ansatz würde nur das klassische Produkt-Repository laden und das Produkt 5555 laden. Dann sagen Sie dem Produkt "Sie sind jetzt mit 4 Sternen bewertet" und speichern.
  • Bei diesem Ansatz erstellt der Controller den Befehl RateProduct und füllt das Produkt 5555 mit den Sternen 4 aus. Im Beispiel wird auch angegeben, wer bewertet wird.
  • In diesem Moment "sendet" die Steuerung den Befehl an das Schreibmodell. Hier haben Sie zwei Möglichkeiten: Rufen Sie einen Befehlshandler auf oder stellen Sie ihn in die Warteschlange.
  • Angenommen, Sie haben keine Warteschlange. Sie erhalten dann die CommandHandler in Ihrem Controller (wahrscheinlich durch Abhängigkeitsinjektion) und platzieren einfach den Befehl dort: h.Handle (c); Dabei ist c der Befehl RateProduct.
  • Angenommen, Sie haben eine Warteschlange. Sie erhalten dann die Warteschlange in Ihrem Controller (wahrscheinlich durch Abhängigkeitsinjektion) und stellen den Befehl dort einfach in die Warteschlange: q.Queue (c);
  • In diesem letzten Fall nimmt der Listener der Warteschlange den Befehl aus der Warteschlange und ruft stattdessen den Befehlshandler auf.
  • Als dritte Option (empfohlen) wählt Ihr Controller nicht, ob der Befehl synchron (Handler abrufen) oder asynchron (Warteschlange abrufen) verarbeitet wird, sondern sollte einen "generischen Ansatz" verwenden, bei dem ein Befehlsbus verwendet wird, in dem die Befehle ausgeführt werden platziert sind. Angenommen, der Befehlsbus ist b, dann würden Sie b.Send (c) ausführen. Dabei ist c der Befehl RateProduct.
  • Bei diesem dritten Ansatz platzieren Sie den Befehlshandler am Ende des Busses und können Middlewares so konfigurieren, dass "der Befehl vom Bus entfernt und in die Warteschlange gestellt wird".

Also 3 Optionen: a) Holen Sie sich den Handler, b) Holen Sie sich die Warteschlange, c) Holen Sie sich einen Bus und lassen Sie ihn entscheiden, den Befehl an den Handler oder an die Warteschlange zu senden.

Was auch immer Sie tun ... Der Hauptunterschied zu "einem klassischen Repository" besteht darin, dass Sie NIEMALS das Repository selbst im Controller aufrufen. Der Controller weiß nicht, wie Entitäten verwaltet werden (klassisches Repository), aber er weiß, wie man Entitäten verwaltet, um Entitäten Dinge zu tun (Befehlshandler).

Dann ist es der Befehlshandler, der - wie der Name schon sagt - den Befehl "verarbeitet", der von irgendwoher kam (ein HTML-Webcontroller, ein API-Controller, eine Befehlszeile, was auch immer), der als Absicht gesendet wurde, und es ist der CommandHandler (die zur Schreibseite gehört), die entscheidet, was mit diesem Befehl geschehen soll.

Beispielsweise kann der CommandHandler ein Repo verwenden, um das Produkt abzurufen, den Status festzulegen und zu speichern (wie im Beispiel), aber er kann auch in ein Ereignisprotokoll schreiben, Elemente zum Aktualisieren der Leseseiten auslösen, externe Konnektoren auslösen oder was auch immer.

Die aufgabenbasierte Benutzeroberfläche des Benutzers, um das Produkt und seinen Controller als AGNOSTISCH zu bewerten, "wo" das Sterne- / Bewertungssystem platziert oder gespeichert ist. Stellen Sie sich vor, Sie entwerfen ein System von Grund auf neu und die Produktklasse enthält bereits die RateProduct () -Methode wie im Beispiel. Gut.

Aber ... was ist, wenn Sie dort ein Legacy-System mit einem "alten" Ansatz für das Produkt haben? Im "Modell" (dh: Business Mind) gibt es keine "Bewertung" des Produkts. Stattdessen möchte der Marketing-Mitarbeiter das vorhandene Produkt "bewerten", aber das gesamte Unternehmen stimmt zu, dass dies eine "externe Sache" ist. Würden Sie das Produkt-Repository verwenden? Oder vielleicht ein anderer Hilfsspeicher, damit Sie das bereits funktionierende und vollständig getestete Produkt und ProductRepository nicht "berühren"?

Wenn Sie Befehle verwenden, macht es dem Schreibcontroller nichts aus. Das Web, die API und die CLI senden den "Befehl" einfach an den Befehlshandler (entweder direkt, über die Warteschlange oder über einen Befehlsbus, der wiederum zum Handler oder zu einer Warteschlange weitergeleitet wird), und sie vergessen. Anschließend entscheidet der Befehlshandler, was mit dem "RateProductCommand" an einem zentralen und kleinen Punkt Ihres Quellcodes zu tun ist. Dadurch wird die Art und Weise, wie dies behandelt wird, vom Anwendungscode entkoppelt, wodurch die Benutzerfreundlichkeit erhöht wird.

Dann entscheidet der Handler, ob es geeignet ist, das ProductRepository oder einen anderen Ansatz zum Speichern der "Bewertung eines Produkts" zu verwenden.

Also, um zu antworten:

CommandHandler => hat nichts mit den Berechtigungen zu tun. Es behandelt die "Absichten des Benutzers" (die letztendlich darin bestehen könnten, Entitäten zu ändern; daher verwendet der Befehlshandler höchstwahrscheinlich ein Repository).
Repository => der tatsächliche Speicher für eine bestimmte Entität.

Ich hoffe zu helfen.
Xavi.

Alle 5 Kommentare

@martinmthomas Vielen Dank für Ihre Frage! Wir werden dies überprüfen und gegebenenfalls ein Update bereitstellen.

@ MikeWasson irgendwelche Gedanken hier?

AB # 160217 - Vielen Dank für derzeit geprüft

@martinmthomas Tatsächlich ist der Befehlshandler kein Repository. Es verwendet ein Repository.

Der Befehlshandler soll "gegebenenfalls die eigentlichen Befehle verarbeiten". Im Beispiel ist die ProductsCommandHandler erhält Klasse ein IRepository<Product> in seinem Konstruktor.

Angenommen, der Benutzer bewertet das Produkt 5555 mit 4 Sternen.

  • Der Benutzer sieht eine Schnittstelle. Nehmen wir eine Webseite an (könnte ein lokales EXE-Programm sein oder was auch immer, stellen wir uns eine Website vor).
  • Der Benutzer klickt auf eine Schaltfläche. Angenommen, dies löst einen AJAX-POST aus, der zu einer Route / Rate mit Daten {"product":"5555","stars":4}
  • Die POST-Route verfügt über einen Controller. Dieser Controller ist einer "Schreib" -Operation zugeordnet, da er von einem POST-Aufruf stammt.
  • Der Controller hier in einem klassischen Ansatz würde nur das klassische Produkt-Repository laden und das Produkt 5555 laden. Dann sagen Sie dem Produkt "Sie sind jetzt mit 4 Sternen bewertet" und speichern.
  • Bei diesem Ansatz erstellt der Controller den Befehl RateProduct und füllt das Produkt 5555 mit den Sternen 4 aus. Im Beispiel wird auch angegeben, wer bewertet wird.
  • In diesem Moment "sendet" die Steuerung den Befehl an das Schreibmodell. Hier haben Sie zwei Möglichkeiten: Rufen Sie einen Befehlshandler auf oder stellen Sie ihn in die Warteschlange.
  • Angenommen, Sie haben keine Warteschlange. Sie erhalten dann die CommandHandler in Ihrem Controller (wahrscheinlich durch Abhängigkeitsinjektion) und platzieren einfach den Befehl dort: h.Handle (c); Dabei ist c der Befehl RateProduct.
  • Angenommen, Sie haben eine Warteschlange. Sie erhalten dann die Warteschlange in Ihrem Controller (wahrscheinlich durch Abhängigkeitsinjektion) und stellen den Befehl dort einfach in die Warteschlange: q.Queue (c);
  • In diesem letzten Fall nimmt der Listener der Warteschlange den Befehl aus der Warteschlange und ruft stattdessen den Befehlshandler auf.
  • Als dritte Option (empfohlen) wählt Ihr Controller nicht, ob der Befehl synchron (Handler abrufen) oder asynchron (Warteschlange abrufen) verarbeitet wird, sondern sollte einen "generischen Ansatz" verwenden, bei dem ein Befehlsbus verwendet wird, in dem die Befehle ausgeführt werden platziert sind. Angenommen, der Befehlsbus ist b, dann würden Sie b.Send (c) ausführen. Dabei ist c der Befehl RateProduct.
  • Bei diesem dritten Ansatz platzieren Sie den Befehlshandler am Ende des Busses und können Middlewares so konfigurieren, dass "der Befehl vom Bus entfernt und in die Warteschlange gestellt wird".

Also 3 Optionen: a) Holen Sie sich den Handler, b) Holen Sie sich die Warteschlange, c) Holen Sie sich einen Bus und lassen Sie ihn entscheiden, den Befehl an den Handler oder an die Warteschlange zu senden.

Was auch immer Sie tun ... Der Hauptunterschied zu "einem klassischen Repository" besteht darin, dass Sie NIEMALS das Repository selbst im Controller aufrufen. Der Controller weiß nicht, wie Entitäten verwaltet werden (klassisches Repository), aber er weiß, wie man Entitäten verwaltet, um Entitäten Dinge zu tun (Befehlshandler).

Dann ist es der Befehlshandler, der - wie der Name schon sagt - den Befehl "verarbeitet", der von irgendwoher kam (ein HTML-Webcontroller, ein API-Controller, eine Befehlszeile, was auch immer), der als Absicht gesendet wurde, und es ist der CommandHandler (die zur Schreibseite gehört), die entscheidet, was mit diesem Befehl geschehen soll.

Beispielsweise kann der CommandHandler ein Repo verwenden, um das Produkt abzurufen, den Status festzulegen und zu speichern (wie im Beispiel), aber er kann auch in ein Ereignisprotokoll schreiben, Elemente zum Aktualisieren der Leseseiten auslösen, externe Konnektoren auslösen oder was auch immer.

Die aufgabenbasierte Benutzeroberfläche des Benutzers, um das Produkt und seinen Controller als AGNOSTISCH zu bewerten, "wo" das Sterne- / Bewertungssystem platziert oder gespeichert ist. Stellen Sie sich vor, Sie entwerfen ein System von Grund auf neu und die Produktklasse enthält bereits die RateProduct () -Methode wie im Beispiel. Gut.

Aber ... was ist, wenn Sie dort ein Legacy-System mit einem "alten" Ansatz für das Produkt haben? Im "Modell" (dh: Business Mind) gibt es keine "Bewertung" des Produkts. Stattdessen möchte der Marketing-Mitarbeiter das vorhandene Produkt "bewerten", aber das gesamte Unternehmen stimmt zu, dass dies eine "externe Sache" ist. Würden Sie das Produkt-Repository verwenden? Oder vielleicht ein anderer Hilfsspeicher, damit Sie das bereits funktionierende und vollständig getestete Produkt und ProductRepository nicht "berühren"?

Wenn Sie Befehle verwenden, macht es dem Schreibcontroller nichts aus. Das Web, die API und die CLI senden den "Befehl" einfach an den Befehlshandler (entweder direkt, über die Warteschlange oder über einen Befehlsbus, der wiederum zum Handler oder zu einer Warteschlange weitergeleitet wird), und sie vergessen. Anschließend entscheidet der Befehlshandler, was mit dem "RateProductCommand" an einem zentralen und kleinen Punkt Ihres Quellcodes zu tun ist. Dadurch wird die Art und Weise, wie dies behandelt wird, vom Anwendungscode entkoppelt, wodurch die Benutzerfreundlichkeit erhöht wird.

Dann entscheidet der Handler, ob es geeignet ist, das ProductRepository oder einen anderen Ansatz zum Speichern der "Bewertung eines Produkts" zu verwenden.

Also, um zu antworten:

CommandHandler => hat nichts mit den Berechtigungen zu tun. Es behandelt die "Absichten des Benutzers" (die letztendlich darin bestehen könnten, Entitäten zu ändern; daher verwendet der Befehlshandler höchstwahrscheinlich ein Repository).
Repository => der tatsächliche Speicher für eine bestimmte Entität.

Ich hoffe zu helfen.
Xavi.

Wie @xmontero erwähnt, haben ProductsCommandHandler und ProductRepository nur eine Beziehung "has". ProductsCommandHandler fungiert als Schnittstelle zwischen ProductApi / Controller und ProductRepository. Diese Implementierung hilft dabei, Befehle von Abfragen getrennt zu halten. Beim herkömmlichen Ansatz verwenden wir direkt das Repository des Controllers, um Befehle und Abfragen zusammen auszuführen, sodass keine andere Schnittstelle erforderlich ist.

Sie haben das CQRS-Entwurfsmuster in Ihrer Anwendung angewendet, wenn Sie Befehle von Abfragen getrennt halten. Es führt Sie in die Richtung, alle in den Artikeln genannten Vorteile von CQRS zu nutzen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen