{"id":38479,"date":"2023-12-27T14:51:26","date_gmt":"2023-12-27T13:51:26","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=38479"},"modified":"2025-06-12T08:54:18","modified_gmt":"2025-06-12T06:54:18","slug":"hexagonale-architektur-spring-boot","status":"publish","type":"post","link":"https:\/\/www.happycoders.eu\/de\/software-craftsmanship\/hexagonale-architektur-spring-boot\/","title":{"rendered":"Hexagonale Architektur mit Spring Boot [Tutorial]"},"content":{"rendered":"\n<p>In diesem letzten Teil der Tutorial-Serie zeige ich dir, wie wir die Anwendung, die wir im Laufe dieses Tutorials \u2013 zun\u00e4chst ohne Framework \u2013 nach der hexagonalen Architektur entwickelt und dann auf Quarkus umgestellt haben, auf das Spring-Boot-Framework migrieren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"rueckblick\">R\u00fcckblick<\/h2>\n\n\n\n<p>Wir haben diese Artikelserie mit den <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur\/\">Grundlagen der hexagonalen Architektur<\/a> begonnen.<\/p>\n\n\n\n<p>Danach haben wir eine <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur-java\/\">Java-Anwendung mit der hexagonalen Architektur<\/a> entwickelt \u2013 zun\u00e4chst ohne ein Application Framework. Anschlie\u00dfend haben wir die Anwendung <a href=\"\/de\/software-craftsmanship\/ports-and-adapters-java-tutorial-db\/\">um einen JPA-Adapter erweitert<\/a>, um die Daten in einer Datenbank zu persistieren anstatt sie lediglich im Arbeitsspeicher zu halten. Im vorangegangenen Teil der Serie haben wir dann die <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur-quarkus\/\">hexagonale Anwendung in Quarkus<\/a> als Application Framework eingebettet.<\/p>\n\n\n\n<p>Alle Erweiterungen der Anwendung \u2013 sowohl die Anbindung eines neuen Adpaters als auch die Migration zu Quarkus \u2013 konnten wir durchf\u00fchren, ohne auch nur eine Zeile Code im Anwendungskern \u00e4ndern zu m\u00fcssen.<\/p>\n\n\n\n<p>Die folgende Grafik zeigt die Architektur unserer Anwendung. Der zuvor erw\u00e4hnte Anwendungskern wird durch das mit \u201eApplication\u201d bezeichnete Hexagon dargestellt:<\/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 Beispiel-Anwendung\" 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\" \/><\/figure>\n<\/div>\n\n\n<p>Den aktuellen Stand der Demo-Anwendung findest du im <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/with-quarkus\" target=\"_blank\">\u201ewith-quarkus\u201d-Branch dieses GitHub-Repositories<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"worum-geht-es-in-diesem-teil\">Worum geht es in diesem Teil? <\/h2>\n\n\n\n<p>In diesem f\u00fcnften und letzten Teil der Serie werden wir unsere Anwendung von <a href=\"https:\/\/quarkus.io\/\" target=\"_blank\" rel=\"noopener\">Quarkus<\/a> zu <a href=\"https:\/\/spring.io\/projects\/spring-boot\/\" target=\"_blank\" rel=\"noopener\">Spring Boot<\/a> migrieren \u2013 und auch das wieder, ohne Code im Anwendungskern \u00e4ndern zu m\u00fcssen.<\/p>\n\n\n\n<p>Wir werden dabei wieder schrittweise vorgehen wie bei der Migration zu Quarkus. Doch da Spring sowohl bei den prim\u00e4ren als auch bei den sekund\u00e4ren Controllern andere Technologien einsetzt (<a rel=\"noopener\" href=\"https:\/\/docs.spring.io\/spring-framework\/reference\/web\/webmvc.html\" target=\"_blank\">Spring Web MVC<\/a> anstelle von <a rel=\"noopener\" href=\"https:\/\/jakarta.ee\/specifications\/restful-ws\/\" target=\"_blank\">Jakarta RESTful Web Services<\/a> im Frontend und <a rel=\"noopener\" href=\"https:\/\/spring.io\/projects\/spring-data-jpa\/\" target=\"_blank\">Spring Data JPA<\/a> anstelle von <a rel=\"noopener\" href=\"https:\/\/quarkus.io\/guides\/hibernate-orm-panache\" target=\"_blank\">Panache<\/a> im Backend), wird die Anwendung zwischen den Schritten nicht kompilierbar und lauff\u00e4hig sein, sondern erst nach Abschluss der Migration.<\/p>\n\n\n\n<p>Wir werden die Anwendung in folgenden Schritten zu Spring Boot migrieren:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Zuerst werden wir die Quarkus-Dependencies in den <em>pom.xml<\/em>-Dateien durch Spring-Boot-Dependencies ersetzen.<\/li>\n\n\n\n<li>Dann werden wir die prim\u00e4ren REST-Adapter anpassen, indem wir die JAX-RS-Annotationen durch Spring-Web-MVC-Annotationen ersetzen.<\/li>\n\n\n\n<li>Danach werden wir die sekund\u00e4ren Persistenzadapter migrieren, indem wir die Panache-Repositories durch Spring-Data-JPA-Repositories ersetzen. Dabei werden wir auch die Annotationen austauschen, die kontrollieren, welche Adapter (In-Memory oder JPA) erzeugt werden.<\/li>\n\n\n\n<li>Dann werden wir die Quarkus-Anwendungskonfiguration im <em>adapter<\/em>-Modul durch eine Spring-Boot-Konfiguration ersetzen, woraufhin das <em>adapter<\/em>-Modul wieder kompilierbar sein wird.<\/li>\n\n\n\n<li>Zuletzt werden wir eine Spring-Boot-Starter-Klasse hinzuf\u00fcgen und die End-to-End-Tests im <em>bootstrap<\/em>-Modul anpassen.<\/li>\n<\/ol>\n\n\n\n<p>Den Stand des Codes nach Abschluss der Migration findest du im <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/with-spring-boot\" target=\"_blank\" rel=\"noopener\">\u201ewith-spring-boot\u201d-Branch<\/a> des GitHub-Repositories. Alle \u00c4nderungen gegen\u00fcber der Quarkus-Version findest du in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/pull\/4\/files\" target=\"_blank\" rel=\"noopener\">diesem Pull Request<\/a>.<\/p>\n\n\n\n<p>Beginnen wir mit Schritt 1...<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"schritt-1-dependencies-austauschen\">Schritt 1: Dependencies austauschen<\/h2>\n\n\n\n<p>Ausgehend vom Projektstand im <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/with-quarkus\" target=\"_blank\" rel=\"noopener\">\u201ewith-quarkus\u201d-Branch<\/a> werden wir in den <em>pom.xml<\/em>-Dateien des Projekts die Quarkus-Bill-of-Materials durch den Spring-Boot-Starter-Parent und alle Quarkus-Dependences durch Spring-Boot-Dependencies ersetzen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"anpassung-der-eltern-pom-xml\">Anpassung der Eltern-pom.xml<\/h3>\n\n\n\n<p>Um die Versionen aller Spring-Boot-Bibliotheken verf\u00fcgbar zu haben, f\u00fcgen wir als erstes in die <em>pom.xml<\/em> des Projektverzeichnisses den Spring-Boot-Starter-Parent ein, z. B. zwischen <code>&lt;modelVersion&gt;<\/code> und <code>&lt;groupId&gt;<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" 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\">parent<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.springframework.boot<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>spring-boot-starter-parent<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.5<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">parent<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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>Alle Quarkus-spezifischen Eintr\u00e4ge k\u00f6nnen wir dann entfernen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>den <code>&lt;quarkus.platform.version&gt;<\/code>-Eintrag aus dem <code>&lt;properties&gt;<\/code>-Block,<\/li>\n\n\n\n<li>die Version der <em>assertj-core<\/em>-Bibliothek, da diese bereits im Spring-Boot-Parent definiert ist,<\/li>\n\n\n\n<li>die Quarkus-BOM-Dependency aus dem Dependency-Management<\/li>\n\n\n\n<li>und das Quarkus-Maven-Plugin.<\/li>\n<\/ul>\n\n\n\n<p>Hier findest du die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/48ab8183d9d757f5a01ccf8712079b50e672e88d#diff-9c5fb3d1b7e3b0f54bc5c4182965c4fe1f9023d449017cece3005d3f90e8e4d8\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a> als GitHub-Commit und hier die ge\u00e4nderte <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/pom.xml\" target=\"_blank\" rel=\"noopener\">pom.xml<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"anpassung-der-adapter-pom-xml\">Anpassung der adapter\/pom.xml<\/h3>\n\n\n\n<p>In der <em>adapter\/pom.xml<\/em> ersetzen wir alle Quarkus-Extensions durch:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Spring-Boot-Libraries, <\/li>\n\n\n\n<li>den MySQL-Treiber <\/li>\n\n\n\n<li>und Testcontainers.<\/li>\n<\/ul>\n\n\n\n<p>(Die letzten beiden Libraries wurden zuvor \u00fcber Quarkus-Extensions als transitive Dependencies ins Projekt importiert.)<\/p>\n\n\n\n<p>Au\u00dferdem m\u00fcssen wir einen H2-Datenbanktreiber hinzuf\u00fcgen. Der Grund daf\u00fcr ist, dass die Spring-Boot-Anwendung durch das Einbinden von <em>spring-boot-starter-data-jpa<\/em> auch im In-Memory-Modus eine g\u00fcltige Datenbankkonfiguration erwartet. Quarkus konnten wir im In-Memory-Modus mit einer Dummy-MySQL-URL abspeisen, was lediglich zu einer Warnung gef\u00fchrt hat. Spring hingegen bricht den Startvorgang bei einer ung\u00fcltigen Datenbank-URL ab. Die Anbindung einer \u2013 letztendlich nicht genutzten \u2013 H2-In-Memory-Datenbank ist ein Workaround, der nicht sch\u00f6n ist, aber f\u00fcr unsere Demo-Anwendung ausreichend.<\/p>\n\n\n\n<p>Nach den \u00c4nderungen sieht der <code>&lt;dependencies&gt;<\/code>-Block der <em>adapter\/pom.xml<\/em> wie folgt aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" 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-comment\">&lt;!-- Internal --&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-comment\">&lt;!-- External --&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.springframework.boot<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>spring-boot-starter<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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.springframework.boot<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>spring-boot-starter-web<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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.springframework.boot<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>spring-boot-starter-data-jpa<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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>com.mysql<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>mysql-connector-j<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>com.h2database<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>h2<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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\n    <span class=\"hljs-comment\">&lt;!-- Test scope --&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.springframework.boot<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>spring-boot-starter-test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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>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\">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.testcontainers<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>mysql<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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\n    <span class=\"hljs-comment\">&lt;!-- To use the \"attached test JAR\" from the \"model\" module --&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\">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<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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 findest du die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/48ab8183d9d757f5a01ccf8712079b50e672e88d#diff-5f136b81a053f044b1b65114a452d0512bf3c9d1552387c704fa2a4c5d5bd1a6\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a> als GitHub-Commit und hier die angepasste <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/pom.xml\" target=\"_blank\" rel=\"noopener\">adapter\/pom.xml<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"anpassung-der-bootstrap-pom-xml\">Anpassung der bootstrap\/pom.xml<\/h3>\n\n\n\n<p>In der <em>bootstrap\/pom.xml<\/em> m\u00fcssen wir die Test-Dependencies auf die Quarkus-Extensions von JUnit und Mockito durch <em>spring-boot-starter-test<\/em> ersetzen und Testcontainers hinzuf\u00fcgen \u2013 dieses war zuvor transitiv \u00fcber die Quarkus-Extensions importiert.<\/p>\n\n\n\n<p>Hier siehst du die Test-Dependencies nach der Migration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- Test scope --&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.springframework.boot<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>spring-boot-starter-test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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>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\">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>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\">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.testcontainers<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>mysql<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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-4\"><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>Au\u00dferdem m\u00fcssen wir das <em>quarkus-maven-plugin<\/em> durch das <em>spring-boot-maven-plugin<\/em> ersetzen. Der <code>&lt;build&gt;<\/code>-Block sieht damit wie folgt aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" 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.springframework.boot<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>spring-boot-maven-plugin<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/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>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><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 findest du die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/48ab8183d9d757f5a01ccf8712079b50e672e88d#diff-bf87b52f3dba78f6ac2fad9c7c1616e9b13ae3a2bbd1a3139a1bac8f3474325d\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a> als GitHub-Commit und hier die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/pom.xml\" target=\"_blank\" rel=\"noopener\">bootstrap\/pom.xml<\/a> nach den \u00c4nderungen.<\/p>\n\n\n\n<p>Beachte, dass die <em>adapter<\/em>- und <em>bootstrap<\/em>-Module der Anwendung zu diesem Zeitpunkt nicht mehr kompiliert werden k\u00f6nnen, die <em>model<\/em>- und <em>application<\/em>-Module hingegen schon, da diese keinerlei technische Details enthalten. <\/p>\n\n\n\n<p>Entsprechend sollte an dieser Stelle ein Aufruf von <code>mvn spotless:apply clean verify<\/code> zu folgender Ausgabe f\u00fchren:<\/p>\n\n\n\n<pre class=\"wp-block-code hc-mvn-out\"><code>[<span class=\"info\">INFO<\/span>] parent ............................................. <span class=\"success\">SUCCESS<\/span> [  0.377 s]\n[<span class=\"info\">INFO<\/span>] model .............................................. <span class=\"success\">SUCCESS<\/span> [  2.908 s]\n[<span class=\"info\">INFO<\/span>] application ........................................ <span class=\"success\">SUCCESS<\/span> [  2.392 s]\n[<span class=\"info\">INFO<\/span>] adapter ............................................ <span class=\"failure\">FAILURE<\/span> [  1.221 s]\n[<span class=\"info\">INFO<\/span>] bootstrap .......................................... <span class=\"skipped\">SKIPPED<\/span>\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------\n[<span class=\"info\">INFO<\/span>] <span class=\"failure\">BUILD FAILURE<\/span>\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------<\/code><\/pre>\n\n\n\n<p>Wir werden in den folgenden Abschnitten nach und nach den Code im <em>adapter<\/em>-Modul und dann im <em>bootstrap<\/em>-Modul anpassen, um das Projekt wieder kompilierbar zu machen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"schritt-2-rest-adapter-von-jax-rs-nach-spring-web-mvc-migrieren\">Schritt 2: REST-Adapter von JAX-RS nach Spring Web MVC migrieren<\/h2>\n\n\n\n<p>Im Adapter-Modul beginnen wir mit den Anpassungen der REST-Adapter. Die Migration von Jakarta RESTful Web Services nach Spring Web MVC bedeutet in erster Linie, dass wir <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Annotationen und <\/li>\n\n\n\n<li>Enum-Konstanten von HTTP-Statuscodes <\/li>\n<\/ul>\n\n\n\n<p>durch ihre jeweiligen Spring-Pendants ersetzen m\u00fcssen. <\/p>\n\n\n\n<p>Au\u00dferdem m\u00fcssen wir den Jakarta-EE-Exception-Handling-Mechanismus nachbauen, da es etwas Vergleichbares meines Wissens nach in Spring nicht gibt. Korrigiere mich bitte \u00fcber die Kommentarfunktion, wenn ich falsch liege!<\/p>\n\n\n\n<p>Zuletzt m\u00fcssen wir in den REST-Assured-Integrationstests den HTTP-Port wieder setzen. Das hatten wir im Zuge der Migration zu Quarkus ausgebaut, da Quarkus das automatisch macht.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"jax-rs-annotationen-durch-spring-annotationen-ersetzen\">JAX-RS-Annotationen durch Spring-Annotationen ersetzen<\/h3>\n\n\n\n<p>Beginnen wir mit den Annotationen...<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">GetCartController<\/h4>\n\n\n\n<p>Fangen wir mit dem einfachsten Controller an, dem <code>GetCartController<\/code>. Hier siehst du den JAX-RS-Controller einschlie\u00dflich der relevanten Imports:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.GET;\n<span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.Path;\n<span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.PathParam;\n<span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.Produces;\n<span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.core.MediaType;\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  . . .\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 = 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-6\"><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>Gehen wir die Annotationen einmal von oben nach unten durch. Die folgende Tabelle listet die JAX-RS-Annotationen auf, erkl\u00e4rt deren Bedeutung und zeigt das jeweilige Spring-Pendant:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><thead><tr><th>JAX-RS-Annotation(en)<\/th><th>Beschreibung<\/th><th>Spring-Pendant<\/th><\/tr><\/thead><tbody><tr><td><code>@Path(\"\/carts\")<\/code><\/td><td>Markiert die Klasse als REST-Controller und definiert den Basis-Pfad<\/td><td><code>@RestController<br>@RequestMapping(\"\/carts\")<\/code><\/td><\/tr><tr><td><code>@Produces(APPLICATION_JSON)<\/code><\/td><td>Definiert das Antwortformat; war in der Anwendung ohne Application Framework notwendig. H\u00e4tte in der Quarkus-Version entfernt werden k\u00f6nnen, da Quarkus JSON als Default setzt.<\/td><td>Entf\u00e4llt, da auch Spring JSON als Default setzt.<\/td><\/tr><tr><td><code>@GET<br>@Path(\"\/{customerId}\")<\/code><\/td><td>Markiert die Methode als GET-Controller und definiert den Pfad.<\/td><td><code>@GetMapping(\"\/{customerId}\")<\/code><\/td><\/tr><tr><td><code>@PathParam(\"customerId\")<\/code><\/td><td>Extrahiert das Pfadelement <em>{customerId}<\/em> in den annotierten Methodenparameter.<\/td><td><code>@PathVariable(\"customerId\")<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Wenn wir die in der Tabelle aufgelisteten Annotationen ersetzen, wird aus dem oben gezeigten Jakarta-RESTful-Webservices-Controller der folgende Spring-Web-MVC-Controller (<a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/GetCartController.java\" target=\"_blank\" rel=\"noopener\">GetCartController<\/a> im GitHub-Repository, hier nur die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-2f226daa2bf9cb028a7979d3208c5e9d9b247380300c3f05c45a36efdfd2bd6b\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a>):<\/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\">import<\/span> org.springframework.web.bind.annotation.GetMapping;\n<span class=\"hljs-keyword\">import<\/span> org.springframework.web.bind.annotation.PathVariable;\n<span class=\"hljs-keyword\">import<\/span> org.springframework.web.bind.annotation.RequestMapping;\n<span class=\"hljs-keyword\">import<\/span> org.springframework.web.bind.annotation.RestController;\n\n\n<span class=\"hljs-meta\">@RestController<\/span>\n<span class=\"hljs-meta\">@RequestMapping<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\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  . . .\n\n  <span class=\"hljs-meta\">@GetMapping<\/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\">(@PathVariable(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString) <\/span>{\n    CustomerId customerId = parseCustomerId(customerIdString);\n    Cart cart = getCartUseCase.getCart(customerId);\n    <span class=\"hljs-keyword\">return<\/span> CartWebModel.fromDomainModel(cart);\n  }\n}\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<p>Wie du siehst, ist der Unterschied zwischen Spring und Jakarta EE minimal. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">EmptyCartController<\/h4>\n\n\n\n<p>Beim <code>EmptyCartController<\/code> k\u00f6nnen wir genauso vorgehen, m\u00fcssen allerdings eine Besonderheit bei den HTTP-Statuscodes beachten \u2013 mehr dazu weiter unten.<\/p>\n\n\n\n<p>Hier zun\u00e4chst der Jakarta-RESTful-Webservices-Code (die Imports drucke ich dieses Mal nicht mit ab):<\/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-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  . . .\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 = parseCustomerId(customerIdString);\n    emptyCartUseCase.emptyCart(customerId);\n  }\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>Wir treffen hier auf eine weitere JAX-RS-Annotation, <code>@DELETE<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><thead><tr><th>JAX-RS-Annotation(en)<\/th><th>Beschreibung<\/th><th>Spring-Pendant<\/th><\/tr><\/thead><tbody><tr><td><code>@DELETE<br>@Path(\"\/{customerId}\")<\/code><\/td><td>Markiert die Methode als DELETE-Controller und definiert den Pfad.<\/td><td><code>@DeleteMapping(\"\/{customerId}\")<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Die JAX-RS-<code>@DELETE<\/code>-Annotation f\u00fchrt dazu, dass der Controller einen HTTP-Statuscode 204 \u201eNo Content\u201d zur\u00fcckliefert. Springs <code>@DeleteMapping<\/code>-Annotation hingegen gibt den HTTP-Statuscode 200 \u201eOK\u201d zur\u00fcck. Das w\u00fcrde die Integrationstests f\u00fcr diese Methode fehlschlagen lassen.<\/p>\n\n\n\n<p>Um das urspr\u00fcngliche Verhalten beizubehalten und auch Spring einen Statuscode 204 zur\u00fcckgeben zu lassen, muss die Methode eine entsprechende <code>ResponseEntity<\/code> zur\u00fcckliefern. Diese k\u00f6nnen wir \u00fcber <code>ResponseEntity.noContent().build()<\/code> erzeugen.<\/p>\n\n\n\n<p>Hier der nach Spring migrierte Controller (<a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/EmptyCartController.java\" target=\"_blank\" rel=\"noopener\">EmptyCartController<\/a> im GitHub-Repository, hier nur die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-9c36c3edae4d82826caebad4fea90937d8b4132696836c9ffb156bbbb3de2abc\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a>):<\/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-meta\">@RestController<\/span>\n<span class=\"hljs-meta\">@RequestMapping<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\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  . . .\n\n  <span class=\"hljs-meta\">@DeleteMapping<\/span>(<span class=\"hljs-string\">\"\/{customerId}\"<\/span>)\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> ResponseEntity&lt;Void&gt; <span class=\"hljs-title\">deleteCart<\/span><span class=\"hljs-params\">(\n      @PathVariable(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString) <\/span>{\n    CustomerId customerId = parseCustomerId(customerIdString);\n    emptyCartUseCase.emptyCart(customerId);\n    <span class=\"hljs-keyword\">return<\/span> ResponseEntity.noContent().build();\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<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"enum-konstanten-der-http-statuscodes-austauschen\">Enum-Konstanten der HTTP-Statuscodes austauschen<\/h3>\n\n\n\n<p>Bei den zwei anderen REST-Controllern, <code>AddToCartController<\/code> und <code>FindProductsController<\/code> m\u00fcssen wir au\u00dferdem die  Enum-Konstanten f\u00fcr die HTTP-Statuscodes austauschen.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">AddToCartController<\/h4>\n\n\n\n<p>Schauen wir zun\u00e4chst auf den <code>AddToCartController<\/code> \u2013 hier der alte Code:<\/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\">. . .\n<span class=\"hljs-keyword\">import<\/span> jakarta.ws.rs.core.Response;\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  . . .\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}\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>Wir sehen zun\u00e4chst zwei weitere Annotationen im Einsatz:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><thead><tr><th>JAX-RS-Annotation(en)<\/th><th>Beschreibung<\/th><th>Spring-Pendant<\/th><\/tr><\/thead><tbody><tr><td><code>@POST<br>@Path(\"\/...\/...\")<\/code><\/td><td>Markiert die Methode als POST-Controller und definiert den Pfad.<\/td><td><code>@PostMapping(\"\/...\/...\")<\/code><\/td><\/tr><tr><td><code>@QueryParam(\"...\")<\/code><\/td><td>Bindet einen Query- bzw. Request-Parameter (zwei Unterschiedliche Bezeichnungen f\u00fcr die mit einem Fragezeichen an die URL angeh\u00e4ngten Parameter) mit dem angegebenen Namen an die annotierte Variable. <\/td><td><code>@RequestParam(\"...\")<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Die JAX-RS-Enum-Klasse <code>jakarta.ws.rs.core.Response.Status<\/code> definiert Konstanten f\u00fcr alle m\u00f6glichen HTTP-Statuscodes. In Spring sind die Statuscodes in der Klasse <code>org.springframework.http.HttpStatus<\/code> definiert.<\/p>\n\n\n\n<p>Das folgende Listing zeigt den vollst\u00e4ndig nach Spring migrierten Controller (<a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/cart\/AddToCartController.java\" target=\"_blank\" rel=\"noopener\">AddToCartController<\/a> im GitHub-Repository, hier die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-a6717dd3df5f86d233b4bde86bc2fd49dbc4ff51c2772609f5eb42a162a1a23a\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">import<\/span> org.springframework.http.HttpStatus;\n. . .\n\n<span class=\"hljs-meta\">@RestController<\/span>\n<span class=\"hljs-meta\">@RequestMapping<\/span>(<span class=\"hljs-string\">\"\/carts\"<\/span>)\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  . . .\n\n  <span class=\"hljs-meta\">@PostMapping<\/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      @PathVariable(<span class=\"hljs-string\">\"customerId\"<\/span>)<\/span> String customerIdString,\n      @<span class=\"hljs-title\">RequestParam<\/span><span class=\"hljs-params\">(<span class=\"hljs-string\">\"productId\"<\/span>)<\/span> String productIdString,\n      @<span class=\"hljs-title\">RequestParam<\/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          HttpStatus.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          HttpStatus.BAD_REQUEST, \n          <span class=\"hljs-string\">\"Only %d items in stock\"<\/span>.formatted(e.itemsInStock()));\n    }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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 die Aufrufe der <code>clientErrorException(...)<\/code>-Methode aktuell noch nicht kompilieren, da wir diese noch nicht auf den Spring-<code>HttpStatus<\/code> migriert haben.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">FindProductsController<\/h4>\n\n\n\n<p>Zuletzt gibt es auch in <code>FindProductsController<\/code> eine Besonderheit zu beachten, n\u00e4mlich beim Query- bzw. Request-Parameter. Hier zun\u00e4chst der alte Code:<\/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-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  . . .\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-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>Und hier der nach Spring migrierte Code (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/product\/FindProductsController.java\" target=\"_blank\" rel=\"noopener\">FindProductsController<\/a>, hier die <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-93c340aa60046a00c97c23e8c4df325e35caa739827d8b4499802187e1eda6c3\" target=\"_blank\" rel=\"noopener\">\u00c4nderungen<\/a>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@RestController<\/span>\n<span class=\"hljs-meta\">@RequestMapping<\/span>(<span class=\"hljs-string\">\"\/products\"<\/span>)\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  . . .\n\n  <span class=\"hljs-meta\">@GetMapping<\/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\">(\n      @RequestParam(value = <span class=\"hljs-string\">\"query\"<\/span>, required = <span class=\"hljs-keyword\">false<\/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(HttpStatus.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(HttpStatus.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}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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 ersetzten Annotationen haben wir im Grunde bereits bei den drei vorherigen Controllern besprochen. Allerdings mussten wir hier bei der <code>@RequestParam<\/code>-Annotation den Parameter <code>required = false<\/code> hinzuf\u00fcgen. Andernfalls w\u00fcrde ein fehlender <code>query<\/code>-Parameter zu einer Fehlermeldung des Frameworks f\u00fchren anstatt zu der Fehlermeldung, die wir zu Beginn der Methode selbst implementiert haben.<\/p>\n\n\n\n<p>Auch hier kompiliert der Aufruf der&nbsp;<code>clientErrorException(...)<\/code>-Methode aktuell noch nicht.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">CustomerIdParser und ProductIdParser<\/h4>\n\n\n\n<p>In den Parser-Klassen geben wir je nach Art des Fehlers bestimmte HTTP-Statuscodes zur\u00fcck. Wir m\u00fcssen also auch in diesen Klassen die entsprechenden <code>Response.Status<\/code>-Konstanten durch <code>HttpStatus<\/code>-Konstanten ersetzen.<\/p>\n\n\n\n<p>Hier die ge\u00e4nderten Klassen:<\/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\">public<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CustomerIdParser<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-title\">CustomerIdParser<\/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> CustomerId <span class=\"hljs-title\">parseCustomerId<\/span><span class=\"hljs-params\">(String string)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> CustomerId(Integer.parseInt(string));\n    } <span class=\"hljs-keyword\">catch<\/span> (IllegalArgumentException e) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(HttpStatus.BAD_REQUEST, <span class=\"hljs-string\">\"Invalid 'customerId'\"<\/span>);\n    }\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>Und:<\/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\">public<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ProductIdParser<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-title\">ProductIdParser<\/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> ProductId <span class=\"hljs-title\">parseProductId<\/span><span class=\"hljs-params\">(String string)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (string == <span class=\"hljs-keyword\">null<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(HttpStatus.BAD_REQUEST, <span class=\"hljs-string\">\"Missing 'productId'\"<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductId(string);\n    } <span class=\"hljs-keyword\">catch<\/span> (IllegalArgumentException e) {\n      <span class=\"hljs-keyword\">throw<\/span> clientErrorException(HttpStatus.BAD_REQUEST, <span class=\"hljs-string\">\"Invalid 'productId'\"<\/span>);\n    }\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>Die \u00c4nderungen findest du in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-d798b67a56b8b25243341238d48d4d59b09ec8916bfe965ca7aa770fb1f9aa01\" target=\"_blank\" rel=\"noopener\">hier<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-3fc3fbbe6c0ef60247ca012202ca49ac6243ff3dd7e2a984f37350120cc40a59\" target=\"_blank\" rel=\"noopener\">hier<\/a> und die Ergebnisse in den angepassten Klassen <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/CustomerIdParser.java\" target=\"_blank\" rel=\"noopener\">CustomerIdParser<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ProductIdParser.java\" target=\"_blank\" rel=\"noopener\">ProductIdParser<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"exception-handler-implementieren\">Exception-Handler implementieren<\/h3>\n\n\n\n<p>In der Jakarta-RESTful-Webservices-Implementierung konnten wir bei Eingabefehlern eine <a href=\"https:\/\/jakarta.ee\/specifications\/platform\/10\/apidocs\/jakarta\/ws\/rs\/clienterrorexception\" target=\"_blank\" rel=\"noopener\">jakarta.ws.rs.ClientErrorException<\/a> werfen. Dieser Exception konnten wir ein <code>Response<\/code>-Objekt \u00fcbergeben, welches den HTTP-Statuscode und einen JSON-Response-Body mit einer Fehlermeldung enth\u00e4lt.<\/p>\n\n\n\n<p>Soweit ich wei\u00df, bietet das Spring-Framework keine entsprechende Exception. Korrigiere mich bitte \u00fcber die Kommentarfunktion, wenn ich falsch liege!<\/p>\n\n\n\n<p>Daher m\u00fcssen wir uns selbst eine solche Exception mitsamt zugeh\u00f6rigem Exception-Handler bauen. Die Exception (<a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ClientErrorException.java\" target=\"_blank\" rel=\"noopener\">ClientErrorException<\/a> im GitHub-Repository) bekommt einen Konstruktor, dem wir eine <code>ResponseEntity<\/code> \u00fcbergeben, die im Feld <code>response<\/code> gespeichert wird:<\/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\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ClientErrorException<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">RuntimeException<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Getter<\/span> <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> ResponseEntity&lt;ErrorEntity&gt; response;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">ClientErrorException<\/span><span class=\"hljs-params\">(ResponseEntity&lt;ErrorEntity&gt; response)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">super<\/span>(getMessage(response));\n    <span class=\"hljs-keyword\">this<\/span>.response = response;\n  }\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> String <span class=\"hljs-title\">getMessage<\/span><span class=\"hljs-params\">(ResponseEntity&lt;ErrorEntity&gt; response)<\/span> <\/span>{\n    ErrorEntity body = response.getBody();\n    <span class=\"hljs-keyword\">return<\/span> body != <span class=\"hljs-keyword\">null<\/span> ? body.errorMessage() : <span class=\"hljs-keyword\">null<\/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>Der Exception-Handler (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ClientErrorHandler.java\" target=\"_blank\" rel=\"noopener\">ClientErrorHandler<\/a>) ist ebenfalls schnell implementiert. Er bekommt eine <code>@RestControllerAdvice<\/code>-Annotation sowie eine <code>@ExceptionHandler<\/code>-annotierte Methode, die die in der <code>ClientErrorException<\/code> hinterlegte <code>ResponseEntity<\/code> zur\u00fcckliefert: <\/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-meta\">@RestControllerAdvice<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ClientErrorHandler<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@ExceptionHandler<\/span>(ClientErrorException<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)\n  <span class=\"hljs-title\">public<\/span> <span class=\"hljs-title\">ResponseEntity<\/span>&lt;<span class=\"hljs-title\">ErrorEntity<\/span>&gt; <span class=\"hljs-title\">handleProductNotFoundException<\/span>(\n      <span class=\"hljs-title\">ClientErrorException<\/span> <span class=\"hljs-title\">ex<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> ex.getResponse();\n  }\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>Dann sind nur noch <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-b064bd01f9e85fe9b09251b06cb5ccc2df8809df512efc0cbd7b73d88528da22\" target=\"_blank\" rel=\"noopener\">kleine Anpassungen<\/a> an der <code>ControllerCommons<\/code>-Klasse n\u00f6tig, um statt der <code>jakarta.ws.rs.ClientErrorException<\/code> unsere eigene <code>ClientErrorException<\/code> zu werfen. Hier die vollst\u00e4ndige <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ControllerCommons.java\" target=\"_blank\" rel=\"noopener\">ControllerCommons<\/a>-Klasse:<\/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\">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      HttpStatus 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> ResponseEntity&lt;ErrorEntity&gt; <span class=\"hljs-title\">errorResponse<\/span><span class=\"hljs-params\">(\n      HttpStatus status, String message)<\/span> <\/span>{\n    ErrorEntity errorEntity = <span class=\"hljs-keyword\">new<\/span> ErrorEntity(status.value(), message);\n    <span class=\"hljs-keyword\">return<\/span> ResponseEntity.status(status.value()).body(errorEntity);\n  }\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>Das Spring-Framework sorgt nun daf\u00fcr, dass eine <code>ClientErrorException<\/code> vom <code>ClientErrorHandler<\/code> abgefangen wird und der REST-Controller den in der Exception gespeicherten Fehlercode und die Fehlernachricht zur\u00fcckliefert.<\/p>\n\n\n\n<p>Nach der Anpassung der <code>ControllerCommons<\/code>-Klasse sollten die Compiler-Fehler in den Controllern und den Parsern verschwunden sein.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"integrationstests-von-quarkus-nach-spring-migrieren\">Integrationstests von Quarkus nach Spring migrieren<\/h3>\n\n\n\n<p>In den Integrationstests m\u00fcssen wir folgende \u00c4nderungen vornehmen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Annotationen austauschen,<\/li>\n\n\n\n<li>in den REST-Assured-Tests den Server-Port setzen,<\/li>\n\n\n\n<li>Jakartas <code>Response.Status<\/code> durch Springs <code>HttpStatus<\/code> ersetzen.<\/li>\n<\/ul>\n\n\n\n<p>Ich zeige die \u00c4nderungen beispielhaft am <code>ProductsControllerTest<\/code>. Dies ist der alte Stand:<\/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\">import<\/span> <span class=\"hljs-keyword\">static<\/span> jakarta.ws.rs.core.Response.Status.BAD_REQUEST;\n\n. . .\n\n<span class=\"hljs-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ProductsControllerTest<\/span> <\/span>{\n\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-meta\">@InjectMock<\/span> FindProductsUseCase findProductsUseCase;\n\n  . . .\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenANullQuery_findProducts_returnsError<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    Response response = given().get(<span class=\"hljs-string\">\"\/products\"<\/span>).then().extract().response();\n\n    assertThatResponseIsError(response, BAD_REQUEST, <span class=\"hljs-string\">\"Missing 'query'\"<\/span>);\n  }\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<h4 class=\"wp-block-heading\">Annotationen austauschen<\/h4>\n\n\n\n<p>Wir tauschen folgende Annotationen aus:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><thead><tr><th>JAX-RS-Annotation(en)<\/th><th>Beschreibung<\/th><th>Spring-Pendant<\/th><\/tr><\/thead><tbody><tr><td><code>@QuarkusTest<\/code><\/td><td>Startet die Anwendung vor dem Integrationstest.<\/td><td><code>@ActiveProfiles(\"test\")<br>@SpringBootTest(<\/code><br><nobr><code>&nbsp;&nbsp;&nbsp;&nbsp;webEnvironment = RANDOM_PORT)<\/code><\/nobr><\/td><\/tr><tr><td><code>@InjectMock<\/code><\/td><td>Mockt eine Bean und injiziert den Mock \u00fcberall dort, wo die Bean verwendet wird.<\/td><td><code>@MockBean<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Server-Port setzen<\/h4>\n\n\n\n<p>Dann informieren wir REST-Assured \u00fcber den Port der Anwendung (mit Quarkus geschah das automatisch). Durch folgende Zeile injiziert Spring den Server-Port in die Variable <code>port<\/code>:<\/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-meta\">@LocalServerPort<\/span> <span class=\"hljs-keyword\">private<\/span> Integer port;<\/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>Dann m\u00fcssen wir nach jedem Aufruf von <code>given()<\/code> den Aufruf <code>port(port)<\/code> einf\u00fcgen. Also z. B.:<\/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\">given()\n    .get(<span class=\"hljs-string\">\"\/products\"<\/span>)\n    .then().extract().response();<\/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>wird zu:<\/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\">given()\n    .port(port)  <span class=\"hljs-comment\">\/\/ \u27f5 set port<\/span>\n    .get(<span class=\"hljs-string\">\"\/products\"<\/span>)\n    .then().extract().response();<\/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<h4 class=\"wp-block-heading\">Response.Status durch HttpStatus ersetzen<\/h4>\n\n\n\n<p>Zuletzt ersetzen wir den statischen Import <code>jakarta.ws.rs.core.Response.Status.BAD_REQUEST<\/code> durch <code>org.springframework.http.HttpStatus.BAD_REQUEST<\/code>. Da der Name der Enum-Konstante, <code>BAD_REQUEST<\/code>, in beiden Frameworks gleich ist, m\u00fcssen wir hier nichts weiter tun.<\/p>\n\n\n\n<p>Die ge\u00e4nderte Testklasse sieht dann wie folgt aus:<\/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\">import<\/span> <span class=\"hljs-keyword\">static<\/span> org.springframework.http.HttpStatus.BAD_REQUEST;\n\n. . .\n\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test\"<\/span>)\n<span class=\"hljs-meta\">@SpringBootTest<\/span>(webEnvironment = WebEnvironment.RANDOM_PORT)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ProductsControllerTest<\/span> <\/span>{\n\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-meta\">@LocalServerPort<\/span> <span class=\"hljs-keyword\">private<\/span> Integer port;\n\n  <span class=\"hljs-meta\">@MockBean<\/span> FindProductsUseCase findProductsUseCase;\n\n  . . .\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenANullQuery_findProducts_returnsError<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    Response response = given().port(port).get(<span class=\"hljs-string\">\"\/products\"<\/span>).then().extract().response();\n\n    assertThatResponseIsError(response, BAD_REQUEST, <span class=\"hljs-string\">\"Missing 'query'\"<\/span>);\n  }\n\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>Der Aufruf von <code>assertThatResponseIsError(...)<\/code> kompiliert aktuell noch nicht, da die Methode noch nicht auf den Spring-<code>HttpStatus<\/code> umgestellt ist. Das machen wir als n\u00e4chstes.<\/p>\n\n\n\n<p>Wir ersetzen in der Klasse <code>HttpTestCommons<\/code> den Typ der Variablen <code>expectedStatus<\/code>, <code>Response.Status<\/code> durch <code>HttpStatus<\/code> und <code>expectedStatus.getStatusCode()<\/code> durch <code>expectedStatus.value()<\/code>. Die ge\u00e4nderte Klasse sieht dann wie folgt aus:<\/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\">public<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">HttpTestCommons<\/span> <\/span>{\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-title\">HttpTestCommons<\/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> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">assertThatResponseIsError<\/span><span class=\"hljs-params\">(\n      Response response,\n      HttpStatus expectedStatus,\n      String expectedErrorMessage)<\/span> <\/span>{\n    assertThat(response.getStatusCode()).isEqualTo(expectedStatus.value());\n\n    JsonPath json = response.jsonPath();\n\n    assertThat(json.getInt(<span class=\"hljs-string\">\"httpStatus\"<\/span>)).isEqualTo(expectedStatus.value());\n    assertThat(json.getString(<span class=\"hljs-string\">\"errorMessage\"<\/span>)).isEqualTo(expectedErrorMessage);\n  }\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>Den <code>CartsControllerTest<\/code> passen wir analog dazu an. Und in <code>CartsControllerAssertions<\/code> und <code>ProductsControllerAssertions<\/code> ersetzen wir den statischen Import <code>jakarta.ws.rs.core.Response.Status.OK<\/code> durch <code>org.springframework.http.HttpStatus.OK<\/code> und den Ausdruck <code>OK.getStatusCode()<\/code> durch <code>OK.value()<\/code>.<\/p>\n\n\n\n<p>Du findest alle \u00c4nderungen an den Tests <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1378747afa1a8d3f30a1f2495dbfd9554f24110d#diff-16bfd3366b313b07f92512d8425af075034654b571669eac5b9edf5b88ad8dbf\" target=\"_blank\" rel=\"noopener\">ab dieser Position im Git-Commit<\/a> und die ge\u00e4nderten Klassen in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/with-spring-boot\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\" target=\"_blank\" rel=\"noopener\">diesem Verzeichnis<\/a> im GitHub-Repository.<\/p>\n\n\n\n<p>Damit haben wir die prim\u00e4ren REST-Adapter von Quarkus nach Spring migriert. Im n\u00e4chsten Schritt wenden wir uns den sekund\u00e4ren Persistenzadaptern zu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"schritt-3-jpa-adapter-von-panache-nach-spring-data-jpa-migrieren\">Schritt 3: JPA-Adapter von Panache nach Spring Data JPA migrieren<\/h2>\n\n\n\n<p>Um die sekund\u00e4ren Persistenzadapter nach Spring zu migrieren, gehen wir wie folgt vor:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wir tauschen die Annotationen aus, die steuern, ob die In-Memory-Adapter oder die JPA-Adapter verwendet werden.<\/li>\n\n\n\n<li>Wir ersetzen die Bean-erzeugenden Annotationen.<\/li>\n\n\n\n<li>Wir ersetzen die Panache-Repositories durch Spring-Data-JPA-Repositories.<\/li>\n\n\n\n<li>Da Springs <code>findById(...)<\/code>-Methoden <code>Optional<\/code>s zur\u00fcckliefern, m\u00fcssen wir ein paar kleine Anpassungen an den Mappern durchf\u00fchren. <\/li>\n\n\n\n<li>Abschlie\u00dfend passen wir die Integrationstests an.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"annotationen-austauschen\">Annotationen austauschen<\/h3>\n\n\n\n<p>In der Quarkus-Version haben wir die folgenden Annotationen verwendet, um zu steuern, welche Persistenzadapter verwendet werden:<\/p>\n\n\n\n<p>An den In-Memory-Adaptern <code>InMemoryCartRepository<\/code> und <code>InMemoryProductRepository<\/code>:<\/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-meta\">@LookupIfProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, stringValue = <span class=\"hljs-string\">\"inmemory\"<\/span>, \n                  lookupIfMissing = <span class=\"hljs-keyword\">true<\/span>)<\/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>An den JPA-Adaptern <code>JpaCartRepository<\/code> und <code>JpaProductRepository<\/code>:<\/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-meta\">@LookupIfProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, stringValue = <span class=\"hljs-string\">\"mysql\"<\/span>)\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>F\u00fcr die Spring-Version ersetzen wir die Annotationen wie folgt:<\/p>\n\n\n\n<p>In-Memory-Adapter:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@ConditionalOnProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, havingValue = <span class=\"hljs-string\">\"inmemory\"<\/span>, \n                       matchIfMissing = <span class=\"hljs-keyword\">true<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><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>JPA-Adapter:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@ConditionalOnProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, havingValue = <span class=\"hljs-string\">\"mysql\"<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><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>Au\u00dferdem ersetzen wir in denselben vier Klassen die Bean-erzeugende Annotation <code>@ApplicationScoped<\/code> durch <code>@Repository<\/code>.<\/p>\n\n\n\n<p>Hier findest du die nach Spring migrierten Klassen <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryCartRepository.java\" target=\"_blank\" rel=\"noopener\">InMemoryCartRepository<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/inmemory\/InMemoryProductRepository.java\" target=\"_blank\" rel=\"noopener\">InMemoryProductRepository<\/a>. An den JPA-Adaptern werden wir im n\u00e4chsten Abschnitt noch weitere \u00c4nderungen vornehmen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"panache-repositories-durch-spring-data-jpa-repositories-ersetzen\">Panache-Repositories durch Spring-Data-JPA-Repositories ersetzen<\/h3>\n\n\n\n<p>In der Quarkus-Variante haben wir Panache verwendet, um JPA-Entities komfortabel aus der Datenbank zu laden. Das Spring-Pendant dazu lautet \u201eSpring Data JPA\u201d.<\/p>\n\n\n\n<p>Das folgende Listing zeigt die beiden Panache-Repositories:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@ApplicationScoped<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaCartPanacheRepository<\/span>\n    <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">PanacheRepositoryBase<\/span>&lt;<span class=\"hljs-title\">CartJpaEntity<\/span>, <span class=\"hljs-title\">Integer<\/span>&gt; <\/span>{}\n\n<span class=\"hljs-meta\">@ApplicationScoped<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaProductPanacheRepository<\/span>\n    <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">PanacheRepositoryBase<\/span>&lt;<span class=\"hljs-title\">ProductJpaEntity<\/span>, <span class=\"hljs-title\">String<\/span>&gt; <\/span>{}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><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 siehst du die entsprechenden Spring-Data-JPA-Repositories (beachte auch die ge\u00e4nderten Klassennamen):<\/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-meta\">@Repository<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">JpaCartSpringDataRepository<\/span>\n    <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">JpaRepository<\/span>&lt;<span class=\"hljs-title\">CartJpaEntity<\/span>, <span class=\"hljs-title\">Integer<\/span>&gt; <\/span>{}\n\n<span class=\"hljs-meta\">@Repository<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">JpaProductSpringDataRepository<\/span>\n    <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">JpaRepository<\/span>&lt;<span class=\"hljs-title\">ProductJpaEntity<\/span>, <span class=\"hljs-title\">String<\/span>&gt; <\/span>{\n\n  <span class=\"hljs-meta\">@Query<\/span>(<span class=\"hljs-string\">\"SELECT p FROM ProductJpaEntity p \"<\/span>\n          + <span class=\"hljs-string\">\"WHERE p.name like ?1 or p.description like ?1\"<\/span>)\n  <span class=\"hljs-function\">List&lt;ProductJpaEntity&gt; <span class=\"hljs-title\">findByNameOrDescriptionLike<\/span><span class=\"hljs-params\">(String pattern)<\/span><\/span>;\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>Es gibt zwei bedeutende Unterschiede:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Die Panache-Repositories sind Klassen, die Spring-Data-JPA-Repositories sind Interfaces.<\/li>\n\n\n\n<li>Im Panache-Repository haben wir die Suche \u00fcber die <code>PanacheRepositoryBase.find(...)<\/code>-Methode realisiert, indem wir dieser eine HQL-WHERE-Query \u00fcbergeben haben. Im Spring-Data-JPA-Repository k\u00f6nnen wir daf\u00fcr eine separate Interface-Methode deklarieren und diese mit einer JPQL-Query-Annotation versehen. Hierauf komme ich weiter unten noch einmal zur\u00fcck, wenn ich die \u00c4nderungen an den Adapter-Klassen zeige. <\/li>\n<\/ul>\n\n\n\n<p>Nun m\u00fcssen wir noch unsere Adapter <code>JpaCartRepository<\/code> und <code>JpaProductRepository<\/code> anpassen und statt der Panache-Repositories die Spring-Data-JPA-Repositories verwenden.<\/p>\n\n\n\n<p>Beginnen wir mit dem einfacheren Adapter, <code>JpaCartRepository<\/code>. Bisher sieht er so aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@LookupIfProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, stringValue = <span class=\"hljs-string\">\"mysql\"<\/span>)\n<span class=\"hljs-meta\">@ApplicationScoped<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaCartRepository<\/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> JpaCartPanacheRepository panacheRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">JpaCartRepository<\/span><span class=\"hljs-params\">(JpaCartPanacheRepository panacheRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.panacheRepository = panacheRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    panacheRepository.getEntityManager().merge(CartMapper.toJpaEntity(cart));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    CartJpaEntity cartJpaEntity = panacheRepository.findById(customerId.value());\n    <span class=\"hljs-keyword\">return<\/span> CartMapper.toModelEntityOptional(cartJpaEntity);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">deleteByCustomerId<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    panacheRepository.deleteById(customerId.value());\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><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 ersetzen die zwei Klassen-Annotationen <code>@LookupIfProperty<\/code> und <code>@ApplicationScoped<\/code> wie im Abschnitt \u201eAnnotationen austauschen\u201d oben beschrieben.<\/p>\n\n\n\n<p>Wir ersetzen das Feld <code>JpaCartPanacheRepository panacheRepository<\/code> durch <code>JpaCartSpringDataRepository springDataRepository<\/code> und passen den Konstruktor entsprechend an.<\/p>\n\n\n\n<p>Den Aufruf <code>panacheRepository.getEntityManager().merge(...)<\/code> ersetzen wir durch <code>springDataRepository.save(...)<\/code>.<\/p>\n\n\n\n<p>W\u00e4hrend die <code>panacheRepository.findById(...)<\/code>-Methode ein <code>CartJpaEntity<\/code> zur\u00fccklieferte, gibt <code>springDataRepository.findById(...)<\/code> ein <code>Optional&lt;CartJpaEntity&gt;<\/code> zur\u00fcck. Entsprechend passen wir den Aufruf des Mappers an.<\/p>\n\n\n\n<p>Hier siehst du die Spring-Version der Adapter-Klasse:<\/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-meta\">@ConditionalOnProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, havingValue = <span class=\"hljs-string\">\"mysql\"<\/span>)\n<span class=\"hljs-meta\">@Repository<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaCartRepository<\/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> JpaCartSpringDataRepository springDataRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">JpaCartRepository<\/span><span class=\"hljs-params\">(JpaCartSpringDataRepository springDataRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.springDataRepository = springDataRepository;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    springDataRepository.save(CartMapper.toJpaEntity(cart));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    Optional&lt;CartJpaEntity&gt; cartJpaEntity =\n        springDataRepository.findById(customerId.value());\n    <span class=\"hljs-keyword\">return<\/span> cartJpaEntity.map(CartMapper::toModelEntity);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">deleteByCustomerId<\/span><span class=\"hljs-params\">(CustomerId customerId)<\/span> <\/span>{\n    springDataRepository.deleteById(customerId.value());\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>Die Methodenreferenz <code>CartMapper::toModelEntity<\/code> f\u00fchrt aktuell noch zu einem Compilerfehler, da die entsprechende Methode noch nicht existiert. Im <code>CartMapper<\/code> haben wir aktuell eine Methode <code>toModelEntityOptional(...)<\/code>, die aus einem <code>CartJpaEntity<\/code> ein <code>Optional&lt;Cart&gt;<\/code> macht:<\/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-function\"><span class=\"hljs-keyword\">static<\/span> Optional&lt;Cart&gt; <span class=\"hljs-title\">toModelEntityOptional<\/span><span class=\"hljs-params\">(CartJpaEntity cartJpaEntity)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (cartJpaEntity == <span class=\"hljs-keyword\">null<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> Optional.empty();\n  }\n\n  CustomerId customerId = <span class=\"hljs-keyword\">new<\/span> CustomerId(cartJpaEntity.getCustomerId());\n  Cart cart = <span class=\"hljs-keyword\">new<\/span> Cart(customerId);\n\n  <span class=\"hljs-keyword\">for<\/span> (CartLineItemJpaEntity lineItemJpaEntity : cartJpaEntity.getLineItems()) {\n    cart.putProductIgnoringNotEnoughItemsInStock(\n        ProductMapper.toModelEntity(lineItemJpaEntity.getProduct()),\n        lineItemJpaEntity.getQuantity());\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> Optional.of(cart);\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>Das Spring-Data-JPA-Repository liefert uns bereits ein <code>Optional&lt;CartJpaEntity&gt;<\/code>, welches wir mit der <code>map(...)<\/code>-Methode auf ein <code>Optional&lt;Cart&gt;<\/code> mappen k\u00f6nnen. Daf\u00fcr ersetzen wir die <code>toModelEntityOptional(...)<\/code>-Methode durch folgende <code>toModelEntity(...)<\/code>-Methode:<\/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-function\"><span class=\"hljs-keyword\">static<\/span> Cart <span class=\"hljs-title\">toModelEntity<\/span><span class=\"hljs-params\">(CartJpaEntity cartJpaEntity)<\/span> <\/span>{\n  CustomerId customerId = <span class=\"hljs-keyword\">new<\/span> CustomerId(cartJpaEntity.getCustomerId());\n  Cart cart = <span class=\"hljs-keyword\">new<\/span> Cart(customerId);\n\n  <span class=\"hljs-keyword\">for<\/span> (CartLineItemJpaEntity lineItemJpaEntity : cartJpaEntity.getLineItems()) {\n    cart.putProductIgnoringNotEnoughItemsInStock(\n        ProductMapper.toModelEntity(lineItemJpaEntity.getProduct()),\n        lineItemJpaEntity.getQuantity());\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> cart;\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><code>JpaCartRepository<\/code> sollte nun keine Compiler-Fehler mehr anzeigen.<\/p>\n\n\n\n<p>Schauen wir als n\u00e4chstes auf die Klasse <code>JpaProductRepository<\/code>. Hier die Quarkus-Version:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@LookupIfProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, stringValue = <span class=\"hljs-string\">\"mysql\"<\/span>)\n<span class=\"hljs-meta\">@ApplicationScoped<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaProductRepository<\/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> JpaProductPanacheRepository panacheRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">JpaProductRepository<\/span><span class=\"hljs-params\">(JpaProductPanacheRepository panacheRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.panacheRepository = panacheRepository;\n  }\n\n  <span class=\"hljs-meta\">@PostConstruct<\/span>\n  <span class=\"hljs-function\"><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-meta\">@Transactional<\/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    panacheRepository.getEntityManager().merge(ProductMapper.toJpaEntity(product));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    ProductJpaEntity jpaEntity = panacheRepository.findById(productId.value());\n    <span class=\"hljs-keyword\">return<\/span> ProductMapper.toModelEntityOptional(jpaEntity);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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 queryString)<\/span> <\/span>{\n    List&lt;ProductJpaEntity&gt; entities =\n        panacheRepository\n            .find(<span class=\"hljs-string\">\"name like ?1 or description like ?1\"<\/span>, <span class=\"hljs-string\">\"%\"<\/span> + queryString + <span class=\"hljs-string\">\"%\"<\/span>)\n            .list();\n\n    <span class=\"hljs-keyword\">return<\/span> ProductMapper.toModelEntities(entities);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><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 machen zun\u00e4chst einmal die gleichen Anpassungen wie beim <code>JpaCartAdapter<\/code>. <\/p>\n\n\n\n<p>In der <code>findByNameOrDescription(...)<\/code>-Methode ersetzen wir au\u00dferdem den Aufruf der <code>find(...)<\/code>-Methode:<\/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\">panacheRepository\n    .find(<span class=\"hljs-string\">\"name like ?1 or description like ?1\"<\/span>, <span class=\"hljs-string\">\"%\"<\/span> + queryString + <span class=\"hljs-string\">\"%\"<\/span>)\n    .list();<\/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>durch einen Aufruf der im Spring-Data-JPA-Interface deklarierten <code>findByNameOrDescriptionLike(...)<\/code>-Methode.<\/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\">springDataRepository.findByNameOrDescriptionLike(<span class=\"hljs-string\">\"%\"<\/span> + queryString + <span class=\"hljs-string\">\"%\"<\/span>);<\/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>Bei der Panache-Variante \u00fcbergeben wir an dieser Stelle eine HQL-(Hibernate Query Language)-WHERE-Query; bei der Spring-Variante haben wir hingegen die  <code>findByNameOrDescriptionLike(...)<\/code>-Methode im <code>JpaProductSpringDataRepository<\/code>-Interface mit einer JPQL-Query annotiert:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@Query<\/span>(<span class=\"hljs-string\">\"SELECT p FROM ProductJpaEntity p \"<\/span>\n        + <span class=\"hljs-string\">\"WHERE p.name like ?1 or p.description like ?1\"<\/span>)\n<span class=\"hljs-function\">List&lt;ProductJpaEntity&gt; <span class=\"hljs-title\">findByNameOrDescriptionLike<\/span><span class=\"hljs-params\">(String pattern)<\/span><\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><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 siehst du die nach  Spring migrierte Klasse:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@ConditionalOnProperty<\/span>(name = <span class=\"hljs-string\">\"persistence\"<\/span>, havingValue = <span class=\"hljs-string\">\"mysql\"<\/span>)\n<span class=\"hljs-meta\">@Repository<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaProductRepository<\/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> JpaProductSpringDataRepository springDataRepository;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">JpaProductRepository<\/span><span class=\"hljs-params\">(JpaProductSpringDataRepository springDataRepository)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.springDataRepository = springDataRepository;\n  }\n\n  <span class=\"hljs-meta\">@PostConstruct<\/span>\n  <span class=\"hljs-function\"><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-meta\">@Transactional<\/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    springDataRepository.save(ProductMapper.toJpaEntity(product));\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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    Optional&lt;ProductJpaEntity&gt; jpaEntity = \n        springDataRepository.findById(productId.value());\n    <span class=\"hljs-keyword\">return<\/span> jpaEntity.map(ProductMapper::toModelEntity);\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-meta\">@Transactional<\/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 queryString)<\/span> <\/span>{\n    List&lt;ProductJpaEntity&gt; entities =\n        springDataRepository.findByNameOrDescriptionLike(<span class=\"hljs-string\">\"%\"<\/span> + queryString + <span class=\"hljs-string\">\"%\"<\/span>);\n\n    <span class=\"hljs-keyword\">return<\/span> ProductMapper.toModelEntities(entities);\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\">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>Du findest alle \u00c4nderungen (einschlie\u00dflich derer an den Mappern, die ich hier nicht mit abgedruckt habe) in diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1ee433f28074752b03b600a2fe22555cf703c99f\" target=\"_blank\" rel=\"noopener\">Git-Commit<\/a> und die Ergebnisse in den Klassen <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/JpaCartRepository.java\" target=\"_blank\" rel=\"noopener\">JpaCartRepository<\/a>, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/JpaCartSpringDataRepository.java\" target=\"_blank\" rel=\"noopener\">JpaCartSpringDataRepository<\/a>, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/CartMapper.java\" target=\"_blank\" rel=\"noopener\">CartMapper<\/a>, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/JpaProductRepository.java\" target=\"_blank\" rel=\"noopener\">JpaProductRepository<\/a>, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/JpaProductSpringDataRepository.java\" target=\"_blank\" rel=\"noopener\">JpaProductSpringDataRepository<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\/jpa\/ProductMapper.java\" target=\"_blank\" rel=\"noopener\">ProductMapper<\/a>.  <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"anpassung-der-integrationstests\">Anpassung der Integrationstests<\/h3>\n\n\n\n<p>In den Integrationstests f\u00fcr die Perstistenzadapter m\u00fcssen wir:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>die Dependency Injection anpassen,<\/li>\n\n\n\n<li>die Konfiguration der Testprofile von Quarkus an Spring anpassen.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Dependency Injection anpassen<\/h4>\n\n\n\n<p>Ich zeige dir die notwendigen Anpassungen an der Dependency Injection an der Klasse <code>AbstractProductRepositoryTest<\/code> \u2013 hier ist der aktuelle Stand:<\/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\">public<\/span> <span class=\"hljs-keyword\">abstract<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Inject<\/span> Instance&lt;ProductRepository&gt; productRepositoryInstance;\n\n  <span class=\"hljs-keyword\">private<\/span> ProductRepository 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 = productRepositoryInstance.get();\n  }\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>Bei Quarkus mussten wir das Produkt-Repository, was entweder ein In-Memory- oder ein JPA-Repository sein kann, als <code>Instance&lt;ProductRepository&gt;<\/code> injizieren und dann in der <code>@BeforeEach<\/code>-Methode \u00fcber <code>Instance.get()<\/code> auslesen.<\/p>\n\n\n\n<p>In Spring geht das einfacher. Wir k\u00f6nnen die konfigurierbare Bean, wie jede andere Bean, ganz ohne Umwege injizieren \u2013 und eine <code>@BeforeEach<\/code>-Methode brauchen wir nicht:<\/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\">public<\/span> <span class=\"hljs-keyword\">abstract<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Autowired<\/span> ProductRepository productRepository;\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>Die Klasse <code>AbstractCartRepositoryTest<\/code> wird analog angepasst. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Quarkus Testprofile nach Spring migrieren<\/h4>\n\n\n\n<p>In der Quarkus-Variante haben wir eine Testprofil-Klasse <code>TestProfileWithMySQL<\/code> definiert, die die Konfiguration <em>persistence=mysql<\/em> setzt:<\/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\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestProfileWithMySQL<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">QuarkusTestProfile<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Map&lt;String, String&gt; <span class=\"hljs-title\">getConfigOverrides<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> Map.of(<span class=\"hljs-string\">\"persistence\"<\/span>, <span class=\"hljs-string\">\"mysql\"<\/span>);\n  }\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>Diese Klasse k\u00f6nnen wir l\u00f6schen. In Spring ben\u00f6tigen wir solch eine Klasse nicht. Testprofile in Spring ben\u00f6tigen einen Namen und eine Properties-Datei. Wir legen im Verzeichnis <em>test\/resources<\/em> des <em>application<\/em>-Moduls die Datei <em>application-test-with-mysql.properties<\/em> an mit folgendem Inhalt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">spring.datasource.url=jdbc:tc:mysql:8.0:\/\/\/shop\nspring.jpa.hibernate.ddl-auto=update\n\npersistence=mysql<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><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>In der letzten Zeile definieren wir den aus Quarkus bekannten Konfigurationseintrag <em>persistence=mysql<\/em>. Die zwei Zeilen zu Beginn legen fest, dass wir als Datenbank Testcontainers (\u201etc\u201d) verwenden und dass Hibernate die Tabellen automatisch anlegen soll. Beides hat Quarkus automatisch gemacht.<\/p>\n\n\n\n<p>Ich hatte zuvor schon erw\u00e4hnt, dass Spring wegen des vorhandenen <em>spring-data-jpa<\/em>-Moduls auch im In-Memory-Modus eine Datenbank ben\u00f6tigt, da es sonst nicht startet. Daf\u00fcr legen wir noch eine Datei <em>application-test.properties<\/em> an mit folgendem Inhalt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">spring.datasource.url=jdbc:h2:mem:testdb\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><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>Im In-Memory-Modus soll also eine H2-Datenbank verwendet werden. Dies ist der einfachste Workaround, der mir f\u00fcr unsere Demo-Anwendung eingefallen ist.<\/p>\n\n\n\n<p>Um nun f\u00fcr die jeweiligen Tests die entsprechenden Profile zu aktivieren, m\u00fcssen wir noch ein paar Annotationen in den folgenden Testklassen \u00e4ndern. So sehen die konkreten Persistenztests in der Quarkus-Variante aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryCartRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractCartRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryProductRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-meta\">@TestProfile<\/span>(TestProfileWithMySQL<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)\n<span class=\"hljs-title\">class<\/span> <span class=\"hljs-title\">JpaCartRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractCartRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-meta\">@TestProfile<\/span>(TestProfileWithMySQL<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)\n<span class=\"hljs-title\">class<\/span> <span class=\"hljs-title\">JpaProductRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><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 ersetzen die Annotationen wie folgt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@QuarkusTest<\/code> ersetzen wir durch <code>@SpringBootTest<\/code>.<\/li>\n\n\n\n<li><code>@TestProfile(TestProfileWithMySQL.class)<\/code> ersetzen wir durch <code>@ActiveProfiles(\"test-with-mysql\")<\/code>.<\/li>\n\n\n\n<li>Und den In-Memory-Tests m\u00fcssen wir noch die Annotation <code>@ActiveProfiles(\"test\")<\/code> hinzuf\u00fcgen, damit sie im <em>test-with-mysql<\/em>-Profil nicht erzeugt werden. Bei Quarkus wurden sie automatisch nicht erzeugt, wenn ein Profil aktiviert ist.<\/li>\n<\/ul>\n\n\n\n<p>Nach den \u00c4nderungen sehen die Testklassen wie folgt aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@SpringBootTest<\/span>\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test\"<\/span>)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryCartRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractCartRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@SpringBootTest<\/span>\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test\"<\/span>)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">InMemoryProductRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@SpringBootTest<\/span>\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test-with-mysql\"<\/span>)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaCartRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractCartRepositoryTest<\/span> <\/span>{}\n\n<span class=\"hljs-meta\">@SpringBootTest<\/span>\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test-with-mysql\"<\/span>)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JpaProductRepositoryTest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">AbstractProductRepositoryTest<\/span> <\/span>{}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><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>Du findest alle \u00c4nderungen an den Tests <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/1ee433f28074752b03b600a2fe22555cf703c99f#diff-690cffd5aa530789cb634a26e754cae2655cc1e62d61c306f6e80e50a3476879\" target=\"_blank\" rel=\"noopener\">ab dieser Position im Git-Commit<\/a> und die ge\u00e4nderten Testklassen in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/tree\/with-spring-boot\/adapter\/src\/test\/java\/eu\/happycoders\/shop\/adapter\/out\/persistence\" target=\"_blank\" rel=\"noopener\">diesem Verzeichnis<\/a> im GitHub-Repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"schritt-4-anwendungskonfiguration-von-quarkus-nach-spring-boot-migrieren\">Schritt 4: Anwendungskonfiguration von Quarkus nach Spring Boot migrieren<\/h2>\n\n\n\n<p>In diesem Schritt ersetzen wir die Quarkus-Anwendungskonfiguration im Adapter-Modul durch eine Spring-Boot-Konfiguration. Am Ende dieses vergleichweise kurzen Schritts werden wir das Adapter-Modul kompilieren und die Tests ausf\u00fchren k\u00f6nnen.<\/p>\n\n\n\n<p>Die Quarkus-Anwendungskonfiguration liegt in der Klasse <code>QuarkusAppConfig<\/code>, die aktuell wie folgt aussieht:<\/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-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">QuarkusAppConfig<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Inject<\/span> Instance&lt;CartRepository&gt; cartRepository;\n\n  <span class=\"hljs-meta\">@Inject<\/span> Instance&lt;ProductRepository&gt; productRepository;\n\n  <span class=\"hljs-meta\">@Produces<\/span>\n  <span class=\"hljs-meta\">@ApplicationScoped<\/span>\n  <span class=\"hljs-function\">GetCartUseCase <span class=\"hljs-title\">getCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> GetCartService(cartRepository.get());\n  }\n\n  <span class=\"hljs-meta\">@Produces<\/span>\n  <span class=\"hljs-meta\">@ApplicationScoped<\/span>\n  <span class=\"hljs-function\">EmptyCartUseCase <span class=\"hljs-title\">emptyCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> EmptyCartService(cartRepository.get());\n  }\n\n  <span class=\"hljs-meta\">@Produces<\/span>\n  <span class=\"hljs-meta\">@ApplicationScoped<\/span>\n  <span class=\"hljs-function\">FindProductsUseCase <span class=\"hljs-title\">findProductsUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> FindProductsService(productRepository.get());\n  }\n\n  <span class=\"hljs-meta\">@Produces<\/span>\n  <span class=\"hljs-meta\">@ApplicationScoped<\/span>\n  <span class=\"hljs-function\">AddToCartUseCase <span class=\"hljs-title\">addToCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> AddToCartService(cartRepository.get(), productRepository.get());\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>Zur Erinnerung:<\/p>\n\n\n\n<p>Wir ben\u00f6tigen diese Klasse, um die im <em>application<\/em>-Modul liegenden Service-Klassen zu instanziieren. Denn das <em>application<\/em>-Modul wei\u00df nichts von Quarkus (es ist ein technisches Detail, das dort nicht hingeg\u00f6rt), und somit k\u00f6nnen und wollen wir dort auch keine <code>@ApplicationScoped<\/code>-Annotationen einsetzen.<\/p>\n\n\n\n<p>Das gleiche gilt f\u00fcr Spring. Das <em>application<\/em>-Modul wei\u00df nichts von Spring, und entsprechend k\u00f6nnen wir dort auch keine <code>@Service<\/code>-Annotation verwenden. <\/p>\n\n\n\n<p>Was m\u00fcssen wir nun konkret \u00e4ndern? <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wir bennenen die Klasse <code>QuarkusAppConfig<\/code> um in <code>SpringAppConfig<\/code>.<\/li>\n\n\n\n<li>Wir annotieren die Klasse mit <code>@SpringBootApplication<\/code>.<\/li>\n\n\n\n<li>Wir ersetzen die <code>@Inject<\/code>-Annotationen durch <code>@Autowired<\/code>.<\/li>\n\n\n\n<li>Wir entfernen die <code>Instance&lt;...&gt;<\/code>-Wrapper um <code>CartRepository<\/code> und <code>ProductRepository<\/code> (so wie wir es auch in den Tests der Persistenzadapter gemacht haben).<\/li>\n\n\n\n<li>Wir ersetzen das Annotation-Paar <code>@Produces<\/code> <code>@ApplicationScoped<\/code> durch <code>@Bean<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>Danach sieht die Klasse wie folgt aus:<\/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-meta\">@SpringBootApplication<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">SpringAppConfig<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@Autowired<\/span> CartRepository cartRepository;\n\n  <span class=\"hljs-meta\">@Autowired<\/span> ProductRepository productRepository;\n\n  <span class=\"hljs-meta\">@Bean<\/span>\n  <span class=\"hljs-function\">GetCartUseCase <span class=\"hljs-title\">getCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> GetCartService(cartRepository);\n  }\n\n  <span class=\"hljs-meta\">@Bean<\/span>\n  <span class=\"hljs-function\">EmptyCartUseCase <span class=\"hljs-title\">emptyCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> EmptyCartService(cartRepository);\n  }\n\n  <span class=\"hljs-meta\">@Bean<\/span>\n  <span class=\"hljs-function\">FindProductsUseCase <span class=\"hljs-title\">findProductsUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> FindProductsService(productRepository);\n  }\n\n  <span class=\"hljs-meta\">@Bean<\/span>\n  <span class=\"hljs-function\">AddToCartUseCase <span class=\"hljs-title\">addToCartUseCase<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> AddToCartService(cartRepository, productRepository);\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>Du findest die \u00c4nderungen in diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/8f86ab11e8a682d85b58b6c8d23d4e7b73a3ee2c\" target=\"_blank\" rel=\"noopener\">Git-Commit<\/a> und das Ergebnis in der Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/SpringAppConfig.java\" target=\"_blank\" rel=\"noopener\">SpringAppConfig<\/a>.<\/p>\n\n\n\n<p>Damit haben wir das Adapter-Modul fertig migriert. Ein Aufruf von <code>mvn spotless:apply clean verify<\/code> sollte nun (trotz einiger Compiler-Fehler im <em>bootstrap<\/em>-Modul) zeigen, dass sich das Adapter-Modul erfolgreich kompilieren l\u00e4sst und dass alle Tests erfolgreich durchlaufen:<\/p>\n\n\n\n<pre class=\"wp-block-code hc-mvn-out\"><code>[<span class=\"info\">INFO<\/span>] parent ............................................. <span class=\"success\">SUCCESS<\/span> [  0.388 s]\n[<span class=\"info\">INFO<\/span>] model .............................................. <span class=\"success\">SUCCESS<\/span> [  3.178 s]\n[<span class=\"info\">INFO<\/span>] application ........................................ <span class=\"success\">SUCCESS<\/span> [  2.586 s]\n[<span class=\"info\">INFO<\/span>] adapter ............................................ <span class=\"success\">SUCCESS<\/span> [ 35.616 s]\n[<span class=\"info\">INFO<\/span>] bootstrap .......................................... <span class=\"failure\">FAILURE<\/span> [  0.901 s]\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------\n[<span class=\"info\">INFO<\/span>] <span class=\"failure\">BUILD FAILURE<\/span>\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------<\/code><\/pre>\n\n\n\n<p>Damit haben wir einen Gro\u00dfteil der Migration von Quarkus zu Spring Boot erledigt. Im n\u00e4chsten Kapitel wenden wir uns dem letzten Modul, dem <em>bootstrap<\/em>-Modul, zu. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"schritt-5-konfiguration-starter-klasse-und-end-to-end-tests\">Schritt 5: Konfiguration, Starter-Klasse und End-to-End-Tests<\/h2>\n\n\n\n<p>Im <em>bootstrap<\/em>-Modul m\u00fcssen wir zun\u00e4chst die Quarkus-Konfiguration in eine Spring-Konfiguration \u00fcbersetzen.<\/p>\n\n\n\n<p>In Quarkus liegen die Konfigurationsparameter f\u00fcr alle Profile in der Datei <code>application.properties<\/code> im <em>src\/main\/resources<\/em>-Verzeichnis des <em>bootstrap<\/em>-Moduls. Den Konfigurationsparametern ist der Profilname \u201eprod\u201d bzw. \u201emysql\u201d mit einem Prozentzeichen vorangestellt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"Properties\" data-shcb-language-slug=\"properties\"><span><code class=\"hljs language-properties\"><span class=\"hljs-meta\">%prod.quarkus.datasource.jdbc.url<\/span>=<span class=\"hljs-string\">dummy<\/span>\n<span class=\"hljs-meta\">%prod.persistence<\/span>=<span class=\"hljs-string\">inmemory<\/span>\n\n<span class=\"hljs-meta\">%mysql.quarkus.datasource.jdbc.url<\/span>=<span class=\"hljs-string\">jdbc:mysql:\/\/localhost:3306\/shop<\/span>\n<span class=\"hljs-meta\">%mysql.quarkus.datasource.username<\/span>=<span class=\"hljs-string\">root<\/span>\n<span class=\"hljs-meta\">%mysql.quarkus.datasource.password<\/span>=<span class=\"hljs-string\">test<\/span>\n<span class=\"hljs-meta\">%mysql.quarkus.hibernate-orm.database.generation<\/span>=<span class=\"hljs-string\">update<\/span>\n<span class=\"hljs-meta\">%mysql.persistence<\/span>=<span class=\"hljs-string\">mysql<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Properties<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">properties<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Im Default-Produktionsprofil \u201eprod\u201d wollen wir die In-Memory-Adapter aktivieren, und im Profil \u201emysql\u201d wollen wir die JPA-Adapter aktivieren und diese mit einer lokalen MySQL-Datenbank verbinden.<\/p>\n\n\n\n<p>F\u00fcr Spring splitten wir die Konfiguration in zwei Dateien auf: <em>application.properties<\/em> f\u00fcr den Default-Modus und <em>application-mysql.properties<\/em> f\u00fcr den MySQL-Modus.<\/p>\n\n\n\n<p><em>application.properties<\/em> enth\u00e4lt die ersten zwei Zeilen mit folgenden Anpassungen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>der Profil-Pr\u00e4fix ist entfernt,<\/li>\n\n\n\n<li>der Key <code>quarkus.datasource.jdbc.url<\/code> ist durch <code>spring.datasource.url<\/code> ersetzt,<\/li>\n\n\n\n<li>die Dummy-Datenquelle, die, wie oben bereits erw\u00e4hnt, Spring zum Absturz bringen w\u00fcrde, ist durch eine H2-Datenquelle ersetzt.<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"Properties\" data-shcb-language-slug=\"properties\"><span><code class=\"hljs language-properties\"><span class=\"hljs-meta\">spring.datasource.url<\/span>=<span class=\"hljs-string\">jdbc:h2:mem:testdb<\/span>\n<span class=\"hljs-attr\">persistence<\/span>=<span class=\"hljs-string\">inmemory<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Properties<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">properties<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <em>application-mysql.properties<\/em> enth\u00e4lt die unteren f\u00fcnf Zeilen der Quarkus-<em>application.properties<\/em>, entsprechend angepasst auf Spring:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"Properties\" data-shcb-language-slug=\"properties\"><span><code class=\"hljs language-properties\"><span class=\"hljs-meta\">spring.datasource.url<\/span>=<span class=\"hljs-string\">jdbc:mysql:\/\/localhost:3306\/shop<\/span>\n<span class=\"hljs-meta\">spring.datasource.username<\/span>=<span class=\"hljs-string\">root<\/span>\n<span class=\"hljs-meta\">spring.datasource.password<\/span>=<span class=\"hljs-string\">test<\/span>\n<span class=\"hljs-meta\">spring.jpa.hibernate.ddl-auto<\/span>=<span class=\"hljs-string\">update<\/span>\n<span class=\"hljs-attr\">persistence<\/span>=<span class=\"hljs-string\">mysql<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Properties<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">properties<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"spring-starter-klasse\">Spring-Starter-Klasse<\/h3>\n\n\n\n<p>Zuletzt brauchen wir f\u00fcr Spring noch eine Starter-Klasse, die bei Quarkus nicht erforderlich war. Wir legen dazu eine Klasse <code>Launcher<\/code> im <em>eu.happycoders.shop.bootstrap<\/em>-Paket des <em>bootstrap<\/em>-Moduls an, mit folgendem Inhalt: <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><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-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    SpringApplication.run(SpringAppConfig<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>, <span class=\"hljs-title\">args<\/span>)<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><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<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"end-to-end-tests-anpassen\">End-to-End-Tests anpassen<\/h3>\n\n\n\n<p>Bevor wir unsere Anwendung starten, passen wir noch die zwei End-to-End-Tests an. Wir m\u00fcssen hier die gleichen Anpassungen vornehmen wie im <em>adapter<\/em>-Modul. Ich zeige dir die \u00c4nderungen beispielhaft am <code>FindProductsTest<\/code> \u2013 hier der bisherige Stand:<\/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-meta\">@QuarkusTest<\/span>\n<span class=\"hljs-meta\">@TestProfile<\/span>(TestProfileWithMySQL<span class=\"hljs-class\">.<span class=\"hljs-keyword\">class<\/span>)\n<span class=\"hljs-title\">class<\/span> <span class=\"hljs-title\">FindProductsTest<\/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\">givenTestProductsAndAQuery_findProducts_returnsMatchingProducts<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    String query = <span class=\"hljs-string\">\"monitor\"<\/span>;\n\n    Response response =\n        given()\n            .queryParam(<span class=\"hljs-string\">\"query\"<\/span>, query)\n            .get(<span class=\"hljs-string\">\"\/products\"<\/span>)\n            .then()\n            .extract()\n            .response();\n\n    assertThatResponseIsProductList(\n        response, List.of(COMPUTER_MONITOR, MONITOR_DESK_MOUNT));\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>Wir passen den Test wie folgt an:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@QuarkusTest<\/code> ersetzen wir durch <code>@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)<\/code>.<\/li>\n\n\n\n<li><code>@TestProfile(TestProfileWithMySQL.class)<\/code> ersetzen wir durch <code>@ActiveProfiles(\"test-with-mysql\")<\/code>.<\/li>\n\n\n\n<li>Um den Port herauszufinden, auf dem die Spring-Anwendung l\u00e4uft, f\u00fcgen wir folgende Zeile ein:<br><code>@LocalServerPort private Integer port;<\/code><\/li>\n\n\n\n<li>Und schlie\u00dflich setzen wir den Port, indem wir <code>given()<\/code> um <code>.port(port)<\/code> erweitern.  <\/li>\n<\/ul>\n\n\n\n<p>Die ge\u00e4nderte Testklasse sieht dann wie folgt aus:<\/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-meta\">@SpringBootTest<\/span>(webEnvironment = WebEnvironment.RANDOM_PORT)\n<span class=\"hljs-meta\">@ActiveProfiles<\/span>(<span class=\"hljs-string\">\"test-with-mysql\"<\/span>)\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">FindProductsTest<\/span> <\/span>{\n\n  <span class=\"hljs-meta\">@LocalServerPort<\/span> <span class=\"hljs-keyword\">private<\/span> Integer port;\n\n  <span class=\"hljs-meta\">@Test<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">givenTestProductsAndAQuery_findProducts_returnsMatchingProducts<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    String query = <span class=\"hljs-string\">\"monitor\"<\/span>;\n\n    Response response =\n        given()\n            .port(port)\n            .queryParam(<span class=\"hljs-string\">\"query\"<\/span>, query)\n            .get(<span class=\"hljs-string\">\"\/products\"<\/span>)\n            .then()\n            .extract()\n            .response();\n\n    assertThatResponseIsProductList(\n        response, List.of(COMPUTER_MONITOR, MONITOR_DESK_MOUNT));\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>Die Testklasse <code>CartTest<\/code> passen wir analog an.<\/p>\n\n\n\n<p>Du findest alle \u00c4nderungen dieses Schritts in <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/commit\/97b1dfdf3af50301f02664b011f302beab839a75\" target=\"_blank\" rel=\"noopener\">diesem Git-Commit<\/a>. Die Ergebnisse der \u00c4nderungen findest du in den Klassen <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/src\/main\/java\/eu\/happycoders\/shop\/bootstrap\/Launcher.java\" target=\"_blank\" rel=\"noopener\">Launcher<\/a>, <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/src\/test\/java\/eu\/happycoders\/shop\/bootstrap\/e2e\/FindProductsTest.java\" target=\"_blank\" rel=\"noopener\">FindProductsTest<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/src\/test\/java\/eu\/happycoders\/shop\/bootstrap\/e2e\/CartTest.java\" target=\"_blank\" rel=\"noopener\">CartTest<\/a> sowie den Konfigurationsdateien <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/src\/main\/resources\/application.properties\" target=\"_blank\" rel=\"noopener\">application.properties<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/bootstrap\/src\/main\/resources\/application-mysql.properties\" target=\"_blank\" rel=\"noopener\">application-mysql.properties<\/a>.<\/p>\n\n\n\n<p>Ein Aufruf von <code>mvn spotless:apply clean verify<\/code> sollte das Projekt jetzt vollst\u00e4ndig kompilieren und alle Tests erfolgreich durchlaufen lassen:<\/p>\n\n\n\n<pre class=\"wp-block-code hc-mvn-out\"><code>[<span class=\"info\">INFO<\/span>] parent ............................................. <span class=\"success\">SUCCESS<\/span> [  0.371 s]\n[<span class=\"info\">INFO<\/span>] model .............................................. <span class=\"success\">SUCCESS<\/span> [  3.027 s]\n[<span class=\"info\">INFO<\/span>] application ........................................ <span class=\"success\">SUCCESS<\/span> [  2.492 s]\n[<span class=\"info\">INFO<\/span>] adapter ............................................ <span class=\"success\">SUCCESS<\/span> [ 33.636 s]\n[<span class=\"info\">INFO<\/span>] bootstrap .......................................... <span class=\"success\">SUCCESS<\/span> [ 33.090 s]\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------\n[<span class=\"info\">INFO<\/span>] <span class=\"success\">BUILD SUCCESS<\/span>\n[<span class=\"info\">INFO<\/span>] ------------------------------------------------------------------------<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"starten-der-anwendung\">Starten der Anwendung<\/h2>\n\n\n\n<p>Nun ist es an der Zeit, die migrierte Anwendung zu starten. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"start-der-anwendung-aus-der-ide-heraus\">Start der Anwendung aus der IDE heraus<\/h3>\n\n\n\n<p>Wir k\u00f6nnen die Anwendung direkt aus der IDE starten \u2013 in IntelliJ z. B. durch die Tastenkombination Ctrl+Shift+F10 in der ge\u00f6ffneten <code>Launcher<\/code>-Klasse oder durch Klick auf das gr\u00fcne Dreieck neben dem Klassennamen oder neben der main-Methode:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"142\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-600x142.png\" alt=\"intellij launch application\" class=\"wp-image-39440\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-600x142.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-224x53.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-336x80.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-504x119.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-672x159.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-400x95.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-800x189.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application-944x223.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-launch-application.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/figure>\n<\/div>\n\n\n<p>Sobald die Anwendung gestartet ist, kannst du die REST-Endpoints aufrufen wie <a href=\"\/de\/software-craftsmanship\/hexagonale-architektur-java\/#start-der-anwendung\">im Abschnitt \u201eStart der Anwendung\u201d im zweiten Teil der Serie<\/a> beschrieben.<\/p>\n\n\n\n<p>In IntelliJ kannst du einfach die Datei <em>sample-requests.http<\/em> im <em>doc<\/em>-Verzeichnis \u00f6ffnen und die verschiedenen GET-, POST- und DELETE-Kommandos anklicken.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"start-der-anwendung-im-mysql-modus\">Start der Anwendung im MySQL-Modus<\/h3>\n\n\n\n<p>Um die Anwendung im MySQL-Modus zu starten, brauchst du zun\u00e4chst eine lokale MySQL-Datenbank. Diese startest du am einfachsten wie folgt \u00fcber Docker (unter Windows musst du den Backslash am Ende der ersten Zeile entfernen und alles in eine Zeile schreiben):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-55\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">docker run --name hexagon-mysql -d -p3306:3306 \\\n    -e MYSQL_DATABASE=shop -e MYSQL_ROOT_PASSWORD=test mysql:8.1<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-55\"><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>Sobald die Datenbank hochgefahren ist, musst du die <code>Launcher<\/code>-Klasse mit der VM-Option <nobr><code>-Dspring.profiles.active=mysql<\/code><\/nobr> starten. In IntelliJ kannst du dazu z. B. auf das gr\u00fcne Dreieck neben der Launcher-Klasse und dann auf \u201eModify Run Configuration...\u201d klicken:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_600\"><img decoding=\"async\" width=\"600\" height=\"143\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-600x143.png\" alt=\"intellij vm options step1.v2\" class=\"wp-image-39405\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-600x143.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-224x53.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-336x80.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-504x120.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-672x160.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-400x95.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-800x191.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2-944x225.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step1.v2.png 1200w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/figure>\n<\/div>\n\n\n<p>Im folgenden Dialog dr\u00fcckst du dann Alt+V, um die Eingabezeile f\u00fcr die VM-Optionen anzuzeigen und tr\u00e4gst dort <code>-Dspring.profiles.active=mysql<\/code> ein:<\/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\/12\/intellij-vm-options-step2-800x601.png\" alt=\"intellij vm options step2\" class=\"wp-image-39406\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-800x601.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-224x168.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-336x252.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-504x379.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-672x505.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-400x301.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-600x451.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-944x709.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2-1200x902.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/intellij-vm-options-step2.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/figure>\n<\/div>\n\n\n<p>Falls deine Datenbank z. B. auf einem anderen Port l\u00e4uft, kannst du diesen in der <em>application-mysql.properties<\/em> \u00e4ndern. Du kannst die Konfiguration auch durch VM-Optionen oder Umgebungsvariablen \u00fcberschreiben, z. B. die Datenbank-URL durch die VM-Option <nobr><code>-Dspring.datasource.url=...<\/code><\/nobr> oder \u00fcber die Environment Variable <code>SPRING_DATASOURCE_URL=...<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fazit\">Fazit<\/h2>\n\n\n\n<p>In diesem f\u00fcnften und letzten Teil der Tutorial-Serie \u00fcber die hexagonale Architektur haben wir eine nach dieser Architektur entwickelte Demo-Anwendung von Quarkus nach Spring Boot migriert.<\/p>\n\n\n\n<p>Dabei mussten wir nicht eine Zeile Code im Anwendungskern \u00e4ndern und haben damit einen der gro\u00dfen Vorteile der hexagonalen Architektur demonstriert: Technische Details \u2013 und dazu z\u00e4hlt auch das Application Framework \u2013 lassen sich austauschen, ohne den Kern der Anwendung, also den fachlichen Code, anpassen zu m\u00fcssen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-ist-besser-spring-oder-quarkus\">Was ist besser \u2013 Spring oder Quarkus?<\/h3>\n\n\n\n<p>Womit lie\u00df sich die hexagonale Architektur einfacher implementieren? Mit Spring oder Quarkus? Letztendlich gibt es keinen gro\u00dfen Unterschied \u2013 vom Programmiermodell her funktionieren beide Frameworks gleich.<\/p>\n\n\n\n<p>Wir haben ein paar Annotationen ausgetauscht, die Panache-Repositories durch Spring-Boot-Data-Repositories ersetzt und die Anwendungs- und Test-Konfigurationen angepasst. <\/p>\n\n\n\n<p>Bei Quarkus sehe ich Vorteile in der vollautomatischen Konfiguration von Testcontainers und von REST-Assured, der vorhandenen <a href=\"https:\/\/github.com\/SvenWoltmann\/hexagonal-architecture-java\/blob\/with-spring-boot\/adapter\/src\/main\/java\/eu\/happycoders\/shop\/adapter\/in\/rest\/common\/ClientErrorException.java\" target=\"_blank\" rel=\"noopener\">ClientErrorException<\/a> \u2013 und nat\u00fcrlich im Developer-Mode, der \u00c4nderungen ohne Kompilieren und Neustart erm\u00f6glicht. Bei Spring mussten wir in den Persistenzadaptern nicht den Umweg \u00fcber die <code>Instance&lt;...&gt;<\/code>-Dependency-Injection machen.<\/p>\n\n\n\n<p>Letztendlich ist die Wahl des Framework eine Frage der Erfahrung und des pers\u00f6nlichen Geschmacks.<\/p>\n\n\n\n<p>Damit sind wir am Ende dieser Tutorial-Serie angekommen. Planst du die hexagonale Architektur in Zukunft einzusetzen? Wenn ja, f\u00fcr welche Art von Anwendung? Mit oder ohne Framework? Mit Quarkus oder Spring? Lass es mich gerne \u00fcber die Kommentar-Funktion wissen!<\/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>Im f\u00fcnften und letzten Teil der Serie \u00fcber hexagonale Architektur migrieren wir die Quarkus-Anwendung zu Spring Boot \u2013 und das wieder, ohne eine Zeile Code im Kern der Anwendung zu \u00e4ndern.<\/p>\n","protected":false},"author":1,"featured_media":38485,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"","_seopress_titles_desc":"Hexagonale-Architektur-Tutorial, Teil 5: Migration zu einer Spring-Boot-Anwendung - ohne Code-\u00c4nderungen im Anwendungskern","_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":56223,"_post_count":0,"footnotes":""},"categories":[204],"tags":[209],"class_list":["post-38479","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\/11\/hexagonal-architecture-spring-boot.jpg",1770,986,false],"thumbnail":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",150,84,false],"medium":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",300,167,false],"medium_large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",768,428,false],"large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",1024,570,false],"feature_thumb_224":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-224x125.jpg",224,125,true],"feature_thumb_336":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-336x187.jpg",336,187,true],"feature_thumb_504":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-504x281.jpg",504,281,true],"feature_thumb_672":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-672x374.jpg",672,374,true],"half_400":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-400x223.jpg",400,223,true],"half_600":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-600x334.jpg",600,334,true],"full_800":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-800x446.jpg",800,446,true],"full_944":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-944x526.jpg",944,526,true],"full_1200":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-1200x668.jpg",1200,668,true],"wide_1180":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-1180x490.jpg",1180,490,true],"wide_1770":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot-1770x735.jpg",1770,735,true],"1536x1536":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",1536,856,false],"2048x2048":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/11\/hexagonal-architecture-spring-boot.jpg",1770,986,false]},"uagb_author_info":{"display_name":"Sven Woltmann","author_link":"https:\/\/www.happycoders.eu\/de\/author\/sven\/"},"uagb_comment_info":1,"uagb_excerpt":"Im f\u00fcnften und letzten Teil der Serie \u00fcber hexagonale Architektur migrieren wir die Quarkus-Anwendung zu Spring Boot \u2013 und das wieder, ohne eine Zeile Code im Kern der Anwendung zu \u00e4ndern.","public_identification_id":"00c64a7f9d154ccf881e22f1d0f4e160","private_identification_id":"78dbe07e79ba4f5287389df425c9bcf8","_links":{"self":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38479","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=38479"}],"version-history":[{"count":10,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38479\/revisions"}],"predecessor-version":[{"id":52456,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38479\/revisions\/52456"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media\/38485"}],"wp:attachment":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media?parent=38479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/categories?post=38479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/tags?post=38479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}