{"id":35265,"date":"2023-07-04T06:00:00","date_gmt":"2023-07-04T04:00:00","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=35265"},"modified":"2025-06-12T08:52:19","modified_gmt":"2025-06-12T06:52:19","slug":"hexagonale-architektur-java","status":"publish","type":"post","link":"https:\/\/www.happycoders.eu\/de\/software-craftsmanship\/hexagonale-architektur-java\/","title":{"rendered":"Hexagonale Architektur mit Java \u2013 Tutorial"},"content":{"rendered":"\n<p>In diesem Artikel zeige ich dir Schritt f\u00fcr Schritt, wie man eine Java-Anwendung mit hexagonaler Architektur implementiert \u2013 und wie man die Einhaltung der Architekturregeln mit Maven und der Library \u201eArchUnit\u201d sicherstellt.<\/p>\n\n\n\n<p>Du wirst dabei die Vorteile der hexagonalen Architektur in der Praxis sehen: <\/p>\n\n\n\n<p>Wir werden mit der Implementierung des Datenmodells und der Gesch\u00e4ftslogik beginnen und f\u00fcr eine ganze Weile ein reines Java-Projekt entwickeln. Technische Details wie REST-Controller und Datenbank werden wir erst relativ sp\u00e4t anbinden. Das bedeutet auch, dass wir die Entscheidung \u00fcber die einzusetzende Technologie aufschieben k\u00f6nnen, bis wir etwas Erfahrung mit der Anwendung gesammelt haben.<\/p>\n\n\n\n<p>In drei Folgeartikeln werden wir:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"\/de\/software-craftsmanship\/ports-and-adapters-java-tutorial-db\/\">die Persistenzl\u00f6sung austauschen<\/a> (von einer In-Memory-L\u00f6sung zu MySQL), <\/li>\n\n\n\n<li><a href=\"\/de\/software-craftsmanship\/hexagonale-architektur-quarkus\/\">die Anwendung in das Quarkus-Framework einbetten<\/a> und <\/li>\n\n\n\n<li>Quarkus durch Spring ersetzen<\/li>\n<\/ol>\n\n\n\n<p>... und all das, ohne auch nur eine Zeile Code im Anwendungskern \u00e4ndern zu m\u00fcssen (OK, wir werden vielleicht einige wenige Annotationen zur Transaktionskontrolle hinzuf\u00fcgen).<\/p>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-76088612 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Wenn du dir noch einmal die Grundlagen der hexagonalen Architektur in Erinnerung rufen m\u00f6chtest, empfehle ich dir zun\u00e4chst den ersten Teil dieser Serie, <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur\/\">Hexagonale Architektur \u2013 Was ist das? Was sind ihre Vorteile?<\/a> zu lesen.<\/p><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"hexagonale-architektur-beispielanwendung\">Hexagonale Architektur \u2013 Beispielanwendung<\/h2>\n\n\n\n<p>In diesem Tutorial werden wir gemeinsam eine kleine Beispielanwendung entwickeln. Diese stellt das (stark vereinfachte) Backend f\u00fcr einen Online-Shop bereit, der folgende Funktionalit\u00e4ten umfasst:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Suche nach Produkten<\/li>\n\n\n\n<li>Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/li>\n\n\n\n<li>Abrufen des Warenkorbs mitsamt der Produkte, ihrer jeweiligen Anzahl und des Gesamtpreises<\/li>\n\n\n\n<li>Leeren des Warenkorbs<\/li>\n<\/ol>\n\n\n\n<p>In der Gesch\u00e4ftslogik wollen wir die folgenden Vor- und Nachbedingungen sicherstellen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Die Menge eines hinzuzuf\u00fcgenden Produkts muss mindestens eins betragen.<\/li>\n\n\n\n<li>Nach dem Hinzuf\u00fcgen eines Produkts darf die Gesamtmenge dieses Produkts im Warenkorb die im Lager verf\u00fcgbare Menge des Produkts nicht \u00fcbersteigen.<\/li>\n<\/ul>\n\n\n\n<p>Das ist auch schon alles. Die Anwendung ist absichtlich einfach gehalten, da der Fokus dieses Artikels auf der Architektur und nicht auf dem Funktionsumfang liegen soll.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"eingesetzte-technologien\">Eingesetzte Technologien<\/h3>\n\n\n\n<p>Wir implementieren die Anwendung mit <a href=\"\/de\/java\/java-20-features\/\">Java 20<\/a> (Mockito unterst\u00fctzt das aktuelle LTS-Release <a href=\"\/de\/java\/java-21-features\/\">Java 21<\/a> leider noch nicht) und zun\u00e4chst ganz ohne Application Framework wie Spring oder Quarkus. Warum? Das Application Framework ist ein <em>technisches Detail<\/em> \u2013 und als solches sollte es nicht das Fundament einer Anwendung sein. Das Fundament einer hexagonalen Anwendung ist dessen Gesch\u00e4ftslogik!<\/p>\n\n\n\n<p>Im vierten und f\u00fcnften Teil der Serie werde ich euch dennoch zeigen, wie ihr die Anwendung in ein Application Framework einbetten k\u00f6nnt, denn dieses liefert viele n\u00fctzliche nicht-funktionale Features wie deklaratives Transaktionsmanagement, Metrik- und Health-Endpoints, und vieles mehr. <\/p>\n\n\n\n<p>F\u00fcrs Erste verwenden wir lediglich:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a rel=\"noopener\" href=\"https:\/\/resteasy.dev\/\" target=\"_blank\">RESTEasy<\/a> als Implementierung von Jakarta RESTful Web Services \u2013 auch bekannt als JAX-RS,<\/li>\n\n\n\n<li><a rel=\"noopener\" href=\"https:\/\/undertow.io\/\" target=\"_blank\">Undertow<\/a> als leichtgewichtigen Webserver,<\/li>\n\n\n\n<li><a href=\"https:\/\/www.archunit.org\/\">ArchUnit<\/a>, um die Einhaltung der Architekturgrenzen zu verifizieren,<\/li>\n\n\n\n<li><a href=\"https:\/\/projectlombok.org\/\">Project Lombok<\/a>, um uns Boilerplate-Code zu ersparen. <\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-b769b372 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><span class=\"uagb-ifb-title-prefix\">Falls du bisher nicht mit Lombok gearbeitet hast:<\/span><\/div><p class=\"uagb-ifb-desc\">Du musst daf\u00fcr das Lombok-Plugin f\u00fcr deine IDE installieren. Hier ist ein <a rel=\"noopener\" href=\"https:\/\/projectlombok.org\/setup\/intellij\" target=\"_blank\">Link zum IntelliJ-Plugin<\/a>; die Plugins f\u00fcr andere IDEs findest du unter dem \u201eInstall\u201d-Men\u00fcpunkt auf der Lombok-Webseite.<\/p><\/div><\/div>\n\n\n\n<p>Zudem werden wir testgetrieben vorgehen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>F\u00fcr jede Domain Entity schreiben wir einen Unit-Test.<\/li>\n\n\n\n<li>F\u00fcr jeden Domain Service schreiben wir einen Unit-Test.<\/li>\n\n\n\n<li>F\u00fcr jeden Adapter schreiben wir einen Integration-Test.<\/li>\n\n\n\n<li>F\u00fcr die wichtigsten Anwendungsf\u00e4lle schreiben wir End-to-end-Tests.<\/li>\n<\/ul>\n\n\n\n<p>Ich werde nicht alle Tests in diesem Artikel abdrucken, sondern nur je ein Beispiel f\u00fcr eine Entity, einen Service, einen prim\u00e4ren und einen sekund\u00e4ren Adapter. Den vollst\u00e4ndigen Quellcode zu diesem Artikel einschlie\u00dflich einer vollst\u00e4ndigen Testsuite findest du in diesem <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\" target=\"_blank\">GitHub-Repository<\/a>.<\/p>\n\n\n\n<p>Am besten checkst du den <em>without-jpa-adapters<\/em>-Branch aus, denn der <em>main<\/em>-Branch enth\u00e4lt bereits die JPA-Persistenzl\u00f6sung, die ich erst im n\u00e4chsten Teil der Serie demonstrieren werde. Sie sollte urspr\u00fcnglich Teil dieses Artikels werden; ich habe mich aber, als mir bewusst wurde, wie lang dieses Tutorial wird, entschieden, diesen Schritt auf einen Folgeartikel zu verschieben.<\/p>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-e6757501 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Ich werde au\u00dferdem das CI\/CD-Tool \u201e<a rel=\"noopener\" href=\"https:\/\/github.com\/features\/actions\" target=\"_blank\">GitHub Actions<\/a>\u201d einsetzen, um die Anwendung auf GitHub zu bauen, zu testen und eine statische Code-Analyse durchzuf\u00fchren. Darauf werden ich aber in diesem Artikel nicht n\u00e4her eingehen. Die eingesetzte <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/.github\/workflows\/build.yml\" target=\"_blank\" rel=\"noopener\">GitHub-Actions-Konfiguration<\/a> werde ich in einem zuk\u00fcnftigen Artikel beschreiben.<\/p><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"hexagonale-architektur-projektstruktur\">Hexagonale Architektur \u2013 Projektstruktur<\/h2>\n\n\n\n<p>Die folgende Grafik zeigt die konkrete hexagonale Architektur, mit der wir die Shop-Anwendung aufsetzen werden.<\/p>\n\n\n\n<p>Im Zentrum werden wir die Modellklassen f\u00fcr unseren Shop platzieren. Das Modell habe ich nicht als Hexagon dargestellt, da es nicht durch die hexagonale Architektur definiert ist (zur Erinnerung: die hexagonale Architektur l\u00e4sst offen, was <em>innerhalb<\/em> des Anwendungshexagons geschieht).<\/p>\n\n\n\n<p>Dennoch ist das Modell ein separates Modul, da es nicht auf die Ports zugreifen k\u00f6nnen soll. Nur die Domain Services, die die Gesch\u00e4ftslogik in den Modellklassen koordinieren, werden auf die Ports zugreifen.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"601\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-800x601.png\" alt=\"Hexagonale Architektur der Beispielanwendung\" class=\"wp-image-36240\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-800x601.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-224x168.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-336x252.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-504x379.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-672x505.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-400x301.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-600x451.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-944x709.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4-1200x902.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java.v4.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Hexagonale Architektur der Beispielanwendung<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Im Application-Hexagon implementieren wir die prim\u00e4ren (links) und sekund\u00e4ren Ports (rechts) sowie die Domain Services \u2013 also die Gesch\u00e4ftsfunktionen (in Form einzelner Use Cases), die wiederum auf die sekund\u00e4ren Ports sowie die Gesch\u00e4ftslogik der Modellklassen zugreifen.<\/p>\n\n\n\n<p>An die Ports werden wir drei Arten von Adaptern anschlie\u00dfen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>an den prim\u00e4ren Port: einen REST-Adapter, \u00fcber den wir die Shop-Funktionen aufrufen k\u00f6nnen,<\/li>\n\n\n\n<li>an den sekund\u00e4ren Port: einen In-Memory-Adapter, der die Shop-Daten im RAM ablegt,<\/li>\n\n\n\n<li><a href=\"\/de\/software-craftsmanship\/ports-and-adapters-java-tutorial-db\/\">im n\u00e4chsten Teil dieser Serie<\/a>, ebenfalls an den sekund\u00e4ren Port: einen JPA-Adapter, der die Shop-Daten via Hibernate in einer MySQL-Datenbank persistiert.<\/li>\n<\/ul>\n\n\n\n<p>Die Bootstrap-Komponente schlie\u00dflich wird die Domain Services und Adapter instanziieren, einen Webserver starten und die Anwendung auf diesem deployen.<\/p>\n\n\n\n<p>Die schwarzen Pfeile stellen die Aufrufrichtungen dar, die wei\u00dfen Pfeile die Richtung der Quellcodeabh\u00e4ngigkeiten (\u201eDependency Rule\u201d).<\/p>\n\n\n\n<p>Wie bilden wir diese Architektur nun auf den Quellcode ab? Dazu werden wir Module (in Form von Maven-Modulen) und, innerhalb der Module, Java-Pakete definieren. Module und Pakete sowie die Verzeichnisstruktur, \u00fcber die diese abgebildet werden, werde ich in den folgenden Abschnitten beschreiben.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"hexagonale-architektur-modulstruktur\">Hexagonale Architektur \u2013 Modulstruktur<\/h3>\n\n\n\n<p>Ich werde den Quellcode in vier Maven-Module aufteilen, die in vier Unterverzeichnissen des Projektverzeichnisses liegen werden:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-regular\"><table><tbody><tr><td>model<\/td><td>Enth\u00e4lt das Dom\u00e4nenmodell, also diejenigen Klassen, die den Warenkorb und die Produkte repr\u00e4sentieren. Gem\u00e4\u00df Domain-driven Design werden wir hier in <em>Entities<\/em> (haben eine Identit\u00e4t und sind ver\u00e4nderbar) und <em>Value Objects<\/em> (haben <em>keine<\/em> Identit\u00e4t und sind immutable) unterscheiden.<\/td><\/tr><tr><td>application<\/td><td>Enth\u00e4lt a) die Ports und b) die Domain Services, die die Use Cases implementieren.<br><em>application<\/em>- und <em>model<\/em>-Modul bilden gemeinsam den Applikationskern.<\/td><\/tr><tr><td>adapter<\/td><td>Enth\u00e4lt die REST- und Persistenz-Adapter.<\/td><\/tr><tr><td>bootstrap<\/td><td>Instanziiert Adapter und Domain Services und startet den integrierten Undertow-Webserver.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Wir werden analog zur Projektstruktur-Grafik oben die Abh\u00e4ngigkeiten zwischen den Maven-Modulen wie folgt definieren:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"71\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-600x71.png\" alt=\"Abh\u00e4ngigkeiten der Maven-Module\" class=\"wp-image-36209\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-600x71.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-224x27.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-336x40.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-504x60.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-672x80.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-400x47.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-800x95.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-944x112.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2-1180x142.png 1180w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-modules-uml.v2.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><figcaption class=\"wp-element-caption\">Abh\u00e4ngigkeiten der Maven-Module<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Dadurch haben wir schon einmal sichergestellt, dass alle Quellcodeabh\u00e4ngigkeiten von au\u00dfen Richtung Kern zeigen.<\/p>\n\n\n\n<p>Sp\u00e4ter werde ich dir zeigen, wie du mit der Library \u201eArchUnit\u201d sicherstellen kannst, dass ein Modul nur auf bestimmte <em>Pakete<\/em> eines anderen Moduls zugreifen kann, also z. B. die Adapter nur auf die Ports des <em>application<\/em>-Moduls, nicht aber auf die im gleichen Modul liegenden Domain Services.\u00b9<\/p>\n\n\n\n<p class=\"hc-footnote\">\u00b9 Falls du an dieser Stelle an das Java-Modulsystem \u201eJigsaw\u201d denkst \u2013 auch darauf werde ich im Abschnitt <a href=\"#ueberwachung-der-architekturgrenzen\">\u00dcberwachung der Architekturgrenzen<\/a> eingehen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"hexagonale-architektur-paketstruktur\">Hexagonale Architektur \u2013 Paketstruktur<\/h3>\n\n\n\n<p>Das Hauptpaket der Beispielanwendung ist <code>eu.happycoders.shop<\/code>. Innerhalb der Module werden wir folgende Paketstruktur anlegen (ich habe mich hier an dem hervorragenden Buch <a href=\"https:\/\/www.happycoders.eu\/de\/buecher\/get-your-hands-dirty-on-clean-architecture\/\">\u201eGet Your Hands Dirty on Clean Architecture\u201c<\/a> von Tom Hombergs orientiert):<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Model<\/h4>\n\n\n\n<p>Das <em>model<\/em>-Modul befindet sich im Paket <code>eu.happycoders.shop.model<\/code>. Darunter werden wir im Laufe des Tutorials Unterpakete erstellen, um die verschiedenen Entities und Value Objects zu gruppieren. Aber diese wollen wir nicht im Voraus planen, sondern w\u00e4hrend der Entwicklung je nach Bedarf anlegen.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">eu.happycoders.shop.model<\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Application<\/h4>\n\n\n\n<p>Das <em>application<\/em>-Modul befindet sich im Paket <code>eu.happycoders.shop.application<\/code> \u2013 mit zwei Unterpaketen f\u00fcr die Ports und die Domain Services. Die Ports wiederum teile ich in eingehende und ausgehende Ports. Bei den ausgehenden Ports lege ich noch ein Unterpaket <code>persistence<\/code> an, um die Applikation f\u00fcr sp\u00e4tere Erweiterungen, z. B. um ausgehende Ports f\u00fcr den Zugriff auf ein externes System, vorzubereiten.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">eu.happycoders.shop.application\n \u251c port\n \u2502  \u251c in\n \u2502  \u2514 out.persistence\n \u2514 service<\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Adapter<\/h4>\n\n\n\n<p>Das <em>adapter<\/em>-Modul befindet sich im Paket <code>eu.happycoders.shop.adapter<\/code>. Darunter befinden sich \u2013 analog zu den Ports \u2013 die Pakete <code>in<\/code> und <code>out.persistence<\/code>. <\/p>\n\n\n\n<p>Da Ports von verschiedenen Arten von Adaptern implementiert werden k\u00f6nnen, legen wir daf\u00fcr entsprechende Unterpakete an:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>in.rest<\/code> f\u00fcr die REST-Adapter und<\/li>\n\n\n\n<li><code>out.persistence.inmemory<\/code> f\u00fcr die In-Memory-Persistenzl\u00f6sung.<\/li>\n<\/ul>\n\n\n\n<p>Wie zu Beginn des Tutorials erw\u00e4hnt, werden wir die Anwendung sp\u00e4ter um eine MySQL-Persistenzl\u00f6sung erweitern \u2013 diese werden wir dann im Paket <code>out.persistence.jpa<\/code> (im Folgenden in Klammern dargestellt) implementieren.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">eu.happycoders.shop.adapter\n \u251c in\n \u2502  \u2514 rest\n \u2514 out.persistence\n    \u251c inmemory\n    \u2514 (jpa) <\/code><\/span><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Bootstrap<\/h4>\n\n\n\n<p>Die Bootstrap-Logik legen wir in das Paket <code>eu.happycoders.shop.bootstrap<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">eu.happycoders.shop.bootstrap<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"hexagonale-architektur-verzeichnisstruktur\">Hexagonale Architektur \u2013 Verzeichnisstruktur<\/h3>\n\n\n\n<p>Es ergibt sich folgende Verzeichnisstruktur f\u00fcr unsere Beispielanwendung (du brauchst diese Verzeichnisse nicht manuell anzulegen; das lassen wir sp\u00e4ter unsere IDE f\u00fcr uns machen):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">project\n \u251c bootstrap\n \u2502  \u2514 src\n \u2502     \u251c main\n \u2502     \u2502  \u2514 java\n \u2502     \u2502     \u2514 eu\/happycoders\/shop\n \u2502     \u2502        \u2514 bootstrap\n \u2502     \u2514 test\n \u2502        \u2514 ...\n \u251c adapter\n \u2502  \u2514 src\n \u2502     \u251c main\n \u2502     \u2502  \u2514 java\n \u2502     \u2502     \u2514 eu\/happycoders\/shop\n \u2502     \u2502        \u2514 adapter\n \u2502     \u2502           \u251c in\n \u2502     \u2502           \u2502  \u2514 rest\n \u2502     \u2502           \u2514 out\n \u2502     \u2502              \u2514 persistence\n \u2502     \u2502                 \u251c inmemory\n \u2502     \u2502                 \u2514 (jpa) \n \u2502     \u2514 test\n \u2502        \u2514 ...\n \u251c application\n \u2502  \u2514 src\n \u2502     \u251c main\n \u2502     \u2502  \u2514 java\n \u2502     \u2502     \u2514 eu\/happycoders\/shop\n \u2502     \u2502        \u2514 application\n \u2502     \u2502           \u2514 port\n \u2502     \u2502              \u251c in\n \u2502     \u2502              \u2514 out\n \u2502     \u2502                 \u2514 persistence\n \u2502     \u2514 test\n \u2502        \u2514 ...\n \u2514 model\n    \u2514 src\n       \u251c main\n       \u2502  \u2514 java\n       \u2502     \u2514 eu\/happycoders\/shop\n       \u2502        \u2514 model\n       \u2514 test\n          \u2514 ...<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und nun genug der Theorie \u2013 lasst uns mit der Praxis beginnen!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"los-gehts-aufsetzen-des-projekts\">Los geht's ... Aufsetzen des Projekts<\/h2>\n\n\n\n<p>Ich werde dir im Folgenden zeigen, wie du das Projekt mit <a rel=\"noopener\" href=\"https:\/\/www.jetbrains.com\/de-de\/idea\/\" target=\"_blank\">IntelliJ IDEA<\/a> aufsetzen kannst. Solltest du eine andere Lieblings-IDE haben, wirst du sicherlich wissen, wo du dort die entsprechenden Funktionen findest.<\/p>\n\n\n\n<p>Wir legen zun\u00e4chst \u00fcber <em>New Project<\/em> im Welcome Screen oder \u00fcber <em>File\u2192New\u2192Project<\/em> im Men\u00fc ein neues Projekt an. Als <em>Build System<\/em> w\u00e4hlen wir <em>Maven<\/em> aus. Ich verwende die aktuelle Java-Version 20; f\u00fcr das Tutorial gen\u00fcgt aber auch Java 16 (eine \u00e4ltere Version wird nicht funktionieren, da wir <a href=\"\/de\/java\/java-records\/\">Records<\/a> einsetzen werden). <\/p>\n\n\n\n<p>Die Checkbox bei \u201eAdd sample code\u201d kannst du deaktivieren, da ansonsten eine <code>Main<\/code>-Klasse angelegt werden w\u00fcrde. Das w\u00e4re aber auch nicht schlimm; du k\u00f6nntest sie hinterher einfach wieder l\u00f6schen.<\/p>\n\n\n\n<p>Ganz unten solltest du noch die \u201eAdvanced Settings\u201d \u00f6ffnen und eine <em>GroupId<\/em> und <em>ArtifactId<\/em> eintragen. Du kannst f\u00fcr dieses Tutorial <em>eu.happycoders.shop<\/em> und <em>hexagonal-architecture-java<\/em> eintragen.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"537\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-800x537.png\" alt=\"Hexagonale Architektur mit Java - Erstellen eines neuen Projekts\" class=\"wp-image-36264\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-800x537.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-224x150.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-336x225.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-504x338.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-672x451.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-400x268.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-600x402.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-944x633.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2-1200x805.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-project.v2.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Erstellen eines neuen Projekts<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Nachdem das Projekt erstellt ist, legen wir zun\u00e4chst die vier Module an. Dazu kannst du einfach mit der rechten Maustaste auf das Projekt klicken und dann <em>New\u2192Module<\/em> ausw\u00e4hlen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"343\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-800x343.png\" alt=\"Hexagonale Architektur mit Java - Anlegen eines neuen Moduls\" class=\"wp-image-36271\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-800x343.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-224x96.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-336x144.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-504x216.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-672x288.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-400x172.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-600x257.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-944x405.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2-1200x515.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module.v2.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Anlegen eines neuen Moduls<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Im folgenden Dialogfenster gibst du als Modulname \u201emodel\u201d ein. Die <em>ArtifactId<\/em> ganz unten sollte sich dadurch automatisch auch auf \u201emodel\u201d \u00e4ndern.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"536\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-800x536.png\" alt=\"Hexagonale Architektur mit Java - Anlegen des \u201emodel\u201d-Moduls\" class=\"wp-image-36273\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-800x536.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-224x150.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-336x225.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-504x338.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-672x450.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-400x268.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-600x402.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-944x632.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog-1200x804.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-new-module-dialog.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Anlegen des <em>model<\/em>-Moduls<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Das ganze wiederholst du f\u00fcr die Module <em>application<\/em>, <em>adapter<\/em> und <em>bootstrap<\/em>. Danach sollte das Projekt wie folgt aussehen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" width=\"1600\" height=\"686\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules.png\" alt=\"Hexagonale Architektur mit Java - Alle vier Module sind angelegt\" class=\"wp-image-36280\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules.png 1600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-224x96.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-336x144.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-504x216.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-672x288.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-400x172.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-600x257.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-800x343.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-944x405.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-all-modules-1200x515.png 1200w\" sizes=\"(max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-element-caption\">Alle vier Module sind angelegt<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Mich st\u00f6rt, dass IntelliJ in den <em>pom.xml<\/em>-Dateien eines jeden Moduls den <code>&lt;properties&gt;<\/code>-Block wiederholt, der bereits in der Parent-POM definiert ist und somit an die Module vererbt wird. Wir entfernen daher den <code>&lt;properties&gt;<\/code>-Block aus allen Modulen.<\/p>\n\n\n\n<p>Wir k\u00f6nnen nun ein Terminalfenster \u00f6ffnen und <code>mvn clean compile<\/code> ausf\u00fchren, um sicherzustellen, dass uns dabei kein Fehler unterlaufen ist.<\/p>\n\n\n\n<p>Nachdem das Grundger\u00fcst steht, k\u00f6nnen wir mit der Implementierung des Dom\u00e4nenmodells beginnen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-des-domaenenmodells\">Implementierung des Dom\u00e4nenmodells<\/h2>\n\n\n\n<p>Bevor wir mit der Implementierung beginnen, m\u00fcssen wir uns ein paar Gedanken \u00fcber die Modellierung der Domain machen. Ich zeige dir im Folgenden, wie ich dabei nach taktischem Design, einer Disziplin des Domain-driven Designs, vorgehe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"modellierung-der-domain\">Modellierung der Domain<\/h3>\n\n\n\n<p>Du erinnerst dich sicher aus dem ersten Teil: Wir wollen <em>nicht <\/em>Datenbank-getrieben modellieren, d. h. wir wollen uns <em>nicht<\/em> zuerst \u00fcberlegen, wie wir Einkaufswagen und Produkte in einer Datenbank speichern. Stattdessen beginnen wir mit der Planung eines Objekt-Modells.<\/p>\n\n\n\n<p>Ich beginne in der Regel mit einer ganz groben Planung. Wir ben\u00f6tigen offensichtlich eine <code>Product<\/code>- und eine <code>Cart<\/code>-Klasse. Da wir von einem Produkt eine bestimmte Menge in den Warenkorb legen k\u00f6nnen, brauchen wir dazwischen noch eine Klasse, die ein <code>Product<\/code> und eine Anzahl speichert: ein <code>CartLineItem<\/code>.<\/p>\n\n\n\n<p>Hier siehst du die erste grobe Planung als UML-Diagramm:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400 is-resized\"><img decoding=\"async\" width=\"400\" height=\"67\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-400x67.png\" alt=\"Hexagonale Architektur mit Java - Beispiel-Modellklassen, Iteration 1\" class=\"wp-image-36298\" style=\"width:400px;height:67px\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-400x67.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-224x38.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-336x56.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-504x84.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-672x113.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1-600x101.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-1.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><figcaption class=\"wp-element-caption\">Shop-Modellklassen, Iteration 1<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Ein <code>Cart<\/code> ist eine Komposition von <code>CartLineItem<\/code>s (<em>Komposition<\/em> bedeutet, dass ein <code>CartLineItem<\/code> ohne <code>Cart<\/code> nicht existieren k\u00f6nnte). Ein <code>CartLineItem<\/code> wiederum speichert eine Anzahl (<code>quantity<\/code>) sowie eine Referenz auf ein <code>Product<\/code>.<\/p>\n\n\n\n<p><code>Cart<\/code> und <code>Product<\/code> sind <em>Entities<\/em>, also Objekte mit einer Identit\u00e4t, die wir \u00fcber ein Repository speichern und wieder laden k\u00f6nnen. F\u00fcr jeden Kunden soll es genau <em>einen<\/em> Warenkorb geben, daher verwende ich f\u00fcr die Identifizierung des Warenkorbs eine <code>CustomerId<\/code>. Produkte identifizieren wir \u00fcber eine <code>ProductId<\/code>.<\/p>\n\n\n\n<p><code>CustomerId<\/code> und <code>ProductId<\/code> sind <em>Value Objects<\/em> in der Domain-driven-Design-Terminologie. Hier siehst du das Klassendiagramm um die zwei IDs erweitert:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"67\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-600x67.png\" alt=\"Hexagonale Architektur mit Java - Beispiel-Modellklassen, Iteration 2\" class=\"wp-image-36300\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-600x67.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-224x25.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-336x38.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-504x56.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-672x75.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-400x45.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-800x89.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-944x105.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2-1180x134.png 1180w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-2.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><figcaption class=\"wp-element-caption\">Shop-Modellklassen, Iteration 2<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Um ein Produkt anzuzeigen, brauchen wir dessen Namen (<code>name<\/code>), eine Beschreibung (<code>description<\/code>) und den Preis (<code>price<\/code>). Den Preis modellieren wir als ein <em>Value Object<\/em> mit dem Namen <code>Money<\/code>, das einen Betrag (<code>amount<\/code>) und eine W\u00e4hrung (<code>currency<\/code>) enth\u00e4lt:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"269\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-600x269.png\" alt=\"Hexagonale Architektur mit Java - Beispiel-Modellklassen, Iteration 3\" class=\"wp-image-36305\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-600x269.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-224x100.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-336x151.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-504x226.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-672x301.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-400x179.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-800x359.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2-944x423.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-3.v2.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><figcaption class=\"wp-element-caption\">Shop-Modellklassen, Iteration 3<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Als n\u00e4chstes brauchen wir eine Methode, um ein Produkt zum Warenkorb hinzuzuf\u00fcgen: <code>Cart.addProduct(\u2026)<\/code>. Wenn wir ein Produkt mehrfach hinzuf\u00fcgen, m\u00fcssen wir die Menge eines existierenden Eintrags erh\u00f6hen; daf\u00fcr ist die Methode <code>CartLineItem.increaseQuantityBy(\u2026)<\/code>.<\/p>\n\n\n\n<p>Um festzustellen, ob ein Produkt \u00fcberhaupt verf\u00fcgbar ist, ben\u00f6tigen wir au\u00dferdem die Anzahl der Produkte im Lager: <code>Product.itemsInStock<\/code>:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"287\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-800x287.png\" alt=\"Hexagonale Architektur mit Java - Beispiel-Modellklassen, Iteration 4\" class=\"wp-image-36307\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-800x287.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-224x80.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-336x121.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-504x181.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-672x241.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-400x144.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-600x215.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-944x339.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4-1200x431.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-4.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Shop-Modellklassen, Iteration 4<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Letztendlich wollen wir zu einem Warenkorb noch den Gesamtpreis anzeigen: Methode <code>subTotal()<\/code> in <code>Cart<\/code> und <code>CartLineItem<\/code> \u2013 und die Gesamtanzahl der Produkte: Methode <code>Cart.numberOfItems()<\/code>:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"287\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-800x287.png\" alt=\"Hexagonale Architektur mit Java - Beispiel-Modellklassen, Iteration 5\" class=\"wp-image-36309\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-800x287.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-224x80.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-336x121.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-504x181.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-672x241.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-400x144.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-600x215.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-944x339.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5-1200x431.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-shop-model-iteration-5.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Shop-Modellklassen, Iteration 5<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Im n\u00e4chsten Abschnitt zeige ich dir die Implementierung der Modellklassen in Java.<\/p>\n\n\n\n<p>In einem echten Projekt f\u00fchre ich Modellierung und Implementierung \u00fcbrigens nicht in zwei separaten Schritten durch. Ich generiere auch nicht so detaillierte Klassendiagramme, sondern skizziere in der Regel blo\u00df einen ersten Entwurf auf Papier. F\u00fcr dieses Tutorial erschien es mir \u00fcbersichtlicher, Modellierung und Implementierung in zwei Schritten darzustellen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-der-domain-klassen\">Implementierung der Domain-Klassen<\/h3>\n\n\n\n<p>Der \u00dcbersicht halber gruppiere ich die Klassen in vier Unterpakete im <em>model<\/em>-Modul unter dem Paket <code>eu.happycoders.shop.model<\/code> (im Screenshot siehst du auch die bisher nicht erw\u00e4hnte Klasse <code>NotEnoughItemsInStockException<\/code> \u2013 auf diese werde ich in K\u00fcrze zu sprechen kommen):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"353\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-400x353.png\" alt=\"Hexagonale Architektur mit Java - Modellklassen\" class=\"wp-image-36381\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-400x353.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-224x198.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-336x297.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-504x445.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-672x593.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes-600x530.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Im Folgenden zeige ich dir den Code der Produktivklassen und einen beispielhaften Unit-Test. Tats\u00e4chlich bin ich testgetrieben vorgegangen und habe zu jeder Klasse vorab Tests erstellt. Diese findest du im <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\">model\/src\/test\/java-Verzeichnis<\/a> im GitHub-Repository.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">CustomerId<\/h4>\n\n\n\n<p>Beginnen wir mit der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/customer\/CustomerId.java\" target=\"_blank\">CustomerId<\/a> (der Link f\u00fchrt zur Klasse im GitHub-Repository) \u2013 diese ist als Record implementiert und stellt einen Wrapper um einen <code>int<\/code>-Wert dar:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.customer;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">CustomerId<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> value)<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">public<\/span> CustomerId {\n    <span class=\"hljs-keyword\">if<\/span> (value &lt; <span class=\"hljs-number\">1<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(<span class=\"hljs-string\">\"'value' must be a positive integer\"<\/span>);\n    }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Wozu ben\u00f6tigen wir solch einen Wrapper? K\u00f6nnen wir die Kundennummer nicht direkt als <code>int<\/code>-Wert im <code>Cart<\/code> ablegen? K\u00f6nnten wir \u2013 das w\u00e4re allerdings ein Code Smell, der sich \u201ePrimitive Obsession\u201d nennt. Der Wrapper hat zwei Vorteile:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wir k\u00f6nnen sicherstellen, dass der Wert g\u00fcltig ist. Im Beispiel muss die Kundennummer eine positive Zahl sein.<\/li>\n\n\n\n<li>Wir k\u00f6nnen die Kundennummer typsicher an Methoden \u00fcbergeben. W\u00e4re die Kundennummer ein <code>int<\/code>-Primitiv, k\u00f6nnten wir bei einer Methode mit mehreren <code>int<\/code>-Parametern versehentlich die Parameter vertauschen.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">ProductId<\/h4>\n\n\n\n<p>Die Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/product\/ProductId.java\" target=\"_blank\">ProductId<\/a> ist ein Wrapper um einen String und bietet die statische Methode <code>randomProductId()<\/code>, um eine zuf\u00e4llige Produkt-ID zu generieren.<\/p>\n\n\n\n<p>Ich werde ich den folgenden Listings die Imports nicht mit abdrucken; moderne IDEs sind in der Lage, diese automatisch hinzuzuf\u00fcgen.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.product;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">ProductId<\/span><span class=\"hljs-params\">(String value)<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String ALPHABET = <span class=\"hljs-string\">\"23456789ABCDEFGHJKLMNPQRSTUVWXYZ\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">int<\/span> LENGTH_OF_NEW_PRODUCT_IDS = <span class=\"hljs-number\">8<\/span>;\n\n  <span class=\"hljs-keyword\">public<\/span> ProductId {\n    Objects.requireNonNull(value, <span class=\"hljs-string\">\"'value' must not be null\"<\/span>);\n    <span class=\"hljs-keyword\">if<\/span> (value.isEmpty()) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(<span class=\"hljs-string\">\"'value' must not be empty\"<\/span>);\n    }\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> ProductId <span class=\"hljs-title\">randomProductId<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    ThreadLocalRandom random = ThreadLocalRandom.current();\n    <span class=\"hljs-keyword\">char<\/span>&#091;] chars = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-keyword\">char<\/span>&#091;LENGTH_OF_NEW_PRODUCT_IDS];\n    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">int<\/span> i = <span class=\"hljs-number\">0<\/span>; i &lt; LENGTH_OF_NEW_PRODUCT_IDS; i++) {\n      chars&#091;i] = ALPHABET.charAt(random.nextInt(ALPHABET.length()));\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductId(<span class=\"hljs-keyword\">new<\/span> String(chars));\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>randomProductId()<\/code>-Methode werden wir in Unit-Tests und sp\u00e4ter zur Erzeugung von Demo-Produkten verwenden.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Money<\/h4>\n\n\n\n<p>Die <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/money\/Money.java\" target=\"_blank\">Money<\/a>-Klasse ist ein Record mit zwei Feldern \u2013 <code>currency<\/code> und <code>amount<\/code> \u2013 sowie einer statischen Factory-Methode und Methoden, um zwei Geldbetr\u00e4ge zu addieren und einen Geldbetrag zu multiplizieren:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.money;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">Money<\/span><span class=\"hljs-params\">(Currency currency, BigDecimal amount)<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">public<\/span> Money {\n    Objects.requireNonNull(currency, <span class=\"hljs-string\">\"'currency' must not be null\"<\/span>);\n    Objects.requireNonNull(amount, <span class=\"hljs-string\">\"'amount' must not be null\"<\/span>);\n    <span class=\"hljs-keyword\">if<\/span> (amount.scale() &gt; currency.getDefaultFractionDigits()) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(\n          (<span class=\"hljs-string\">\"Scale of amount %s is greater \"<\/span>\n                  + <span class=\"hljs-string\">\"than the number of fraction digits used with currency %s\"<\/span>)\n              .formatted(amount, currency));\n    }\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> Money <span class=\"hljs-title\">of<\/span><span class=\"hljs-params\">(Currency currency, <span class=\"hljs-keyword\">int<\/span> mayor, <span class=\"hljs-keyword\">int<\/span> minor)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">int<\/span> scale = currency.getDefaultFractionDigits();\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Money(\n        currency, BigDecimal.valueOf(mayor).add(BigDecimal.valueOf(minor, scale)));\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Money <span class=\"hljs-title\">add<\/span><span class=\"hljs-params\">(Money augend)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>.currency.equals(augend.currency())) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(\n          <span class=\"hljs-string\">\"Currency %s of augend does not match this money's currency %s\"<\/span>\n              .formatted(augend.currency(), <span class=\"hljs-keyword\">this<\/span>.currency));\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Money(currency, amount.add(augend.amount()));\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Money <span class=\"hljs-title\">multiply<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> multiplicand)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Money(currency, amount.multiply(BigDecimal.valueOf(multiplicand)));\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Im <em>main<\/em>-Branch des GitHub-Repositories wirst du noch zwei weitere Methoden, <code>ofMinor(\u2026)<\/code> und <code>amountInMinorUnit()<\/code>, sehen. Diese ben\u00f6tigen wir erst im folgenden Teil der Serie, um einen Geldbetrag in der Datenbank abzuspeichern.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Product<\/h4>\n\n\n\n<p>Ein Produkt ist ver\u00e4nderlich: der Verk\u00e4ufer soll z. B. die Beschreibung und den Preis \u00e4ndern k\u00f6nnen. Es kann daher nicht als Record implementiert werden.<\/p>\n\n\n\n<p>Um uns die Schreibarbeit f\u00fcr Getter und Setter zu ersparen, k\u00f6nnen wir Lombok-Annotationen einsetzen. Dazu musst du zun\u00e4chst die entsprechende Dependency in die <em>pom.xml<\/em>-Datei im Root-Verzeichnis des Projekts eintragen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.projectlombok<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>lombok<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>1.18.30<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>provided<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Danach k\u00f6nnen wir die <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/product\/Product.java\" target=\"_blank\">Product<\/a>-Klasse wie folgt implementieren:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.product;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-meta\">@Data<\/span>\n<span class=\"hljs-meta\">@Accessors<\/span>(fluent = <span class=\"hljs-keyword\">true<\/span>)\n<span class=\"hljs-meta\">@AllArgsConstructor<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Product<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> ProductId id;\n  <span class=\"hljs-keyword\">private<\/span> String name;\n  <span class=\"hljs-keyword\">private<\/span> String description;\n  <span class=\"hljs-keyword\">private<\/span> Money price;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">int<\/span> itemsInStock;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">CartLineItem<\/h4>\n\n\n\n<p>Weiter geht es mit dem <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/cart\/CartLineItem.java\" target=\"_blank\">CartLineItem<\/a>. Da sich die Anzahl eines Produkts im Warenkorb \u00e4ndern kann, ist auch dieses kein Record, sondern eine regul\u00e4re Klasse mit Lombok-Annotationen.<\/p>\n\n\n\n<p>Allerdings verwende ich hier <code>@Getter<\/code> anstelle der <code>@Data<\/code>-Annotation. Ich m\u00f6chte hier keine Setter haben, denn das einzige Feld, das sich \u00e4ndern kann, ist <code>quantity<\/code>. Und das soll nur \u00fcber die Methode <code>increaseQuantityBy(\u2026)<\/code> ge\u00e4ndert werden k\u00f6nnen (Kapselung!).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-meta\">@Getter<\/span>\n<span class=\"hljs-meta\">@Accessors<\/span>(fluent = <span class=\"hljs-keyword\">true<\/span>)\n<span class=\"hljs-meta\">@RequiredArgsConstructor<\/span>\n<span class=\"hljs-meta\">@AllArgsConstructor<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CartLineItem<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Product product;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">int<\/span> quantity;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">increaseQuantityBy<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> augend, <span class=\"hljs-keyword\">int<\/span> itemsInStock)<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> NotEnoughItemsInStockException <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (augend &lt; <span class=\"hljs-number\">1<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(<span class=\"hljs-string\">\"You must add at least one item\"<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">int<\/span> newQuantity = quantity + augend;\n    <span class=\"hljs-keyword\">if<\/span> (itemsInStock &lt; newQuantity) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> NotEnoughItemsInStockException(\n          (<span class=\"hljs-string\">\"Product %s has less items in stock (%d) \"<\/span>\n                  + <span class=\"hljs-string\">\"than the requested total quantity (%d)\"<\/span>)\n              .formatted(product.id(), product.itemsInStock(), newQuantity),\n          product.itemsInStock());\n    }\n\n    <span class=\"hljs-keyword\">this<\/span>.quantity = newQuantity;\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Money <span class=\"hljs-title\">subTotal<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> product.price().multiply(quantity);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier haben wir Gesch\u00e4ftslogik, n\u00e4mlich das Erh\u00f6hen der Anzahl eines Produkts im Warenkorb, innerhalb einer Modellklasse implementiert. Das nennt sich \u201eRich Domain Model\u201d \u2013 im Gegensatz zu einem \u201eAnemic Domain Model\u201d, bei dem die Modellklassen lediglich Felder, Getter und Setter enthalten, und die Gesch\u00e4ftslogik in Service-Klassen implementiert ist.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">NotEnoughItemsInStockException<\/h4>\n\n\n\n<p>Die <code>increaseQuantityBy(\u2026)<\/code>-Methode pr\u00fcft auch die Vor- und Nachbedingungen. Ist die Vorbedingung nicht erf\u00fcllt (die hinzuzuf\u00fcgende Anzahl ist kleiner als eins), wirft die Methode eine <code>IllegalArgumentException<\/code> (denn es sollte gar nicht erst m\u00f6glich sein, die Methode mit einer zu niedrigen Anzahl aufzurufen).<\/p>\n\n\n\n<p>Ist die Nachbedingung nicht erf\u00fcllt (der Warenkorb darf nicht mehr als die verf\u00fcgbare Anzahl Artikel enthalten), wirft die Methode eine <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/cart\/NotEnoughItemsInStockException.java\" target=\"_blank\">NotEnoughItemsInStockException<\/a>. Diese enth\u00e4lt die verf\u00fcgbare Menge als Parameter, sodass wir im Frontend eine entsprechende Meldung anzeigen k\u00f6nnen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.cart;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">NotEnoughItemsInStockException<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Exception<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">int<\/span> itemsInStock;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">NotEnoughItemsInStockException<\/span><span class=\"hljs-params\">(String message, <span class=\"hljs-keyword\">int<\/span> itemsInStock)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">super<\/span>(message);\n    <span class=\"hljs-keyword\">this<\/span>.itemsInStock = itemsInStock;\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> <span class=\"hljs-title\">itemsInStock<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> itemsInStock;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Cart<\/h4>\n\n\n\n<p>Kommen wir zum Kern des Modells, der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/cart\/Cart.java\" target=\"_blank\">Cart<\/a>-Klasse. Sie speichert die Warenkorbeintr\u00e4ge in einer Map von <code>ProductId<\/code> zu <code>CartLineItem<\/code>, sodass wir beim Hinzuf\u00fcgen eines Produkts \u2013 in der Methode <code>addProduct(\u2026)<\/code> \u2013 pr\u00fcfen k\u00f6nnen, ob sich dieses bereits im Warenkorb befindet.<\/p>\n\n\n\n<p>Die Methode <code>lineItems()<\/code> gibt eine Kopie der in der Map enthaltenen Werte zur\u00fcck, sodass die <code>lineItems<\/code>-Datenstruktur nicht von au\u00dferhalb der Klasse ver\u00e4ndert werden kann.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-meta\">@Accessors<\/span>(fluent = <span class=\"hljs-keyword\">true<\/span>)\n<span class=\"hljs-meta\">@RequiredArgsConstructor<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Cart<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Getter<\/span> <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> CustomerId id;\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Map&lt;ProductId, CartLineItem&gt; lineItems = <span class=\"hljs-keyword\">new<\/span> LinkedHashMap&lt;&gt;();\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">addProduct<\/span><span class=\"hljs-params\">(Product product, <span class=\"hljs-keyword\">int<\/span> quantity)<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> NotEnoughItemsInStockException <\/span>{\n    lineItems\n        .computeIfAbsent(product.id(), ignored -&gt; <span class=\"hljs-keyword\">new<\/span> CartLineItem(product))\n        .increaseQuantityBy(quantity, product.itemsInStock());\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;CartLineItem&gt; <span class=\"hljs-title\">lineItems<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> List.copyOf(lineItems.values());\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">int<\/span> <span class=\"hljs-title\">numberOfItems<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> lineItems.values().stream().mapToInt(CartLineItem::quantity).sum();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Money <span class=\"hljs-title\">subTotal<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> lineItems.values().stream()\n        .map(CartLineItem::subTotal)\n        .reduce(Money::add)\n        .orElse(<span class=\"hljs-keyword\">null<\/span>);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist unser Datenmodell fertig implementiert. Im folgenden Abschnitt zeige ich dir noch einen beispielhaften Unit-Test.<\/p>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-ddcc9986 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Falls auch du dich immer wieder fragst, wie du nicht ben\u00f6tigte Variablen bezeichnen sollst (wie die <code>ignored<\/code>-Variable in der <code>computeIfAbsent()<\/code>-Methode oben), dann habe ich eine gute Nachricht f\u00fcr dich: Ab <a href=\"\/de\/java\/java-21-features\/#Unnamed_Patterns_and_Variables_Preview_-_JEP_443\">Java 21<\/a> kannst du (zun\u00e4chst als Preview-Feature) unben\u00f6tigte Variablen mit \u201e_\u201d bezeichnen.<\/p><\/div><\/div>\n\n\n\n<h4 class=\"wp-block-heading\">CartTest<\/h4>\n\n\n\n<p>Wie angek\u00fcndigt zeige ich dir hier einen beispielhaften Unit-Test. Daf\u00fcr m\u00fcssen wir vorab Dependencies zu JUnit und AssertJ in die <em>pom.xml<\/em> im Rootverzeichnis (wir werden diese Dependencies in allen Modulen ben\u00f6tigen) eintragen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.junit.jupiter<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>junit-jupiter<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>5.10.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.assertj<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>assertj-core<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>3.24.2<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Tats\u00e4chlich habe ich das vorab gemacht und die Tests gemeinsam mit den Entity-Klassen geschrieben. Da der Fokus dieses Artikels nicht auf TDD (Test-driven Development) liegt, zeige ich die Tests erst an dieser Stelle. Hier siehst du einen Screenshot aller Modell-Tests:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"378\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-400x378.png\" alt=\"Hexagonale Architektur mit Java - Modell-Testklassen\" class=\"wp-image-36536\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-400x378.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-224x212.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-336x318.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-504x476.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-672x635.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes-600x567.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-model-test-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Die im vorherigen Abschnitt beschriebene Modellklasse <code>Cart<\/code> testen wir mit der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/cart\/CartTest.java\" target=\"_blank\">CartTest<\/a>-Klasse (ich drucke hier zwei statische Imports mit ab, die zumindest IntelliJ nicht selbstst\u00e4ndig aufl\u00f6sen kann):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.model.cart;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.model.money.TestMoneyFactory.euros;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.assertj.core.api.Assertions.assertThat;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CartTest<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenEmptyCart_addTwoProducts_numberOfItemsAndSubTotalIsCalculatedCorrectly<\/span><span class=\"hljs-params\">()<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> NotEnoughItemsInStockException <\/span>{\n    Cart cart = TestCartFactory.emptyCartForRandomCustomer();\n\n    Product product1 = TestProductFactory.createTestProduct(euros(<span class=\"hljs-number\">12<\/span>, <span class=\"hljs-number\">99<\/span>));\n    Product product2 = TestProductFactory.createTestProduct(euros(<span class=\"hljs-number\">5<\/span>, <span class=\"hljs-number\">97<\/span>));\n\n    cart.addProduct(product1, <span class=\"hljs-number\">3<\/span>);\n    cart.addProduct(product2, <span class=\"hljs-number\">5<\/span>);\n\n    assertThat(cart.numberOfItems()).isEqualTo(<span class=\"hljs-number\">8<\/span>);\n    assertThat(cart.subTotal()).isEqualTo(euros(<span class=\"hljs-number\">68<\/span>, <span class=\"hljs-number\">82<\/span>));\n  }\n\n  <span class=\"hljs-comment\">\/\/ more tests<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Der Test erzeugt \u00fcber die <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/cart\/TestCartFactory.java\" target=\"_blank\">TestCartFactory<\/a> einen leeren Warenkorb und \u00fcber die <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/product\/TestProductFactory.java\" target=\"_blank\">TestProductFactory<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/money\/TestMoneyFactory.java\" target=\"_blank\" rel=\"noopener\">TestMoneyFactory<\/a> zwei Produkte zu 12,99 \u20ac und 5,97 \u20ac, f\u00fcgt dann dem Warenkorb drei mal Produkt 1 hinzu und f\u00fcnf mal Produkt 2 und verifiziert anschlie\u00dfend, dass der Warenkorb insgesamt acht Produkte zu einem Gesamtpreis von 68,82 \u20ac enth\u00e4lt. <\/p>\n\n\n\n<p>Dies ist nur ein Test von vielen. Im <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\" target=\"_blank\" rel=\"noopener\">GitHub-Repository<\/a> findest du zahlreiche weitere Modell-Tests.<\/p>\n\n\n\n<p>Falls du das Tutorial bis hierhin nachprogrammierst und die Tests aus dem GitHub-Repository \u00fcbernommen hast, kannst du in einem Terminal mit <code>mvn clean test<\/code> ausprobieren, ob alle Tests erfolgreich durchlaufen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-des-application-hexagons\">Implementierung des Application-Hexagons<\/h2>\n\n\n\n<p>Im Application-Hexagon implementieren wir Ports und Domain Services f\u00fcr die folgenden Use Cases:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Suche nach Produkten<\/li>\n\n\n\n<li>Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/li>\n\n\n\n<li>Abrufen des Warenkorbs mitsamt der Produkte, ihrer jeweiligen Anzahl und des Gesamtpreises<\/li>\n\n\n\n<li>Leeren des Warenkorbs<\/li>\n<\/ol>\n\n\n\n<p>Das <a rel=\"noopener\" href=\"https:\/\/de.wikipedia.org\/wiki\/Single-Responsibility-Prinzip\" target=\"_blank\">Single-Responsibility-Prinzip<\/a> besagt, dass es nur einen Grund geben sollte, um eine Klasse zu \u00e4ndern. Dementsprechend werde ich f\u00fcr jeden Use Case einen separaten prim\u00e4ren Port und eine separate Service-Klasse anlegen. In der Beispielanwendung wird das keinen gro\u00dfen Unterschied machen, doch in echten Anwendung begegne ich regelm\u00e4\u00dfig un\u00fcbersichtlichen und schwer wartbaren Service-Klassen mit mehreren Tausend Zeilen Code. Dem beugen wir so von Anfang an vor.<\/p>\n\n\n\n<p>Den prim\u00e4ren Ports gebe ich Klassennamen, die auf <code>UseCase<\/code> enden.<\/p>\n\n\n\n<p>Die sekund\u00e4ren Ports werden zur Persistierung von Entities verwendet, entsprechen in der Domain-driven-Design-Terminologie also Repositories. Da DDD-Repositories sowohl zum Speichern als auch zum Laden von Entities eingesetzt werden und in der Regel nicht gro\u00df werden, werde ich diese nicht weiter unterteilen.<\/p>\n\n\n\n<p>Das folgende UML-Klassendiagramm zeigt alle Ports (oben die prim\u00e4ren, unten die sekund\u00e4ren) und Services (in der mittleren Reihe) des Application-Hexagons. Die prim\u00e4ren Ports und Services sind von links nach rechts in derselben Reihenfolge angeordnet wie die Use Cases in der Aufz\u00e4hlung zu Beginn dieses Abschnitts.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"465\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-800x465.png\" alt=\"Hexagonale Architektur mit Java - Ports und Services der Beispielanwendung\" class=\"wp-image-36534\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-800x465.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-224x130.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-336x195.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-504x293.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-672x391.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-400x233.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-600x349.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-944x549.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5-1200x698.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-and-services.v5.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Ports und Services der Beispielanwendung<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Der \u00dcbersicht halber habe ich unter den Paketen f\u00fcr die prim\u00e4ren Ports und Services jeweils zwei Unterpakete, <code>product<\/code> und <code>cart<\/code>, angelegt. Der folgende Screenshot zeigt die Pakete und Klassen des <em>application<\/em>-Moduls in der IDE:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"533\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-400x533.png\" alt=\"Hexagonale Architektur mit Java - Application-Klassen\" class=\"wp-image-36510\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-400x533.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-224x298.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-336x448.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-504x672.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-672x895.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2-600x800.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-classes.v2.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Im Folgenden werde ich dir die Implementierungen der Ports und Services zeigen, und zwar Use Case f\u00fcr Use Case.<\/p>\n\n\n\n<p>Doch zuerst m\u00fcssen wir noch daf\u00fcr sorgen, dass das <em>application<\/em>-Modul auf das <em>model<\/em>-Modul zugreifen kann. Das machen wir mit folgendem Eintrag in der <em>pom.xml<\/em>-Datei des <em>application<\/em>-Moduls:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>eu.happycoders.shop<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>model<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>${project.version}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und jetzt geht es los mit den Use Cases...<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"use-case-1-suche-nach-produkten\">Use Case 1: Suche nach Produkten<\/h3>\n\n\n\n<p>Beschreibung des Use Case: <em>Die Kundin soll einen Text in ein Suchfeld eingeben k\u00f6nnen. Der Suchtext soll mindestens zwei Zeichen lang sein. Es sollen alle Produkte zur\u00fcckgeliefert werden, bei denen der Suchtext im Titel oder in der Beschreibung vorkommt.<\/em><\/p>\n\n\n\n<p>Wir beginnen mit unserem ersten prim\u00e4ren Port, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/in\/product\/FindProductsUseCase.java\" target=\"_blank\">FindProductsUseCase<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.in.product;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">FindProductsUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-function\">List&lt;Product&gt; <span class=\"hljs-title\">findByNameOrDescription<\/span><span class=\"hljs-params\">(String query)<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Das ist tats\u00e4chlich schon alles. Komplizierter werden die Ports nicht.<\/p>\n\n\n\n<p>Implementiert wird der Port durch die Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/service\/product\/FindProductsService.java\" target=\"_blank\">FindProductsService<\/a>. Der Service muss auf den sekund\u00e4ren Port <code>ProductRepository<\/code> zugreifen (diesen findest du im Anschluss), um die Produkte in der eingesetzten Persistenzl\u00f6sung zu suchen. <\/p>\n\n\n\n<p>Den Port \u2013 oder besser gesagt: dessen Implementierung \u2013 \u00fcbergeben wir sp\u00e4ter im <em>bootstrap<\/em>-Modul an den Konstruktor des Services (\u201eConstructor Depencency Injection\u201d).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.service.product;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">FindProductsService<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">FindProductsUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> ProductRepository productRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">FindProductsService<\/span><span class=\"hljs-params\">(ProductRepository productRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.productRepository = productRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;Product&gt; <span class=\"hljs-title\">findByNameOrDescription<\/span><span class=\"hljs-params\">(String query)<\/span> <\/span>{\n    Objects.requireNonNull(query, <span class=\"hljs-string\">\"'query' must not be null\"<\/span>);\n    <span class=\"hljs-keyword\">if<\/span> (query.length() &lt; <span class=\"hljs-number\">2<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(<span class=\"hljs-string\">\"'query' must be at least two characters long\"<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> productRepository.findByNameOrDescription(query);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und hier ist das injizierte <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/out\/persistence\/ProductRepository.java\" target=\"_blank\">ProductRepository<\/a> (das Interface im GitHub-Repository hat bereits zwei weitere Methoden \u2013 eine f\u00fcr den n\u00e4chsten Use Case und eine f\u00fcr Integration Tests):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.out.persistence;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">ProductRepository<\/span> <\/span>{\n\n  <span class=\"hljs-function\">List&lt;Product&gt; <span class=\"hljs-title\">findByNameOrDescription<\/span><span class=\"hljs-params\">(String query)<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist der Use Case aus Sicht des Application-Hexagons fertig implementiert. Wie die Produkte in der Persistenzl\u00f6sung gesucht werden, ist sp\u00e4ter Sache desjenigen Adapters, der das <code>ProductRepository<\/code>-Interface (also den sekund\u00e4ren Port) implementieren wird.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"use-case-2-hinzufuegen-eines-produkts-zum-warenkorb\">Use Case 2: Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/h3>\n\n\n\n<p>Beschreibung des Use Case: <em>Der Kunde soll ein Produkt in einer bestimmten Anzahl zu seinem Warenkorb hinzuf\u00fcgen k\u00f6nnen.<\/em><\/p>\n\n\n\n<p>Hier zun\u00e4chst der prim\u00e4re Port, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/in\/cart\/AddToCartUseCase.java\" target=\"_blank\">AddToCartUseCase<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.in.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">AddToCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-function\">Cart <span class=\"hljs-title\">addToCart<\/span><span class=\"hljs-params\">(CustomerId customerId, ProductId productId, <span class=\"hljs-keyword\">int<\/span> quantity)<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> ProductNotFoundException, NotEnoughItemsInStockException<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die einzige Methode dieses Interfaces deklariert zwei Exceptions. Die <code>NotEnoughItemsInStockException<\/code> haben wir bereits im vorherigen Kapitel im <em>model<\/em>-Modul definiert.<\/p>\n\n\n\n<p>Die zweite Exception, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/in\/cart\/ProductNotFoundException.java\" target=\"_blank\">ProductNotFoundException<\/a> kommt nicht aus dem Modell, sondern wird im Application-Hexagon, im gleichen Paket wie der Port, definiert. Denn ob ein Produkt existiert oder nicht, wird beim Zugriff des Services auf das Repository (also in der Applikation, nicht im Modell) ermittelt. <\/p>\n\n\n\n<p>Der Quellcode der Exception ist trivial:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.in.cart;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ProductNotFoundException<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Exception<\/span> <\/span>{}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Es folgt der Quellcode der Use-Case-Implementierung, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/service\/cart\/AddToCartService.java\" target=\"_blank\">AddToCartService<\/a>.<\/p>\n\n\n\n<p>Die Methode <code>addToCart(\u2026)<\/code> validiert zun\u00e4chst die Eingabeparameter, l\u00e4dt Produkt und Warenkorb aus Repositories (bzw. legt einen neuen Warenkorb an), f\u00fcgt dem Warenkorb das Produkt in der gew\u00fcnschten Menge hinzu und speichert den Warenkorb wieder:  <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.service.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AddToCartService<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">AddToCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> CartRepository cartRepository;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> ProductRepository productRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">AddToCartService<\/span><span class=\"hljs-params\">(\n      CartRepository cartRepository, ProductRepository productRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.cartRepository = cartRepository;\n    <span class=\"hljs-keyword\">this<\/span>.productRepository = productRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Cart <span class=\"hljs-title\">addToCart<\/span><span class=\"hljs-params\">(CustomerId customerId, ProductId productId, <span class=\"hljs-keyword\">int<\/span> quantity)<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> ProductNotFoundException, NotEnoughItemsInStockException <\/span>{\n    Objects.requireNonNull(customerId, <span class=\"hljs-string\">\"'customerId' must not be null\"<\/span>);\n    Objects.requireNonNull(productId, <span class=\"hljs-string\">\"'productId' must not be null\"<\/span>);\n    <span class=\"hljs-keyword\">if<\/span> (quantity &lt; <span class=\"hljs-number\">1<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> IllegalArgumentException(<span class=\"hljs-string\">\"'quantity' must be greater than 0\"<\/span>);\n    }\n\n    Product product =\n        productRepository.findById(productId).orElseThrow(ProductNotFoundException::<span class=\"hljs-keyword\">new<\/span>);\n\n    Cart cart =\n        cartRepository\n            .findByCustomerId(customerId)\n            .orElseGet(() -&gt; <span class=\"hljs-keyword\">new<\/span> Cart(customerId));\n\n    cart.addProduct(product, quantity);\n\n    cartRepository.save(cart);\n\n    <span class=\"hljs-keyword\">return<\/span> cart;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Bei diesem Service injizieren wir zwei Repositories (sekund\u00e4re Ports): <\/p>\n\n\n\n<p>Erstens das bereits bekannte <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/out\/persistence\/ProductRepository.java\" target=\"_blank\">ProductRepository<\/a> \u2013 dieses erweitern wir um die Methode <code>findById(\u2026)<\/code>, um ein konkretes Produkt zu laden \u2013 und auch gleich um die Methode <code>save(\u2026)<\/code>, die wir sp\u00e4ter brauchen werden, um Testprodukte anzulegen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.out.persistence;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">ProductRepository<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">save<\/span><span class=\"hljs-params\">(Product product)<\/span><\/span>;\n\n  <span class=\"hljs-function\">Optional&lt;Product&gt; <span class=\"hljs-title\">findById<\/span><span class=\"hljs-params\">(ProductId productId)<\/span><\/span>;\n\n  <span class=\"hljs-function\">List&lt;Product&gt; <span class=\"hljs-title\">findByNameOrDescription<\/span><span class=\"hljs-params\">(String query)<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und zweitens das neue <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/out\/persistence\/CartRepository.java\" target=\"_blank\">CartRepository<\/a>, um den Warenkorb zu speichern und zu laden (im GitHub-Repository findest du bereits eine dritte Methode, <code>deleteById(\u2026)<\/code>, die wir f\u00fcr den vierten Use Case hinzuf\u00fcgen werden):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.out.persistence;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">CartRepository<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">save<\/span><span class=\"hljs-params\">(Cart cart)<\/span><\/span>;\n\n  <span class=\"hljs-function\">Optional&lt;Cart&gt; <span class=\"hljs-title\">findByCustomerId<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist auch der zweite Use Case fertig implementiert; kommen wir zum dritten...<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"use-case-3-abrufen-des-warenkorbs\">Use Case 3: Abrufen des Warenkorbs<\/h3>\n\n\n\n<p>Beschreibung des Use Case: <em>Die Kundin soll ihren Warenkorb abrufen k\u00f6nnen mitsamt der Produkte, ihrer jeweiligen Anzahl, der Gesamtanzahl an Produkten und des Gesamtpreises.<\/em><\/p>\n\n\n\n<p>Wir beginnen wieder mit dem prim\u00e4ren Port, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/in\/cart\/GetCartUseCase.java\" target=\"_blank\" rel=\"noopener\">GetCartUseCase<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.in.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">GetCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-function\">Cart <span class=\"hljs-title\">getCart<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span><\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die Implementierung, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/service\/cart\/GetCartService.java\" target=\"_blank\">GetCartService<\/a>, leitet den Aufruf an die bereits f\u00fcr den vorherigen Use Case angelegte Repository-Methode <code>findByCustomerId(\u2026)<\/code> weiter. Wenn kein Warenkorb existiert, wird ein neuer angelegt, sodass diese Methode niemals <code>null<\/code> zur\u00fcckliefern wird.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.service.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GetCartService<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">GetCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> CartRepository cartRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">GetCartService<\/span><span class=\"hljs-params\">(CartRepository cartRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.cartRepository = cartRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Cart <span class=\"hljs-title\">getCart<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    Objects.requireNonNull(customerId, <span class=\"hljs-string\">\"'customerId' must not be null\"<\/span>);\n\n    <span class=\"hljs-keyword\">return<\/span> cartRepository\n        .findByCustomerId(customerId)\n        .orElseGet(() -&gt; <span class=\"hljs-keyword\">new<\/span> Cart(customerId));\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Beachte, dass wir die Berechnung der Gesamtanzahl der Produkte und des Gesamtpreises bereits in der Modellklasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/main\/java\/eu\/happycoders\/shop\/model\/cart\/Cart.java\" target=\"_blank\" rel=\"noopener\">Cart<\/a> implementiert haben.<\/p>\n\n\n\n<p>Kommen wir zum vierten und letzten Use Case...<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"use-case-4-leeren-des-warenkorbs\">Use Case 4: Leeren des Warenkorbs<\/h3>\n\n\n\n<p>Beschreibung des Use Case: <em>Der Kunde soll seinen Warenkorb vollst\u00e4ndig leeren k\u00f6nnen.<\/em><\/p>\n\n\n\n<p>Zun\u00e4chst wieder der prim\u00e4ren Port, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/in\/cart\/EmptyCartUseCase.java\" target=\"_blank\">EmptyCartUseCase<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.in.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">EmptyCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">emptyCart<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span><\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und dessen Implementierung, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/service\/cart\/EmptyCartService.java\" target=\"_blank\">EmptyCartService<\/a>. Wir leeren einen Warenkorb, indem wir ihn l\u00f6schen. Wenn der User ihn wieder abfragt, liefert der im vorherigen Abschnitt beschriebene <code>GetCartUseCase<\/code> einfach ein neues <code>Cart<\/code>-Objekt zur\u00fcck.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.service.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmptyCartService<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">EmptyCartUseCase<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> CartRepository cartRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">EmptyCartService<\/span><span class=\"hljs-params\">(CartRepository cartRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.cartRepository = cartRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">emptyCart<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    Objects.requireNonNull(customerId, <span class=\"hljs-string\">\"'customerId' must not be null\"<\/span>);\n\n    cartRepository.deleteById(customerId);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>F\u00fcr diesen Use Case f\u00fcgen wir dem sekund\u00e4ren Port <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/main\/java\/eu\/happycoders\/shop\/application\/port\/out\/persistence\/CartRepository.java\" target=\"_blank\" rel=\"noopener\">CartRepository<\/a> eine letzte Methode, <code>deleteById(\u2026)<\/code> hinzu; er sieht damit wie folgt aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.port.out.persistence;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">CartRepository<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">save<\/span><span class=\"hljs-params\">(Cart cart)<\/span><\/span>;\n\n  <span class=\"hljs-function\">Optional&lt;Cart&gt; <span class=\"hljs-title\">findByCustomerId<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span><\/span>;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">deleteById<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span><\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist das Application-Hexagon \u2013 und damit auch die Gesch\u00e4ftslogik \u2013 vollst\u00e4ndig implementiert. <\/p>\n\n\n\n<p>Noch ein Hinweis zur Sicherheit: In einer echten Anwendung w\u00fcrden wir die Kundennummer nat\u00fcrlich nicht einfach als Parameter \u00fcbergeben, sondern die Kunden sich authentifizieren lassen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"unit-tests-fuer-domain-services\">Unit-Tests f\u00fcr Domain Services<\/h3>\n\n\n\n<p>Auch bei den Domain Services im <em>application<\/em>-Modul bin ich testgetrieben vorgegangen. Ich werde hier aber, wie angek\u00fcndigt, nur einen beispielhaften Test abdrucken.<\/p>\n\n\n\n<p>Der Screenshot aus meiner IDE gibt einen \u00dcberblick \u00fcber alle Domain-Service-Tests, wie du sie im <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/application\/src\/test\/java\/eu\/happycoders\/shop\/application\/service\" target=\"_blank\">GitHub-Repository<\/a> finden wirst:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"288\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-400x288.png\" alt=\"Hexagonale Architektur mit Java - Application-Testklassen\" class=\"wp-image-36551\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-400x288.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-224x161.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-336x242.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-504x363.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-672x484.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes-600x432.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-application-test-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Die Ports, mit denen die Services kommunizieren, werden wir mocken. Daf\u00fcr ben\u00f6tigen wir eine Dependency zu Mockito in der <em>pom.xml<\/em> des Projektverzeichnisses:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.mockito<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>mockito-core<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>5.5.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Im <em>model<\/em>-Modul liegen einige Factory-Klassen, z. B. <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/product\/TestProductFactory.java\" target=\"_blank\">TestProductFactory<\/a> und <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/model\/src\/test\/java\/eu\/happycoders\/shop\/model\/money\/TestMoneyFactory.java\" target=\"_blank\">TestMoneyFactory<\/a>, um Test-Produkte zu erzeugen. Doch obwohl wir eine Dependency vom <em>application<\/em>-Modul auf das <em>model<\/em>-Modul definiert haben, k\u00f6nnen wir nicht ohne weiteres aus dem <em>application<\/em>-Modul auf die <em>Test<\/em>klassen des <em>model<\/em>-Moduls zugreifen.<\/p>\n\n\n\n<p>Diese m\u00fcssen wir zun\u00e4chst aus dem <em>model<\/em>-Modul exportieren, indem wir eine sogenannte \u201eAttached Test JAR\u201d erzeugen. Das machen wir mit folgendem Eintrag in der <em>pom.xml<\/em> des <em>model<\/em>-Moduls:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">build<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">plugins<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">plugin<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.apache.maven.plugins<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>maven-jar-plugin<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>3.3.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">executions<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">execution<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">goals<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">goal<\/span>&gt;<\/span>test-jar<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">goal<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">goals<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">execution<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">executions<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">plugin<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">plugins<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">build<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Als n\u00e4chstes m\u00fcssen wir die \u201eAttached Test JAR\u201d im <em>application<\/em>-Modul importieren. Dazu tragen wir folgende Dependency in die <em>pom.xml<\/em> des <em>application<\/em>-Moduls ein:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>eu.happycoders.shop<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>model<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>${project.version}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">classifier<\/span>&gt;<\/span>tests<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">classifier<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">type<\/span>&gt;<\/span>test-jar<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">type<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist alles vorbereitet f\u00fcr die eigentlichen Tests.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Test f\u00fcr Use Case 2: Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/h4>\n\n\n\n<p>In den Domainklassen-Tests habe ich gezeigt, wie wir die <code>Cart.addProduct(\u2026)<\/code>-Methode testen k\u00f6nnen. Passend dazu zeige ich dir hier, wie wir <code>AddToCartService.addToCart(\u2026)<\/code> testen k\u00f6nnen.<\/p>\n\n\n\n<p>Du findest im Folgenden einen auszugsweisen Abdruck der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/application\/src\/test\/java\/eu\/happycoders\/shop\/application\/service\/cart\/AddToCartServiceTest.java\" target=\"_blank\">AddToCartServiceTest<\/a>. Ich habe wieder nur diejenigen statischen Imports mit abgedruckt, die meine IDE nicht automatisch erkennen kann.<\/p>\n\n\n\n<p>Im ersten Code-Block erstellen wir eine Test-Kundennnummer und zwei Testprodukte mit Hilfe der aus dem <em>model<\/em>-Modul importieren Test-Factories. Kundennummer und Testprodukte speichern wir in statischen Variablen, um sie in allen Tests verwenden zu k\u00f6nnen.<\/p>\n\n\n\n<p>Im zweiten Code-Block erzeugen wir mit Mockito Test-Doubles f\u00fcr die sekund\u00e4ren Ports, <code>cartRepository<\/code> und <code>productRepository<\/code>. Diese injizieren wir \u00fcber den Konstruktor in den <code>AddToCartService<\/code>. Test-Doubles und Service sind <em>nicht<\/em> statisch, da diese f\u00fcr jeden Test frisch initialisiert sein sollen (JUnit legt f\u00fcr jeden Test eine neue Instanz der Testklasse an).<\/p>\n\n\n\n<p>In der mit <code>@BeforeEach<\/code> annotierten <code>initTestDoubles()<\/code>-Methode definieren wir via <code>Mockito.when(\u2026)<\/code>, dass die <code>findById(\u2026)<\/code>-Methode des sekund\u00e4re Ports <code>productRepository<\/code> die Testprodukte zur\u00fcckliefert.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.application.service.cart;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.model.money.TestMoneyFactory.euros;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.model.product.TestProductFactory.createTestProduct;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.assertj.core.api.Assertions.assertThat;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.mockito.Mockito.mock;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.mockito.Mockito.verify;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.mockito.Mockito.when;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AddToCartServiceTest<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> CustomerId TEST_CUSTOMER_ID = <span class=\"hljs-keyword\">new<\/span> CustomerId(<span class=\"hljs-number\">61157<\/span>);\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Product TEST_PRODUCT_1 = createTestProduct(euros(<span class=\"hljs-number\">19<\/span>, <span class=\"hljs-number\">99<\/span>));\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Product TEST_PRODUCT_2 = createTestProduct(euros(<span class=\"hljs-number\">25<\/span>, <span class=\"hljs-number\">99<\/span>));\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> CartRepository cartRepository = mock(CartRepository<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> ProductRepository productRepository = mock(ProductRepository<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> AddToCartService addToCartService =\n      <span class=\"hljs-keyword\">new<\/span> AddToCartService(cartRepository, productRepository);\n\n  <span class=\"hljs-meta\">@BeforeEach<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">initTestDoubles<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    when(productRepository.findById(TEST_PRODUCT_1.id()))\n        .thenReturn(Optional.of(TEST_PRODUCT_1));\n\n    when(productRepository.findById(TEST_PRODUCT_2.id()))\n        .thenReturn(Optional.of(TEST_PRODUCT_2));\n  }\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenExistingCart_addToCart_cartWithAddedProductIsSavedAndReturned<\/span><span class=\"hljs-params\">()<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> NotEnoughItemsInStockException, ProductNotFoundException <\/span>{\n    <span class=\"hljs-comment\">\/\/ Arrange<\/span>\n    Cart persistedCart = <span class=\"hljs-keyword\">new<\/span> Cart(TEST_CUSTOMER_ID);\n    persistedCart.addProduct(TEST_PRODUCT_1, <span class=\"hljs-number\">1<\/span>);\n\n    when(cartRepository.findByCustomerId(TEST_CUSTOMER_ID))\n        .thenReturn(Optional.of(persistedCart));\n\n    <span class=\"hljs-comment\">\/\/ Act<\/span>\n    Cart cart = addToCartService.addToCart(TEST_CUSTOMER_ID, TEST_PRODUCT_2.id(), <span class=\"hljs-number\">3<\/span>);\n\n    <span class=\"hljs-comment\">\/\/ Assert<\/span>\n    verify(cartRepository).save(cart);\n\n    assertThat(cart.lineItems()).hasSize(<span class=\"hljs-number\">2<\/span>);\n    assertThat(cart.lineItems().get(<span class=\"hljs-number\">0<\/span>).product()).isEqualTo(TEST_PRODUCT_1);\n    assertThat(cart.lineItems().get(<span class=\"hljs-number\">0<\/span>).quantity()).isEqualTo(<span class=\"hljs-number\">1<\/span>);\n    assertThat(cart.lineItems().get(<span class=\"hljs-number\">1<\/span>).product()).isEqualTo(TEST_PRODUCT_2);\n    assertThat(cart.lineItems().get(<span class=\"hljs-number\">1<\/span>).quantity()).isEqualTo(<span class=\"hljs-number\">3<\/span>);\n  }\n\n  <span class=\"hljs-comment\">\/\/ more tests<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In der eigentlichen Testmethode legen wir zun\u00e4chst einen Warenkorb an und f\u00fcgen diesem das erste Produkt hinzu. Mittels <code>Mockito.when(\u2026)<\/code> legen wir fest, dass <code>cartRepository.findByCustomerId(TEST_CUSTOMER_ID)<\/code> genau diesen Warenkorb zur\u00fcckliefern soll.<\/p>\n\n\n\n<p>Dann rufen wir die zu testende Methode <code>AddToCartService.addToCart(\u2026)<\/code> auf, um dem Warenkorb ein zweites Produkt hinzuzuf\u00fcgen. Diese Methode liefert den aktualisierten Warenkorb zur\u00fcck.<\/p>\n\n\n\n<p>Schlie\u00dflich pr\u00fcfen wir, zum einen via <code>Mockito.verify(\u2026)<\/code>, dass der aktualisierte Warenkorb \u00fcber <code>CartRepository.save(\u2026)<\/code> persistiert wurde, und zum anderen, dass der Warenkorb beide Produkte in der erwarteten Menge enth\u00e4lt.<\/p>\n\n\n\n<p>Dieser Test zeigt eindrucksvoll, wie wir die Gesch\u00e4ftslogik der Anwendung ganz ohne REST-Controller und Datenbank testen k\u00f6nnen.<\/p>\n\n\n\n<p>Du findest alle weiteren Tests des <em>application<\/em>-Moduls im <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/application\/src\/test\/java\/eu\/happycoders\/shop\/application\/service\" target=\"_blank\">GitHub-Repository<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-der-adapter\">Implementierung der Adapter<\/h2>\n\n\n\n<p>Lauff\u00e4hig ist unsere Anwendung noch nicht. Wir brauchen noch Adapter:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>REST-Adapter, um die Use Cases aufzurufen, und <\/li>\n\n\n\n<li>Persistenz-Adapter, um Warenk\u00f6rbe und Produkte zu speichern.<\/li>\n<\/ul>\n\n\n\n<p>Wir erstellen f\u00fcr alle prim\u00e4ren Ports (Use Cases) je einen REST-Adapter (obere Reihe im folgenden Klassendiagramm) und f\u00fcr alle sekund\u00e4ren Ports (Repositories) je einen In-Memory-Persistenz-Adapter (untere Reihe):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"776\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-800x776.png\" alt=\"Hexagonale Architektur mit Java - Ports, Services und Adapter der Beispielanwendung\" class=\"wp-image-36531\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-800x776.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-224x217.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-336x326.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-504x489.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-672x652.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-400x388.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-600x582.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-944x916.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3-1200x1164.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters.v3.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Ports, Services und Adapter der Beispielanwendung<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Technisch notwendig ist diese Eins-zu-Eins-Zuordnung nicht. Wir k\u00f6nnten auch z. B. einen Cart-REST-Adapter, einen Product-REST-Adapter und einen einzigen Persistenz-Adapter anlegen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"776\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-800x776.png\" alt=\"Hexagonale Architektur mit Java - Alternative Adapter\" class=\"wp-image-36532\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-800x776.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-224x217.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-336x326.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-504x489.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-672x652.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-400x388.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-600x582.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-944x916.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2-1200x1164.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-ports-services-adapters-alternative.v2.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Alternative Adapter<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Das w\u00fcrde allerdings das Single-Responsibility-Prinzip verletzen. Bei zwei oder drei implementierten Methoden pro Klasse ist das noch \u00fcbersichtlich. Aber ich habe schon gesehen, wie daraus schnell zehn oder sogar hundert Methoden pro Klasse werden.<\/p>\n\n\n\n<p>Der folgende Screenshot zeigt alle Pakete des <em>adapter<\/em>-Moduls in der IDE. Wie du siehst, sind es noch einige Klassen mehr als die im Klassendiagramm dargestellten. Ich werde sie alle in den n\u00e4chsten Abschnitten beschreiben.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"555\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-400x555.png\" alt=\"Hexagonale Architektur mit Java - Adapter-Klassen\" class=\"wp-image-36512\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-400x555.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-224x311.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-336x466.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-504x699.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-672x932.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2-600x833.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-adapter-classes.v2.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Beginnen wir mit der Implementierung der REST-Adapter...<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-der-rest-adapter\">Implementierung der REST-Adapter<\/h3>\n\n\n\n<p>Bisher haben wir reinen Java-Code geschrieben. Wenn wir von Lombok absehen (was die Arbeit vereinfacht hat, aber nicht zwingend notwendig war) und den Test-Libraries, haben wir noch keine zus\u00e4tzliche Library verwendet. Das wird sich jetzt \u00e4ndern, denn einen REST-Adapter wollen wir nicht ohne Hilfe implementieren.<\/p>\n\n\n\n<p>Wir verwenden daf\u00fcr den Standard \u201eJakarta RESTful Web Services\u201d (vor der \u00dcbergabe von Java EE an die Eclipse Foundation bekannt als \u201eJAX-RS\u201d). Als Implementierung dieses Standards verwenden wir das weit verbreitete RESTEasy aus dem Hause JBoss bzw. Red Hat.<\/p>\n\n\n\n<p>Wir tragen dazu folgende Abh\u00e4ngigkeiten in die <em>pom.xml<\/em>-Datei des <em>adapter<\/em>-Moduls ein:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>eu.happycoders.shop<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>application<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>${project.version}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>jakarta.ws.rs<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>jakarta.ws.rs-api<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>3.1.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Beachte, dass hier lediglich eine Abh\u00e4ngigkeit auf die \u201eJakarta RESTful Web Services\u201d-<em>API<\/em> ben\u00f6tigt wird \u2013 keine auf die RESTEasy-Implementierung dieser API! Diese ben\u00f6tigen wir erst sp\u00e4ter f\u00fcr Integration-Tests sowie im <em>bootstrap<\/em>-Modul, um die Anwendung zu starten.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">REST-Adapter 1: Suche nach Produkten<\/h4>\n\n\n\n<p>Den REST-Adapter f\u00fcr die Produktsuche implementieren wir im <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/product\/FindProductsController.java\" target=\"_blank\">FindProductsController<\/a>. Da dieser Controller den <code>FindProductsUseCase<\/code> (also den prim\u00e4ren Port) aufrufen muss, injizieren wir diesen \u00fcber den Konstruktor.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.product;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.common.ControllerCommons.clientErrorException;\n<span class=\"hljs-comment\">\/\/ ... more imports ... <\/span>\n\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/products\"<\/span>)\n<span class=\"hljs-meta\">@Produces<\/span>(MediaType.APPLICATION_JSON)\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">FindProductsController<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> FindProductsUseCase findProductsUseCase;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">FindProductsController<\/span><span class=\"hljs-params\">(FindProductsUseCase findProductsUseCase)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.findProductsUseCase = findProductsUseCase;\n  }\n\n  <span class=\"hljs-meta\">@GET<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;ProductInListWebModel&gt; <span class=\"hljs-title\">findProducts<\/span><span class=\"hljs-params\">(@QueryParam(<span class=\"hljs-string\">\"query\"<\/span>)<\/span> String query) <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (query == <span class=\"hljs-keyword\">null<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(Response.Status.BAD_REQUEST, <span class=\"hljs-string\">\"Missing 'query'\"<\/span>);\n    }\n\n    List&lt;Product&gt; products;\n\n    <span class=\"hljs-keyword\">try<\/span> {\n      products = findProductsUseCase.findByNameOrDescription(query);\n    } <span class=\"hljs-keyword\">catch<\/span> (IllegalArgumentException e) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(Response.Status.BAD_REQUEST, <span class=\"hljs-string\">\"Invalid 'query'\"<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> products.stream().map(ProductInListWebModel::fromDomainModel).toList();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In der <code>findProducts(\u2026)<\/code>-Methode laden wir die Produkte \u00fcber die <code>findByNameOrDescription(\u2026)<\/code>-Methode des <code>FindProductsUseCase<\/code>-Ports. Diese Methode wirft eine <code>IllegalArgumentException<\/code>, wenn der Suchbegriff zu kurz ist. <\/p>\n\n\n\n<p>Die Exception fangen wir ab und werfen stattdessen eine <code>ClientErrorException<\/code>, die wir \u00fcber die in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ControllerCommons.java\" target=\"_blank\" rel=\"noopener\">ControllerCommons<\/a> implementierte Hilfsmethode <code>clientErrorException(\u2026)<\/code> erzeugen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.common;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ControllerCommons<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-title\">ControllerCommons<\/span><span class=\"hljs-params\">()<\/span> <\/span>{}\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> ClientErrorException <span class=\"hljs-title\">clientErrorException<\/span><span class=\"hljs-params\">(\n      Response.Status status, String message)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ClientErrorException(errorResponse(status, message));\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> Response <span class=\"hljs-title\">errorResponse<\/span><span class=\"hljs-params\">(Response.Status status, String message)<\/span> <\/span>{\n    ErrorEntity errorEntity = <span class=\"hljs-keyword\">new<\/span> ErrorEntity(status.getStatusCode(), message);\n    <span class=\"hljs-keyword\">return<\/span> Response.status(status).entity(errorEntity).build();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>ClientErrorException<\/code> ist in der \u201eJakarta RESTful Web Services\u201d-API definiert. Wenn eine Controller-Methode diese Exception wirft, gibt der Controller einen HTTP-Fehlercode und die der Exception \u00fcbergebenen <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ErrorEntity.java\" target=\"_blank\">ErrorEntity<\/a> als JSON-String zur\u00fcck.<\/p>\n\n\n\n<p><code>ErrorEntity<\/code> ist ein simpler Record:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.common;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">ErrorEntity<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> httpStatus, String errorMessage)<\/span> <\/span>{}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die Fehlermeldung w\u00fcrde sp\u00e4ter bei einem ung\u00fcltigen Aufruf via <code>curl<\/code> z. B. so aussehen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"Shell Session\" data-shcb-language-slug=\"shell\"><span><code class=\"hljs language-shell\"><span class=\"hljs-meta\">$<\/span><span class=\"bash\"> curl http:\/\/localhost:8081\/products?query=x -i<\/span>\nHTTP\/1.1 400 Bad Request\n&#091;...]\n\n{\"httpStatus\":400,\"errorMessage\":\"Invalid 'query'\"}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Shell Session<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">shell<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Kommen wir noch einmal zur\u00fcck zur <code>findProducts(\u2026)<\/code>-Methode im <code>FindProductsController<\/code>. Sollte keine <code>IllegalArgumentException<\/code> aufgetreten zu sein, wird die folgende, letzte Zeile der Methode ausgef\u00fchrt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">return<\/span> products.stream().map(ProductInListWebModel::fromDomainModel).toList();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>FindProductsUseCase.findByNameOrDescription(\u2026)<\/code>-Methode hat eine Liste von <code>Product<\/code>-Entities zur\u00fcckgeliefert. Diese sind im <em>model<\/em>-Modul definiert und enthalten alle Informationen eines Produkts, einschlie\u00dflich derer, die wir dem Benutzer gar nicht anzeigen wollen.<\/p>\n\n\n\n<p>Wir mappen daher (s. <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur\/#mapping\">Abschnitt \u201eMapping\u201d im ersten Teil der Serie<\/a>) die Domain-Modellklasse <code>Product<\/code> auf eine Adapter-spezifische Modellklasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/product\/ProductInListWebModel.java\" target=\"_blank\">ProductInListWebModel<\/a>. Diese ist als Record implementiert und enth\u00e4lt die statische Factory-Methode <code>fromDomainModel(\u2026)<\/code>, die wir oben als Methodenreferenz an die <code>Stream.map(\u2026)<\/code>-Methode \u00fcbergeben haben.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.product;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">ProductInListWebModel<\/span><span class=\"hljs-params\">(\n    String id, String name, Money price, <span class=\"hljs-keyword\">int<\/span> itemsInStock)<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> ProductInListWebModel <span class=\"hljs-title\">fromDomainModel<\/span><span class=\"hljs-params\">(Product product)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductInListWebModel(\n        product.id().value(), product.name(), product.price(), product.itemsInStock());\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Damit ist unser Controller fertig implementiert. Den Rest erledigen sp\u00e4ter RESTEasy und der Undertow-Webserver. Wir werden, wenn die Anwendung fertig ist, den Controller \u00fcber folgende URL aufrufen k\u00f6nnen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">http:&#47;&#47;localhost:8081\/products\/?query=monitor<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>... und daraufhin folgende Antwort erhalten:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"JSON \/ JSON mit Kommentaren\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">&#091;\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-string\">\"K3SR7PBX\"<\/span>,\n    <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"27-Inch Curved Computer Monitor\"<\/span>,\n    <span class=\"hljs-attr\">\"price\"<\/span>: {\n      <span class=\"hljs-attr\">\"currency\"<\/span>: <span class=\"hljs-string\">\"EUR\"<\/span>,\n      <span class=\"hljs-attr\">\"amount\"<\/span>: <span class=\"hljs-number\">159.99<\/span>\n    },\n    <span class=\"hljs-attr\">\"itemsInStock\"<\/span>: <span class=\"hljs-number\">24081<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-string\">\"Q3W43CNC\"<\/span>,\n    <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"Dual Monitor Desk Mount\"<\/span>,\n    <span class=\"hljs-attr\">\"price\"<\/span>: {\n      <span class=\"hljs-attr\">\"currency\"<\/span>: <span class=\"hljs-string\">\"EUR\"<\/span>,\n      <span class=\"hljs-attr\">\"amount\"<\/span>: <span class=\"hljs-number\">119.9<\/span>\n    },\n    <span class=\"hljs-attr\">\"itemsInStock\"<\/span>: <span class=\"hljs-number\">1079<\/span>\n  }\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON mit Kommentaren<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Unser erster REST-Adapter ist fertig.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">REST-Adapter 2: Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/h4>\n\n\n\n<p>Nach dem gleichen Muster erstellen wir den REST-Adapter, um dem Warenkorb ein Produkt hinzuzuf\u00fcgen (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/AddToCartController.java\" target=\"_blank\" rel=\"noopener\">AddToCartController<\/a>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.common.ControllerCommons.clientErrorException;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.common.CustomerIdParser.parseCustomerId;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.common.ProductIdParser.parseProductId;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\n<span class=\"hljs-meta\">@Produces<\/span>(MediaType.APPLICATION_JSON)\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AddToCartController<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> AddToCartUseCase addToCartUseCase;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">AddToCartController<\/span><span class=\"hljs-params\">(AddToCartUseCase addToCartUseCase)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.addToCartUseCase = addToCartUseCase;\n  }\n\n  <span class=\"hljs-meta\">@POST<\/span>\n  <span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/{customerId}\/line-items\"<\/span>)\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> CartWebModel <span class=\"hljs-title\">addLineItem<\/span><span class=\"hljs-params\">(\n      @PathParam(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString,\n      @<span class=\"hljs-title\">QueryParam<\/span><span class=\"hljs-params\">(<span class=\"hljs-string\">\"productId\"<\/span>)<\/span> String productIdString,\n      @<span class=\"hljs-title\">QueryParam<\/span><span class=\"hljs-params\">(<span class=\"hljs-string\">\"quantity\"<\/span>)<\/span> <span class=\"hljs-keyword\">int<\/span> quantity) <\/span>{\n    CustomerId customerId = parseCustomerId(customerIdString);\n    ProductId productId = parseProductId(productIdString);\n\n    <span class=\"hljs-keyword\">try<\/span> {\n      Cart cart = addToCartUseCase.addToCart(customerId, productId, quantity);\n      <span class=\"hljs-keyword\">return<\/span> CartWebModel.fromDomainModel(cart);\n    } <span class=\"hljs-keyword\">catch<\/span> (ProductNotFoundException e) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(\n          Response.Status.BAD_REQUEST, <span class=\"hljs-string\">\"The requested product does not exist\"<\/span>);\n    } <span class=\"hljs-keyword\">catch<\/span> (NotEnoughItemsInStockException e) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(\n          Response.Status.BAD_REQUEST, \n          <span class=\"hljs-string\">\"Only %d items in stock\"<\/span>.formatted(e.itemsInStock()));\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Wir parsen zun\u00e4chst Kunden- und Produktnummer \u00fcber zwei Hilfsmethoden der Klassen <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/CustomerIdParser.java\" target=\"_blank\">CustomerIdParser<\/a> und <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ProductIdParser.java\" target=\"_blank\">ProductIdParser<\/a> (hier nicht abgedruckt). Beide werfen bei ung\u00fcltiger Eingabe die bereits oben besprochene <code>ClientErrorException<\/code>, die dann von RESTEasy in einen entsprechenden HTTP-Fehler umgewandelt wird.<\/p>\n\n\n\n<p>Wir rufen dann die Use-CaseMethode <code>AddToCartUseCase.addToCart(\u2026)<\/code> auf. Diese gibt ein Objekt der Domainmodell-Klasse <code>Cart<\/code> zur\u00fcck, welche wir \u00fcber die statische Factory-Methode <code>CartWebModel.fromDomainModel(\u2026)<\/code> in die Adapter-spezifische Modellklasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/CartWebModel.java\" target=\"_blank\">CartWebModel<\/a> mappen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">CartWebModel<\/span><span class=\"hljs-params\">(\n    List&lt;CartLineItemWebModel&gt; lineItems, <span class=\"hljs-keyword\">int<\/span> numberOfItems, Money subTotal)<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">static<\/span> CartWebModel <span class=\"hljs-title\">fromDomainModel<\/span><span class=\"hljs-params\">(Cart cart)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> CartWebModel(\n        cart.lineItems().stream().map(CartLineItemWebModel::fromDomainModel).toList(),\n        cart.numberOfItems(),\n        cart.subTotal());\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>CartWebModel<\/code> ist ein Record, der neben der Gesamtanzahl der Produkte und des Gesamtpreises eine Liste von <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/CartLineItemWebModel.java\" target=\"_blank\">CartLineItemWebModel<\/a>-Objekten enth\u00e4lt. Diese werden \u00fcber die statische Factory-Methode <code>CartLineItemWebModel.fromDomainModel(\u2026)<\/code> aus den <code>CartLineItem<\/code>-Eintr\u00e4gen des <code>Cart<\/code>-Modells erzeugt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> record <span class=\"hljs-title\">CartLineItemWebModel<\/span><span class=\"hljs-params\">(\n    String productId, String productName, Money price, <span class=\"hljs-keyword\">int<\/span> quantity)<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> CartLineItemWebModel <span class=\"hljs-title\">fromDomainModel<\/span><span class=\"hljs-params\">(CartLineItem lineItem)<\/span> <\/span>{\n    Product product = lineItem.product();\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> CartLineItemWebModel(\n        product.id().value(), product.name(), product.price(), lineItem.quantity());\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier sehen wir, dass das Web-Modell auch ganz anders aussehen kann als das Domain-Modell: W\u00e4hrend <code>CartLineItem<\/code> eine Referenz auf ein <code>Product<\/code> enth\u00e4lt, speichert <code>CartLineItemWebModel<\/code> die relevanten Produktdaten (ID, Name, Preis) direkt.<\/p>\n\n\n\n<p>Sollte der Port-Aufruf <code>addToCartUseCase.addToCart(\u2026)<\/code> eine <code>ProductNotFoundException<\/code> oder eine <code>NotEnoughItemsInStockException<\/code> werfen, werden diese in eine entsprechende <code>ClientErrorException<\/code> umgewandelt.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">REST-Adapter 3: Abrufen des Warenkorbs<\/h4>\n\n\n\n<p>Der dritte REST-Adapter, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/GetCartController.java\" target=\"_blank\" rel=\"noopener\">GetCartController<\/a> gestaltet sich recht einfach, da wir bereits alle ben\u00f6tigten Bausteine haben:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\n<span class=\"hljs-meta\">@Produces<\/span>(MediaType.APPLICATION_JSON)\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GetCartController<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> GetCartUseCase getCartUseCase;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">GetCartController<\/span><span class=\"hljs-params\">(GetCartUseCase getCartUseCase)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.getCartUseCase = getCartUseCase;\n  }\n\n  <span class=\"hljs-meta\">@GET<\/span>\n  <span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/{customerId}\"<\/span>)\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> CartWebModel <span class=\"hljs-title\">getCart<\/span><span class=\"hljs-params\">(@PathParam(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString) <\/span>{\n    CustomerId customerId = CustomerIdParser.parseCustomerId(customerIdString);\n    Cart cart = getCartUseCase.getCart(customerId);\n    <span class=\"hljs-keyword\">return<\/span> CartWebModel.fromDomainModel(cart);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">REST-Adapter 4: Leeren des Warenkorbs<\/h4>\n\n\n\n<p>Das gleiche gilt f\u00fcr den vierten und letzten REST-Adapter, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/EmptyCartController.java\" target=\"_blank\" rel=\"noopener\">EmptyCartController<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\n<span class=\"hljs-meta\">@Produces<\/span>(MediaType.APPLICATION_JSON)\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmptyCartController<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> EmptyCartUseCase emptyCartUseCase;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">EmptyCartController<\/span><span class=\"hljs-params\">(EmptyCartUseCase emptyCartUseCase)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.emptyCartUseCase = emptyCartUseCase;\n  }\n\n  <span class=\"hljs-meta\">@DELETE<\/span>\n  <span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/{customerId}\"<\/span>)\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">deleteCart<\/span><span class=\"hljs-params\">(@PathParam(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString) <\/span>{\n    CustomerId customerId = CustomerIdParser.parseCustomerId(customerIdString);\n    emptyCartUseCase.emptyCart(customerId);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Da die <code>deleteCart(\u2026)<\/code>-Methode keinen R\u00fcckgabewert hat, brauchen wir hier auch keinen Modell-Konverter aufzurufen, sodass die Methode sogar nur zwei statt drei Zeilen lang ist.<\/p>\n\n\n\n<p>Damit haben wir alle vier REST-Adapter implementiert. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"integration-tests-fuer-rest-adapter\">Integration-Tests f\u00fcr REST-Adapter<\/h3>\n\n\n\n<p>Wir k\u00f6nnten die REST-Adapter wieder mit Unit-Tests testen \u2013 also z. B. einen <code>AddToCartController<\/code> mit einem gemockten <code>AddToCartUseCase<\/code> instanziieren und dann pr\u00fcfen, ob ein Aufruf der <code>addLineItem(\u2026)<\/code> korrekt an <code>addToCartUseCase.addToCart(\u2026)<\/code> \u00fcbergeben wird.<\/p>\n\n\n\n<p>Dabei w\u00fcrden wir uns dann allerdings darauf verlassen m\u00fcssen, dass alle Annotationen korrekt sind, dass wir sp\u00e4ter RESTEasy und Untertow korrekt konfigurieren und auch, dass eine Library f\u00fcr JSON-Serialisierung auf dem Classpath liegt. Sich auf solche Tests zu verlassen, ist \u00e4u\u00dferst riskant.<\/p>\n\n\n\n<p>Ich w\u00fcrde gerne sicherstellen, dass die Adapter <em>wirklich<\/em> funktionieren; und das geht nur mit einem Integration-Test, bei dem wir einen Webserver starten und die Controller \u00fcber HTTP aufrufen.<\/p>\n\n\n\n<p>Daf\u00fcr brauchen wir ein paar zus\u00e4tzliche Dependencies:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>resteasy-undertow<\/code> \u2013 der Undertow-Webserver kombiniert mit der RESTEasy-Library<\/li>\n\n\n\n<li><code>resteasy-jackson2-provider<\/code> \u2013 ein RESTEasy-Modul zur Umwandlung von Java-Objekten in JSON (und umgekehrt)<\/li>\n\n\n\n<li><code>rest-assured<\/code> \u2013 eine Library, mit der wir aus Integration-Tests heraus HTTP-Aufrufe ausf\u00fchren k\u00f6nnen<\/li>\n<\/ul>\n\n\n\n<p>Wir machen dazu folgende Eintr\u00e4ge in der <em>pom.xml<\/em>-Datei des <em>adapter<\/em>-Moduls (falls du dich \u00fcber die umgedrehte Reihenfolge wunderst \u2013 ich ordne die Dependencies gerne alphabetisch sortiert an):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>io.rest-assured<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>rest-assured<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>5.3.2<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.jboss.resteasy<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>resteasy-jackson2-provider<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>6.2.5.Final<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.jboss.resteasy<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>resteasy-undertow<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>6.2.5.Final<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-72240f68 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Dar\u00fcber, dass ich die RESTEasy-Libraries im <em>test<\/em>-Scope importiere, l\u00e4sst sich streiten. Technisch reichen sie uns hier im <em>test<\/em>-Scope. Allerdings brauchen wir sie sp\u00e4testens im <em>bootstrap<\/em>-Modul im <em>compile-<\/em> bzw. <em>runtime<\/em>-Scope. Wir k\u00f6nnten den Scope daher auch schon hier auf <em>compile<\/em> (f\u00fcr <em>resteasy-undertow<\/em>) und auf <em>runtime<\/em> (f\u00fcr <em>resteasy-jackson2-provider<\/em>) setzen und uns damit einen weiteren Import im <em>bootstrap<\/em>-Modul sparen.<\/p><\/div><\/div>\n\n\n\n<p>Wir brauchen, wie im <em>application<\/em>-Modul, auch hier wieder eine Dependency auf das \u201eAttached Test JAR\u201d des <em>model<\/em>-Moduls, um die dort implementierten Test-Factories wie z. B. <code>TestProductFactory.createTestProduct(\u2026)<\/code> verwenden zu k\u00f6nnen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>eu.happycoders.shop<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>model<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>${project.version}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">classifier<\/span>&gt;<\/span>tests<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">classifier<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">type<\/span>&gt;<\/span>test-jar<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">type<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier ein Screenshot aus meiner IDE mit einer \u00dcbersicht aller REST-Adapter-Integration-Tests und zugeh\u00f6riger Hilfsklassen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"358\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-400x358.png\" alt=\"Hexagonale Architektur mit Java - REST-Adapter-Testklassen\" class=\"wp-image-36604\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-400x358.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-224x200.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-336x301.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-504x451.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-672x601.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes-600x537.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-rest-adapter-test-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Im n\u00e4chsten Abschnitt findest du einen beispielhaften REST-Adapter-Test. Alle weitere Tests findest du <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\" target=\"_blank\">hier im GitHub-Repository<\/a>. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Integration-Test f\u00fcr REST-Adapter 2: Hinzuf\u00fcgen eines Produkts zum Warenkorb<\/h4>\n\n\n\n<p>Vielleicht ist es dir im letzten Screenshot schon aufgefallen: Es gibt nur einen <code>CartsControllerTest<\/code> \u2013 keine separaten Tests f\u00fcr jeden Use Case. Das hat einen praktischen Grund: Das Hochfahren des Undertow-Servers dauert eine Weile. Wenn wir den Test auf drei Tests aufteilen w\u00fcrden, w\u00fcrde das Testen dreimal so lange dauern.<\/p>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-79bc04de uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Wenn wir die Anwendung sp\u00e4ter in ein Application Framework wie Spring oder Quarkus einbetten, k\u00f6nnen wir die Tests aufteilen, denn diese Frameworks machen es uns leicht, eine einmal gestartete Anwendung aus mehreren Tests aufzurufen.<\/p><\/div><\/div>\n\n\n\n<p>Im Folgenden siehst du einen Ausschnitt der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/CartsControllerTest.java\" target=\"_blank\">CartsControllerTest<\/a> f\u00fcr den Use Case \u201eHinzuf\u00fcgen eines Produkts zum Warenkorb\u201d.<\/p>\n\n\n\n<p>Die erste zwei Code-Bl\u00f6cke sollten dir aus dem vorherigen Test bekannt vorkommen: Im ersten legen wir Testdaten an, im zweiten die Test-Doubles.<\/p>\n\n\n\n<p>In der mit <code>@BeforeAll<\/code> annotierten <code>init()<\/code>-Methode konfigurieren wir unseren Server (die <code>TEST_PORT<\/code>-Konstante ist in der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/HttpTestCommons.java\" target=\"_blank\">HttpTestCommons<\/a> definiert), starten ihn und deployen unsere Test-Anwendung. Dazu \u00fcbergeben wir der <code>deploy(\u2026)<\/code>-Methode eine Instanz einer anonymen inneren Klasse. Diese erweitert <code>jakarta.ws.rs.core.Application<\/code> und \u00fcberschreibt deren <code>getSingletons()<\/code>-Methode, die ein Set aller REST-Controller zur\u00fcckliefert, denen wir die Test-Doubles f\u00fcr die Ports injizieren.<\/p>\n\n\n\n<p>Die mit <code>@AfterAll<\/code> annotierte <code>stop()<\/code>-Methode f\u00e4hrt den Server wieder herunter. <\/p>\n\n\n\n<p>Die eigentliche Testmethode beschreibe ich unter dem Quellcode-Auszug.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.in.rest.cart;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.HttpTestCommons.TEST_PORT;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.adapter.in.rest.cart.CartsControllerAssertions.assertThatResponseIsCart;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.model.money.TestMoneyFactory.euros;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> eu.happycoders.shop.model.product.TestProductFactory.createTestProduct;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> io.restassured.RestAssured.given;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.mockito.Mockito.mock;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.mockito.Mockito.when;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CartsControllerTest<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> CustomerId TEST_CUSTOMER_ID = <span class=\"hljs-keyword\">new<\/span> CustomerId(<span class=\"hljs-number\">61157<\/span>);\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> Product TEST_PRODUCT_1 = createTestProduct(euros(<span class=\"hljs-number\">19<\/span>, <span class=\"hljs-number\">99<\/span>));\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> AddToCartUseCase addToCartUseCase = mock(AddToCartUseCase<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> GetCartUseCase getCartUseCase = mock(GetCartUseCase<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> EmptyCartUseCase emptyCartUseCase = mock(EmptyCartUseCase<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> UndertowJaxrsServer server;\n\n  <span class=\"hljs-meta\">@BeforeAll<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">init<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    server =\n        <span class=\"hljs-keyword\">new<\/span> UndertowJaxrsServer()\n            .setPort(TEST_PORT)\n            .start()\n            .deploy(\n                <span class=\"hljs-keyword\">new<\/span> Application() {\n                  <span class=\"hljs-meta\">@Override<\/span>\n                  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Set&lt;Object&gt; <span class=\"hljs-title\">getSingletons<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n                    <span class=\"hljs-keyword\">return<\/span> Set.of(\n                        <span class=\"hljs-keyword\">new<\/span> AddToCartController(addToCartUseCase),\n                        <span class=\"hljs-keyword\">new<\/span> GetCartController(getCartUseCase),\n                        <span class=\"hljs-keyword\">new<\/span> EmptyCartController(emptyCartUseCase));\n                  }\n                });\n  }\n\n  <span class=\"hljs-meta\">@AfterAll<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">stop<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    server.stop();\n  }\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenSomeTestData_addLineItem_invokesAddToCartUseCaseAndReturnsUpdatedCart<\/span><span class=\"hljs-params\">()<\/span>\n      <span class=\"hljs-keyword\">throws<\/span> NotEnoughItemsInStockException, ProductNotFoundException <\/span>{\n    <span class=\"hljs-comment\">\/\/ Arrange<\/span>\n    CustomerId customerId = TEST_CUSTOMER_ID;\n    ProductId productId = TEST_PRODUCT_1.id();\n    <span class=\"hljs-keyword\">int<\/span> quantity = <span class=\"hljs-number\">5<\/span>;\n\n    Cart cart = <span class=\"hljs-keyword\">new<\/span> Cart(customerId);\n    cart.addProduct(TEST_PRODUCT_1, quantity);\n\n    when(addToCartUseCase.addToCart(customerId, productId, quantity)).thenReturn(cart);\n\n    <span class=\"hljs-comment\">\/\/ Act<\/span>\n    Response response =\n        given()\n            .port(TEST_PORT)\n            .queryParam(<span class=\"hljs-string\">\"productId\"<\/span>, productId.value())\n            .queryParam(<span class=\"hljs-string\">\"quantity\"<\/span>, quantity)\n            .post(<span class=\"hljs-string\">\"\/carts\/\"<\/span> + customerId.value() + <span class=\"hljs-string\">\"\/line-items\"<\/span>)\n            .then()\n            .extract()\n            .response();\n\n    <span class=\"hljs-comment\">\/\/ Assert<\/span>\n    assertThatResponseIsCart(response, cart);\n  }\n\n  <span class=\"hljs-comment\">\/\/ more tests<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In der Testmethode legen wir einen Warenkorb mit einem Produkt an und definieren \u00fcber <code>Mockito.when(\u2026)<\/code>, dass die <code>AddToCartUseCase.addToCart(\u2026)<\/code>-Methode diesen Warenkorb zur\u00fcckliefern soll, wenn sie mit der entsprechenden Kundennummer, Produkt-ID und Anzahl aufgerufen wird.<\/p>\n\n\n\n<p>Danach benutzen wir die Library REST Assured, um einen echten HTTP-Aufruf mit eben diesen Parametern an den Server zu schicken.<\/p>\n\n\n\n<p>Mit <code>assertThatResponseIsCart(\u2026)<\/code> pr\u00fcfen wir, ob der HTTP-Aufruf den erwarteten Warenkorb im JSON-Format zur\u00fcckgeliefert hat. Diese Methode ist in der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/CartsControllerAssertions.java\" target=\"_blank\">CartsControllerAssertions<\/a> definiert (hier nicht abgedruckt).<\/p>\n\n\n\n<p>Alle weiteren Tests der REST-Adapter sehen \u00e4hnlich aus. Du findest sie <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\" target=\"_blank\">hier im GitHub-Repository<\/a>.<\/p>\n\n\n\n<p>Um unsere Anwendung endlich in Betrieb nehmen zu k\u00f6nnen, fehlen noch die Persistenz-Adapter. Diese implementieren wir als n\u00e4chstes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-der-persistence-adapter-mit-einem-in-memory-store\">Implementierung der Persistence-Adapter mit einem In-Memory-Store<\/h3>\n\n\n\n<p>Um ein erstes Gef\u00fchl f\u00fcr die laufende Anwendung zu bekommen, ist es nicht n\u00f6tig, dass wir die Daten in einer Datenbank persistieren. Wir k\u00f6nnen es uns f\u00fcrs Erste einfach machen und sie zun\u00e4chst im Arbeitsspeicher halten. Wir nutzen hier einer der gro\u00dfen Vorteile der hexagonalen Architektur: Die Entscheidung \u00fcber technische Belange, wie z. B. die Datenbank, l\u00e4sst sich aufschieben, und sie lassen sich sp\u00e4ter einfach austauschen (wie du noch sehen wirst).<\/p>\n\n\n\n<p>Wir beginnen mit der Implementierung des sekund\u00e4ren Ports <code>CartRepository<\/code> durch die Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryCartRepository.java\" target=\"_blank\" rel=\"noopener\">InMemoryCartRepository<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.out.persistence.inmemory;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryCartRepository<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">CartRepository<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Map&lt;CustomerId, Cart&gt; carts = <span class=\"hljs-keyword\">new<\/span> ConcurrentHashMap&lt;&gt;();\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">save<\/span><span class=\"hljs-params\">(Cart cart)<\/span> <\/span>{\n    carts.put(cart.id(), cart);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Optional&lt;Cart&gt; <span class=\"hljs-title\">findByCustomerId<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> Optional.ofNullable(carts.get(customerId));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">deleteById<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    carts.remove(customerId);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>save(\u2026)<\/code>-Methode speichert den Warenkorb in einer <code>Map<\/code>, die <code>findByCustomerId(\u2026)<\/code>-Methode l\u00e4dt sie aus der <code>Map<\/code>, und die <code>deleteByCustomerId(\u2026)<\/code>-Methode l\u00f6scht den Eintrag aus der <code>Map<\/code>.<\/p>\n\n\n\n<p>Fast genauso einfach implementiert ist auch das <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryProductRepository.java\" target=\"_blank\">InMemoryProductRepository<\/a>. Um die Anwendung sinnvoll nutzen zu k\u00f6nnen, legen wir im Konstruktor des Repositories ein paar Demo-Produkte an. Dazu \u00fcbergen wir jedes in der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/DemoProducts.java\" target=\"_blank\">DemoProducts<\/a> gelistete Produkte an die <code>safe(\u2026)<\/code>-Methode.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.out.persistence.inmemory;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryProductRepository<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">ProductRepository<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Map&lt;ProductId, Product&gt; products = <span class=\"hljs-keyword\">new<\/span> ConcurrentHashMap&lt;&gt;();\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">InMemoryProductRepository<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    createDemoProducts();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">createDemoProducts<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    DemoProducts.DEMO_PRODUCTS.forEach(<span class=\"hljs-keyword\">this<\/span>::save);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">save<\/span><span class=\"hljs-params\">(Product product)<\/span> <\/span>{\n    products.put(product.id(), product);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Optional&lt;Product&gt; <span class=\"hljs-title\">findById<\/span><span class=\"hljs-params\">(ProductId productId)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> Optional.ofNullable(products.get(productId));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;Product&gt; <span class=\"hljs-title\">findByNameOrDescription<\/span><span class=\"hljs-params\">(String query)<\/span> <\/span>{\n    String queryLowerCase = query.toLowerCase(Locale.ROOT);\n    <span class=\"hljs-keyword\">return<\/span> products.values().stream()\n        .filter(product -&gt; matchesQuery(product, queryLowerCase))\n        .toList();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">boolean<\/span> <span class=\"hljs-title\">matchesQuery<\/span><span class=\"hljs-params\">(Product product, String query)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> product.name().toLowerCase(Locale.ROOT).contains(query)\n        || product.description().toLowerCase(Locale.ROOT).contains(query);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Neben dem Speichern und Laden eines Produkts ist das Repository auch f\u00fcr die Suche nach Produkten zust\u00e4ndig. Diese implementieren wir in der <code>findByNameOrDescription(\u2026)<\/code>, indem wir \u00fcber alle gespeicherten Produkte iterieren und pr\u00fcfen, ob deren Name oder Beschreibung den Suchbegriff enth\u00e4lt.<\/p>\n\n\n\n<p>Im n\u00e4chsten Teil der Artikelserie zeige ich dir, wie du den In-Memory-Adapter durch einen JPA-Adapter ersetzen kannst, der die Daten in einer MySQL-Datenbank speichert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"unit-tests-fuer-persistenz-adapter\">Unit-Tests f\u00fcr Persistenz-Adapter<\/h3>\n\n\n\n<p>Solange die Persistenz-Adapter noch nicht mit einem externen System wie einer Datenbank kommunizieren, k\u00f6nnen wir mit einfachen Unit-Tests arbeiten.<\/p>\n\n\n\n<p>Da wir jetzt schon wissen, dass wir sp\u00e4ter zus\u00e4tzliche JPA-Adapter implementieren werden \u2013 dann also die gleiche Funktionalit\u00e4t mit zwei verschiedenen Adaptern testen m\u00fcssen \u2013 schreibe ich die Tests je in einer abstrakten Klasse, die dann von einer konkreten Klasse erweitert wird, die den jeweils zu testenden Adapter erzeugt.<\/p>\n\n\n\n<p>Das h\u00f6rt sich komplizierter an, als es ist. Das folgende Klassendiagramm sollte zum Verst\u00e4ndnis beitragen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"428\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-800x428.png\" alt=\"Hexagonale Architektur mit Java - Persistenz-Adapter-Testklassen-Hierarchie\" class=\"wp-image-36659\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-800x428.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-224x120.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-336x180.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-504x270.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-672x360.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-400x214.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-600x321.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-944x505.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram-1200x642.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-class-diagram.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Persistenz-Adapter-Testklassen-Hierarchie<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Die abstrakte Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/AbstractProductRepositoryTest.java\" target=\"_blank\">AbstractProductRepositoryTest<\/a> enth\u00e4lt eine Instanz von <code>ProductRepository<\/code>, die \u00fcber die abstrakte Methode <code>createProductRepository()<\/code> instanziiert wird. Auf dieser Instanz werden alle Tests ausgef\u00fchrt.<\/p>\n\n\n\n<p>In den konkreten Klassen <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryProductRepositoryTest.java\" target=\"_blank\">InMemoryProductRepositoryTest<\/a> und, im n\u00e4chsten Teil der Serie, <code>JpaProductRepositoryTest<\/code>, wird die Methode <code>createProductRepository()<\/code> implementiert und liefert den konkreten zu testenden Adapter zur\u00fcck, also das <code>InMemoryProductRepository<\/code> bzw. das <code>JpaProductRepository<\/code>. Der <code>JpaProductRepositoryTest<\/code> wird dar\u00fcberhinaus noch eine echte MySQL-Datenbank starten und stoppen \u2013 dazu aber mehr im n\u00e4chsten Teil der Serie.<\/p>\n\n\n\n<p>Mit dieser Struktur k\u00f6nnen wir f\u00fcr beide Adapter-Implementierungen die gleichen Tests ausf\u00fchren, ohne alle Tests doppelt schreiben zu m\u00fcssen.<\/p>\n\n\n\n<p>Hier siehst du der Vollst\u00e4ndigkeit halber noch einen Screenshot der Test-Klassen in meiner IDE (ohne die JPA-Repository-Tests):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"311\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-400x311.png\" alt=\"Hexagonale Architektur mit Java - Persistenz-Adapter-Testklassen\" class=\"wp-image-36664\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-400x311.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-224x174.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-336x261.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-504x392.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-672x522.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes-600x467.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-persistence-adapter-test-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Im n\u00e4chsten Abschnitt zeige ich dir einen beispielhaften <code>ProductRepository<\/code>-Adapter-Test. Alle weitere Tests findest du <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\" target=\"_blank\" rel=\"noopener\">hier im GitHub-Repository<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Unit-Test f\u00fcr ProductRepository-Adapter<\/h4>\n\n\n\n<p>Der folgende Code zeigt einen Ausschnitt der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/AbstractProductRepositoryTest.java\" target=\"_blank\">AbstractProductRepositoryTest<\/a>-Klasse. Im oberen Teil siehst du die im vorherigen Abschnitt beschriebene Initialisierung des Repositories \u00fcber die abstrakte Methode <code>createProductRepository()<\/code>.<\/p>\n\n\n\n<p>Die eigentliche Testmethode ruft die <code>ProductRepository.findByNameOrDescription(\u2026)<\/code>-Methode auf und pr\u00fcft, ob die erwarteten Produkte, also die zum Suchbegriff passenden, im <code>InMemoryProductRepository<\/code>-Konstruktor angelegten Testprodukte, zur\u00fcckgeliefert werden.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.out.persistence;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.assertj.core.api.Assertions.assertThat;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">abstract<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span>&lt;<span class=\"hljs-title\">T<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">ProductRepository<\/span>&gt; <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> T productRepository;\n\n  <span class=\"hljs-meta\">@BeforeEach<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">initRepository<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    productRepository = createProductRepository();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected<\/span> <span class=\"hljs-keyword\">abstract<\/span> T <span class=\"hljs-title\">createProductRepository<\/span><span class=\"hljs-params\">()<\/span><\/span>;\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenTestProducts_findByNameOrDescription_returnsMatchingProducts<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    String query = <span class=\"hljs-string\">\"monitor\"<\/span>;\n\n    List&lt;Product&gt; products = productRepository.findByNameOrDescription(query);\n\n    assertThat(products)\n        .containsExactlyInAnyOrder(\n            DemoProducts.COMPUTER_MONITOR, DemoProducts.MONITOR_DESK_MOUNT);\n  }\n\n  <span class=\"hljs-comment\">\/\/ more tests<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Bei einer echten Anwendung, die nicht automatisch Demo-Produkte anlegt, w\u00fcrden wir entweder in der mit <code>@BeforeEach<\/code> annotierten <code>initRepository()<\/code>-Methode oder direkt in der Testmethode die Test-Produkte anlegen (je nachdem, ob wir sie f\u00fcr alle Tests oder nur f\u00fcr diesen einen Test ben\u00f6tigen).<\/p>\n\n\n\n<p>Die konkrete Testklasse, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryProductRepositoryTest.java\" target=\"_blank\" rel=\"noopener\">InMemoryProductRepositoryTest<\/a>, implementiert lediglich die <code>createProductRepository()<\/code>-Methode:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.adapter.out.persistence.inmemory;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryProductRepositoryTest<\/span>\n    <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span>&lt;<span class=\"hljs-title\">InMemoryProductRepository<\/span>&gt; <\/span>{\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected<\/span> InMemoryProductRepository <span class=\"hljs-title\">createProductRepository<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> InMemoryProductRepository();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Alle Persistenz-Adapter-Tests findest du <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\" target=\"_blank\" rel=\"noopener\">hier im GitHub-Repository<\/a>.<\/p>\n\n\n\n<p>OK, wir haben alle Komponenten f\u00fcr die Anwendung fertig: unser Modell, die Ports und Services, prim\u00e4re und sekund\u00e4re Adapter. Mit folgendem Terminal-Kommando k\u00f6nnen wir alles testen:<\/p>\n\n\n\n<p><code>mvn clean test<\/code><\/p>\n\n\n\n<p>Aber wie starten wir die Anwendung jetzt? Das erf\u00e4hrst du im n\u00e4chsten Kapitel.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"implementierung-des-bootstrap-moduls\">Implementierung des Bootstrap-Moduls<\/h2>\n\n\n\n<p>Ein bisschen Code m\u00fcssen wir noch schreiben, um die Anwendung endlich starten zu k\u00f6nnen. Aber nicht viel, versprochen! Der folgende Screenshot beweist es:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"152\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-400x152.png\" alt=\"Hexagonale Architektur mit Java - Bootstrap-Klassen\" class=\"wp-image-36683\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-400x152.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-224x85.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-336x128.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-504x192.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-672x255.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes-600x228.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/hexagonal-architecture-java-bootstrap-classes.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/figure>\n<\/div>\n\n\n<p>Zun\u00e4chst m\u00fcssen wir ein paar Dependencies definieren \u2013 zum einen zum <em>adapter<\/em>-Modul, zum anderen zu Undertow und dem RESTEasy-JSON-Modul (beide kennst du bereits aus den Adapter-Integration-Tests).<\/p>\n\n\n\n<p>Trage dazu Folgendes in die <em>pom.xml<\/em> des <em>bootstrap<\/em>-Moduls ein:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>eu.happycoders.shop<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>adapter<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>${project.version}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.jboss.resteasy<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>resteasy-undertow<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>6.2.5.Final<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.jboss.resteasy<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>resteasy-jackson2-provider<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>6.2.5.Final<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>runtime<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Als n\u00e4chstes m\u00fcssen wir \u2013 so wie im REST-Adapter-Integration-Test \u2013 die <code>Application<\/code>-Klasse erweitern. Statt einer anonymen inneren Klasse verwenden wir dieses Mal eine regul\u00e4re Klasse, da wir mehr Objekte initialisieren und verdrahten m\u00fcssen als in den Tests:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Startpunkt ist die \u00fcberschriebene <code>getSingletons()<\/code>-Methode. Diese ruft zun\u00e4chst <code>initPersistenceAdapters()<\/code> auf, um die In-Memory-Repositories zu initialisieren.<\/li>\n\n\n\n<li>Danach werden die vier Controller initialisiert und pro Controller jeweils ein Service.<\/li>\n\n\n\n<li>Die vier Controller werden dann in einem <code>Set<\/code> zur\u00fcckgegeben; den Rest erledigt der Undertow-Webserver.<\/li>\n<\/ul>\n\n\n\n<p>Du findest den Code in der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/without-jpa-adapters\/bootstrap\/src\/main\/java\/eu\/happycoders\/shop\/bootstrap\/RestEasyUndertowShopApplication.java\" target=\"_blank\">RestEasyUndertowShopApplication<\/a>-Klasse im <em>without-jpa-adapters<\/em>-Branch des GitHub-Repositories. (Falls du den <em>main<\/em>-Branch ausgecheckt hast, ist diese Klasse bereits um die Option zur Initialisierung der JPA-\/MySQL-Adapter erweitert.)<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-53\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.bootstrap;\n\n<span class=\"hljs-comment\">\/\/ ... imports ...<\/span>\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RestEasyUndertowShopApplication<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Application<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> CartRepository cartRepository;\n  <span class=\"hljs-keyword\">private<\/span> ProductRepository productRepository;\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Set&lt;Object&gt; <span class=\"hljs-title\">getSingletons<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    initPersistenceAdapters();\n\n    <span class=\"hljs-keyword\">return<\/span> Set.of(\n        addToCartController(),\n        getCartController(),\n        emptyCartController(),\n        findProductsController());\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">initPersistenceAdapters<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    cartRepository = <span class=\"hljs-keyword\">new<\/span> InMemoryCartRepository();\n    productRepository = <span class=\"hljs-keyword\">new<\/span> InMemoryProductRepository();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> AddToCartController <span class=\"hljs-title\">addToCartController<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    AddToCartUseCase addToCartUseCase =\n        <span class=\"hljs-keyword\">new<\/span> AddToCartService(cartRepository, productRepository);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> AddToCartController(addToCartUseCase);\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> GetCartController <span class=\"hljs-title\">getCartController<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    GetCartUseCase getCartUseCase = <span class=\"hljs-keyword\">new<\/span> GetCartService(cartRepository);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> GetCartController(getCartUseCase);\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> EmptyCartController <span class=\"hljs-title\">emptyCartController<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    EmptyCartUseCase emptyCartUseCase = <span class=\"hljs-keyword\">new<\/span> EmptyCartService(cartRepository);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> EmptyCartController(emptyCartUseCase);\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> FindProductsController <span class=\"hljs-title\">findProductsController<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    FindProductsUseCase findProductsUseCase = <span class=\"hljs-keyword\">new<\/span> FindProductsService(productRepository);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> FindProductsController(findProductsUseCase);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-53\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Als n\u00e4chstes brauchen wir noch eine <code>main()<\/code>-Methode, um die Anwendung und den Undertow-Server zu starten.<\/p>\n\n\n\n<p>Dieser Start-Code befindet sich in der <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/bootstrap\/src\/main\/java\/eu\/happycoders\/shop\/bootstrap\/Launcher.java\" target=\"_blank\">Launcher<\/a>-Klasse (im GitHub-Repository findest du noch zwei weitere Methoden, <code>startOnPort(\u2026)<\/code> und <code>stop()<\/code> \u2013 diese ben\u00f6tigen wir f\u00fcr die End-to-End-Tests).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-54\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.bootstrap;\n\n<span class=\"hljs-keyword\">import<\/span> org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Launcher<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> UndertowJaxrsServer server;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">main<\/span><span class=\"hljs-params\">(String&#091;] args)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">new<\/span> Launcher().startOnDefaultPort();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">startOnDefaultPort<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    server = <span class=\"hljs-keyword\">new<\/span> UndertowJaxrsServer();\n    startServer();\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">startServer<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    server.start();\n    server.deploy(RestEasyUndertowShopApplication<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-54\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und das war's \u2013 unsere Anwendung ist fertig!<\/p>\n\n\n\n<p>Du findest <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/main\/bootstrap\/src\/test\/java\/eu\/happycoders\/shop\/bootstrap\/e2e\" target=\"_blank\">hier im GitHub-Repository<\/a> noch einige End-to-End-Tests. Diese benutzen die <code>Launcher<\/code>-Klasse, um die Anwendung zu starten und senden dann \u2013 wie bereits bei den REST-Adapter-Tests gezeigt, HTTP-Aufrufe an die REST-Controller und verifizieren die Antwort.<\/p>\n\n\n\n<p>Die End-to-End-Tests verwenden dazu REST Assured und einige der Hilfsfunktionen der Adapter-Tests. Deshalb muss das <em>adapter<\/em>-Modul die Testklassen exportieren und das <em>bootstrap<\/em>-Modul sie importieren. Du findest die entsprechenden <em>pom.xml<\/em>-Eintr\u00e4ge <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/bootstrap\/pom.xml#L74\" target=\"_blank\">hier<\/a>, <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/adapter\/pom.xml#L87\" target=\"_blank\">hier<\/a> und <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/bootstrap\/pom.xml#L80\" target=\"_blank\">hier<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"start-der-anwendung\">Start der Anwendung<\/h2>\n\n\n\n<p>Am einfachsten startest du die Anwendung direkt aus deiner IDE.<\/p>\n\n\n\n<p>Danach kannst du die REST-Endpunkte im Terminal mit <code>curl<\/code> oder mit einem anderen Tool, wie z. B. <a rel=\"noopener\" href=\"https:\/\/www.postman.com\/\" target=\"_blank\">Postman<\/a>, oder direkt aus IntelliJ aufrufen (ich wei\u00df nicht, inwieweit andere IDEs dies unterst\u00fctzen).<\/p>\n\n\n\n<p>In IntelliJ \u00f6ffnest du dazu die Datei <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/doc\/sample-requests.http\" target=\"_blank\">sample-requests.http<\/a> und klickst auf einen der gr\u00fcnen Pfeile. Im Fenster rechts unten siehst du dann die Antwort des Controllers, hier z. B. das Suchergebnis f\u00fcr den Begriff \u201emonitor\u201d (zweite Beispiel-Query in Zeile 5):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"600\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-800x600.png\" alt=\"Testen von REST-Controllern in IntelliJ\" class=\"wp-image-36703\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-800x600.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-224x168.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-336x252.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-504x378.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-672x504.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-400x300.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-600x450.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-944x708.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-1200x900.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Testen von REST-Controllern in IntelliJ<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Spiel ein bisschen mit den Requests herum. F\u00fcge ein paar Produkte zum Warenkorb hinzu, und beobachte, wie sich der Gesamtpreis erh\u00f6ht. <\/p>\n\n\n\n<p>Vom ersten Demo-Produkt, \u201ePlastic Sheeting\u201d, sind \u00fcbrigens nur 55 vorhanden, und der Beispiel-Request in Zeile 14 legt 20 davon in den Warenkorb. Wenn du diese Anfrage also ein drittes Mal ausf\u00fchrst, solltest du folgende Fehlermeldung sehen:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"228\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-600x228.png\" alt=\"Testen von REST-Controllern in IntelliJ - Bad Request\" class=\"wp-image-36704\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-600x228.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-224x85.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-336x128.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-504x192.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-672x255.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-400x152.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-800x304.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request-944x359.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/06\/testing-rest-controllers-in-intellij-bad-request.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/figure>\n<\/div>\n\n\n<p>Herzlichen Gl\u00fcckwunsch! Du hast erfolgreich eine voll funktionsf\u00e4hige Anwendung nach hexagonaler Architektur entwickelt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"ueberwachung-der-architekturgrenzen\">\u00dcberwachung der Architekturgrenzen<\/h2>\n\n\n\n<p>Ein letztes Kapitel gibt es noch. Ich w\u00fcrde dir gerne noch zeigen, wie du die Architekturgrenzen \u00fcberwachst und sicherstellst, dass niemand (also weder du selbst noch jemand anders) die Regeln der hexagonalen Architektur verletzt.<\/p>\n\n\n\n<p>Von der ersten M\u00f6glichkeit haben wir bereits intensiv Gebrauch gemacht:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"maven-module\">Maven-Module<\/h3>\n\n\n\n<p>Dadurch, dass wir unsere Anwendung in Maven-Module unterteilt und Abh\u00e4ngkeiten entsprechend der \u201eDependency Rule\u201d definiert haben, haben wir schon ein wichtiges Ziel erreicht: n\u00e4mlich, dass alle Quellcode-Abh\u00e4ngigkeiten Richtung Kern zeigen.<\/p>\n\n\n\n<p>Durch die strikte Aufteilung sind wir au\u00dferdem dazu gezwungen, bei jeder neuen Klasse gut dar\u00fcber nachzudenken, in welchem Modul wir sie anlegen. Das verhindert ein Arbeiten nach dem Motto \u201eich lege die Klasse mal eben schnell hier ab und verschiebe sie sp\u00e4ter an die richtige Stelle\u201d.<\/p>\n\n\n\n<p>Maven-Module sind allerdings recht grobgranular, und doppelt h\u00e4lt ja bekanntlich besser \u2013 daher zeige ich dir im n\u00e4chsten Abschnitt noch eine weitere Methode.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"archunit\">ArchUnit<\/h3>\n\n\n\n<p>Die Library <a href=\"https:\/\/www.archunit.org\/\">ArchUnit<\/a> erm\u00f6glicht es, Tests zu schreiben, die fehlschlagen, wenn definierte Architekturregeln verletzt werden. Dadurch k\u00f6nnte z. B. eine CI\/CD-Pipeline zum Abbruch gebracht werden.<\/p>\n\n\n\n<p>Dar\u00fcberhinaus k\u00f6nnen wir feingranularere Regeln definieren, z. B. auf Paket-Ebene, und damit z. B. sicherstellen, dass Adapter nur auf Ports zugreifen und nicht direkt auf die Domain-Services.<\/p>\n\n\n\n<p>Um ArchUnit nutzen zu k\u00f6nnen, m\u00fcssen wir zun\u00e4chst folgende Dependency in die <em>pom.xml<\/em> des <em>bootstrap<\/em>-Moduls eintragen (das <em>bootstrap<\/em>-Modul ist der geeignete Ort, da wir von hier aus Sicht auf alle anderen Module haben):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-55\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>com.tngtech.archunit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>archunit-junit5<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>1.1.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-55\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In der Testklasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/main\/bootstrap\/src\/test\/java\/eu\/happycoders\/shop\/bootstrap\/archunit\/DependencyRuleTest.java\" target=\"_blank\">DependencyRuleTest<\/a> k\u00f6nnen wir dann die Architekturregeln verifizieren lassen. Ich habe dazu die Hilfsmethode <code>checkNoDependencyFromTo(\u2026)<\/code> geschrieben, die pr\u00fcft, dass keine Dependencies von einem Paket zu einem anderen existieren und rufe diese in der Testmethode <code>checkDependencyRule()<\/code> f\u00fcr alle nicht erlaubten Abh\u00e4ngigkeiten auf:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-56\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">package<\/span> eu.happycoders.shop.bootstrap.archunit;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">static<\/span> com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;\n<span class=\"hljs-comment\">\/\/ ... more imports ...<\/span>\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">DependencyRuleTest<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String ROOT_PACKAGE = <span class=\"hljs-string\">\"eu.happycoders.shop\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String MODEL_PACKAGE = <span class=\"hljs-string\">\"model\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String APPLICATION_PACKAGE = <span class=\"hljs-string\">\"application\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String PORT_PACKAGE = <span class=\"hljs-string\">\"application.port\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String SERVICE_PACKAGE = <span class=\"hljs-string\">\"application.service\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String ADAPTER_PACKAGE = <span class=\"hljs-string\">\"adapter\"<\/span>;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> String BOOTSTRAP_PACKAGE = <span class=\"hljs-string\">\"bootstrap\"<\/span>;\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">checkDependencyRule<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    String importPackages = ROOT_PACKAGE + <span class=\"hljs-string\">\"..\"<\/span>;\n    JavaClasses classesToCheck = <span class=\"hljs-keyword\">new<\/span> ClassFileImporter().importPackages(importPackages);\n\n    checkNoDependencyFromTo(MODEL_PACKAGE, APPLICATION_PACKAGE, classesToCheck);\n    checkNoDependencyFromTo(MODEL_PACKAGE, ADAPTER_PACKAGE, classesToCheck);\n    checkNoDependencyFromTo(MODEL_PACKAGE, BOOTSTRAP_PACKAGE, classesToCheck);\n\n    checkNoDependencyFromTo(APPLICATION_PACKAGE, ADAPTER_PACKAGE, classesToCheck);\n    checkNoDependencyFromTo(APPLICATION_PACKAGE, BOOTSTRAP_PACKAGE, classesToCheck);\n\n    checkNoDependencyFromTo(PORT_PACKAGE, SERVICE_PACKAGE, classesToCheck);\n\n    checkNoDependencyFromTo(ADAPTER_PACKAGE, SERVICE_PACKAGE, classesToCheck);\n    checkNoDependencyFromTo(ADAPTER_PACKAGE, BOOTSTRAP_PACKAGE, classesToCheck);\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">checkNoDependencyFromTo<\/span><span class=\"hljs-params\">(\n      String fromPackage, String toPackage, JavaClasses classesToCheck)<\/span> <\/span>{\n    noClasses()\n        .that()\n        .resideInAPackage(fullyQualified(fromPackage))\n        .should()\n        .dependOnClassesThat()\n        .resideInAPackage(fullyQualified(toPackage))\n        .check(classesToCheck);\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> String <span class=\"hljs-title\">fullyQualified<\/span><span class=\"hljs-params\">(String packageName)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> ROOT_PACKAGE + <span class=\"hljs-string\">'.'<\/span> + packageName + <span class=\"hljs-string\">\"..\"<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-56\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Wenn wir jetzt im Code eine nicht erlaubte Abh\u00e4ngigkeit einf\u00fcgen und den Test ausf\u00fchren, bekommen wir eine Fehlermeldung wie die folgende:<\/p>\n\n\n\n<p><em>Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'eu.happycoders.shop.application.port..' should depend on classes that reside in a package 'eu.happycoders.shop.application.service..'' was violated (1 times)<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"java-module\">Java-Module<\/h3>\n\n\n\n<p>Seit Java 9 haben wir die M\u00f6glichkeit, Java-Module zu definieren. Dazu m\u00fcssten wir in jedes der Maven-Module eine <em>module-info.java<\/em>-Datei legen und in dieser definieren, welche Pakete ein Modul exportiert und welche es importiert.<\/p>\n\n\n\n<p>F\u00fcr die Pakete unserer Anwendung ist das einfach. Wir m\u00fcssten aber auch Imports f\u00fcr alle Third-Party-Dependencies definieren und teilweise sogar f\u00fcr transitive Dependencies. Dar\u00fcberhinaus m\u00fcssen wir auch festlegen, auf welche Teile unseres Codes Third-Party-Dependencies per Reflection zugreifen k\u00f6nnen.<\/p>\n\n\n\n<p>Ich habe das interessehalber einmal ausprobiert, rate aber davon ab, es einzusetzen, da die Konfiguration sehr komplex ist (insbesondere in Kombination mit Unit- und Integration-Tests) und wenig Mehrwert gegen\u00fcber Maven-Modulen und ArchUnit liefert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fazit-und-ausblick\">Fazit und Ausblick<\/h2>\n\n\n\n<p>Du hast in diesem Tutorial gelernt, wie man eine Java-Anwendung nach hexagonaler Architektur implementiert und die Einhaltung der Architekturregeln mit Maven und ArchUnit sicherstellt.<\/p>\n\n\n\n<p>Wir haben die gesamte Gesch\u00e4ftslogik in den ersten zwei Modulen, <em>model<\/em> und <em>application<\/em> (zusammen bilden diese den Anwendungskern) implementiert und diese durch die Ports von den technischen Belangen, wie REST-Controllern und Persistenzl\u00f6sung, isoliert. Diese haben wir erst sp\u00e4ter, im <em>adapter<\/em>-Modul hinzugef\u00fcgt.<\/p>\n\n\n\n<p>Diese Isolierung erlaubt es uns zudem, Entscheidungen \u00fcber technische Details hinauszuz\u00f6gern. W\u00e4hrend wir bei der herk\u00f6mmlichen Schichtenarchitektur mit der Planung einer Datenbank beginnen, auf der dann die ganze Anwendung basiert, haben wir unsere hexagonale Anwendung bis jetzt noch immer nicht mit einer Datenbank verbunden.<\/p>\n\n\n\n<p>Das werden wir <a href=\"\/de\/software-craftsmanship\/ports-and-adapters-java-tutorial-db\/\">im n\u00e4chsten Teil dieser Serie<\/a> nachholen \u2013 und zwar ohne auch nur eine Zeile Code im Anwendungskern \u00e4ndern zu m\u00fcssen. Wir werden lediglich neue Adapter hinzuf\u00fcgen und diese im <em>bootstrap<\/em>-Modul initialisieren m\u00fcssen.<\/p>\n\n\n\n<p>Sollten wir also von nun an alle Anwendungen auf Grundlage der hexagonalen Architektur implementieren?<\/p>\n\n\n\n<p>Nein, denn diese Architektur erfordert eine Menge Overhead. Ob sich dieser Aufwand lohnt, h\u00e4ngt von der Art und Gr\u00f6\u00dfe der Anwendung ab. Eine einfache CRUD-Anwendung, die einen einzigen Resource-Typ in einer Datenbank speichert und per REST-Schnittstelle abrufbar\/\u00e4nderbar macht, ist mit drei Klassen (Entity, Controller, Repository) implementierbar \u2013 oder sogar mit zwei, wenn man das <a rel=\"noopener\" href=\"https:\/\/de.wikipedia.org\/wiki\/Active_Record\" target=\"_blank\">Active-Record-Pattern<\/a> anwendet. Daf\u00fcr w\u00e4re eine hexagonale Architektur \u00fcberdimensioniert.<\/p>\n\n\n\n<p>Sieh die hexagonale Architektur als ein weiteres Werkzeug an, das dir zur Verf\u00fcgung steht. In diesem Tutorial hast du gelernt, wie man dieses Werzeug einsetzt.<\/p>\n\n\n\n<p>Muss eine Anwendung nach hexagonaler Architektur immer der Struktur dieses Tutorials folgen?<\/p>\n\n\n\n<p>Auch das nicht. Du kannst auch ohne Maven-Module arbeiten, oder mit weniger oder mehr als in diesem Tutorial. Du kannst die Pakete anders anordnen oder benennen.<\/p>\n\n\n\n<p>Ich habe mit dieser Struktur, auch in gro\u00dfen Projekten, sehr gute Erfahrung gemacht. Und wenn die Struktur einmal steht, steht sie. Den Aufwand daf\u00fcr hat man nur einmal. Wir k\u00f6nnten jetzt zahlreiche zus\u00e4tzliche Modell-Klassen, Ports, Services und Adapter hinzuf\u00fcgen, ohne an der Modul-Struktur, der Maven-Konfiguration oder dem ArchUnit-Test irgendetwas \u00e4ndern zu m\u00fcssen.<\/p>\n\n\n\n<p>Schreibe mir einen Kommentar, falls du mit dieser oder einer anderen Struktur gute Erfahrung gemacht hast oder andere Anmerkungen zu diesem Tutorial hast!<\/p>\n<aside><p>Wenn dir der Artikel weitergeholfen hat, w\u00fcrde ich mich sehr \u00fcber eine positive Bewertung auf meinem <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">ProvenExpert-Profil<\/a> freuen. Dein Feedback hilft mir, meine Inhalte weiter zu verbessern und motiviert mich, neue informative Artikel zu schreiben.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">Bewertung abgeben<\/a><\/p>\r\n                        <p>M\u00f6chtest du auf dem Laufenden bleiben und informiert werden, wenn neue Artikel auf HappyCoders.eu ver\u00f6ffentlicht werden? Dann <a href=\"#\" data-formkit-toggle=\"d8ee997126\">klicke hier<\/a>, um dich f\u00fcr den HappyCoders-Newsletter anzumelden.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"#\" data-formkit-toggle=\"d8ee997126\">Newsletter-Anmeldung<\/a><\/p><\/aside>","protected":false},"excerpt":{"rendered":"<p>In diesem Artikel zeige ich dir Schritt f\u00fcr Schritt, wie man eine Java-Anwendung mit hexagonaler Architektur implementiert \u2013 und wie man die Einhaltung der Architekturregeln mit Maven und der Library \u201eArchUnit\u201d sicherstellt.<\/p>\n","protected":false},"author":1,"featured_media":35472,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"","_seopress_titles_desc":"Schritt-f\u00fcr-Schritt-Tutorial f\u00fcr die Implementierung einer Anwendung nach hexagonaler Architektur - mit Java, Maven und ArchUnit.","_seopress_robots_index":"","_uag_custom_page_level_css":"","_wp_convertkit_post_meta":{"form":"-1","landing_page":"","tag":"0","restrict_content":"0"},"_metis_text_type":"standard","_metis_text_length":87063,"_post_count":0,"footnotes":""},"categories":[204],"tags":[209],"class_list":["post-35265","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-craftsmanship","tag-hexagonale-architektur"],"uagb_featured_image_src":{"full":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",1770,986,false],"thumbnail":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",150,84,false],"medium":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",300,167,false],"medium_large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",768,428,false],"large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",1024,570,false],"feature_thumb_224":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-224x125.jpg",224,125,true],"feature_thumb_336":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-336x187.jpg",336,187,true],"feature_thumb_504":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-504x281.jpg",504,281,true],"feature_thumb_672":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-672x374.jpg",672,374,true],"half_400":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-400x223.jpg",400,223,true],"half_600":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-600x334.jpg",600,334,true],"full_800":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-800x446.jpg",800,446,true],"full_944":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-944x526.jpg",944,526,true],"full_1200":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-1200x668.jpg",1200,668,true],"wide_1180":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-1180x490.jpg",1180,490,true],"wide_1770":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java-1770x735.jpg",1770,735,true],"1536x1536":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",1536,856,false],"2048x2048":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/05\/hexagonal-architecture-java.jpg",1770,986,false]},"uagb_author_info":{"display_name":"Sven Woltmann","author_link":"https:\/\/www.happycoders.eu\/de\/author\/sven\/"},"uagb_comment_info":2,"uagb_excerpt":"In diesem Artikel zeige ich dir Schritt f\u00fcr Schritt, wie man eine Java-Anwendung mit hexagonaler Architektur implementiert \u2013 und wie man die Einhaltung der Architekturregeln mit Maven und der Library \u201eArchUnit\u201d sicherstellt.","public_identification_id":"0557f11118c74ad0b6e6d6e8f3079ddc","private_identification_id":"9e5223736238436d844074ffa56a95b0","_links":{"self":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/35265","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/comments?post=35265"}],"version-history":[{"count":11,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/35265\/revisions"}],"predecessor-version":[{"id":54392,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/35265\/revisions\/54392"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media\/35472"}],"wp:attachment":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media?parent=35265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/categories?post=35265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/tags?post=35265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}