Warum Ravioli mein Lieblingsrezept sind
Nein, in diesem Beitrag geht es nicht um mein Lieblingsrezept. Oder vielleicht doch irgendwie. Dies ist ein Beitrag über Sechseckige Architekturein architektonisches Muster für die Erstellung von Software. Ich werde Ihnen erklären, was es mit der hexagonalen Architektur auf sich hat und was sie mit dem Kochen von Ravioli zu tun hat. Um zu veranschaulichen, wie es in der Praxis aussieht, zeige ich Ihnen, wie Sie "Spaghetti-Code" in hochgradig zusammenhängenden, lose gekoppelten und hochgradig testbaren Code verwandeln können, indem Sie die Prinzipien der hexagonalen Architektur und domänenspezifisches Design. Dazu schreibe ich eine Kotlin- und Spring Boot-Anwendung mit einer hexagonalen Architektur neu.Mehrschichtige Architektur
Um zu verstehen, was es mit der hexagonalen Architektur auf sich hat, werfen wir zunächst einen Blick auf die klassische, geschichtete Architektur. Bei dieser Art von Softwarearchitektur, die oft mit Lasagne verglichen wird, unterteilen Sie Ihren Code in mehrere Schichten oder Ebenen. In einer 3-Schichten-Architektur wären dies: die Präsentationsschicht, die Logik- oder Domänenschicht und die Persistenzschicht
3-Tier-Architektur[/caption]
Präsentationsschicht
Diese Schicht bietet die Schnittstelle, über die Benutzer oder andere Systeme auf Ihre Anwendung zugreifen können, entweder zur Dateneingabe oder zur Nutzung. Sie präsentiert den Benutzern oder Clients Ergebnisse und ermöglicht es den Benutzern, mit Ihrer Anwendung zu interagieren. Dies kann zum Beispiel über eine grafische Benutzeroberfläche, eine Webseite oder eine REST-Schnittstelle geschehen. In Bezug auf Softwarekomponenten ist dies die Schicht, in der Sie Ihre Controller und Ihre Ansichten unterbringen würden.
Logische Ebene
Dies ist der Kern Ihrer Anwendung. Hier findet die eigentliche Arbeit statt, die Geschäftslogik. Hier wird der Wert Ihrer Anwendung erzeugt. Normalerweise definieren Sie hier Ihre Domänenmodelle und Ihre Dienste. Diese Dienste bieten eine Schnittstelle, auf die die Präsentationsschicht zugreifen kann.
Datenebene
Die Datenschicht kümmert sich um die Persistenz der Daten Ihrer Anwendung, z.B. über eine Datenbank, einen Datenspeicher oder eine Dateifreigabe. Sie stellt eine Datenzugriffsschicht, Ihre Datenzugriffsobjekte (DAO), zur Verfügung, mit der die Logikschicht interagieren kann.
Eine unübersichtliche Domäne
Viele Projekte beinhalten die Integration oder Kommunikation mit externen Softwaresystemen. Denken Sie an Datenbanken, Dienste von Drittanbietern, aber auch Anwendungsplattformen oder SDKs. In der Regel wird die Domänenschicht mit Details oder Besonderheiten in Bezug auf diese Integrationen überfrachtet. Die Domänenschicht enthält nicht mehr nur Geschäftslogik, sondern es geht viel mehr vor sich. Zum Beispiel: Integrationen von Drittanbietern, die eine Abhängigkeit von einer bestimmten API-Version mit sich bringen. Oder durch die Abhängigkeit von der Persistenzschicht und dadurch die Notwendigkeit, mit einem bestimmten Persistenz-Framework umzugehen. Oder vielleicht haben Sie es mit einem bestimmten Anwendungsframework oder SDK zu tun. Solche Integrationen und Abhängigkeiten können Ihnen schnell in die Quere kommen, Ihre Kerndomäne unübersichtlich machen und die Testbarkeit Ihrer Kerngeschäftslogik verringern.
Um dies zu veranschaulichen, nehmen wir eine imaginäre Problemdomäne. Stellen Sie sich eine Kotlin- und Spring Boot-Anwendung für die Verwaltung der Geldbörse eines Benutzers vor, die in einer 3-Tier-Architektur aufgebaut ist. Wahrscheinlich ist es nicht sehr überraschend, dass eine der Hauptfunktionen darin besteht, eine Einzahlung auf das Konto eines Benutzers in einer bestimmten Währung vorzunehmen. Die Logik für diesen Vorgang wird in der Domänenschicht verarbeitet. Nehmen Sie zum Beispiel den folgenden Dienst

Dieser Dienst ist für die Einzahlung von Geldern in einer bestimmten Währung zuständig. Um das tun zu können, ist er von einer Reihe von Abhängigkeiten abhängig. Die Logik ist mit der Integration all dieser Abhängigkeiten vermischt. Es ist nicht der schlechteste Code der Welt, aber es geht hier eine Menge vor sich.
Zunächst einmal hängt der Dienst von einigen Repositories aus der Persistenzschicht ab. In dem obigen Code rufen wir zwei verschiedene Entitäten aus zwei verschiedenen Repositories ab. Entitäten können schwierig zu handhaben sein, da sie (normalerweise) veränderbar sind. Mit der Veränderlichkeit ist es einfacher, sich selbst in den Fuß zu schießen, insbesondere wenn Gleichzeitigkeit ins Spiel kommt. Das ist häufiger der Fall, als Sie vielleicht denken. Wenn Sie eine Webanwendung entwickeln, müssen Sie bereits die Gleichzeitigkeit im Blick haben.
Zweitens hängt unser Service von einem Börsenclient ab, bei dem es sich möglicherweise um einen Dienst eines Drittanbieters oder eine REST-Api handelt. Außerdem hängt er von einem Event Bus ab, um Aktualisierungen des Kontostands zu veröffentlichen. Das bedeutet, dass sich unser Code an ein bestimmtes API-Modell halten muss. Und zwar eine bestimmte Version davon. Um unsere Logik testen zu können, müssen wir diese API-Aufrufe nachbilden. Das kann zu einer Menge Mocking führen und wenn es eine neue Version gibt, müssen wir unsere Logik, alle unsere Tests und unsere Mocks aktualisieren.
Drittens: Neben der Logik gibt es auch eine Menge Orchestrierung. Unsere Einzahlungsfunktion ist mit dem @Transactional Annotation gibt es einen expliziten Aufruf zur Persistenz des aktualisierten Kontos in der Datenbank und wir veröffentlichen es in einem Event Bus, um andere Dienste zu benachrichtigen. Natürlich müssen wir über Transaktionen, Sicherheit, Protokollierung, Benachrichtigungen und Persistenz nachdenken. Das sind alles sehr wichtige geschäftliche Belange, aber sie sind nicht Teil der eigentlichen Geschäftslogik. Daher ist dies vielleicht nicht der beste Ort, um sich damit zu befassen, denn es verkompliziert Ihre Logik und das Testen derselben.
Zusammenfassend lässt sich sagen, dass die Vermischung all dieser Arten von Integrationen und der Umgang mit übergreifenden geschäftlichen Belangen in Ihrem Service zu Spaghetti-Code führen wird. Ich mag Spaghetti durchaus, aber nicht in meiner Codebasis. Der Code ist dann viel schwerer zu verstehen, viel schwerer zu pflegen und viel schwerer zu ändern.
- Es verringert die Testbarkeit. Um unsere Logik testen zu können, müssen wir all diese Abhängigkeiten und die Orchestrierung wegmocken. Das ist eine wirklich mühsame Arbeit und belastet unsere Tests.
- Die Abhängigkeit von bestimmten APIs oder Plattformen führt zu enge Kopplung zwischen unserer Servicelogik und dem Code von Drittanbietern. Wie gehen wir mit Änderungen der API-Versionen um? Müssen wir vielleicht mehrere Versionen unterstützen? Das alles kann sehr mühsam sein und schnell teuer werden.
- Die Verwendung von veränderbaren Datenstrukturen, wie z.B. Entitäten, in Ihrer Logik erhöht die Wahrscheinlichkeit, dass Sie Fehler machen (z.B. in Bezug auf Gleichzeitigkeit).
Inversion von Abhängigkeiten
Was wäre, wenn wir für einen Moment jede Integration vergessen und uns ausschließlich auf die Geschäftslogik konzentrieren könnten? Was wäre, wenn wir unsere Kernlogik isolieren und sicherstellen könnten, dass sie ordnungsgemäß getestet wird, und uns dann später um die REST (Wortspiel beabsichtigt) kümmern könnten? Um dies zu erreichen, müssen wir unsere Software entkoppeln. Bereich, die Kernlogik, von allem anderen, dem Infrastruktur. Wir brauchen Umkehrung der Abhängigkeiten. Betrachten Sie das folgende Diagramm.
Inversion der Abhängigkeit[/caption]
Das Diagramm sieht der 3-Schichten-Architektur am Anfang dieses Artikels sehr ähnlich, mit einem kleinen, aber wichtigen Unterschied: Der Pfeil von der Persistenzschicht zeigt jetzt in Richtung Domänenschicht. Dies zeigt an, dass die Persistenzschicht nun von der Domänenschicht abhängt und nicht mehr andersherum. Die Domäne wird nun zur Zentrum unserer Anwendung. Alles hängt von der Domäne ab, während die Domäne von nichts abhängt. Dies ist der Kerngedanke einer hexagonalen Architektur.
Sechseckige Architektur
Bevor wir unsere Kotlin- und Spring Boot-Anwendung unter Verwendung der hexagonalen Architektur neu schreiben können, müssen wir zunächst ihre Designprinzipien besser verstehen. Bei der hexagonalen Architektur geht es um die Infrastruktur von Ihrer Kerndomäne zu trennen. Es geht darum, die Domäne zum Zentrum Ihrer Anwendung zu machen.
Domäne umgeben von API und SPI-Schicht[/caption]
Die Domain ist von einer API-Schicht und einer Service Provider-Schnittstelle (SPI) umgeben. Die API sorgt dafür, dass Ihre Domäne für die Außenwelt abfragbar ist, zum Beispiel über eine Webschnittstelle. Das Ziel der Dienstanbieterschnittstelle ist das Senden oder Abrufen von Daten an oder von externen Systemen. Denken Sie an das Abrufen von Daten von einer Drittanbieter-API oder das Speichern von Daten in einer Datenbank.
Ports und Adapter
Eine alternative Bezeichnung für die hexagonale Architektur ist Ports und Adapter (Architektur). Ports und Adapter sind die Hauptelemente einer hexagonalen Architektur. Sowohl die API- als auch die Service Provider-Schnittstellenschicht sind in Ports unterteilt. Ports bilden eine technikfeindlich Schnittstelle, die um das Zweck der Interaktion. Das bedeutet, dass es bei den Häfen um die was und nicht die wie. In unserem Beispiel für die Brieftasche würde das bedeuten, dass es einen Port gibt, der eine Schnittstelle für eine Einzahlung definiert, ohne dass Frameworks (z. B. Spring Boot), Protokolle, Datenbanken usw. spezifiziert werden (oder davon abhängig sind). Weiter unten in diesem Artikel finden Sie eine Beispielimplementierung, um zu sehen, wie dies in der Praxis aussehen würde.
Die technologieunabhängige Schnittstelle, die von Ports bereitgestellt wird, kann implementiert werden durch Adapter. Im Gegensatz zu Ports sind Adapter technologiespezifisch. Adapter verwenden Ports, um die Interaktionsabsicht in eine technische Implementierung zu übersetzen. Denken Sie an Adapter zum Persistieren einer Entität in MongoDB oder zum Veröffentlichen eines Ereignisses in Kafka. In diesem Sinne geht es bei Adaptern um die wie als die was bereits durch Ports definiert ist.
Das folgende Diagramm zeigt, wie die API- und Service Provider-Schnittstellenschicht in Ports unterteilt sind, die unsere Kerndomäne umgeben. Mit ein wenig Fantasie kann man sich dies als Sechseck vorstellen, daher der Name hexagonale Architektur.
Beispiel für Ports und Adapter[/caption]
Wickeln Sie sie wie Ravioli
Das Wichtigste ist, dass unser Bereich schön von der Außenwelt isoliert ist. Die guten, wichtigen Dinge sind schön eingewickelt, wie bei einer Ravioli. Daher wird ein solches Design manchmal auch als Ravioli-Code. In einem Ravioli Architektur jedes der Module ist unabhängig und in sich geschlossen. Jede Komponente kann geändert oder ersetzt werden, ohne dass sich dies auf andere Komponenten auswirkt.
Unsere Domain ist in sich geschlossen und hat keine externen Abhängigkeiten. Das macht es einfach, die Korrektheit der Implementierung zu überprüfen. Man kann sehr einfache Unit-Tests schreiben, die keine (vielen) Mocks benötigen und nicht von einem Framework abhängen. Infolgedessen werden diese Tests auch sehr schnell ausgeführt. Wir müssen Spring Boot nicht hochfahren, um unsere Geschäftslogik zu testen.
Die Ports haben auch keine externen Abhängigkeiten. Das bedeutet, dass es einfach ist, eine bestimmte Technologie oder Implementierung auszutauschen, indem Sie einen anderen Adapter hinzufügen. Das Ändern oder Hinzufügen von Adaptern hat nur geringe Auswirkungen auf den Rest unserer Software, da wir unsere Ports oder unsere Kerndomäne nicht ändern müssen. Daher hat eine Änderung der Technologie nur geringe Auswirkungen auf das Unternehmen.
Kochen Sie es 'al dente'.
Wenn Sie Ravioli zu lange kochen, ist das nicht sehr schön. Sie fallen einfach auseinander. Das Gleiche gilt für übertriebene Modularisierung und lose Kopplung. Wenn Sie es mit der losen Kopplung übertreiben, verlieren Sie den Zusammenhalt und Ihr Code wird auseinanderfallen. Jede Geschäftsoperation beinhaltet viele Aufrufe an verschiedene Komponenten. Dies führt zu aufgeblähten Aufrufstapeln und die einfache Navigation durch die Codebasis wird schwierig. Auch Dinge wie die Transaktionsverwaltung werden schwierig, da Sie Transaktionen über mehrere (asynchrone) Grenzen hinweg abwickeln müssen.
Al dente: 'so dass sie beim Biss noch fest sind'.
Aus den oben genannten Gründen ist es wichtig, das richtige Gleichgewicht und die richtige Aufteilung zu finden, wenn Sie festlegen, was zu Ihrem Kernbereich gehören soll. Ihre Softwaremodule müssen 'al dente' gekocht werden, um bei der Metapher der Kochnudeln zu bleiben.
Wie Sie die Domain finden
Wie finden wir also das richtige Gleichgewicht? Wie treffen wir die richtige Aufteilung? Was gehört in unsere Kerndomäne, was ist eigentlich Teil einer anderen (Problem-)Domäne und was sollte als Infrastruktur betrachtet werden? Um diese Fragen zu beantworten, können wir das domänenorientierte Design als Leitfaden verwenden.
Bereich: Ein bestimmter Bereich des Denkens, der Aktivität oder des Interesses
Was verstehen wir unter Domäne? Aus der obigen Definition können wir schließen, dass eine Domäne sowohl die was und die wie von allem, was eine Organisation tut. Vor allem in größeren Organisationen kann das eine ganze Menge sein. Um die Dinge überschaubar zu halten, müssen wir die Domäne in kleinere, unabhängige Domänenmodelle mit klaren Grenzen aufteilen. Ein Schlüsselbegriff im Domain Driven Design für ein solches unabhängiges Modell ist begrenzter Kontext. Ein abgegrenzter Kontext ist ein eindeutiger und klarer Teil des Bereichs.
Laut Martin Fowler liegt der Schlüssel zur Beherrschung der Komplexität von Software in der Erstellung eines guten Domänenmodells. Eric Evans' Domain-Driven Design: Tackling Complexity in the Heart of Software enthält viele bewährte Entwurfspraktiken, erfahrungsbasierte Techniken und grundlegende Prinzipien, die die Entwicklung eines guten Domänenmodells in Softwareprojekten mit komplexen Domänen erleichtern.
Im nächsten Abschnitt zeige ich, wie wir die Best Practices, Techniken und Prinzipien des domänenorientierten Designs anwenden können, um das Design unserer Kotlin- und Spring Boot-Depot-Anwendung in eine hexagonale Architektur zu verwandeln.
Bausteine des Domain Driven Design
Es gibt mehrere Bausteine des domänenorientierten Designs, die bei der Entwicklung hochgradig kohärenter, lose gekoppelter Softwaremodule zum Einsatz kommen. In diesem Abschnitt gehe ich auf die für unseren Anwendungsfall relevantesten ein und zeige, wie Sie das domänenorientierte Design anwenden können, um unser Codebeispiel am Anfang dieses Artikels zu verbessern. Lassen Sie uns die hexagonale Architektur in unsere Kotlin- und Spring Boot-Anwendung einführen.
Wert Objekte
Der erste Baustein ist das Wertobjekt. Wertvolle Objekte sind kleine und kohärente Modelle eines Teils Ihrer Domäne. Sie sind staatenlos, unveränderlich, thread-safe und frei von Nebeneffekten. Wertobjekte sind Das heißt, wenn alle Eigenschaften von zwei Wertobjekten gleich sind, werden beide Objekte als gleich betrachtet (im Gegensatz zu Entitäten). Es ist eine gute Praxis, Geschäftslogik und Validierungen, die auf das Objekt selbst angewendet werden können, in das jeweilige Wertobjekt aufzunehmen. Betrachten Sie das folgende Beispiel:
Wertobjekt Beispiel[/caption]Wir verwenden Kotlin-Datenklassen, um unveränderliche (stark typisierte) Eigenschaften und Kopierkonstruktoren zu erhalten. Die Geschäftsvorgänge add und convert sind Teil des Money Klasse selbst. Wir brauchen sie nicht in die Serviceschicht zu packen. Es ist natürlicher und besser für die Kohäsion, sie in die Money Klasse selbst, denn es handelt sich um einen Zustandsübergang in einer Money Instanz. Dank der nullbaren Typen von Kotlin haben wir auch implizite Nullprüfungen. Die convert Funktion nimmt ein anderes Wertobjekt, ExchangeRateDtoals Eingabe, die einfach nur kapselt rate und currency Eigenschaften.
Entitäten
Ein weiterer Baustein, den Sie oft verwenden müssen, ist eine Entität. Verwenden Sie Entitäten um Datensätze in einer Datenbank oder einem Speichersystem darzustellen. Im Gegensatz zu den bereits erwähnten Wertobjekten sind Entitäten in der Regel veränderbar und durch ihre Identität definiert und nicht deren Wert (Eigenschaften). Selbst wenn alle Eigenschaften zweier Entitäten unterschiedlich sind, aber der Bezeichner (id) derselbe ist, haben wir es immer noch mit derselben Entität zu tun, z.B. mit demselben Datenbankeintrag. Er hat lediglich einen anderen Status. Da sich Ihre Entitäten im Infrastrukturmodul befinden, ist es in Ordnung, darin datenbankspezifische Anmerkungen zu verwenden. Außerdem ist es besser, aussagekräftige Funktionen zu definieren, die die geschäftlichen Absichten widerspiegeln, als nur Setter. Betrachten Sie das folgende Beispiel:
Beispiel für eine Entität[/caption]
Auch hier verwenden wir eine Kotlin-Datenklasse, aber diesmal mit veränderbaren (neu zuweisbaren) Eigenschaften. Da sich diese Klasse im Infrastrukturmodell und nicht in unserem Domänenmodul befindet, ist es in Ordnung, MongoDB-spezifische Annotationen von Spring Data zu verwenden wie @Document und @Id. Anstatt nur Setter zu verwenden, stellen wir eine sinnvollere Funktion zur Verfügung updateName die den Zustandsübergang und die notwendigen Nebeneffekte ausführt.
Domain-Dienste
Der dritte Baustein des domänenbasierten Designs, den wir uns ansehen, ist ein Domänenservice. Verwenden Sie Domain-Services für Geschäftslogik, die nicht natürlich in Wertobjekte passt. Domänendienste sind auch zustandslos und hochgradig kohärent. Die Verwendung zustandsloser Komponenten und unveränderlicher Datenstrukturen erleichtert die Konstruktion von Objekten (auch in Unit-Tests) und es ist weniger wahrscheinlich, dass Sie sich selbst in den Fuß schießen, wenn Gleichzeitigkeit ins Spiel kommt (z.B. bei der Bedienung von Anfragen in einer Webanwendung). Hier ist ein Beispiel dafür, was unser DepositService aussehen könnte, wenn wir ihn zu einem Domänenservice mit Wertobjekten und DTOs machen:
Beispiel für einen Domänenservice[/caption]
Unser Einzahlungsdienst verwendet nicht direkt die Entität des Benutzerkontos. Er verwendet nur unsere Wertobjekte: Money und UserAccountDto. Ich habe die Implementierung von UserAccountDto der Kürze halber, aber für unseren Anwendungsfall könnte es so einfach sein wie:
data class UserAccountDTO(val balance: Geld)
Die gesamte erforderliche Logik zur Aktualisierung des Saldos (d.h. Umrechnung zwischen Währungen und Summierung von Beträgen) ist bereits in der Money Klasse. Unser Einlagenservice muss lediglich die Wechselkurse über eine ExchangeRateApiClientPort. Die Implementierung hinter diesem Port ist für den Einzahlungsdienst irrelevant. Alles, was zählt, ist der Vertrag: eine Quell- und eine Zielwährung gehen ein, ein ExchangeRateDto herauskommt. Beachten Sie auch, dass es hier keine Spring-Anmerkungen gibt. Die Verkabelung nehmen wir später vor.
Der folgende Codeausschnitt zeigt eine Beispielimplementierung eines Adapters, der die ExchangeRateApiClientPort implementiert.
Beispiel für einen Adapter[/caption]
In der obigen Implementierung verwende ich die Moneta-API(javamoney.github.io/ri.html), aber ich könnte diese später genauso gut durch einen Webservice ersetzen. Der Einzahlungsdienst würde von einer solchen Neuimplementierung nicht betroffen sein.
Anwendungsdienste
Der vierte und letzte Baustein des domänenorientierten Designs, den wir uns ansehen, ist der Anwendungsdienst. Verwenden Sie Anwendungsdienste Anwendungsdienste können entweder eine Portschnittstelle implementieren, um externen Systemen den Zugriff auf Ihre Anwendung zu ermöglichen (z.B. Webinterface oder Message Bus), oder sie verwenden eine von einem Adapter implementierte Portschnittstelle für den Zugriff auf ein externes System. Anwendungsdienste sind zustandslos und orchestrieren Geschäftstätigkeiten und nicht die Implementierung von Geschäftslogik. Sie kümmern sich um übergreifende Belange wie Transaktionsmanagement, Protokollierung, Benachrichtigungen, Persistenz usw. Hier ist ein Beispiel:
Anwendungsdienst Beispiel[/caption]
Die DepositOrchestrationService bindet alles zusammen. Neben der Delegierung eines Einzahlungsantrags an den DepositServicefügt es die Transaktionsverwaltung hinzu (unter Verwendung der @TransactionalAnnotation des Spring tx-Moduls), stellt sicher, dass das aktualisierte Benutzerkonto persistiert wird und dass ein Aktualisierungsereignis über das UserAccountRepositoryPort bzw. die UserAccountEventPublisherPort. Falls der Benutzer, für den die Einzahlungsanfrage gestellt wurde, nicht gefunden wird, geben wir einfach null zurück (wiederum unter Verwendung von nullbaren Typen). Die Präsentationsschicht, z.B. eine REST-Api, kann dann entscheiden, wie sie solche Situationen dem Endbenutzer präsentiert, z.B. indem sie eine HTTP 404-Antwort zurückgibt.
Dies war der letzte Schritt beim Umschreiben unserer Kotlin- und Spring Boot-Anwendung unter Verwendung der Prinzipien der hexagonalen Architektur und der Bausteine des domänenorientierten Designs.
Fazit
Obwohl wir am Ende etwas mehr Code hatten als zu Beginn, hoffe ich, dass Sie mir zustimmen, dass wir durch die Anwendung der Prinzipien der hexagonalen Architektur und des domänenorientierten Designs in unserer Kotlin- und Spring Boot-Anwendung einige Verbesserungen erzielt haben. Es gibt jetzt eine klare Trennung der Anliegen, unser Code hat hohe Kohäsion, lose Kopplung und unsere Kernlogik kann getestet werden, ohne dass wir uns um das Mocking technischer Abhängigkeiten oder Frameworks kümmern müssen. Wir haben die Auswirkungen von Versions-Upgrades in unseren Abhängigkeiten und anderen Änderungen in der Infrastruktur auf den Kernteil unserer Software minimiert. Auf diese Weise konnten wir die Auswirkungen solcher Änderungen auf unser Geschäft minimieren.
Die wichtigsten Erkenntnisse für die Entwicklung von Software nach den Prinzipien der hexagonalen Architektur sind:
- Beginnen Sie mit dem Entwurf einer technik-unabhängigen Bereich. Darin liegt schließlich der Wert der Software. Wenn Sie mit der Domäne beginnen, können Sie Frühzeitig Wert schaffen für Ihre Interessengruppen und Entscheidungen über die technische Umsetzung verzögern bis Sie genug Wissen gesammelt haben, um sie zu erstellen.
- Stellen Sie sicher, dass die Domain eine Alleinstehend, isoliertes Modul mit eingebetteten (Unit-)Tests. Machen Sie einen klaren Schnitt, indem Sie es beispielsweise zu einem separaten Maven-Modul machen. Auf diese Weise sind Sie sich der Abhängigkeiten Ihres Domain-Moduls bewusster und können nicht unwissentlich technische Abhängigkeiten hineinmischen.
- Behalten Sie (technologiespezifische) Adapter in Ihrem Infrastrukturmodul. Schreiben Sie so viele Adapter, wie Sie benötigen. Sie können Adapter auszutauschen oder neu zu implementieren, ohne den Rest der Software zu beeinträchtigen.
- Es mag Situationen geben, in denen die hexagonale Architektur ein wenig übertrieben ist. Sie kommt vor allem dann zum Tragen, wenn Sie eine tatsächliche Domäne modellieren müssen. Wenn Sie zum Beispiel nur Daten von einem Format in ein anderes umwandeln, lohnt sich der Aufwand nicht. Abgesehen davon ist der Overhead bei der Verwendung von Sprachfunktionen wie Datenklassen oder Datensatztypen in der Regel recht gering.
Danke, dass Sie bis hierher gelesen haben! Ich hoffe, der Artikel hat Ihnen gefallen und Sie werden die hexagonale Architektur für Ihre Projekte in Betracht ziehen. Die meisten (wenn auch nicht alle) Codebeispiele finden Sie unter github.com kotlin-hexagonal-architecture-example. Wenn Sie in Erwägung ziehen, die hexagonale Architektur für ein Spring Boot- und Kotlin-Projekt zu verwenden, sollten Sie meine Vorlagen-Repository auf github.com kotlin-hexagonal-architecture. Für weitere Informationen zu diesem Thema verweise ich auf meinen J-Fall 2021 Vortrag(J-Fall 2021: Jeroen Rosenberg - Cooking your Ravioli "Al Dente" with Hexagonal Architecture ).
Verfasst von
Jeroen Rosenberg
Dev of the Ops / foodie / speaker / blogger. Founder of @amsscala. Passionate about Agile, Continuous Delivery. Proud father of three.
Unsere Ideen
Weitere Blogs
Contact




