
Dieser Artikel ist Teil
der Chronik der Softwarearchitektur , einer Reihe von Artikeln zur Softwarearchitektur. In ihnen schreibe ich darĂŒber, was ich ĂŒber Softwarearchitektur gelernt habe, was ich darĂŒber denke und wie ich Wissen benutze. Der Inhalt dieses Artikels ist möglicherweise sinnvoller, wenn Sie die vorherigen Artikel in der Reihe lesen.
Nach meinem UniversitĂ€tsabschluss begann ich als Highschool-Lehrer zu arbeiten, aber vor einigen Jahren kĂŒndigte ich und ging zu den Vollzeit-Softwareentwicklern.
Seitdem hatte ich immer das GefĂŒhl, dass ich die âverloreneâ Zeit wiederherstellen und so schnell wie möglich so viel wie möglich herausfinden muss. Deshalb begann ich mich ein wenig mit Experimenten zu beschĂ€ftigen, viel zu lesen und zu schreiben, wobei ich besonders auf das Design und die Architektur der Software achtete. Deshalb schreibe ich diese Artikel, um mir beim Studium zu helfen.
In den letzten Artikeln habe ich ĂŒber viele Konzepte und Prinzipien gesprochen, die ich gelernt habe, und ein wenig darĂŒber, wie ich darĂŒber nachdenke. Aber ich stelle sie mir als Fragmente eines groĂen Puzzles vor.
In diesem Artikel geht es darum, wie ich all diese Fragmente zusammengesetzt habe. Ich denke, ich sollte ihnen einen Namen geben, also werde ich sie
explizite Architektur nennen . DarĂŒber hinaus werden alle diese Konzepte
âim Kampf getestetâ und in der Produktion auf hochzuverlĂ€ssigen Plattformen eingesetzt. Eine davon ist eine SaaS-E-Commerce-Plattform mit Tausenden von Online-Shops auf der ganzen Welt, die andere ist eine Handelsplattform, die in zwei LĂ€ndern mit einem Nachrichtenbus betrieben wird, der mehr als 20 Millionen Nachrichten pro Monat verarbeitet.
Grundlegende Blöcke des Systems
Beginnen wir mit dem
Abrufen der EBI- und
Ports & Adapter- Architekturen. Beide trennen klar den internen und externen Code der Anwendung sowie die Adapter zum Verbinden des internen und externen Codes.
DarĂŒber hinaus definiert die
Ports & Adapter- Architektur explizit die drei grundlegenden Codeblöcke im System:
- Auf diese Weise können Sie die BenutzeroberflĂ€che unabhĂ€ngig von ihrem Typ ausfĂŒhren.
- SystemgeschĂ€ftslogik oder Anwendungskern . Es wird von der BenutzeroberflĂ€che verwendet, um echte Transaktionen durchzufĂŒhren.
- Der Infrastrukturcode , der den Kern unserer Anwendung mit Tools wie der Datenbank, der Suchmaschine oder APIs von Drittanbietern verbindet.

Der Kern der Anwendung ist das Wichtigste, worĂŒber man nachdenken muss. Mit diesem Code können Sie echte Aktionen im System ausfĂŒhren, dh dies ist unsere Anwendung. Es können mehrere BenutzeroberflĂ€chen (eine progressive Webanwendung, eine mobile Anwendung, eine CLI, eine API usw.) damit arbeiten, alles lĂ€uft auf einem Kern.
Wie Sie sich vorstellen können, geht ein typischer AusfĂŒhrungsfluss vom Code in der BenutzeroberflĂ€che ĂŒber den Anwendungskern zum Infrastrukturcode zurĂŒck zum Anwendungskern und schlieĂlich wird die Antwort an die BenutzeroberflĂ€che gesendet.

Die Werkzeuge
Weit entfernt vom wichtigsten Kernel-Code gibt es immer noch Tools, die die Anwendung verwendet. Zum Beispiel die Datenbankmaschine, die Suchmaschine, der Webserver und die CLI-Konsole (obwohl die beiden letzteren auch Ăbermittlungsmechanismen sind).

Es erscheint seltsam, die CLI-Konsole im selben Themenbereich wie das DBMS zu platzieren, da sie einen anderen Zweck hat. TatsÀchlich sind beide Tools von der Anwendung verwendet. Der Hauptunterschied besteht darin, dass die CLI-Konsole und der Webserver
die Anwendung anweisen, etwas zu tun . Der DBMS-Kernel
empfÀngt im Gegenteil
Befehle von der Anwendung . Dies ist ein sehr wichtiger Unterschied, da dies einen groĂen Einfluss darauf hat, wie wir Code schreiben, um diese Tools mit dem Anwendungskern zu verbinden.
Verbinden von Tools und Bereitstellungsmechanismen mit dem Anwendungskern
Codeblöcke, die Tools mit dem Anwendungskern verbinden, werden als Adapter (
Ports & Adapter-Architektur ) bezeichnet. Sie ermöglichen es der GeschÀftslogik, mit einem bestimmten Tool zu interagieren und umgekehrt.
Adapter, die die Anwendung anweisen, etwas zu tun, werden als
PrimÀr- oder Steueradapter bezeichnet , wÀhrend Adapter, die die Anwendung anweisen, etwas zu tun, als
sekundÀre oder verwaltete Adapter bezeichnet werden .
Ports
Diese
Adapter werden jedoch nicht zufÀllig erstellt, sondern entsprechen einem bestimmten Einstiegspunkt im
Port des Anwendungskerns. Ein Port ist
nichts anderes als eine Spezifikation, wie das Tool den Anwendungskern verwenden kann oder umgekehrt. In den meisten Sprachen und in seiner einfachsten Form wird dieser Port eine Schnittstelle sein, aber tatsÀchlich kann er aus mehreren Schnittstellen und DTO bestehen.
Es ist wichtig zu beachten, dass sich
Ports (Schnittstellen) innerhalb der GeschĂ€ftslogik und Adapter auĂerhalb befinden. Damit diese Vorlage ordnungsgemÀà funktioniert, ist es Ă€uĂerst wichtig, Ports entsprechend den Anforderungen des Anwendungskerns zu erstellen und nicht nur die Tool-APIs nachzuahmen.
PrimÀr- oder Steueradapter
PrimÀr- oder Steueradapter
wickeln sich um einen Port und teilen dem Anwendungskern mit, was zu tun ist.
Sie wandeln alle Daten aus dem Ăbermittlungsmechanismus in Methodenaufrufe im Anwendungskern um.
Mit anderen Worten, unsere Steuerungsadapter sind Controller oder Konsolenbefehle. Sie sind in ihren Konstruktor mit einem Objekt eingebettet, dessen Klasse die Schnittstelle (Port) implementiert, die ein Controller- oder Konsolenbefehl benötigt.
In einem spezifischeren Beispiel kann der Port die Dienstschnittstelle oder die Repository-Schnittstelle sein, die der Controller benötigt. Eine bestimmte Implementierung eines Dienstes, Repositorys oder einer Anforderung wird dann implementiert und in der Steuerung verwendet.
DarĂŒber hinaus kann der Port ein Befehlsbus oder eine Abfragebusschnittstelle sein. In diesem Fall wird eine bestimmte Implementierung des Befehls- oder Anforderungsbusses in die Steuerung eingegeben, die dann einen Befehl oder eine Anforderung erstellt und an den entsprechenden Bus weiterleitet.
SekundÀre oder verwaltete Adapter
Im Gegensatz zu Steueradaptern, die einen Port
umschlieĂen, implementieren
verwaltete Adapter einen Port und eine Schnittstelle und geben dann den Anwendungskern ein, in dem der Port benötigt wird (mit Typ).

Zum Beispiel haben wir eine native Anwendung, die Daten speichern muss. Wir erstellen eine Persistenzschnittstelle mit einer Methode zum
Speichern eines Datenarrays und einer Methode zum
Löschen einer Zeile in einer Tabelle anhand ihrer ID. Von nun an benötigen wir im Konstruktor ĂŒberall dort, wo die Anwendung Daten speichern oder löschen muss, ein Objekt, das die von uns definierte Persistenzschnittstelle implementiert.
Erstellen Sie nun einen MySQL-spezifischen Adapter, der diese Schnittstelle implementiert. Es wird Methoden zum Speichern des Arrays und zum Löschen der Zeile in der Tabelle geben, und wir werden es ĂŒberall dort einfĂŒhren, wo die Persistenzschnittstelle erforderlich ist.
Wenn wir uns irgendwann dazu entschlieĂen, den Datenbankanbieter beispielsweise auf PostgreSQL oder MongoDB zu Ă€ndern, mĂŒssen wir nur einen neuen Adapter erstellen, der die fĂŒr PostgreSQL spezifische Persistenzschnittstelle implementiert, und anstelle des alten einen neuen Adapter einfĂŒhren.
Kontrollinversion
Ein charakteristisches Merkmal dieser Vorlage ist, dass die Adapter von einem bestimmten Tool und einem bestimmten Port abhÀngen (durch Implementierung einer Schnittstelle). Unsere GeschÀftslogik hÀngt jedoch nur vom Port (Schnittstelle) ab, der den Anforderungen der GeschÀftslogik entspricht und nicht von einem bestimmten Adapter oder Tool abhÀngt.

Dies bedeutet, dass die AbhÀngigkeiten auf das Zentrum gerichtet sind, dh es gibt eine
Umkehrung des Steuerprinzips auf architektonischer Ebene .
Auch hier ist
es unbedingt erforderlich, dass die Ports gemÀà den Anforderungen des Anwendungskerns erstellt werden und nicht nur die Tool-APIs nachahmen .
Organisation des Anwendungskerns
Die Onion-Architektur nimmt die DDD-Schichten auf und integriert sie in die
Port- und Adapterarchitektur . Diese Ebenen sollen der GeschĂ€ftslogik, dem Inneren des âSechsecksâ von Ports und Adaptern, Ordnung verleihen. Nach wie vor ist die Richtung der AbhĂ€ngigkeiten zur Mitte gerichtet.
Anwendungsschicht (Anwendungsschicht)
AnwendungsfĂ€lle sind Prozesse, die ĂŒber eine oder mehrere BenutzeroberflĂ€chen im Kernel gestartet werden können. Beispielsweise kann ein CMS eine BenutzeroberflĂ€che fĂŒr regulĂ€re Benutzer, eine andere unabhĂ€ngige BenutzeroberflĂ€che fĂŒr CMS-Administratoren, eine andere CLI und eine Web-API haben. Diese BenutzeroberflĂ€chen (Anwendungen) können eindeutige oder hĂ€ufige AnwendungsfĂ€lle auslösen.
AnwendungsfÀlle werden auf Anwendungsebene definiert - der ersten Ebene von DDD und der Onion-Architektur.

Diese Schicht enthĂ€lt Anwendungsdienste (und ihre Schnittstellen) als erstklassige Objekte sowie Port- und Adapterschnittstellen (Ports), einschlieĂlich ORM-Schnittstellen, Suchmaschinenschnittstellen, Messaging-Schnittstellen usw. In dem Fall, in dem wir sie verwenden Der Befehlsbus und / oder der Anforderungsbus sind auf dieser Ebene die entsprechenden Befehls- und Anforderungshandler.
Anwendungsdienste und / oder Befehlshandler enthalten die Bereitstellungslogik eines Anwendungsfalls, eines GeschÀftsprozesses. Ihre Rolle ist in der Regel wie folgt:
- Verwenden Sie das Repository, um nach einer oder mehreren EntitÀten zu suchen.
- Bitten Sie diese EntitĂ€ten, eine DomĂ€nenlogik auszufĂŒhren.
- Verwenden Sie den Speicher, um EntitÀten erneut zu speichern und DatenÀnderungen effektiv zu speichern.
Befehlshandler können auf zwei Arten verwendet werden:
- Sie können Logik zum AusfĂŒhren eines Anwendungsfalls enthalten.
- Sie können als einfache Teile einer Verbindung in unserer Architektur verwendet werden, die einen Befehl erhalten und einfach die im Anwendungsdienst vorhandene Logik aufrufen.
Welcher Ansatz verwendet werden soll, hÀngt vom Kontext ab, zum Beispiel:
- Wir haben bereits Anwendungsdienste und jetzt wird der Befehlsbus hinzugefĂŒgt?
- Können Sie mit dem Befehlsbus eine Klasse / Methode als Handler angeben oder mĂŒssen Sie vorhandene Klassen oder Schnittstellen erweitern oder implementieren?
Diese Ebene enthÀlt auch auslösende
Anwendungsereignisse , die ein Ergebnis eines Anwendungsfalls darstellen. Diese Ereignisse lösen eine Logik aus, die ein Nebeneffekt eines Anwendungsfalls ist, z. B. das Senden von E-Mails, das Benachrichtigen einer Drittanbieter-API, das Senden einer Push-Benachrichtigung oder sogar das Starten eines anderen Anwendungsfalls, der zu einer anderen Komponente der Anwendung gehört.
DomÀnenebene
Weiter innen gibt es eine Domain-Ebene. Objekte auf dieser Ebene enthalten Daten und Logik zum Verwalten dieser Daten, die fĂŒr die DomĂ€ne selbst spezifisch und unabhĂ€ngig von den GeschĂ€ftsprozessen sind, die diese Logik auslösen. Sie sind unabhĂ€ngig und kennen die Anwendungsebene ĂŒberhaupt nicht.

DomÀnendienste
Wie oben erwÀhnt, ist die Rolle des Anwendungsdienstes:
- Verwenden Sie das Repository, um nach einer oder mehreren EntitÀten zu suchen.
- Bitten Sie diese EntitĂ€ten, eine DomĂ€nenlogik auszufĂŒhren.
- Verwenden Sie den Speicher, um EntitÀten erneut zu speichern und DatenÀnderungen effektiv zu speichern.
Manchmal stoĂen wir jedoch auf eine DomĂ€nenlogik, die verschiedene EntitĂ€ten desselben oder unterschiedlichen Typs umfasst, und diese DomĂ€nenlogik gehört nicht zu den EntitĂ€ten selbst, dh die Logik liegt nicht in ihrer direkten Verantwortung.
Daher besteht unsere erste Reaktion möglicherweise darin, diese Logik auĂerhalb der EntitĂ€ten im Anwendungsdienst zu platzieren. Dies bedeutet jedoch, dass in anderen FĂ€llen die DomĂ€nenlogik nicht wiederverwendet wird: Die DomĂ€nenlogik muss auĂerhalb der Anwendungsebene bleiben!
Die Lösung besteht darin, einen DomĂ€nendienst zu erstellen, dessen Aufgabe darin besteht, eine Reihe von EntitĂ€ten abzurufen und eine GeschĂ€ftslogik darauf auszufĂŒhren. Ein DomĂ€nendienst gehört zu einer DomĂ€nenebene und weiĂ daher nichts ĂŒber Klassen auf Anwendungsebene, wie z. B. Anwendungsdienste oder Repositorys. Andererseits kann es andere DomĂ€nendienste und natĂŒrlich DomĂ€nenmodellobjekte verwenden.
DomÀnenmodell
Im Zentrum steht das Domain-Modell. Es hĂ€ngt von nichts auĂerhalb dieses Kreises ab und enthĂ€lt GeschĂ€ftsobjekte, die etwas in der DomĂ€ne darstellen. Beispiele fĂŒr solche Objekte sind in erster Linie EntitĂ€ten sowie Wertobjekte, AufzĂ€hlungen und alle im DomĂ€nenmodell verwendeten Objekte.
DomĂ€nenereignisse werden auch im DomĂ€nenmodell gespeichert. Wenn sich ein bestimmter Datensatz Ă€ndert, werden diese Ereignisse ausgelöst, die neue Werte der geĂ€nderten Eigenschaften enthalten. Diese Ereignisse sind beispielsweise ideal fĂŒr die Verwendung im Event-Sourcing-Modul.
Komponenten
Bisher haben wir Code in Schichten isoliert, aber dies ist eine zu detaillierte Code-Isolierung. Ebenso wichtig ist es, das Bild allgemeiner zu betrachten. Wir sprechen ĂŒber die Aufteilung des Codes in SubdomĂ€nen und
verwandte Kontexte gemÀà den Ideen von Robert Martin, die in
schreiender Architektur ausgedrĂŒckt werden [das heiĂt, die Architektur sollte ĂŒber die Anwendung selbst "schreien" und nicht darĂŒber, welche Frameworks sie verwendet - ca. trans.]. Sie sprechen ĂŒber das Organisieren von Paketen nach Funktion oder Komponente, nicht nach Ebene, und Simon Brown hat dies in seinem Artikel
âKomponentenpakete und Testen gemÀà Architekturâ in seinem Blog recht gut erklĂ€rt:

Ich bin ein BefĂŒrworter der Organisation von Komponentenpaketen und möchte das Diagramm von Simon Brown schamlos wie folgt Ă€ndern:

Diese Abschnitte des Codes sind fĂŒr alle zuvor beschriebenen Ebenen ĂŒbergreifend, und dies sind die
Komponenten unserer Anwendung. Beispiele fĂŒr Komponenten sind Abrechnung, Benutzer, ĂberprĂŒfung oder Konto, sie sind jedoch immer einer DomĂ€ne zugeordnet. EingeschrĂ€nkte Kontexte wie Autorisierung und / oder Authentifizierung sollten als externe Tools betrachtet werden, fĂŒr die wir einen Adapter erstellen und uns hinter einem Port verstecken.

Komponententrennung
Genau wie bei feinkörnigen Codeeinheiten (Klassen, Schnittstellen, Eigenschaften, Mixins usw.) profitieren groĂe Einheiten (Komponenten) von einer schwachen Kopplung und einer engen KonnektivitĂ€t.
Um Klassen zu trennen, verwenden wir die AbhĂ€ngigkeitsinjektion, fĂŒhren AbhĂ€ngigkeiten in die Klasse ein, anstatt sie innerhalb der Klasse zu erstellen, und invertieren die AbhĂ€ngigkeiten, wodurch die Klasse von Abstraktionen (Schnittstellen und / oder abstrakten Klassen) anstelle bestimmter Klassen abhĂ€ngig wird. Dies bedeutet, dass die abhĂ€ngige Klasse nichts ĂŒber die spezifische Klasse weiĂ, die sie verwenden wird, und keinen Verweis auf den vollstĂ€ndigen Namen der Klassen hat, von denen sie abhĂ€ngt.
In Ă€hnlicher Weise weiĂ bei vollstĂ€ndig getrennten Komponenten jede Komponente nichts ĂŒber eine andere Komponente. Mit anderen Worten, es gibt keine VerknĂŒpfung zu einem feinkörnigen Codeblock einer anderen Komponente, auch nicht zur Schnittstelle! Dies bedeutet, dass AbhĂ€ngigkeitsinjektion und AbhĂ€ngigkeitsinversion nicht ausreichen, um Komponenten zu trennen. Wir benötigen eine Art Architekturkonstruktion. Ereignisse, ein gemeinsamer Kern, eventuelle Konsistenz und sogar ein Erkennungsdienst können erforderlich sein!

Auslöselogik in anderen Komponenten
Wenn eine unserer Komponenten (Komponente B) etwas tun muss, wenn in einer anderen Komponente (Komponente A) etwas anderes passiert, können wir nicht einfach einen direkten Aufruf von Komponente A an die Klasse / Methode von Komponente B durchfĂŒhren, weil dann wird A mit B verbunden.
Wir können jedoch den Ereignismanager verwenden, um das Anwendungsereignis auszulösen, das an alle Komponenten gesendet wird, die es abhören, einschlieĂlich B, und der Ereignis-Listener in B löst die gewĂŒnschte Aktion aus. Dies bedeutet, dass Komponente A vom Ereignismanager abhĂ€ngt, jedoch von Komponente B getrennt ist.
Wenn das Ereignis selbst in A "lebt", bedeutet dies, dass B ĂŒber die Existenz von A Bescheid weiĂ und damit verbunden ist. Um diese AbhĂ€ngigkeit zu beseitigen, können wir eine Bibliothek mit einer Reihe von Funktionen des Anwendungskerns erstellen, die von allen Komponenten
gemeinsam genutzt werden - einem
gemeinsamen Kern . Dies bedeutet, dass beide Komponenten vom gemeinsamen Kern abhĂ€ngen, aber voneinander getrennt sind. Ein gemeinsamer Kern enthĂ€lt Funktionen wie Anwendungs- und DomĂ€nenereignisse, kann jedoch auch Spezifikationsobjekte und alles enthalten, was fĂŒr die Freigabe sinnvoll ist. Gleichzeitig sollte es eine MindestgröĂe haben, da sich alle Ănderungen im gemeinsamen Kernel auf alle Anwendungskomponenten auswirken. Wenn wir ein polyglottes System haben, beispielsweise ein Ăkosystem von Mikrodiensten in verschiedenen Sprachen, sollte der gemeinsame Kern nicht von der Sprache abhĂ€ngen, damit alle Komponenten ihn verstehen. Anstelle eines gemeinsamen Kernels mit einer Ereignisklasse enthĂ€lt er beispielsweise eine Beschreibung des Ereignisses (dh einen Namen, Eigenschaften, möglicherweise sogar Methoden, obwohl sie im Spezifikationsobjekt nĂŒtzlicher wĂ€ren) in einer universellen Sprache wie JSON, damit alle Komponenten / Mikrodienste es interpretieren können und vielleicht sogar automatisch ihre eigenen spezifischen Implementierungen generieren.
Dieser Ansatz funktioniert sowohl in monolithischen als auch in verteilten Anwendungen wie Mikroservice-Ăkosystemen. Wenn Ereignisse jedoch nur asynchron ĂŒbermittelt werden können, reicht dieser Ansatz nicht fĂŒr Kontexte aus, in denen die Triggerlogik in anderen Komponenten sofort funktionieren sollte! Hier muss Komponente A einen direkten HTTP-Aufruf an Komponente B durchfĂŒhren. In diesem Fall benötigen wir einen Erkennungsdienst, um die Komponenten zu trennen. Komponente A fragt sie, wohin sie die Anforderung senden soll, um die gewĂŒnschte Aktion einzuleiten. Alternativ können Sie eine Anforderung an den Erkennungsdienst senden, der diese an den entsprechenden Dienst weiterleitet und schlieĂlich eine Antwort an den Anforderer zurĂŒckgibt.
Dieser Ansatz ordnet Komponenten einem Erkennungsdienst zu, ordnet sie jedoch nicht miteinander zu.Daten von anderen Komponenten abrufen
Aus meiner Sicht darf die Komponente keine Daten Ă€ndern, die sie nicht âbesitztâ, kann jedoch Daten anfordern und verwenden.Gemeinsamer Datenspeicher fĂŒr Komponenten
Wenn die Komponente Daten verwenden muss, die zu einer anderen Komponente gehören (z. B. muss die Abrechnungskomponente den Namen des Kunden verwenden, der zur Kontenkomponente gehört), enthĂ€lt sie das Anforderungsobjekt fĂŒr den Datenspeicher. Das heiĂt, die Abrechnungskomponente kann ĂŒber jeden Datensatz Bescheid wissen, muss jedoch schreibgeschĂŒtzte Daten aus anderen LĂ€ndern verwenden.Separate Datenspeicherung fĂŒr die Komponente
In diesem Fall wird dieselbe Vorlage angewendet, die Datenspeicherebene wird jedoch komplizierter. Das Vorhandensein von Komponenten mit einem eigenen Data Warehouse bedeutet, dass jedes Data Warehouse Folgendes enthÀlt:- Ein Datensatz, den eine Komponente besitzt und Àndern kann, wodurch sie zur einzigen Quelle der Wahrheit wird.
- Ein Dataset, das eine Kopie der Daten anderer Komponenten ist, die nicht selbst geĂ€ndert werden können, die jedoch fĂŒr die FunktionalitĂ€t der Komponente erforderlich sind. Diese Daten sollten aktualisiert werden, wenn sie sich in der EigentĂŒmerkomponente Ă€ndern.
Jede Komponente erstellt eine lokale Kopie der benötigten Daten von anderen Komponenten, die bei Bedarf verwendet werden. Wenn sich Daten in der Komponente Ă€ndern, zu der sie gehören, löst diese EigentĂŒmerkomponente ein DomĂ€nenereignis aus, das DatenĂ€nderungen enthĂ€lt. Komponenten, die eine Kopie dieser Daten enthalten, hören dieses DomĂ€nenereignis ab und aktualisieren ihre lokale Kopie entsprechend.Kontrollfluss
Wie oben erwĂ€hnt, geht der Kontrollfluss vom Benutzer zum Anwendungskern, zu den Infrastruktur-Tools und dann wieder zum Anwendungskern - und zurĂŒck zum Benutzer. Aber wie genau arbeiten die Klassen zusammen? Wer hĂ€ngt von wem ab? Wie komponieren wir sie?Wie Onkel Bob werde ich in meinem Artikel ĂŒber saubere Architektur versuchen, den Ablauf der UMLish-Schemaverwaltung zu erklĂ€ren ...Ohne Befehls- / Anforderungsbus
Wenn wir den Befehlsbus nicht verwenden, hĂ€ngen die Controller entweder vom Anwendungsdienst oder vom Abfrageobjekt ab.[Nachtrag 18.11.2017] Ich habe das DTO, mit dem ich Daten aus der Anfrage zurĂŒckgebe, vollstĂ€ndig ĂŒbersprungen und es jetzt hinzugefĂŒgt. Dank MorphineAdministered , das ein Leerzeichen anzeigte . In der obigen Abbildung verwenden wir die Schnittstelle fĂŒr den Anwendungsdienst, obwohl wir sagen können, dass sie nicht wirklich benötigt wird, da der Anwendungsdienst Teil unseres Anwendungscodes ist. Wir wollen die Implementierung jedoch nicht Ă€ndern, obwohl wir ein vollstĂ€ndiges Refactoring durchfĂŒhren können.
Das Abfrageobjekt enthĂ€lt eine optimierte Abfrage, die einfach einige Rohdaten zurĂŒckgibt, die dem Benutzer angezeigt werden. Diese Daten werden an das DTO zurĂŒckgegeben, das in das ViewModel eingebettet ist. Dieses ViewModel verfĂŒgt möglicherweise ĂŒber eine Ansichtslogik und wird zum AuffĂŒllen der Ansicht verwendet.Auf der anderen Seite enthĂ€lt der Anwendungsdienst eine Anwendungsfalllogik, die ausgelöst wird, wenn wir etwas auf dem System tun und nicht nur einige Daten anzeigen möchten. Der Anwendungsdienst hĂ€ngt von Repositorys ab, die EntitĂ€ten zurĂŒckgeben, die die zu initiierende Logik enthalten. Es kann auch vom DomĂ€nendienst abhĂ€ngen, den DomĂ€nenprozess ĂŒber mehrere EntitĂ€ten hinweg zu koordinieren. Dies ist jedoch ein seltener Fall.Nach dem Parsen des Anwendungsfalls kann der Anwendungsdienst das gesamte System benachrichtigen, dass ein Anwendungsfall aufgetreten ist. AnschlieĂend hĂ€ngt es vom Ereignis-Dispatcher ab, das Ereignis auszulösen.Es ist interessant festzustellen, dass wir Schnittstellen sowohl auf der Persistenz-Engine als auch auf Repositorys hosten. Dies mag ĂŒberflĂŒssig erscheinen, dient jedoch unterschiedlichen Zwecken:- Die Persistenzschnittstelle ist eine Abstraktionsschicht ĂŒber ORM, sodass wir ORM austauschen können, ohne den Anwendungskern zu Ă€ndern.
- persistence-. , MySQL MongoDB. persistence- , ORM, . , , , , , , MongoDB SQL.
C /
Wenn unsere Anwendung den Befehls- / Anforderungsbus verwendet, bleibt das Diagramm nahezu unverĂ€ndert, auĂer dass die Steuerung jetzt vom Bus sowie von Befehlen oder Anforderungen abhĂ€ngt. Hier wird eine Instanz eines Befehls oder einer Anforderung erstellt und an den Bus ĂŒbergeben, der den geeigneten Handler zum Empfangen und Verarbeiten des Befehls findet.In der folgenden Abbildung verwendet der Befehlshandler den Anwendungsdienst. Dies ist jedoch nicht immer erforderlich, da der Handler in den meisten FĂ€llen die gesamte Logik des Anwendungsfalls enthĂ€lt. Alles, was wir tun mĂŒssen, ist, die Logik aus dem Handler in einen separaten Anwendungsdienst zu extrahieren, wenn wir dieselbe Logik in einem anderen Handler wiederverwenden mĂŒssen.[Nachtrag 18.11.2017] Ich habe das DTO, mit dem ich Daten aus der Anfrage zurĂŒckgebe, vollstĂ€ndig ĂŒbersprungen und es jetzt hinzugefĂŒgt. Vielen DankMorphineAdministered , das ein Leerzeichen anzeigt . Möglicherweise haben Sie bemerkt, dass es keine AbhĂ€ngigkeiten zwischen Bus, Befehl, Anforderung und Handlern gibt. TatsĂ€chlich mĂŒssen sie nicht voneinander wissen, um eine gute Trennung zu gewĂ€hrleisten. Das Verfahren zum Weiterleiten des Busses an einen bestimmten Handler zum Verarbeiten eines Befehls oder einer Anforderung wird in einer einfachen Konfiguration konfiguriert. In beiden FĂ€llen zeigen alle Pfeile - AbhĂ€ngigkeiten, die die Kernelgrenze der Anwendung ĂŒberschreiten - nach innen. Wie bereits erlĂ€utert, ist dies die Grundregel der Ports & Adapter-, Zwiebel- und Clean-Architektur.

Fazit
Wie immer besteht das Ziel darin, eine getrennte Codebasis mit hoher KonnektivitĂ€t zu erhalten, in der Sie einfach, schnell und sicher Ănderungen vornehmen können.PlĂ€ne sind nutzlos, aber Planung ist alles. - Eisenhower
Diese Infografik ist eine Konzeptkarte. Wenn Sie all diese Konzepte kennen und verstehen, können Sie eine gesunde Architektur und eine funktionsfÀhige Anwendung planen.Allerdings:Eine Karte ist kein Gebiet. - Alfred Korzybsky
Mit anderen Worten, dies sind nur Empfehlungen! Eine Anwendung ist ein Gebiet, eine RealitĂ€t, ein spezifischer Anwendungsfall, in dem wir unser Wissen anwenden mĂŒssen, und sie bestimmt, wie die reale Architektur aussehen wird!Wir mĂŒssen all diese Muster verstehen, aber auch immer darĂŒber nachdenken und verstehen, was genau unsere Anwendung benötigt und wie weit wir gehen können, um Trennung und Verbundenheit zu erreichen. Diese Entscheidung hĂ€ngt von vielen Faktoren ab, die von den funktionalen Anforderungen des Projekts ĂŒber den Zeitpunkt der Anwendungsentwicklung, die Lebensdauer, die Erfahrung des Entwicklungsteams usw. reichen.So stelle ich mir das alles vor.Diese Ideen werden im nĂ€chsten Artikel ausfĂŒhrlicher behandelt: âMehr als nur konzentrische Schichten . â