it-swarm.com.de

Präsentationsschicht Zugriff auf Geschäftslogik

Ich habe in letzter Zeit viel Material über DDD (Business Entity Objects) und andere gängige Muster in n-Tiered (Layered) Architektur gelesen. Eine Sache, mit der ich Probleme habe, ist, dass die meisten Artikel, Blogs, Beispiele usw. nur mit einem Aspekt eines Systems zu sprechen scheinen. Einige sprechen möglicherweise über das Erstellen einer BLL zum Enthalten von Geschäftslogik. Einige sprechen nur davon, Daten über eine DAL zu speichern (Lesen ignorieren). Einige sprechen nur von DTOs. Ich habe noch nichts gefunden, das auf einer grundlegenden Ebene davon spricht, all dies zusammenzustellen. Wenn ich versuche, viel von diesem Material selbst zusammenzustellen, scheinen sich die Informationen manchmal gegenseitig auszuschließen.

Hier schließen sich (für mich) die Dinge gegenseitig aus:

  • Geschäftslogik ist in den DDD-Geschäftsentitätsobjekten enthalten, die Ihre Benutzeroberfläche nicht direkt erstellt (sie erhält DTOs).
  • Daten werden über DTOs an die Benutzeroberfläche übergeben, die kein Geschäft enthalten sollten Logik. Das DTO sollte nur Getter und Setter enthalten.

Was zu meiner Frage führt:

Wie konfiguriert sich Ihre Benutzeroberfläche selbst, wenn Geschäftslogik erforderlich ist, um die Daten dieser DTOs zu analysieren und Entscheidungen über die Benutzeroberfläche zu treffen?

Sie entwickeln beispielsweise die Benutzeroberfläche und haben diese Anforderung. Sie müssen im Code festlegen, ob die Schaltfläche Löschen aktiviert werden soll oder nicht:

Systemanforderung: Eine Bestellung kann nur gelöscht werden, wenn sich keine Produkte in der Bestellung befinden und der Status der Bestellung "Offen" lautet.

Natürlich können Sie Ihre Benutzeroberfläche wie folgt überprüfen:

Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.Products.Count = 0 And _order.Status = "Open" Then 
    DeleteButton.Enabled = True
End If

Aber fügt das jetzt nicht Geschäftslogik in den UI-Code ein? Was ist, wenn neue oder geänderte Geschäftsregeln festgelegt werden, wann eine Bestellung gelöscht werden kann? Während Sie eine Funktion .CanDelete() zum OrderDTO hinzufügen könnten, ist dies der falsche Ort, basierend auf allem, was ich gelesen habe, weil geschäftlich Logik gehört zum Entitätsobjekt Order. Wie weist man die Benutzeroberfläche an, diese verdammte Löschtaste zu aktivieren oder zu deaktivieren?

Eine Möglichkeit besteht darin, immer die Schaltfläche Löschen zu aktivieren und eine Ausnahme auszulösen, wenn die Aktion Löschen im Validierungsobjekt "Bestellung" fehlschlägt (Randnotiz: FluentValidation wurde gerade gefunden und es scheint erstaunlich!). Aber das ist eine schlampige Benutzeroberfläche. Was sind die "gute Architektur" -Alternativen, um die Benutzeroberfläche so zu gestalten, wie sie benötigt wird?

Dasselbe kann für viele andere UI-Situationen gelten, in denen Sie basierend auf verschiedenen Kombinationen der in einem DTO enthaltenen Daten (d. H. Geschäftslogik) einige Eingabefelder sperren oder ausblenden usw. können.

Oft scheinen die Artikel über DDD, DAL, BLL und DTO die praktischen Anforderungen an die Benutzeroberfläche zu ignorieren.

9
HardCode

Systemanforderung: Die Benutzeroberfläche sollte die Schaltfläche Löschen für eine Bestellung nur aktivieren, wenn die Bestellung keine Produkte enthält und der Status der Bestellung "Offen" lautet.

Dies ist der klassische Anforderungsfehler, bei dem Implementierungsdetails in eine Anforderung gelangen. Bis zu dem Punkt hat diese Anforderung absolut kein Recht zu wissen, dass die Art und Weise, wie ein Benutzer ein Löschen anfordert, die Verwendung einer Schaltfläche ist. Die Anforderung ist überhaupt keine UI-Anforderung.

Die grundlegendste Frage im Software-Design lautet: "Was weiß über was?"

Es ist die Aufgabe der Benutzeroberfläche, zu wissen, dass es eine Schaltfläche gibt. Es ist nicht der Job der Präsentationsebenen. Die Aufgabe der Präsentationsebenen besteht darin, zu wissen, dass eine Befehlsfunktion deaktiviert ist. Weiß nicht, ob es ein Knopf ist. Weiß nicht, ob es mit "Löschen", "Effacer" oder "Löschen" gekennzeichnet ist.

Wissen zu isolieren ist der Sinn all dessen. Also ja. Dieser Code sollte nicht in der Benutzeroberfläche enthalten sein.

Aber .CanDelete() ist eine kaputte Strategie, einfach weil sie dem Benutzer den Status "Aktiviert/Deaktiviert" nicht anzeigt, bevor er den Befehl ausgibt (wie auch immer er ihn ausgibt). Das ist Ihre UI-Anforderung.

Das Aktivieren und Deaktivieren in der Benutzeroberfläche erfordert keine Geschäftslogik in der Benutzeroberfläche. Es ist nur erforderlich, dass die Benutzeroberfläche Statusaktualisierungen akzeptiert, wenn sie gesendet werden. Es muss nicht verstehen warum.

Wenn Sie also nicht abfragen möchten, indem Sie .CanDelete() in einer Endlosschleife durchlaufen, sollten Sie diese Geschäftslogik immer dann aufrufen, wenn _order.Products.Count oder _order.Status könnte den Zustand geändert haben.

Du willst das alles zusammenfügen? Beginnen Sie mit etwas, das funktioniert. Finden Sie dann Orte, an denen zu viele Ideen miteinander vermischt wurden, und ziehen Sie sie auseinander. Wenn Sie dies genügend oft tun, werden Sie am Anfang sehen, was Sie trennen müssen, und sich Zeit sparen.

Es ist sehr wichtig, so zu beginnen. Wenn Sie immer nur Systeme erstellen, deren Anliegen perfekt voneinander getrennt sind, werden Sie nie lernen, wie Sie Systeme reparieren, bei denen sie miteinander vermischt sind.

5
candied_orange

Was ist, wenn neue oder geänderte Geschäftsregeln festgelegt werden, wann eine Bestellung gelöscht werden kann? Während Sie dem OrderDTO eine Funktion .CanDelete () hinzufügen könnten, ist dies der falsche Ort, basierend auf allem, was ich gelesen habe, da die Geschäftslogik zum Order-Entity-Objekt gehört.

Was für mich funktioniert: Ich betrachte das Domänenmodell als eine endliche Zustandsmaschine. Die Zustandsmaschine reagiert auf Nachrichten , bei denen Nachrichten einfache Dinge wie "Die Zeit ist vergangen" mit einem Messwert aus einem Zeitstempel einer lokalen Uhr sein können, oder etwas kompliziertes wie ein Buchungsagent, der eine bestimmte Reiseroute auswählt. Die Zustandsmaschine kopiert die gewünschten Informationen aus der Nachricht und berechnet ihren nächsten Zustand (der sogar der aktuelle Zustand sein kann, wenn kein Übergang vorliegt oder wenn der Übergang zum aktuellen Zustand zurückkehrt).

Wenn das Modell seinen aktuellen Status beschreibt (auch als Ansicht für den Anwendungsfall der Änderung bezeichnet), bietet es nicht nur eine Darstellung seines berechneten Status, sondern auch eine Darstellung der Kandidatenbefehle, die es empfangen möchte.

In Ihrem speziellen Beispiel würde diese Liste von Kandidatenbefehlen für eine leere Bestellung sowohl den Befehl "Element hinzufügen" als auch den Befehl "Reihenfolge löschen" enthalten. In dem Fall, in dem die Domänenlogik das Löschen verbietet, ist der Befehl "Reihenfolge löschen" nicht vorhanden in der Kandidatenliste.

Es ist dann, wie @candied_orange hervorhob, die Aufgabe eines anderen, herauszufinden, wie jeder der Kandidatenbefehle in die entsprechenden Benutzerleistungen übersetzt werden kann.

In einer Webschnittstelle verfügen Sie möglicherweise über ein Modul, das die Liste der Kandidatenbefehle aufnimmt und für jedes HTML-Formular erstellt. Es ist keine "Domänenlogik" in dem Sinne, dass es nicht die Entscheidungen des Domänenmodells trifft, sondern von der Nachricht des Domänenmodells in eine allgemeinere Darstellung übersetzt wird.

Sie können die Weboberfläche jedoch genauso einfach durch eine Befehlszeilenschnittstelle ersetzen. Die Liste der Kandidatenbefehle ist unverändert, aber der Ausdruck in der Befehlszeilenschnittstelle ist sicherlich anders.

In gewissem Sinne ist das Hinzufügen von ".CanDelete" zum DTO genau die richtige Idee, obwohl diese Schreibweise möglicherweise nicht verwendet wird. Ihr DTO ist eine unveränderliche Datenstruktur mit einer speicherinternen Darstellung der Liste der Kandidatenbefehle, und Sie können ein beliebiges Design für die Abfrage auswählen.

Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.commands.contains("http://example.org/commands/deleteOrder") Then 
    DeleteButton.Enabled = True
End If

Denken Sie daran, dass die Grundidee eines Datenübertragungsobjekts darin besteht, die Anzahl der Methodenaufrufe zu reduzieren, dh, es enthält viele interessante Antworten. Wir codieren also einige redundante Informationen in das DTO und erhalten im Gegenzug eine zentralisierte Domänenlogik, die einfacher zu ändern ist.

Oft scheinen die Artikel über DDD, DAL, BLL und DTO die praktischen Anforderungen an die Benutzeroberfläche zu ignorieren.

Ja. Insbesondere DDD ist schlecht darin, "Sanitär" und die tatsächlichen Komplikationen, die es mit sich bringen kann, zu diskutieren.

3
VoiceOfUnreason

Ich spüre deinen Schmerz und habe vor ein paar Jahren die gleichen Beobachtungen gemacht, die mich dazu gebracht haben, neu zu bewerten, was Objektorientierung sein soll und was ein wartbares Design ausmacht. Kurze Antwort: Sie können in diesem Thema nicht viel finden, weil es funktioniert nicht. N-Tier-Architekturen, DDD (wie von den meisten praktiziert) und insbesondere DTOs sind allesamt nicht optimale Ideen. Die persistenzunabhängige Geschäftsschicht funktioniert nicht. Geschäftsunabhängige Benutzeroberfläche in diesen Designs existiert nicht wirklich.

Erstens: Es ist völlig vernünftig, einige Schaltflächen basierend auf bestimmten Regeln auszublenden. Die Benutzeroberfläche ist das, was Benutzer sehen. Es ist für sie völlig selbstverständlich, die Terminologie der Benutzeroberfläche zu übernehmen.

Implementierung: Lassen Sie uns Schritt für Schritt darüber nachdenken. Wo soll das Wissen sein, um festzustellen, ob ein Order löschbar ist? Es scheint ziemlich vernünftig, dies im Order selbst zu erwarten. Es hängt vom internen Zustand und der Semantik des Order ab, also sollte es dort sein.

Wie gelangen die Informationen von Order zur Benutzeroberfläche? Hier schlagen alle oben genannten Designs fehl. Alle versuchen irgendwie, diese Informationen raus des Order zu bekommen. Und unabhängig davon, wie Sie das tun: ob es sich um ein neues Feld im DTO handelt, ob es sich um Statusaktualisierungen oder Ereignisse handelt oder was auch immer, es bedeutet immer, dass Sie jetzt Paar all diese Dinge dazu einfache Funktion.

Die einzige Möglichkeit, dies an einem Ort zu erhalten und zu warten, besteht darin, diese Informationen nicht herauszubekommen des Order überhaupt. Holen Sie sich stattdessen das Verhalten, das diese Informationen benötigt in das Order, und das ist vorhanden das Order auf der Benutzeroberfläche.

Es folgt also ziemlich vernünftig, dass sich das Order mit deaktivierter/aktivierter Löschtaste und allem präsentieren kann. Tatsächlich sollte die Benutzeroberfläche nicht vom "Geschäft" abhängen, das "Geschäft" sollte von der Benutzeroberfläche abhängen. (Oder das "Business" hat eine Benutzeroberfläche, wenn Sie so wollen)

Ich hoffe, das klingt auch für Sie völlig vernünftig, auch wenn es fast allem widerspricht, was uns diese Architektur- und Designmuster in den letzten 2-3 Jahrzehnten erzählt haben.

Pseudocode-Beispiel aktualisieren :

class Order {
   ...state: i.e. products, whatever...

   void cancel() { ... }

   ...

   UIComponent display() {
      return Panel(
         new Table(products),
         new Button("Track", ...),
         new Button("Cancel", products.empty?ENABLED:DISABLED, this->cancel()),
         ...);
   }
}

Dies zeigt, was ich meine. Das Order weiß sich zu präsentieren. Dies folgt KISS, Objektorientierung, Demeter-Gesetz, ist wartbar usw. Wenn sich "Business" und "UI" in derselben Anwendung befinden, gibt es keinerlei Grund, nicht dies zu tun diesen Weg.

Beachten Sie, dass die Entscheidung, ob die Bestellung stornierbar ist, innerhalb von Order bleibt. Beachten Sie auch, dass keine Details der Präsentation in das Order selbst gelangen. Keine Farben, Layouts oder ähnliches.

1

Ich bin mir nicht sicher, welche Art von Architektur Sie mit der Idee hatten, DTOs auf diese Weise zu verwenden, aber lassen Sie uns Bob Martins sehr beliebte saubere Architektur nehmen und einen Blick darauf werfen, wie das Problem dort gelöst wird.

In einer solchen Architektur würde ein UI-Objekt eine Schnittstelle für bereitstellen

  • bestelldaten abrufen und für

  • Aktivieren und Deaktivieren von Schaltflächen (jedoch ohne Logik, wie und wann dies zu tun ist).

Ein Presenter-Objekt (das eine solche Schnittstelleninstanz enthält) könnte die Daten von der Auftragsentität abrufen, diese Daten - möglicherweise als Auftrags-DTO - über die Schnittstelle an die Benutzeroberfläche übergeben und die Methode .CanDelete() der Bestellung überprüfen Entität und rufen Sie die Schnittstelle auf, um die Schaltfläche Löschen entsprechend zu deaktivieren oder zu aktivieren. Der Präsentator kann auch bestimmte Ereignisse im System abhören, die den Status der Reihenfolge ändern können, und dann die Auswertung von .CanDelete() (und die Aktualisierung der Benutzeroberfläche) wiederholen.

Kurz gesagt, ich weiß nicht, auf welche "Artikel, Blogs, Beispiele usw." Sie sich beziehen, aber die Zutat, die dort zu fehlen scheint, sind einfach Schnittstellen und Ereignisse. Die Idee "Daten werden über DTO an die Benutzeroberfläche übergeben" ist möglicherweise nur ein Missverständnis oder eine übermäßige Vereinfachung. Da Sie jedoch keine Referenzen angegeben haben, kann ich Ihnen nicht sagen, aus welcher Quelle Sie diese Daten tatsächlich bezogen haben.

1
Doc Brown

Da das DTO bei den Datenentitäten nicht 1 zu 1 sein muss, würde ich mit Ihrem CanDelete () im DTO die Geschäftsregeln im BL verwenden. Ich würde es wahrscheinlich als Attribut "IsDeletable" neu verpacken, um es datenähnlicher zu machen.

Wenn dies ein reguläres Muster ist, möchten Sie möglicherweise eine Struktur einbetten. (IsDeletable, IsUpdatable).

Wenn Sie eine API anstelle einer Benutzeroberfläche erstellt haben (und eine API ist meiner Meinung nach eine Art Benutzeroberfläche), ist dies wie https://en.wikipedia.org/wiki/HATEOAS in abstrakter Form.

Es gibt eine Menge Dogmen sowie eine absolut gültige Praxis, die nicht gut ist. Es ist eine Fähigkeit, das auszuwählen, was für Sie funktioniert und es Ihnen ermöglicht, etwas zu liefern, während Sie das Gleichgewicht zwischen perfektionistischem Handdrücken und Hacken finden.

1
LoztInSpace