Die bereinigte Architektur schlägt vor, dass ein Anwendungsfall-Interakteur die tatsächliche Implementierung des Präsentators (der nach dem DIP injiziert wird) aufruft, um die Antwort / Anzeige zu handhaben. Ich sehe jedoch Leute, die diese Architektur implementieren, die Ausgabedaten vom Interaktor zurückgeben und dann den Controller (in der Adapterebene) entscheiden lassen, wie er damit umgeht. Verliert die zweite Lösung nicht nur die Verantwortung für die Anwendung, sondern definiert sie auch nicht eindeutig die Eingabe- und Ausgabeports für den Interaktor?
Eingangs- und Ausgangsanschlüsse
Angesichts der Definition der sauberen Architektur und insbesondere des kleinen Flussdiagramms, das die Beziehungen zwischen einem Controller, einem Use-Case-Interaktor und einem Presenter beschreibt, bin ich mir nicht sicher, ob ich den "Use-Case-Ausgabeport" richtig verstehe.
Eine saubere Architektur unterscheidet wie eine hexagonale Architektur zwischen primären Ports (Methoden) und sekundären Ports (Schnittstellen, die von Adaptern implementiert werden sollen). Im Anschluss an den Kommunikationsfluss erwarte ich, dass der "Use Case Input Port" ein primärer Port (also nur eine Methode) ist und der "Use Case Output Port" eine zu implementierende Schnittstelle ist, möglicherweise ein Konstruktorargument, das den eigentlichen Adapter übernimmt. damit der Interakteur es benutzen kann.
Codebeispiel
Um ein Codebeispiel zu erstellen, könnte dies der Controller-Code sein:
Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();
Die Presenter-Oberfläche:
// Use Case Output Port
interface Presenter
{
public void present(Data data);
}
Zum Schluss der Interaktor selbst:
class UseCase
{
private Repository repository;
private Presenter presenter;
public UseCase(Repository repository, Presenter presenter)
{
this.repository = repository;
this.presenter = presenter;
}
// Use Case Input Port
public void doSomething()
{
Data data = this.repository.getData();
this.presenter.present(data);
}
}
Auf dem Interakteur ruft der Moderator an
Die vorige Interpretation scheint durch das vorgenannte Diagramm selbst bestätigt zu werden, in dem die Beziehung zwischen dem Controller und dem Eingangsport durch einen durchgezogenen Pfeil mit einem "scharfen" Kopf dargestellt wird (UML für "Assoziation", was "hat ein" bedeutet, wobei das controller "hat einen" Anwendungsfall), während die Beziehung zwischen dem Presenter und dem Ausgabeport durch einen durchgezogenen Pfeil mit einem "weißen" Kopf dargestellt wird (UML für "Vererbung", was nicht diejenige für "Implementierung" ist, aber wahrscheinlich das ist sowieso die Bedeutung).
Darüber hinaus beschreibt Robert Martin in dieser Antwort auf eine andere Frage genau einen Anwendungsfall, bei dem der Interakteur den Präsentator auf eine Leseanforderung hin anruft:
Durch Klicken auf die Karte wird entweder der placePinController aufgerufen. Es sammelt die Position des Klicks und aller anderen kontextbezogenen Daten, erstellt eine placePinRequest-Datenstruktur und übergibt sie an den PlacePinInteractor, der die Position der Stecknadel überprüft, sie gegebenenfalls validiert, eine Place-Entität zum Aufzeichnen der Stecknadel erstellt und eine EditPlaceReponse erstellt Objekt und übergibt es an den EditPlacePresenter, der den Bereichseditorbildschirm aufruft.
Damit dies mit MVC gut funktioniert, könnte ich annehmen, dass die Anwendungslogik, die traditionell in den Controller eingeht, hier auf den Interaktor verschoben wird, da keine Anwendungslogik außerhalb der Anwendungsebene verloren gehen soll. Der Controller in der Adapterebene ruft einfach den Interaktor auf und konvertiert dabei möglicherweise einige kleinere Datenformate:
Die Software in dieser Schicht besteht aus einer Reihe von Adaptern, die Daten aus dem für die Anwendungsfälle und Entitäten am besten geeigneten Format in das für eine externe Agentur wie die Datenbank oder das Web am besten geeignete Format konvertieren.
aus dem Originalartikel über Schnittstellenadapter.
Auf dem Interaktor werden Daten zurückgegeben
Mein Problem bei diesem Ansatz ist jedoch, dass sich der Anwendungsfall um die Präsentation selbst kümmern muss. Nun sehe ich, dass der Zweck der Presenter
Schnittstelle darin besteht, abstrakt genug zu sein, um verschiedene Arten von Präsentatoren (GUI, Web, CLI usw.) darzustellen, und dass dies wirklich nur "Ausgabe" bedeutet, was ein Anwendungsfall sein könnte sehr gut haben, aber immer noch bin ich nicht ganz sicher damit.
Wenn ich mich im Web nach Anwendungen mit einer sauberen Architektur umsehe, sehe ich anscheinend nur Leute, die den Ausgabeport als Methode interpretieren, mit der DTO zurückgegeben wird. Das wäre so etwas wie:
Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious
Dies ist attraktiv, weil wir die Verantwortung für das "Aufrufen" der Präsentation aus dem Anwendungsfall heraus verlagern, sodass der Anwendungsfall sich nicht mehr mit dem Wissen befasst, was mit den Daten zu tun ist, sondern nur noch mit der Bereitstellung der Daten. Auch in diesem Fall wird die Abhängigkeitsregel nicht verletzt, da der Anwendungsfall immer noch nichts über die äußere Ebene weiß.
Der Anwendungsfall kontrolliert jedoch nicht mehr den Zeitpunkt, zu dem die eigentliche Präsentation durchgeführt wird (was beispielsweise nützlich sein kann, um an diesem Punkt zusätzliche Aufgaben wie die Protokollierung auszuführen oder sie bei Bedarf ganz abzubrechen). Beachten Sie außerdem, dass wir den Use-Case-Eingabeport verloren haben, da der Controller jetzt nur noch die getData()
Methode verwendet (dies ist unser neuer Ausgabeport). Darüber hinaus scheint es mir, dass wir hier das Prinzip "Tell, Don't Ask" brechen, weil wir den Interaktor auffordern, mit einigen Daten etwas zu tun, anstatt ihm zu sagen, dass es die eigentliche Sache im Internet macht erster Platz.
Auf den Punkt gebracht
Ist also eine dieser beiden Alternativen die "richtige" Interpretation des Use-Case-Ausgabeports gemäß der bereinigten Architektur? Sind sie beide lebensfähig?