Lernpfad

#Lernpfad Enterprise Java: Spring, Jakarta EE & moderne Frameworks

Ziel: Dieses Lernbuch führt dich systematisch von den Grundlagen zu produktionsnahen Enterprise-Anwendungen mit Spring / Spring Boot, Jakarta EE, MicroProfile, Quarkus, Micronaut, Helidon und dem wichtigsten Enterprise-Java-Ökosystem. Zusätzlich enthält es ein komplexes, modulares Microservice-Beispielprojekt mit Maven, Code, Docker Compose und Architektur-Erklärung. Es ist als praktischer Lernpfad aufgebaut: erst verstehen, dann kleine Übungen, danach Mini-Projekte und zum Schluss Enterprise-Architektur.

Stand: Juli 2026
Zielgruppe: Java-Entwickler, Backend-Entwickler, Full-Stack-Entwickler mit Java-Schwerpunkt, Umsteiger von Java EE/Jakarta EE zu Spring oder umgekehrt, sowie Entwickler, die moderne Cloud-native-Java-Frameworks vergleichen wollen.
Voraussetzung: Java-Grundlagen, OOP, Collections, Exceptions, generics, HTTP-Grundlagen und SQL-Basiswissen.


#Inhaltsverzeichnis


#1. Orientierung: Was du am Ende können sollst

Nach diesem Lernpfad solltest du in der Lage sein:

Fähigkeit Spring / Spring Boot Jakarta EE
Dependency Injection verstehen @Component, @Service, @Bean, ApplicationContext CDI: @ApplicationScoped, @Inject, Producer, Qualifier
REST APIs bauen Spring MVC: @RestController, @RequestMapping Jakarta REST/JAX-RS: @Path, @GET, @POST
Datenbankzugriff umsetzen Spring Data JPA, JDBC, Transactions Jakarta Persistence/JPA, JTA
Sicherheit einbauen Spring Security, OAuth2 Resource Server, Method Security Jakarta Security, Container Security, Rollen
Tests schreiben Unit Tests, Slice Tests, Integrationstests Unit Tests, Container-/Deployment-Tests
Enterprise Build verstehen Maven Parent/BOM, Profiles, Plugin Management WAR/EAR, Application Server, Maven Profiles
Produktivbetrieb vorbereiten Actuator, Observability, Docker, CI/CD App Server Deployment, Health Checks, Logs, Config
Architektur entscheiden Microservice, modulare Monolithen, hexagonal standardisierte Enterprise-Plattform, App-Server-zentriert

Merksatz: Spring ist ein Framework-Ökosystem, das viele Entscheidungen pragmatisch vereinfacht. Jakarta EE ist eine standardisierte Plattform aus Spezifikationen und APIs, die von kompatiblen Servern implementiert werden.


#2. Lernstrategie und Reihenfolge

#2.1 Nicht alles gleichzeitig lernen

Spring und Jakarta EE überschneiden sich stark, aber sie haben unterschiedliche Denkmodelle:

  • Spring: Du startest meistens mit Spring Boot, wählst Starter Dependencies und baust eine lauffähige Anwendung als JAR.
  • Jakarta EE: Du entwickelst gegen Spezifikationen und deployest häufig ein WAR oder EAR auf einen kompatiblen Application Server.

Die beste Reihenfolge:

  1. Java, Maven, HTTP und SQL festigen.
  2. Dependency Injection lernen.
  3. REST API bauen.
  4. Persistenz und Transaktionen verstehen.
  5. Validierung und Fehlerbehandlung ergänzen.
  6. Security einbauen.
  7. Tests systematisch schreiben.
  8. Deployment, Monitoring und Betrieb verstehen.
  9. Architektur-Patterns anwenden.

#2.2 Drei Lernmodi

Modus Zweck Beispiel
Lesen Begriffe und Konzepte verstehen Was ist CDI? Was ist Auto-Configuration?
Nachbauen Muskelgedächtnis aufbauen Controller, Repository, Service schreiben
Erklären Wissen stabilisieren Ein Plugin, eine Annotation oder einen Request Flow dokumentieren

#2.3 Der wichtigste Perspektivwechsel

In Enterprise Java geht es selten nur darum, „Code zum Laufen zu bringen“. Wichtig sind:

  • Wartbarkeit
  • klare Modulgrenzen
  • nachvollziehbare Builds
  • stabile Tests
  • sichere Konfiguration
  • kontrollierte Releases
  • gute Fehlerdiagnose

#3. Technisches Setup

#3.1 Empfohlene Werkzeuge

Bereich Werkzeug
JDK JDK 17, 21 oder passend zum Ziel-Framework
Build Maven 3.9+ oder kompatible Unternehmensversion
IDE IntelliJ IDEA, Eclipse, VS Code mit Java-Erweiterungen
Datenbank lokal PostgreSQL oder MariaDB per Docker Compose
API Tests curl, HTTPie, Postman oder IntelliJ HTTP Client
Container Docker oder Podman
Git Feature Branch Workflow, Pull Requests

Spring Framework 6 wechselte auf den jakarta.*-Namespace und eine Jakarta-EE-9+-Basis; Spring Framework 7 nutzt eine Jakarta-EE-11-Basis, während die Spring-Dokumentation weiterhin die Spring-Module wie Core, Testing, Data Access, Web MVC und WebFlux als zentrale Teile beschreibt.[^spring-overview][^spring-versions]

Jakarta EE stellt eine standardisierte Enterprise-Java-Plattform bereit; Jakarta EE 11 ist die aktuelle Plattformlinie und Jakarta EE 12 ist offiziell als „under development“ geführt.1[^jakarta-ee11][^jakarta-ee12]

#3.2 Maven-Grundlagen prüfen

Du solltest folgende Maven-Befehle sicher verstehen:

mvn clean test
mvn clean package
mvn clean verify
mvn clean install
mvn dependency:tree
mvn help:effective-pom
mvn help:effective-settings

Wichtige Begriffe:

Begriff Bedeutung
pom.xml Projektmodell mit Dependencies, Plugins, Build-Konfiguration
Parent-POM zentrale Vererbung von Versionen und Build-Regeln
BOM Bill of Materials zur konsistenten Dependency-Versionierung
Lifecycle Maven-Phasen wie compile, test, package, verify, install, deploy
Plugin Erweiterung, die Build-Aufgaben ausführt
Profile alternative Build-Konfigurationen für Umgebungen

#4. Gemeinsame Grundlagen für Spring und Jakarta EE

#4.1 HTTP und REST

Du musst sicher erklären können:

  • HTTP-Methoden: GET, POST, PUT, PATCH, DELETE
  • Statuscodes: 200, 201, 204, 400, 401, 403, 404, 409, 500
  • Header: Content-Type, Accept, Authorization, Location
  • JSON als Standarddatenformat
  • Idempotenz bei PUT und DELETE
  • Unterschied zwischen Ressource, DTO und Entity

Beispiel einer sauberen REST-Ressource:

GET    /api/customers
POST   /api/customers
GET    /api/customers/{id}
PUT    /api/customers/{id}
DELETE /api/customers/{id}

#4.2 Dependency Injection

Dependency Injection bedeutet: Eine Klasse erzeugt ihre Abhängigkeiten nicht selbst, sondern bekommt sie von einem Container geliefert.

Schlecht:

class OrderService {
    private final OrderRepository repository = new JdbcOrderRepository();
}

Besser:

class OrderService {
    private final OrderRepository repository;

    OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}

Vorteile:

  • besser testbar
  • weniger Kopplung
  • klare Abhängigkeiten
  • austauschbare Implementierungen

#4.3 Schichtenarchitektur

Eine typische Enterprise-Struktur:

api/          REST Controller oder JAX-RS Resources
dto/          Request- und Response-Objekte
application/  Use Cases / Application Services
domain/       Entities, Value Objects, Domain Services
persistence/  Repositories, JPA, SQL, Migrations
config/       Framework-Konfiguration
security/     Authentifizierung und Autorisierung

Regel: Die Fachlogik sollte nicht in Controller oder Resource-Klassen liegen.

#4.4 Transaktionen

Transaktionen schützen fachliche Konsistenz:

Bestellung anlegen
→ Lagerbestand reduzieren
→ Zahlung reservieren
→ Ereignis speichern

Wenn Schritt 3 fehlschlägt, sollen Schritt 1 und 2 typischerweise zurückgerollt werden.

In Spring verwendest du oft:

@Transactional
public OrderId placeOrder(PlaceOrderCommand command) {
    // fachlicher Ablauf
}

In Jakarta EE verwendest du je nach Umgebung containerverwaltete Transaktionen oder JTA.


#5. Spring-Lernpfad

Die Spring-Dokumentation beschreibt Spring als Familie von Projekten; das Spring Framework bildet die Grundlage und enthält unter anderem Core Container, Testing, Data Access, Spring MVC und Spring WebFlux.2

Spring Boot erleichtert das Erstellen eigenständig lauffähiger, produktionsnaher Spring-Anwendungen und setzt auf eine meinungsstarke Konfiguration mit wenig manueller Einrichtung.3

#5.1 Modul S1: Spring Core und Dependency Injection

#Lernziele

Nach diesem Modul kannst du:

  • Spring Beans erklären
  • Constructor Injection anwenden
  • Bean Scopes unterscheiden
  • Konfiguration mit @Configuration und @Bean schreiben
  • Profile und Properties verwenden
  • zyklische Abhängigkeiten erkennen

#Kernbegriffe

Begriff Erklärung
Bean Objekt, das vom Spring Container verwaltet wird
ApplicationContext zentraler Spring Container
Component Scan automatisches Finden von Klassen mit Stereotyp-Annotationen
Constructor Injection bevorzugte Injektion über Konstruktor
Profile Aktivierung bestimmter Beans oder Konfigurationen pro Umgebung

#Beispiel

@Service
public class InvoiceService {
    private final TaxCalculator taxCalculator;

    public InvoiceService(TaxCalculator taxCalculator) {
        this.taxCalculator = taxCalculator;
    }

    public Invoice createInvoice(Money netAmount) {
        Money tax = taxCalculator.calculate(netAmount);
        return new Invoice(netAmount, tax);
    }
}

#Übung

Baue drei Klassen:

  1. PriceCalculator
  2. DiscountPolicy
  3. CheckoutService

CheckoutService darf keine Implementierung selbst mit new erzeugen. Alle Abhängigkeiten müssen injiziert werden.

#Typische Fehler

  • Feldinjektion statt Constructor Injection
  • Geschäftslogik in Konfigurationsklassen
  • zu viele Beans ohne klare Verantwortlichkeit
  • zyklische Abhängigkeiten zwischen Services

#5.2 Modul S2: Spring Boot Grundlagen

#Lernziele

Du kannst danach:

  • ein Spring-Boot-Projekt strukturieren
  • Starter Dependencies verstehen
  • application.yml verwenden
  • Auto-Configuration erklären
  • Actuator aktivieren
  • Profile für dev, test, prod einsetzen

#Minimaler Maven-Build

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>customer-service</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

#Beispiel application.yml

spring:
  application:
    name: customer-service
  profiles:
    active: dev

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

#Übung

Erstelle ein Projekt mit:

  • /api/hello Endpoint
  • dev und prod Profil
  • unterschiedlichem Server-Port pro Profil
  • Actuator Health Endpoint

#5.3 Modul S3: REST APIs mit Spring MVC

#Lernziele

Du kannst:

  • Controller sauber von Services trennen
  • DTOs verwenden
  • Bean Validation einsetzen
  • Fehler zentral behandeln
  • HTTP-Statuscodes bewusst setzen

#Beispiel Controller

@RestController
@RequestMapping("/api/customers")
public class CustomerController {
    private final CustomerService service;

    public CustomerController(CustomerService service) {
        this.service = service;
    }

    @PostMapping
    public ResponseEntity<CustomerResponse> create(@Valid @RequestBody CreateCustomerRequest request) {
        CustomerResponse created = service.create(request);
        URI location = URI.create("/api/customers/" + created.id());
        return ResponseEntity.created(location).body(created);
    }

    @GetMapping("/{id}")
    public CustomerResponse findById(@PathVariable UUID id) {
        return service.findById(id);
    }
}

#DTO mit Validation

public record CreateCustomerRequest(
    @NotBlank String firstName,
    @NotBlank String lastName,
    @Email String email
) {}

#Zentrale Fehlerbehandlung

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(CustomerNotFoundException.class)
    ResponseEntity<ApiError> handleNotFound(CustomerNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ApiError("CUSTOMER_NOT_FOUND", ex.getMessage()));
    }
}

#Übung

Baue CRUD für Product:

  • POST /api/products
  • GET /api/products/{id}
  • GET /api/products
  • PUT /api/products/{id}
  • DELETE /api/products/{id}

Achte darauf, dass Controller keine Datenbanklogik enthalten.


#5.4 Modul S4: Spring Data, JPA und Transaktionen

Spring Data JPA bietet Repository-Unterstützung für Jakarta Persistence API/JPA und vereinfacht Datenzugriff mit einem konsistenten Programmiermodell.[^spring-data-jpa]

#Lernziele

Du kannst:

  • Entities und DTOs trennen
  • Repositories definieren
  • Query Methods verwenden
  • Transaktionen an der Service-Schicht setzen
  • Lazy Loading und N+1-Probleme erklären
  • Migrationen mit Flyway oder Liquibase planen

#Entity

@Entity
@Table(name = "customers")
public class CustomerEntity {
    @Id
    private UUID id;

    @Column(nullable = false)
    private String firstName;

    @Column(nullable = false)
    private String lastName;

    @Column(nullable = false, unique = true)
    private String email;

    protected CustomerEntity() {}

    public CustomerEntity(UUID id, String firstName, String lastName, String email) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
}

#Repository

public interface CustomerRepository extends JpaRepository<CustomerEntity, UUID> {
    boolean existsByEmail(String email);
    Optional<CustomerEntity> findByEmail(String email);
}

#Service mit Transaktion

@Service
public class CustomerService {
    private final CustomerRepository repository;

    public CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public CustomerResponse create(CreateCustomerRequest request) {
        if (repository.existsByEmail(request.email())) {
            throw new DuplicateCustomerEmailException(request.email());
        }

        CustomerEntity entity = new CustomerEntity(
            UUID.randomUUID(),
            request.firstName(),
            request.lastName(),
            request.email()
        );

        CustomerEntity saved = repository.save(entity);
        return CustomerResponse.from(saved);
    }
}

#Übung

Baue eine Datenbankmigration:

CREATE TABLE customers (
    id UUID PRIMARY KEY,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(200) NOT NULL UNIQUE
);

Dann schreibe:

  • Repository-Test
  • Service-Test
  • Integrationstest mit echter Datenbank

#5.5 Modul S5: Spring Security

Spring Security bietet umfassende Unterstützung für OAuth 2.0 und kann unter anderem als OAuth2 Resource Server JWT- oder opaque-token-basierte Bearer Tokens absichern.[^spring-security-oauth2][^spring-security-resource]

#Lernziele

Du kannst:

  • Authentication und Authorization unterscheiden
  • Security Filter Chain erklären
  • Endpoints nach Rollen absichern
  • JWT Resource Server konfigurieren
  • Method Security einsetzen
  • sichere Fehlerantworten planen

#Beispiel Security-Konfiguration

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/products/**").hasAuthority("SCOPE_products:read")
                .requestMatchers(HttpMethod.POST, "/api/products/**").hasAuthority("SCOPE_products:write")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .build();
    }
}

#Übung

Sichere eine Produkt-API so ab:

Aktion Scope
Produkt lesen products:read
Produkt anlegen products:write
Produkt löschen products:admin

Schreibe Tests für 401, 403 und 200.


#5.6 Modul S6: Testing in Spring

Spring bietet im spring-test-Modul Unterstützung für Integrationstests mit Spring Container; das TestContext Framework bietet generische, annotationsgetriebene Testunterstützung.[^spring-testing][^spring-testcontext]

#Test-Pyramide

Ebene Ziel Beispiel
Unit Test reine Fachlogik PriceCalculatorTest
Slice Test ein Ausschnitt @WebMvcTest, @DataJpaTest
Integrationstest mehrere Schichten @SpringBootTest
Systemtest komplette App API Test gegen laufenden Container

#Beispiel MVC-Test

@WebMvcTest(CustomerController.class)
class CustomerControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    CustomerService customerService;

    @Test
    void createReturns201() throws Exception {
        when(customerService.create(any())).thenReturn(new CustomerResponse(UUID.randomUUID(), "Ada", "Lovelace"));

        mockMvc.perform(post("/api/customers")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com"}
                """))
            .andExpect(status().isCreated());
    }
}

#Übung

Erstelle für eine Order-API:

  • Unit Test für Preisberechnung
  • @WebMvcTest für Controller
  • @DataJpaTest für Repository
  • @SpringBootTest für vollständigen Request Flow

#5.7 Modul S7: Spring für Produktion

#Lernziele

Du kannst:

  • Health Checks und Readiness erklären
  • Actuator Endpoints sicher bereitstellen
  • Logs strukturieren
  • Metriken exportieren
  • Container Images bauen
  • Konfiguration externalisieren

#Wichtige Produktionsfragen

Frage Warum wichtig?
Wie erkennt Kubernetes, ob die App lebt? Liveness und Readiness
Welche Version läuft gerade? Release-Diagnose
Welche Metriken gibt es? Monitoring und SLOs
Wo liegen Secrets? Security und Compliance
Wie werden DB-Migrationen ausgeführt? Release-Sicherheit

#Beispiel Dockerfile

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/customer-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

#5.8 Modul S8: Spring Cloud und verteilte Systeme

Spring Cloud stellt Werkzeuge für typische Muster verteilter Systeme bereit, darunter Configuration Management, Service Discovery, Circuit Breaker und Routing.4

Spring Cloud Gateway baut auf dem Spring-Ökosystem auf und adressiert Routing sowie Querschnittsthemen wie Security, Monitoring/Metrics und Resilienz.[^spring-cloud-gateway]

#Lernziele

Du kannst:

  • zentrale Konfiguration erklären
  • API Gateway Patterns verstehen
  • Circuit Breaker einsetzen
  • Timeouts und Retries bewusst konfigurieren
  • Service-to-Service-Kommunikation absichern

#Lernreihenfolge

  1. Warum verteilte Systeme schwierig sind
  2. Timeouts, Retries, Circuit Breaker
  3. Gateway und Routing
  4. zentrale Konfiguration
  5. Observability über Service-Grenzen hinweg
  6. Security mit OAuth2/OIDC

#6. Jakarta-EE-Lernpfad

Jakarta EE ist laut offizieller Dokumentation eine Sammlung von Services für Enterprise-Anwendungen auf der Java-Plattform; sie stellt Infrastruktur bereit, damit Entwickler sich stärker auf Fachlogik konzentrieren können.5

#6.1 Modul J1: Jakarta EE Plattform verstehen

#Lernziele

Du kannst:

  • Spezifikation und Implementierung unterscheiden
  • Application Server erklären
  • Profile verstehen: Platform, Web Profile, Core Profile
  • jakarta.* Namespace erklären
  • WAR, JAR und EAR unterscheiden

#Spezifikation vs. Implementierung

Begriff Beispiel
Spezifikation Jakarta REST, Jakarta Persistence, Jakarta CDI
API jakarta.ws.rs-api, jakarta.persistence-api
Implementierung Server oder Bibliothek, z. B. ein kompatibler Jakarta-EE-Server
Deployment WAR oder EAR auf Application Server

#Typische Projektstruktur

jakarta-customer-app/
├── pom.xml
└── src/
    ├── main/
    │   ├── java/com/example/customer/
    │   │   ├── boundary/CustomerResource.java
    │   │   ├── control/CustomerService.java
    │   │   └── entity/Customer.java
    │   ├── resources/META-INF/persistence.xml
    │   └── webapp/WEB-INF/beans.xml
    └── test/java/...

#6.2 Modul J2: CDI – Contexts and Dependency Injection

#Lernziele

Du kannst:

  • @Inject verwenden
  • Scopes erklären
  • Qualifier definieren
  • Producer Methoden schreiben
  • Interceptors einsetzen
  • CDI Events verstehen

#Beispiel CDI Service

@ApplicationScoped
public class CustomerService {
    @Inject
    CustomerRepository repository;

    public Customer create(CreateCustomerCommand command) {
        Customer customer = new Customer(command.name(), command.email());
        repository.persist(customer);
        return customer;
    }
}

#Qualifier

@Qualifier
@Retention(RUNTIME)
@Target({ FIELD, PARAMETER, METHOD, TYPE })
public @interface ExternalPaymentProvider {}
@ApplicationScoped
@ExternalPaymentProvider
public class StripePaymentClient implements PaymentClient {
    public PaymentResult charge(PaymentCommand command) {
        return PaymentResult.accepted();
    }
}

#Übung

Baue zwei Implementierungen eines NotificationSender:

  • EmailNotificationSender
  • SmsNotificationSender

Wähle die passende Implementierung über einen CDI Qualifier aus.


#6.3 Modul J3: REST APIs mit Jakarta REST

#Lernziele

Du kannst:

  • JAX-RS/Jakarta REST Resource-Klassen schreiben
  • JSON Requests und Responses verarbeiten
  • Statuscodes zurückgeben
  • Exception Mapper verwenden
  • Bean Validation integrieren

#Application-Klasse

@ApplicationPath("/api")
public class CustomerApplication extends Application {
}

#Resource-Klasse

@Path("/customers")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CustomerResource {

    @Inject
    CustomerService service;

    @POST
    public Response create(@Valid CreateCustomerRequest request, @Context UriInfo uriInfo) {
        CustomerResponse created = service.create(request);
        URI location = uriInfo.getAbsolutePathBuilder()
            .path(created.id().toString())
            .build();
        return Response.created(location).entity(created).build();
    }

    @GET
    @Path("/{id}")
    public CustomerResponse findById(@PathParam("id") UUID id) {
        return service.findById(id);
    }
}

#Exception Mapper

@Provider
public class CustomerNotFoundMapper implements ExceptionMapper<CustomerNotFoundException> {
    @Override
    public Response toResponse(CustomerNotFoundException exception) {
        return Response.status(Response.Status.NOT_FOUND)
            .entity(new ApiError("CUSTOMER_NOT_FOUND", exception.getMessage()))
            .build();
    }
}

#Übung

Baue eine Jakarta REST API für InventoryItem mit:

  • GET /api/items
  • POST /api/items
  • GET /api/items/{id}
  • Exception Mapper für 404
  • Validation für Pflichtfelder

#6.4 Modul J4: Jakarta Persistence und Transaktionen

Jakarta Persistence stellt Object/Relational Mapping für relationale Daten in Java-Anwendungen bereit.[^jakarta-persistence]

#Lernziele

Du kannst:

  • JPA Entities modellieren
  • EntityManager verwenden
  • JPQL schreiben
  • Beziehungen modellieren
  • Transaktionen erklären
  • persistence.xml konfigurieren

#Entity

@Entity
@Table(name = "customers")
public class Customer {
    @Id
    private UUID id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false, unique = true)
    private String email;

    protected Customer() {}

    public Customer(String name, String email) {
        this.id = UUID.randomUUID();
        this.name = name;
        this.email = email;
    }
}

#Repository mit EntityManager

@ApplicationScoped
public class CustomerRepository {

    @PersistenceContext
    EntityManager em;

    public void persist(Customer customer) {
        em.persist(customer);
    }

    public Optional<Customer> findById(UUID id) {
        return Optional.ofNullable(em.find(Customer.class, id));
    }
}

#persistence.xml

<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
             version="3.2">
    <persistence-unit name="customerPU" transaction-type="JTA">
        <jta-data-source>java:/jdbc/customerDb</jta-data-source>
        <properties>
            <property name="jakarta.persistence.schema-generation.database.action" value="none"/>
        </properties>
    </persistence-unit>
</persistence>

#Übung

Erweitere Customer um Address:

  • @Embedded oder eigene Entity?
  • Welche Entscheidung passt besser?
  • Was bedeutet das für Queries?
  • Wie testest du die Persistenz?

#6.5 Modul J5: Jakarta Security

#Lernziele

Du kannst:

  • Container Security erklären
  • Rollen und Berechtigungen unterscheiden
  • sichere Endpoints definieren
  • Authentifizierungsmechanismen verstehen
  • Security-Konfiguration serverseitig einordnen

#Beispiel Rollenprüfung

@Path("/admin/reports")
@RolesAllowed("ADMIN")
public class AdminReportResource {

    @GET
    public ReportOverview overview() {
        return new ReportOverview("ok");
    }
}

#Übung

Sichere Endpoints so ab:

Endpoint Rolle
/api/public keine Authentifizierung
/api/customers USER
/api/admin ADMIN

Dokumentiere, was bei nicht authentifizierten und nicht autorisierten Requests passieren soll.


#6.6 Modul J6: Batch, Messaging und Enterprise Integration

#Lernziele

Du kannst:

  • Batch-Verarbeitung von Request/Response unterscheiden
  • Jobs, Steps und Chunk-Verarbeitung erklären
  • Messaging-Grundbegriffe verstehen
  • asynchrone Verarbeitung fachlich begründen

#Wann Batch?

Batch passt, wenn:

  • große Datenmengen regelmäßig verarbeitet werden
  • Import/Export-Prozesse laufen
  • Fehler wiederholbar behandelt werden müssen
  • Verarbeitung auditierbar sein muss

Beispiele:

  • nächtlicher Kundendatenimport
  • Rechnungsbatch
  • CSV-Export
  • Datenbereinigung

#6.7 Modul J7: Jakarta Deployment und Application Server

#Lernziele

Du kannst:

  • WAR Deployment erklären
  • Server-Konfiguration von Anwendungscode trennen
  • Datasources serverseitig konfigurieren
  • Logs und Health Checks planen
  • Migration von Java EE zu Jakarta EE einschätzen

#Maven WAR Beispiel

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>jakarta-customer-app</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <jakartaee.version>11.0.0</jakartaee.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Wichtig: provided bedeutet, dass die API zum Kompilieren verfügbar ist, aber zur Laufzeit vom Application Server bereitgestellt wird.


#7. Vergleich: Spring vs. Jakarta EE

#7.1 Denkmodell

Thema Spring / Spring Boot Jakarta EE
Primärer Stil Framework-Ökosystem Standardisierte Plattform
Laufzeit meistens ausführbares JAR häufig WAR/EAR auf Server
Konfiguration Auto-Configuration, Starter Server stellt viele Dienste bereit
Portabilität stark innerhalb Spring-Ökosystem API-Portabilität zwischen Implementierungen
Einstieg sehr schnell mit Boot stärker Plattform- und Serververständnis nötig
Enterprise-Betrieb App bringt viel selbst mit Server übernimmt viele Dienste
Typische Teams Microservices, Cloud-native klassische Enterprise-Systeme, standardisierte Plattformen

#7.2 Wann Spring?

Spring passt besonders gut, wenn:

  • du schnell produktionsfähige Services bauen willst
  • Microservices oder modulare Monolithen geplant sind
  • ein Team hohe Kontrolle über Abhängigkeiten und Runtime haben will
  • Cloud Deployment, Container und CI/CD im Fokus stehen
  • Spring Security, Spring Data und Spring Cloud genutzt werden sollen

#7.3 Wann Jakarta EE?

Jakarta EE passt besonders gut, wenn:

  • du gegen Standards entwickeln willst
  • ein Unternehmen Application Server standardisiert betreibt
  • bestehende Java-EE/Jakarta-EE-Systeme erweitert werden
  • WAR/EAR-Deployment gesetzt ist
  • API-Portabilität wichtiger ist als Framework-Komfort

#7.4 Mischwelt: Spring und Jakarta

Moderne Spring-Versionen verwenden ebenfalls viele Jakarta APIs, zum Beispiel Servlet, JPA und Bean Validation über jakarta.*. Darum ist Jakarta-Wissen auch für Spring-Entwickler wertvoll.

Beispiel:

import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;

Der Unterschied liegt oft nicht in den APIs selbst, sondern darin, wer die Runtime und Integration bereitstellt:

  • Bei Spring Boot bringt die Anwendung sehr viel über Dependencies und Auto-Configuration mit.
  • Bei Jakarta EE stellt der Application Server viele Dienste bereit.

#8. Enterprise-Architektur-Lernpfad

#8.1 Modul A1: Architekturformen

#Modularer Monolith

Ein modularer Monolith ist eine Anwendung mit klaren internen Modulen, aber einem gemeinsamen Deployment.

Vorteile:

  • einfacheres Deployment
  • geringere Netzwerkkomplexität
  • gute Transaktionskontrolle
  • leichteres Debugging

Risiken:

  • Module verschwimmen
  • Datenbank wird zum Kopplungspunkt
  • Build kann langsam werden

#Microservices

Microservices sind unabhängig deploybare Services mit klaren fachlichen Grenzen.

Vorteile:

  • unabhängige Releases
  • Team-Autonomie
  • Skalierung pro Service

Risiken:

  • verteilte Transaktionen
  • Netzwerkfehler
  • Observability-Aufwand
  • API-Versionierung

#8.2 Modul A2: Hexagonale Architektur

Eine robuste Struktur:

domain/
  model/
  service/
application/
  usecase/
adapter/
  in/web/
  out/persistence/
  out/messaging/
config/

Regel:

  • Domain kennt kein Spring und kein Jakarta.
  • Adapter kennen Frameworks.
  • Use Cases orchestrieren fachliche Abläufe.

#8.3 Modul A3: Fehlerbehandlung

Eine Enterprise API braucht konsistente Fehlerantworten:

{
  "code": "CUSTOMER_NOT_FOUND",
  "message": "Customer was not found",
  "correlationId": "f3bd4b2e-0ad6-4bbd-a6a1-1d9498e2d1ea"
}

Lernaufgabe:

  • Definiere 10 fachliche Fehlercodes.
  • Ordne jedem Fehler einen HTTP-Status zu.
  • Dokumentiere, welche Fehler geloggt werden.

#8.4 Modul A4: Security-Architektur

Wichtige Fragen:

  • Wer ist der Identity Provider?
  • Nutzt ihr OIDC/OAuth2?
  • Welche Services validieren Tokens?
  • Gibt es Service-to-Service Tokens?
  • Werden Rollen oder Scopes verwendet?
  • Wie werden Admin-Rechte auditiert?

#8.5 Modul A5: Build, Release und CI/CD

Eine Enterprise Pipeline sollte mindestens enthalten:

validate
compile
unit tests
static analysis
integration tests
package
container build
security scan
deploy to test
smoke test
promote to stage/prod

Maven-Phasen:

mvn clean verify

Für Releases:

mvn -Pproduction clean package

#9. Praxisprojekte

#9.1 Projekt 1: Spring Customer Service

#Ziel

Baue einen produktionsnahen Spring Boot Service für Kundenverwaltung.

#Funktionen

  • Kunde anlegen
  • Kunde lesen
  • Kunde suchen
  • Kunde deaktivieren
  • Validierung
  • Fehlerbehandlung
  • Datenbankmigration
  • Security mit JWT
  • Actuator Health
  • Integrationstests

#Akzeptanzkriterien

  • mvn clean verify läuft grün.
  • Kein Controller enthält Datenbanklogik.
  • Alle öffentlichen Fehler haben einen Fehlercode.
  • POST /api/customers gibt 201 Created zurück.
  • GET /actuator/health ist ohne Auth erreichbar.
  • Schreiboperationen benötigen einen Write-Scope.

#9.2 Projekt 2: Jakarta Inventory Application

#Ziel

Baue eine Jakarta EE WAR-Anwendung für Lagerverwaltung.

#Funktionen

  • Artikel anlegen
  • Bestand erhöhen
  • Bestand reduzieren
  • Mindestbestand prüfen
  • REST API mit Jakarta REST
  • CDI Services
  • JPA Persistenz
  • rollenbasierte Admin-Endpunkte

#Akzeptanzkriterien

  • Anwendung wird als WAR gebaut.
  • Jakarta EE API ist provided.
  • REST Resource enthält keine Persistenzlogik.
  • Transaktionen werden bewusst dokumentiert.
  • Fehler werden über Exception Mapper vereinheitlicht.

#9.3 Projekt 3: Migration Java EE / javax zu Jakarta / jakarta

#Ziel

Migriere eine kleine Anwendung von javax.* nach jakarta.*.

#Checkliste

  • Imports ersetzen
  • Dependencies aktualisieren
  • Application Server prüfen
  • Persistence-Konfiguration prüfen
  • Servlet-Version prüfen
  • Tests ausführen
  • Deployment neu testen

Beispiel:

// alt
import javax.persistence.Entity;
import javax.validation.constraints.NotBlank;

// neu
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;

#9.4 Projekt 4: Architekturvergleich

Baue denselben Use Case zweimal:

  1. mit Spring Boot
  2. mit Jakarta EE

Use Case: Bestellung anlegen.

Vergleiche danach:

  • Codeumfang
  • Konfiguration
  • Testbarkeit
  • Deployment
  • Fehlerbehandlung
  • Security
  • Team-Verständlichkeit

#10. Prüfungsfragen und Selbstkontrolle

#10.1 Spring Fragen

  1. Was ist der Unterschied zwischen @Component, @Service und @Repository?
  2. Warum ist Constructor Injection meistens besser als Field Injection?
  3. Was macht Spring Boot Auto-Configuration?
  4. Was ist ein Starter?
  5. Was ist der Unterschied zwischen @WebMvcTest und @SpringBootTest?
  6. Warum sollte @Transactional oft auf Service-Methoden liegen?
  7. Was ist der Unterschied zwischen Authentication und Authorization?
  8. Wann entsteht ein N+1-Problem?
  9. Was ist ein Actuator Health Endpoint?
  10. Wie würdest du Secrets in Produktion bereitstellen?

#10.2 Jakarta EE Fragen

  1. Was ist der Unterschied zwischen Spezifikation und Implementierung?
  2. Warum ist die Jakarta EE API oft provided?
  3. Was macht CDI?
  4. Was ist ein Qualifier?
  5. Was ist eine JAX-RS Resource?
  6. Was ist ein Exception Mapper?
  7. Was ist der Zweck von persistence.xml?
  8. Wie unterscheidet sich WAR Deployment von executable JAR Deployment?
  9. Was bedeutet containerverwaltete Transaktion?
  10. Was musst du bei Migration von javax zu jakarta prüfen?

#10.3 Architekturfragen

  1. Wann ist ein modularer Monolith besser als Microservices?
  2. Warum sollte Domain-Code frameworkfrei sein?
  3. Wie entwirfst du eine stabile Fehlerantwort?
  4. Was gehört in eine CI/CD Pipeline?
  5. Welche Metriken braucht eine produktive API?
  6. Wie versionierst du APIs?
  7. Was ist ein Correlation ID?
  8. Warum sind Timeouts Pflicht?
  9. Was sind idempotente Operationen?
  10. Wie erkennst du zu starke Kopplung zwischen Modulen?

#11. Empfohlene Reihenfolge als 16-Wochen-Plan

Woche Fokus Ergebnis
1 Maven, HTTP, SQL auffrischen kleine CLI-App + Maven Build
2 Dependency Injection allgemein DI ohne Framework erklären können
3 Spring Core Beans, Profiles, Properties
4 Spring Boot Grundlagen lauffähige REST App
5 Spring MVC + Validation saubere CRUD API
6 Spring Data JPA Datenbankanbindung + Migration
7 Spring Testing Testpyramide umsetzen
8 Spring Security JWT Resource Server absichern
9 Spring Produktion Actuator, Logs, Docker
10 Jakarta EE Plattform WAR, Server, APIs, provided
11 CDI Injection, Scopes, Qualifier
12 Jakarta REST JAX-RS API mit Fehlerbehandlung
13 Jakarta Persistence EntityManager, JPA, Transaktionen
14 Jakarta Security + Deployment Rollen, Server-Konfiguration
15 Architektur hexagonal, modularer Monolith, Microservices
16 Abschlussprojekt Spring und Jakarta Projekt vergleichen

#12. Referenz-Templates

#12.1 Spring Projektstruktur

src/main/java/com/example/customer/
├── CustomerApplication.java
├── api/
│   ├── CustomerController.java
│   ├── CreateCustomerRequest.java
│   ├── CustomerResponse.java
│   └── ApiExceptionHandler.java
├── application/
│   └── CustomerService.java
├── domain/
│   ├── Customer.java
│   └── CustomerEmail.java
├── persistence/
│   ├── CustomerEntity.java
│   └── CustomerRepository.java
├── security/
│   └── SecurityConfig.java
└── config/
    └── JacksonConfig.java

#12.2 Jakarta EE Projektstruktur

src/main/java/com/example/inventory/
├── boundary/
│   ├── InventoryApplication.java
│   ├── InventoryResource.java
│   └── InventoryExceptionMapper.java
├── control/
│   └── InventoryService.java
├── entity/
│   ├── InventoryItem.java
│   └── StockMovement.java
└── security/
    └── Roles.java

src/main/resources/META-INF/persistence.xml
src/main/webapp/WEB-INF/beans.xml

#12.3 Maven Quality Parent Beispiel

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.0</version>
                <configuration>
                    <release>${java.version}</release>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.3</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.5.3</version>
            </plugin>

            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.12</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

#12.4 Docker Compose für lokale Datenbank

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: enterprise
      POSTGRES_USER: enterprise
      POSTGRES_PASSWORD: enterprise
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U enterprise"]
      interval: 10s
      timeout: 5s
      retries: 5

#12.5 Lernnotiz-Template

# Thema

## Was ist es?

## Warum braucht man es?

## Minimalbeispiel

## Typische Fehler

## Enterprise-Relevanz

## Prüfungsfrage


#13. Erweiterter Framework-Kompass

Dieser Abschnitt erweitert den ursprünglichen Spring/Jakarta-Lernpfad um Frameworks, Tools und Plattformen, die in Enterprise-Java-Projekten häufig neben oder anstelle von Spring und Jakarta EE auftauchen.

#13.1 Die wichtigsten Ökosysteme auf einen Blick

Bereich Typische Technologie Warum du sie lernen solltest
Spring-Ökosystem Spring Boot, Spring MVC, Spring Data, Spring Security, Spring Cloud Sehr verbreitet, produktiv, riesiges Ökosystem
Jakarta-Ökosystem Jakarta EE, CDI, Jakarta REST, Jakarta Persistence, Jakarta Security Standardisierte Enterprise-Java-APIs, Application Server, langfristige Portabilität
MicroProfile Config, Health, OpenAPI, Fault Tolerance, REST Client, Telemetry Standardisierte Microservice-Erweiterungen für Jakarta-nahe Runtimes
Cloud-native Java Quarkus, Micronaut, Helidon Startzeit, geringer Speicherverbrauch, Kubernetes, Native Image, Microservices
Persistenz JPA, Hibernate, Spring Data, jOOQ, Flyway, Liquibase Fast jedes Enterprise-System braucht robuste Datenhaltung und Migrationen
Messaging Kafka, RabbitMQ, JMS, Spring Cloud Stream, Apache Camel Entkopplung, Events, Integration, asynchrone Prozesse
Security Spring Security, Jakarta Security, OAuth2, OIDC, Keycloak Authentifizierung, Autorisierung, Token, Rollen, Zero Trust
Observability Actuator, Micrometer, OpenTelemetry, Prometheus, Grafana, Loki Produktivbetrieb verstehen, Fehler analysieren, SLAs überwachen
Testing JUnit, Mockito, AssertJ, Testcontainers, WireMock, Pact, ArchUnit Qualität, Refactoring-Sicherheit, echte Integrationstests
Deployment Docker, Kubernetes, Helm, Kustomize, GitOps Reproduzierbare Releases, Skalierung, Cloud-Betrieb

#13.2 Lernreihenfolge nach Rolle

Rolle Fokus der ersten 8 Wochen Fokus danach
Backend-Entwickler Spring Boot, REST, JPA, Testing Security, Messaging, Observability
Jakarta-EE-Entwickler CDI, Jakarta REST, JPA, App Server MicroProfile, Cloud Deployment, Migration zu jakarta.*
Cloud-native-Entwickler Docker, Kubernetes, Quarkus/Micronaut Native Image, Observability, Event-driven Architektur
Softwarearchitekt Architekturpatterns, Modulgrenzen, Datenkonsistenz Plattformstrategie, Migration, Governance, Qualitätsmetriken
DevOps/Platform Engineer Build, Container, Kubernetes, CI/CD Observability, Security Scanning, Release-Automatisierung

#13.3 Lernregel: Frameworks nicht isoliert lernen

Ein Framework wird erst wirklich verständlich, wenn du dieselbe Fachfunktion mehrfach umsetzt:

  1. CustomerController als Spring MVC REST API.
  2. CustomerResource als Jakarta REST API.
  3. dieselbe API mit Quarkus REST.
  4. dieselbe API mit Micronaut Controller.
  5. dieselbe API mit Tests, Datenbank, Security und Docker.

Dadurch lernst du nicht nur Syntax, sondern Framework-Denken, Konfiguration, Build, Testing und Betrieb.


#14. MicroProfile-Lernpfad

MicroProfile ergänzt Enterprise Java um Spezifikationen, die besonders für Microservices wichtig sind. Es ist kein einzelnes Framework wie Spring Boot, sondern ein Spezifikationsbündel, das von Runtimes wie Open Liberty, Payara, Quarkus oder Helidon MP implementiert werden kann.

#14.1 Was du zuerst verstehen solltest

Thema Zweck
CDI als Basis Dependency Injection und Bean-Lifecycle
Jakarta REST REST-Endpunkte als Grundlage vieler MicroProfile-Spezifikationen
MicroProfile Config Externe Konfiguration aus Properties, Umgebungsvariablen oder anderen Quellen
MicroProfile Health Readiness- und Liveness-Prüfungen für Plattformen wie Kubernetes
MicroProfile OpenAPI Automatische API-Dokumentation für REST-Services
MicroProfile Rest Client Typsichere REST-Clients
MicroProfile Fault Tolerance Timeout, Retry, Circuit Breaker, Bulkhead, Fallback
MicroProfile Telemetry Tracing, Metriken und Logs mit OpenTelemetry-Konzepten

#14.2 Minimalbeispiel: Config + REST

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@Path("/hello")
public class HelloResource {

    @ConfigProperty(name = "app.greeting", defaultValue = "Hallo")
    String greeting;

    @GET
    public String hello() {
        return greeting + " Enterprise Java";
    }
}

#14.3 Minimalbeispiel: Health Check

import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
import jakarta.enterprise.context.ApplicationScoped;

@Readiness
@ApplicationScoped
public class DatabaseReadinessCheck implements HealthCheck {
    @Override
    public HealthCheckResponse call() {
        boolean databaseAvailable = true;
        return HealthCheckResponse.named("database")
                .status(databaseAvailable)
                .build();
    }
}

#14.4 Übung

Baue einen kleinen order-service mit diesen Anforderungen:

  • REST-Endpunkt POST /orders
  • Konfiguration order.max-items
  • Health-Check für Datenbankverbindung
  • OpenAPI-Dokumentation
  • Rest Client für einen fiktiven customer-service
  • Fault-Tolerance-Regel: Bei Timeout soll ein Fallback ausgeführt werden

#14.5 Typische Fehler

Fehler Folge Bessere Lösung
Alles als MicroProfile lernen, ohne CDI verstanden zu haben Annotationen bleiben Magie Erst CDI, Scope, Producer und Qualifier lernen
Health Checks mit Business-Checks überladen Kubernetes startet/killt Pods falsch Readiness und Liveness klar trennen
Retry ohne Idempotenz doppelte Bestellungen, doppelte Zahlungen Retry nur bei sicheren Operationen oder mit Idempotency Key
Config-Werte fest in Code schreiben Umgebung schwer änderbar MicroProfile Config oder externe Konfigurationsquelle nutzen

#15. Quarkus-Lernpfad

Quarkus ist ein modernes Cloud-native-Java-Framework. Es kombiniert Jakarta- und MicroProfile-nahe APIs mit Build-Time-Optimierung, schnellem Dev-Mode, Kubernetes-Fokus und guter Native-Image-Unterstützung.

#15.1 Wann Quarkus besonders interessant ist

Situation Warum Quarkus passt
Kubernetes-first Microservices gute Kubernetes- und Container-Unterstützung
kurze Startzeit wichtig Build-Time-Verarbeitung und Native-Image-Option
geringer Speicherverbrauch wichtig optimiertes Runtime-Modell
Team kennt Jakarta/CDI viele Konzepte fühlen sich vertraut an
Event-driven Services Erweiterungen für Kafka, Messaging, REST Clients

#15.2 Quarkus-Grundlagen

Lerne in dieser Reihenfolge:

  1. Projektstruktur und Dev Mode.
  2. REST-Endpunkte.
  3. CDI Beans und Injection.
  4. Konfiguration.
  5. Hibernate ORM mit Panache oder klassischem Repository-Stil.
  6. Tests mit Quarkus Test.
  7. Container Image.
  8. Kubernetes Deployment.
  9. Native Image.
  10. Observability und Health.

#15.3 Minimalbeispiel: REST Resource

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/customers")
public class CustomerResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<CustomerDto> list() {
        return List.of(new CustomerDto(1L, "Ada Lovelace"));
    }
}

#15.4 Quarkus mit Panache

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;

@Entity
public class Customer extends PanacheEntity {
    public String name;
    public String email;
}
@Path("/customers")
public class CustomerResource {
    @GET
    public List<Customer> all() {
        return Customer.listAll();
    }
}

#15.5 Übung

Implementiere denselben Customer Service einmal mit Spring Boot und einmal mit Quarkus. Vergleiche:

  • Projektstart
  • Konfiguration
  • REST-Annotationen
  • Testaufbau
  • Container Image
  • Startzeit lokal
  • Speicherverbrauch grob mit docker stats

#15.6 Quarkus-Merksätze

  • Quarkus verschiebt viel Arbeit in die Build-Zeit.
  • Extensions sind zentral. Man fügt nicht nur Libraries hinzu, sondern Quarkus-Erweiterungen.
  • Native Image ist mächtig, aber nicht automatisch immer die beste Wahl.
  • Quarkus fühlt sich für Jakarta- und MicroProfile-Entwickler natürlicher an als für reine Spring-Entwickler.

#16. Micronaut-Lernpfad

Micronaut ist ein modernes JVM-Framework für modulare, testbare Microservices und serverless Anwendungen. Ein Kernmerkmal ist die starke Compile-Time-Verarbeitung: Dependency Injection, AOP-ähnliche Features und Konfiguration werden möglichst früh analysiert.

#16.1 Warum Micronaut lernen?

Stärke Bedeutung
Compile-Time DI weniger Reflection, schnelle Starts
Cloud-native Fokus gut für Microservices und serverless Szenarien
Multi-Language JVM Java, Kotlin und Groovy
Testbarkeit Framework ist stark auf testbare Komponenten ausgelegt
HTTP Client/Server gute Unterstützung für deklarative Clients und APIs

#16.2 Minimalbeispiel: Controller

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/hello")
public class HelloController {

    @Get
    public String hello() {
        return "Hallo Micronaut";
    }
}

#16.3 Bean und Konfiguration

import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;

@Singleton
public class GreetingService {

    private final String greeting;

    public GreetingService(@Value("${app.greeting:Hallo}") String greeting) {
        this.greeting = greeting;
    }

    public String greet(String name) {
        return greeting + " " + name;
    }
}

#16.4 Lernprojekt

Baue einen notification-service:

  • REST API für Benachrichtigungen
  • deklarativer HTTP Client zu einem user-service
  • Konfiguration pro Umgebung
  • Tests mit eingebettetem Server
  • Docker Image
  • optional: Native Image

#16.5 Vergleich zu Spring Boot

Frage Spring Boot Micronaut
Lernkurve sehr viele Tutorials, großer Markt kleineres, modernes Ökosystem
DI-Modell Runtime + AOT-Unterstützung stark Compile-Time-orientiert
Auto-Konfiguration sehr umfassend ebenfalls stark, aber anders strukturiert
Enterprise-Adoption sehr hoch selektiv, oft bei Cloud-native/Serverless Teams

#17. Helidon-Lernpfad

Helidon ist ein Java-Microservices-Framework mit zwei Denkmodellen:

  • Helidon SE: funktionaler, expliziter, leichtgewichtiger Stil.
  • Helidon MP: MicroProfile-Stil mit CDI, Jakarta REST und bekannten Enterprise-Annotationen.

#17.1 Wann Helidon lernen?

Helidon lohnt sich besonders, wenn du Enterprise Java mit MicroProfile verbinden willst oder wenn du bewusst eine Alternative zu Spring Boot und Quarkus evaluierst.

#17.2 Helidon MP Minimalbeispiel

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/ping")
@ApplicationScoped
public class PingResource {
    @GET
    public String ping() {
        return "pong";
    }
}

#17.3 Helidon SE Denkweise

Bei Helidon SE konfigurierst du vieles expliziter. Das ist weniger „magisch“, verlangt aber mehr bewusstes Design.

import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;

public class Main {
    public static void main(String[] args) {
        WebServer.builder()
                .routing(Main::routing)
                .port(8080)
                .build()
                .start();
    }

    static void routing(HttpRouting.Builder routing) {
        routing.get("/hello", (req, res) -> res.send("Hallo Helidon"));
    }
}

#17.4 Übung

Setze denselben Health-Endpunkt in drei Varianten um:

  1. Spring Boot Actuator.
  2. MicroProfile Health mit Helidon MP.
  3. expliziter Helidon-SE-Endpunkt.

Dokumentiere danach: Was ist am schnellsten geschrieben? Was ist am transparentesten? Was ist am besten standardisiert?


#18. Persistenz-Vertiefung: JPA, Hibernate, Spring Data, jOOQ, Flyway, Liquibase

Persistenz ist in Enterprise Java oft der schwierigste Teil, nicht wegen der Syntax, sondern wegen Datenmodell, Transaktionen, Performance und Migration.

#18.1 Lernreihenfolge Persistenz

  1. SQL wiederholen: Joins, Indexe, Constraints, Transaktionen.
  2. JDBC verstehen: Connection, PreparedStatement, ResultSet.
  3. JPA-Grundlagen: Entity, Persistence Context, Dirty Checking.
  4. Hibernate-Verhalten: Lazy Loading, Fetch Joins, N+1-Problem.
  5. Spring Data Repositories oder Jakarta Persistence DAO.
  6. Migrationen mit Flyway oder Liquibase.
  7. Performance: Indexe, Batching, Pagination, Query-Plan.
  8. Datenkonsistenz in verteilten Systemen: Outbox, Saga, Idempotenz.

#18.2 JPA Entity

import jakarta.persistence.*;
import java.time.Instant;

@Entity
@Table(name = "orders")
public class OrderEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String customerId;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private OrderStatus status;

    @Version
    private long version;

    private Instant createdAt;
}

#18.3 Spring Data Repository

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
    List<OrderEntity> findByCustomerId(String customerId);
}

#18.4 Klassisches Jakarta DAO

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;

@ApplicationScoped
public class OrderDao {

    @PersistenceContext
    EntityManager em;

    public List<OrderEntity> findByCustomerId(String customerId) {
        return em.createQuery("select o from OrderEntity o where o.customerId = :customerId", OrderEntity.class)
                 .setParameter("customerId", customerId)
                 .getResultList();
    }
}

#18.5 Flyway vs. Liquibase

Frage Flyway Liquibase
Denkmodell versionierte Migrationen, oft SQL-first Changesets, XML/YAML/JSON/SQL möglich
Einfachheit sehr einfach und konventionsorientiert mächtiger, mehr Struktur und Features
Enterprise Governance gut, aber oft über Konventionen stark bei Auditing, Rollback-Konzepten, Policies
Lernaufwand niedriger höher

#18.6 Flyway-Beispiel

-- src/main/resources/db/migration/V1__create_orders.sql
create table orders (
    id bigserial primary key,
    customer_id varchar(100) not null,
    status varchar(40) not null,
    version bigint not null default 0,
    created_at timestamp not null
);

#18.7 Liquibase-Beispiel

databaseChangeLog:
  - changeSet:
      id: 001-create-orders
      author: enterprise-team
      changes:
        - createTable:
            tableName: orders
            columns:
              - column:
                  name: id
                  type: bigint
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: customer_id
                  type: varchar(100)
                  constraints:
                    nullable: false

#18.8 Typische Persistenzfehler

Fehler Symptom Lösung
Entity direkt als REST DTO ausgeben Lazy-Loading-Fehler, Datenlecks DTOs verwenden
Kein @Version verlorene Updates Optimistic Locking einsetzen
Migrationen manuell in DB ausführen Umgebungen driften auseinander Migrationen versionieren und automatisieren
Unbegrenzte Queries Speicherprobleme Pagination und Limits
N+1 ignorieren schlechte Performance Fetch Join, EntityGraph, Query-Optimierung

#19. API-Design: REST, OpenAPI, GraphQL, gRPC und Async APIs

Enterprise APIs sind Verträge. Ein Controller ist nur die technische Oberfläche. Wichtig sind Versionierung, Fehlerformat, Security, Idempotenz, Dokumentation und Kompatibilität.

#19.1 REST API Design Checkliste

  • Ressourcen statt Verben: /orders statt /createOrder.
  • HTTP-Methoden korrekt nutzen: GET, POST, PUT, PATCH, DELETE.
  • Statuscodes bewusst verwenden.
  • Fehler standardisieren, zum Beispiel Problem Details.
  • Pagination, Sortierung und Filterung einheitlich gestalten.
  • Idempotency Keys für kritische POST-Operationen.
  • API-Versionierung planen.
  • OpenAPI-Spezifikation erzeugen und prüfen.

#19.2 Spring REST Fehlerformat

import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
class ApiExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    ProblemDetail notFound(OrderNotFoundException ex) {
        ProblemDetail problem = ProblemDetail.forStatus(404);
        problem.setTitle("Order not found");
        problem.setDetail(ex.getMessage());
        return problem;
    }
}

#19.3 Jakarta REST ExceptionMapper

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

@Provider
public class OrderNotFoundMapper implements ExceptionMapper<OrderNotFoundException> {
    @Override
    public Response toResponse(OrderNotFoundException exception) {
        return Response.status(Response.Status.NOT_FOUND)
                .entity(new ErrorDto("ORDER_NOT_FOUND", exception.getMessage()))
                .build();
    }
}

#19.4 GraphQL lernen, wenn ...

GraphQL ist sinnvoll, wenn Clients sehr flexible Datenabfragen brauchen und du mehrere REST-Aufrufe bündeln willst. Es ist weniger sinnvoll, wenn dein Hauptproblem einfache CRUD-APIs sind.

Lernziele:

  • Schema-Design
  • Query, Mutation, Subscription
  • Resolver
  • N+1 bei GraphQL
  • Security pro Feld und Operation
  • Persisted Queries

#19.5 gRPC lernen, wenn ...

gRPC eignet sich für interne Service-zu-Service-Kommunikation, hohe Performance und stark typisierte Verträge. Es ist nicht automatisch besser als REST, aber sehr stark bei internen Plattformen.

Lernziele:

  • Protocol Buffers
  • unary und streaming calls
  • Deadline/Timeout
  • Backward Compatibility
  • Fehlercodes
  • Observability

#20. Security-Lernpfad: Spring Security, Jakarta Security, OAuth2, OIDC, Keycloak

Security ist ein Querschnittsthema. Es reicht nicht, eine Login-Seite zu bauen. Du musst Authentifizierung, Autorisierung, Token, Session, CSRF, CORS, Secrets und Auditierung verstehen.

#20.1 Grundbegriffe

Begriff Bedeutung
Authentication Wer bist du?
Authorization Was darfst du?
Principal Identität des Benutzers oder Systems
Role grobe Berechtigung, z. B. ADMIN
Scope OAuth2-Berechtigungsumfang, z. B. orders:read
Claim Information im Token, z. B. sub, email, roles
OIDC Identity-Schicht auf OAuth2
Resource Server API, die Access Tokens prüft

#20.2 Spring Security Resource Server Beispiel

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
class SecurityConfig {

    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/orders/**").hasAuthority("SCOPE_orders:read")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt())
            .build();
    }
}

#20.3 Jakarta Security Denkmodell

Jakarta Security ist stärker an standardisierte Security-Kontexte, Container und Rollenmodelle gebunden. Je nach Runtime konfigurierst du Identity Stores, Rollen und Security-Constraints.

import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/admin")
public class AdminResource {

    @GET
    @RolesAllowed("ADMIN")
    public String adminOnly() {
        return "secret";
    }
}

#20.4 Keycloak-Lernprojekt

Baue eine lokale Umgebung:

  • Keycloak per Docker Compose.
  • Realm enterprise.
  • Client order-service.
  • Rollen USER, ADMIN.
  • Spring Boot Resource Server.
  • Jakarta- oder Quarkus-Service mit JWT-Prüfung.
  • Integrationstest mit Testcontainers oder WireMock für Token-Szenarien.

#20.5 Security-Checkliste für Enterprise-Projekte

  • Keine Secrets im Git.
  • Token-Lebensdauer bewusst setzen.
  • Refresh Token nicht im Browser Local Storage ablegen.
  • CORS nicht mit * und Credentials kombinieren.
  • CSRF bei Cookie-basierten Sessions beachten.
  • Methode und URL absichern.
  • Audit-Events schreiben.
  • Security Tests automatisieren.
  • Dependency-Scanning in CI.
  • Rollen nicht mit Geschäftsregeln verwechseln.

#21. Messaging und Integration: Kafka, RabbitMQ, JMS, Spring Integration, Apache Camel

Messaging ist wichtig, wenn Systeme entkoppelt werden sollen. Es bringt aber neue Probleme: Reihenfolge, Zustellung, Idempotenz, Dead Letter Queues, Monitoring und Schema-Evolution.

#21.1 Kafka vs. RabbitMQ vs. JMS

Technologie Typischer Einsatz Denkmodell
Kafka Event Streaming, Event Logs, Analytics, Integration großer Datenströme Topics, Partitionen, Consumer Groups, Offset
RabbitMQ klassische Message Queues, Routing, Work Queues Exchanges, Queues, Bindings, Acknowledgements
JMS standardisierte Java-Messaging-API, oft im Jakarta/App-Server-Umfeld Queue, Topic, MessageListener
Spring Integration Enterprise Integration Patterns im Spring-Stil Channels, Gateways, Transformer, Router
Apache Camel umfangreiche Integrationsrouten und Konnektoren Routes, Components, EIPs

#21.2 Spring Kafka Consumer Beispiel

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventConsumer {

    @KafkaListener(topics = "orders", groupId = "billing-service")
    public void onOrderCreated(OrderCreatedEvent event) {
        // idempotent verarbeiten
    }
}

#21.3 RabbitMQ Denkbeispiel

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class PaymentMessageConsumer {

    @RabbitListener(queues = "payment.requests")
    public void handle(PaymentRequest request) {
        // Zahlung ausführen und Ergebnis publizieren
    }
}

#21.4 Outbox Pattern

Das Outbox Pattern löst ein klassisches Problem: Du willst Datenbankänderung und Event-Publishing konsistent halten. Statt direkt nach Kafka zu schreiben, speicherst du ein Event in einer Outbox-Tabelle innerhalb derselben Transaktion.

create table outbox_event (
    id uuid primary key,
    aggregate_type varchar(100) not null,
    aggregate_id varchar(100) not null,
    event_type varchar(100) not null,
    payload jsonb not null,
    created_at timestamp not null,
    published_at timestamp null
);

#21.5 Übung

Baue einen Event-Flow:

  1. order-service speichert Bestellung und Outbox Event.
  2. Outbox Publisher sendet OrderCreated nach Kafka.
  3. billing-service konsumiert das Event.
  4. Bei Fehlern landet das Event in einer Dead Letter Queue oder einem Retry Topic.
  5. Metriken zeigen verarbeitete und fehlerhafte Events.

#22. Testing-Lernpfad: Von Unit Tests bis Architekturtests

Testing ist ein eigener Lernpfad. In Enterprise-Projekten brauchst du nicht nur Unit Tests, sondern eine Teststrategie.

#22.1 Testpyramide für Enterprise Java

Ebene Tools Ziel
Unit Tests JUnit, Mockito, AssertJ Geschäftslogik schnell prüfen
Slice Tests Spring MVC Test, Data JPA Test einzelne technische Schichten prüfen
Integrationstests Testcontainers, Quarkus Test, Arquillian echte Datenbanken, Broker, Server prüfen
Contract Tests Pact, Spring Cloud Contract Service-Verträge absichern
End-to-End Tests Playwright, Selenium, API Tests kritische Nutzerflüsse prüfen
Architekturtests ArchUnit Modulgrenzen und Abhängigkeitsregeln prüfen
Mutation Tests PIT Qualität der Tests messen

#22.2 JUnit + AssertJ Beispiel

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class PriceCalculatorTest {

    @Test
    void appliesDiscount() {
        Money result = new PriceCalculator().applyDiscount(Money.eur(100), 10);
        assertThat(result).isEqualTo(Money.eur(90));
    }
}

#22.3 Testcontainers mit PostgreSQL

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;

class OrderRepositoryIT {

    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Test
    void savesOrder() {
        postgres.start();
        String jdbcUrl = postgres.getJdbcUrl();
        // Repository gegen echte PostgreSQL testen
    }
}

#22.4 WireMock für externe HTTP APIs

stubFor(get(urlEqualTo("/customers/42"))
    .willReturn(okJson("""
        { "id": "42", "name": "Ada" }
    """)));

#22.5 ArchUnit Beispiel

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

@AnalyzeClasses(packages = "com.example")
class ArchitectureTest {

    @ArchTest
    static final ArchRule controllersShouldNotAccessRepositories =
        classes().that().resideInAPackage("..web..")
                 .should().onlyAccessClassesThat()
                 .resideOutsideOfPackage("..repository..");
}

#22.6 Teststrategie dokumentieren

Für jedes Projekt solltest du eine TESTING.md pflegen:

# Teststrategie

## Ziel
Welche Risiken sollen Tests absichern?

## Testarten
- Unit Tests
- Integrationstests
- Contract Tests
- Architekturtests

## Was wird nicht automatisiert?

## Lokale Ausführung

## CI-Ausführung

## Testdaten

## Flaky-Test-Regeln

#23. Observability-Lernpfad: Logs, Metriken, Traces

Observability bedeutet, dass du das Verhalten eines Systems aus seinen Ausgaben verstehen kannst. In modernen Enterprise-Systemen brauchst du Logs, Metriken und Traces zusammen.

#23.1 Drei Säulen

Signal Frage Beispiel
Logs Was ist passiert? Fehlermeldung mit Correlation ID
Metriken Wie oft und wie stark? Request Rate, Error Rate, Latenz
Traces Wo war die Anfrage unterwegs? Gateway → Order → Billing → Database

#23.2 Spring Boot Actuator

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      probes:
        enabled: true

#23.3 OpenTelemetry Java Agent

java \
  -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -jar order-service.jar

#23.4 Logging-Regeln

  • Jede eingehende Anfrage bekommt eine Correlation ID.
  • Logs sind strukturiert, zum Beispiel JSON.
  • Keine Passwörter, Tokens oder personenbezogenen Daten loggen.
  • Business Events und technische Logs trennen.
  • Fehler mit Kontext loggen, aber nicht mehrfach in jeder Schicht.

#23.5 Metriken, die jedes Backend haben sollte

Metrik Warum wichtig?
HTTP Request Count Last und Traffic verstehen
HTTP Error Rate Fehleranstieg erkennen
Latenz p95/p99 Nutzererfahrung und SLOs überwachen
DB Connection Pool Usage Engpässe erkennen
Kafka Consumer Lag Rückstau erkennen
JVM Heap/GC Speicherprobleme analysieren
Thread Count Blockaden oder Leaks erkennen

#24. Cloud-native Deployment: Docker, Kubernetes, Helm und GitOps

Cloud-native Java bedeutet nicht nur „JAR in Container packen“. Du musst Konfiguration, Health Checks, Ressourcenlimits, Rollouts, Secrets und Observability beherrschen.

#24.1 Dockerfile für Spring Boot

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/order-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

#24.2 Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: registry.example.com/order-service:1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"

#24.3 Kubernetes Service

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080

#24.4 Helm lernen

Lerne Helm erst, wenn du einfache Manifeste verstanden hast. Helm löst Wiederverwendung und Parametrisierung, kann aber Komplexität verstecken.

Lernziele:

  • Chart-Struktur
  • values.yaml
  • Templates
  • Releases
  • Rollback
  • Umgebungswerte pro Stage

#24.5 GitOps lernen

GitOps bedeutet: Git ist die deklarative Quelle für den Zielzustand. Tools wie Argo CD oder Flux gleichen Cluster-Zustand mit Git ab.

Übung:

  1. Lege ein Repository platform-config an.
  2. Speichere Kubernetes-Manifeste für dev und stage.
  3. Simuliere eine Versionserhöhung im Image Tag.
  4. Dokumentiere den Rollout-Prozess.

#25. Build, CI/CD und Qualitätssicherung

Enterprise-Builds müssen reproduzierbar, nachvollziehbar und sicher sein.

#25.1 Maven und Gradle

Thema Maven Gradle
Konvention sehr stark flexibel
Enterprise-Nachvollziehbarkeit sehr gut gut, aber Build-Logik kann komplex werden
Multi-Module Standard sehr mächtig
Performance gut oft sehr gut mit Build Cache
Lernkurve niedriger höher

#25.2 CI/CD Pipeline Stufen

commit
  -> compile
  -> unit tests
  -> static analysis
  -> integration tests
  -> package
  -> container image
  -> security scan
  -> deploy to dev
  -> smoke tests
  -> approval
  -> deploy to stage/prod

#25.3 GitHub Actions Beispiel

name: build
on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'
          cache: maven
      - run: mvn -B clean verify

#25.4 Maven Quality Gate

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <executions>
        <execution>
            <goals><goal>enforce</goal></goals>
            <configuration>
                <rules>
                    <requireJavaVersion>
                        <version>[21,)</version>
                    </requireJavaVersion>
                    <dependencyConvergence />
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

#25.5 Security in CI

  • Dependency Scanning.
  • Container Image Scanning.
  • Secret Scanning.
  • SBOM erzeugen.
  • Signierte Artefakte.
  • Reproduzierbare Builds.
  • Keine Deployments direkt von Entwicklerlaptops.

#26. Performance und moderne JVM: Virtual Threads, GraalVM Native Image, Profiling

Performance beginnt nicht bei Optimierung, sondern bei Messbarkeit. Ohne Metriken, Profiler und Lasttests optimierst du oft die falsche Stelle.

#26.1 Virtual Threads

Virtual Threads sind besonders interessant für blockierende IO-Workloads, zum Beispiel klassische REST-APIs mit Datenbankzugriff. Sie ersetzen aber keine Architekturentscheidungen und lösen keine Datenbankengpässe.

Lernziele:

  • Unterschied Platform Thread vs. Virtual Thread.
  • Blocking IO und Thread-per-request.
  • Pinning verstehen.
  • Connection Pools richtig dimensionieren.
  • Lasttest vor und nach Änderung.

#26.2 Spring Boot Virtual Threads Beispiel

spring:
  threads:
    virtual:
      enabled: true

#26.3 Native Image

GraalVM Native Image kann Java-Anwendungen Ahead-of-Time in native Executables kompilieren. Das kann Startzeit und Speicherverbrauch verbessern, bringt aber neue Build- und Kompatibilitätsfragen mit sich.

mvn -Pnative native:compile

#26.4 Wann Native Image sinnvoll ist

Sinnvoll Vorsicht
serverless Funktionen große, klassische Enterprise-App mit viel Reflection
kurzlebige Jobs wenig Nutzen bei sehr langen Laufzeiten
viele kleine Microservices Build-Zeit kann steigen
Startup kritisch Debugging und Profiling anders

#26.5 Profiling-Lernpfad

  1. Logs und Metriken prüfen.
  2. JVM-Parameter verstehen.
  3. Heap Dump analysieren.
  4. Thread Dump lesen.
  5. Java Flight Recorder nutzen.
  6. Datenbank-Query-Plan prüfen.
  7. Lasttests mit k6, Gatling oder JMeter durchführen.

#27. Architekturpatterns für Enterprise Java

Framework-Wissen reicht nicht. Du musst Entscheidungen begründen können.

#27.1 Layered Architecture

Klassische Schichten:

Controller / Resource
Service / Use Case
Domain
Repository / DAO
Infrastructure

Vorteil: einfach zu verstehen. Nachteil: Domäne wird oft an Framework und Datenbankmodell gekoppelt.

#27.2 Hexagonale Architektur

Inbound Adapter  ->  Application Port  ->  Domain  ->  Outbound Port  ->  Outbound Adapter
REST Controller      Use Case Interface     Model      Repository Port      JPA Adapter

Vorteil: Fachlogik ist unabhängiger von Frameworks. Nachteil: mehr Klassen und Disziplin nötig.

#27.3 Modulith

Ein Modulith ist ein modularer Monolith: Deployment bleibt einfach, aber Code ist fachlich getrennt. Das ist oft besser als zu frühe Microservices.

Lernübung:

  • Erstelle Module customer, order, billing.
  • Verhindere direkte Zugriffe auf interne Pakete.
  • Kommuniziere zwischen Modulen über Events oder klar definierte APIs.
  • Prüfe Regeln mit ArchUnit.

#27.4 Microservices

Microservices sind sinnvoll, wenn Teams, Deployment-Zyklen und Skalierungsanforderungen unabhängig sind. Sie sind nicht sinnvoll, nur weil ein Framework es einfach macht.

Checkliste vor Microservices:

  • Hat jedes Team Ownership?
  • Gibt es CI/CD pro Service?
  • Gibt es Observability?
  • Gibt es eine Security-Strategie?
  • Gibt es Datenbesitz pro Service?
  • Sind Konsistenzanforderungen verstanden?
  • Gibt es eine Plattform für Deployment und Betrieb?

#27.5 DDD-Grundlagen

Begriff Bedeutung
Entity Objekt mit Identität
Value Object Objekt ohne Identität, über Werte gleich
Aggregate Konsistenzgrenze
Repository Zugriff auf Aggregate
Domain Event fachliches Ereignis
Bounded Context fachliche Modellgrenze
Ubiquitous Language gemeinsame Sprache von Fachbereich und Technik

#28. Daten- und Integrationspatterns

#28.1 Transaktionaler Monolith

Ein Monolith kann mit einer relationalen Datenbank und ACID-Transaktionen sehr robust sein. Das ist kein Fehler, sondern oft die richtige Anfangsarchitektur.

#28.2 Saga Pattern

Eine Saga koordiniert mehrere lokale Transaktionen. Es gibt zwei Varianten:

Variante Erklärung
Choreography Services reagieren auf Events, keine zentrale Steuerung
Orchestration ein Orchestrator steuert die Schritte

#28.3 Idempotenz

Idempotenz bedeutet: dieselbe Operation kann mehrfach ausgeführt werden, ohne mehrfachen fachlichen Schaden zu verursachen.

create table idempotency_key (
    key varchar(200) primary key,
    response_hash varchar(200),
    created_at timestamp not null
);

#28.4 CQRS

Command Query Responsibility Segregation trennt Schreibmodell und Lesemodell. Das lohnt sich, wenn Lese- und Schreibanforderungen stark unterschiedlich sind.

#28.5 Event Sourcing

Bei Event Sourcing speicherst du Ereignisse als Quelle der Wahrheit. Das ist mächtig, aber komplex. Lerne es erst, wenn du Events, Idempotenz und Projektionen verstanden hast.

#28.6 Anti-Pattern

Anti-Pattern Warum problematisch
Shared Database zwischen Microservices Services sind nicht wirklich unabhängig
Chatty Services zu viele synchrone Calls, hohe Latenz
Event ohne Schema Governance Consumer brechen bei Änderungen
Retry Storm Fehler wird durch Wiederholungen verstärkt
Distributed Transaction als Standardlösung schwer skalierbar und operativ komplex

#29. Migration-Lernpfade

#29.1 Java EE / javax.* zu Jakarta EE / jakarta.*

Viele ältere Enterprise-Projekte nutzen noch javax.*. Moderne Jakarta-EE-Versionen nutzen jakarta.*.

Vorgehen:

  1. Inventar erstellen: Dependencies, App Server, APIs, Build Plugins.
  2. Prüfen, welche Framework-Versionen jakarta.* unterstützen.
  3. Automatisierte Imports umstellen.
  4. Tests ausführen.
  5. Deployment Descriptor prüfen.
  6. Application Server aktualisieren.
  7. Smoke Tests und Regressionstests ausführen.

#29.2 Spring Boot Major Migration

Spring-Migrationen solltest du nicht „blind“ durchführen.

Checkliste:

  • Release Notes lesen.
  • Java-Version prüfen.
  • Jakarta Namespace prüfen.
  • Deprecated APIs ersetzen.
  • Security-Konfiguration prüfen.
  • Actuator-Endpoint-Namen prüfen.
  • Test-Slices prüfen.
  • Container Image und Runtime prüfen.

#29.3 Monolith zu Modulith

Bevor du Microservices baust, extrahiere fachliche Module.

com.example.shop
├── customer
│   ├── api
│   ├── internal
│   └── CustomerModule.java
├── order
│   ├── api
│   ├── internal
│   └── OrderModule.java
└── billing
    ├── api
    ├── internal
    └── BillingModule.java

#29.4 App Server zu Container

Migration von klassischem Application Server zu Container-Deployment:

Alt Neu
EAR/WAR auf zentralem Server einzelner Service im Container
JNDI-Datasource Konfiguration über Environment/Secrets
Serverweite Libraries app-spezifische Dependencies
manuelles Deployment CI/CD Pipeline
Server-Logs zentrale Logplattform
lokale Admin-Konsole Kubernetes/GitOps/Monitoring

#30. Framework-Entscheidungsmatrix

#30.1 Entscheidung nach Projektart

Projektart Empfehlung
klassischer Enterprise-CRUD-Service Spring Boot oder Jakarta EE
neues Cloud-native Microservice-System Spring Boot, Quarkus oder Micronaut evaluieren
standardisierte App-Server-Landschaft Jakarta EE + MicroProfile
sehr schnelle Startzeit wichtig Quarkus, Micronaut oder Spring Native/AOT prüfen
Team hat viel Spring-Wissen Spring Boot als Standard, Quarkus/Micronaut gezielt evaluieren
Team hat viel Java-EE/Jakarta-Wissen Jakarta EE, MicroProfile, Quarkus oder Helidon MP
Event Streaming Plattform Kafka + Framework-Integration
viele Integrationsrouten Apache Camel oder Spring Integration

#30.2 Bewertungsraster

Bewerte jedes Framework mit 1 bis 5 Punkten:

Kriterium Gewicht Spring Boot Jakarta EE Quarkus Micronaut Helidon
Team-Wissen 5
Ökosystem 5
Betriebsmodell 4
Startzeit/Speicher 3
Standardisierung 4
Cloud/Kubernetes 4
Security-Integration 5
Testing-Support 5
Langfristige Wartbarkeit 5

#30.3 Entscheidungsregel

Die beste Technologie ist selten die mit den meisten Features. In Enterprise-Projekten gewinnt oft die Technologie, die das Team zuverlässig betreiben, testen, aktualisieren und erklären kann.


#31. Erweiterter 24-Wochen-Lernplan

#Phase 1: Fundament, Wochen 1–4

Woche Fokus Ergebnis
1 Java 17/21, Maven, HTTP, SQL kleine CLI-App + REST-Grundlagen verstanden
2 Dependency Injection Spring Bean + CDI Bean erklären können
3 REST APIs Spring MVC und Jakarta REST Minimal-API
4 JPA und Transaktionen Entity, Repository/DAO, Migration

#Phase 2: Spring und Jakarta produktiv, Wochen 5–8

Woche Fokus Ergebnis
5 Spring Boot intern Auto-Configuration, Properties, Profiles
6 Jakarta EE intern CDI, Scopes, App Server Deployment
7 Security OAuth2 Resource Server + Rollenmodell
8 Testing Unit, Integration, Testcontainers

#Phase 3: Erweiterte Frameworks, Wochen 9–12

Woche Fokus Ergebnis
9 MicroProfile Config, Health, OpenAPI, Rest Client
10 Quarkus REST, CDI, Hibernate, Tests, Container
11 Micronaut Controller, DI, Config, HTTP Client
12 Helidon SE vs. MP, MicroProfile-Anwendung

#Phase 4: Enterprise Integration, Wochen 13–16

Woche Fokus Ergebnis
13 Kafka und Events Producer/Consumer + Outbox
14 RabbitMQ/JMS Queue, Retry, DLQ
15 API Contracts OpenAPI, Contract Tests, Versionierung
16 Integration Patterns Camel/Spring Integration Überblick

#Phase 5: Betrieb, Cloud und Qualität, Wochen 17–20

Woche Fokus Ergebnis
17 Docker Container Image, Compose, lokale Umgebung
18 Kubernetes Deployment, Service, Probes, ConfigMap, Secret
19 Observability Actuator, Micrometer, OpenTelemetry
20 CI/CD Pipeline, Quality Gate, Security Scan

#Phase 6: Architektur und Abschluss, Wochen 21–24

Woche Fokus Ergebnis
21 Modulith und Hexagonal modulare Beispielanwendung
22 Microservices und DDD Bounded Contexts, Events, Saga
23 Migration javax zu jakarta, App Server zu Container
24 Abschlussprojekt vollständiges Enterprise-Java-System

#32. Abschlussprojekt: Enterprise Shop Plattform

Baue ein kleines, aber realistisches System:

enterprise-shop/
├── customer-service      # Spring Boot
├── order-service         # Quarkus oder Spring Boot
├── inventory-service     # Jakarta EE oder Helidon MP
├── billing-service       # Micronaut oder Spring Boot
├── platform-config       # Kubernetes/Helm/GitOps
├── docs                  # Architekturentscheidungen
└── docker-compose.yml

#32.1 Muss-Anforderungen

  • REST APIs mit OpenAPI.
  • PostgreSQL pro Service.
  • Flyway oder Liquibase Migrationen.
  • Security mit OAuth2/OIDC.
  • Events über Kafka oder RabbitMQ.
  • Integrationstests mit Testcontainers.
  • Observability mit Logs, Metriken und Traces.
  • Docker Compose für lokale Entwicklung.
  • Kubernetes-Manifeste für Deployment.
  • CI Pipeline mit Tests und Quality Gate.

#32.2 Erweiterte Anforderungen

  • Outbox Pattern.
  • Idempotency Keys.
  • Contract Tests.
  • ArchUnit-Regeln.
  • Load Test.
  • SBOM.
  • Native Image für mindestens einen Service.
  • Migration eines Service von Spring Boot zu Quarkus oder umgekehrt.

#32.3 Dokumentationsartefakte

Erstelle im Projektordner:

docs/
├── architecture.md
├── api-guidelines.md
├── testing-strategy.md
├── security-model.md
├── deployment.md
├── observability.md
└── adr/
    ├── 0001-framework-selection.md
    ├── 0002-database-per-service.md
    └── 0003-eventing-strategy.md

#33. Lernkarten: Was du mündlich erklären können solltest

#33.1 Spring

  • Was ist der Unterschied zwischen @Component, @Service und @Bean?
  • Wie funktioniert Spring Boot Auto-Configuration?
  • Was ist ein Starter?
  • Wie funktioniert Spring Security Filter Chain?
  • Was ist der Unterschied zwischen @Transactional auf Service- und Repository-Ebene?
  • Wie funktionieren Spring Profiles?
  • Wofür ist Actuator?

#33.2 Jakarta EE

  • Was ist CDI?
  • Was ist der Unterschied zwischen @ApplicationScoped und @RequestScoped?
  • Wie funktioniert Jakarta REST?
  • Was ist JTA?
  • Was ist eine Persistence Unit?
  • Was bedeutet Deployment auf einem Application Server?
  • Was ist der Unterschied zwischen Web Profile und Platform?

#33.3 Cloud-native Frameworks

  • Warum optimiert Quarkus zur Build-Zeit?
  • Was bedeutet Compile-Time DI in Micronaut?
  • Wann ist Helidon MP sinnvoller als Helidon SE?
  • Was ist MicroProfile Config?
  • Was ist der Unterschied zwischen Health, Metrics und Telemetry?

#33.4 Architektur

  • Wann ist ein Modulith besser als Microservices?
  • Was ist ein Bounded Context?
  • Warum sind verteilte Transaktionen schwierig?
  • Was löst das Outbox Pattern?
  • Wie testet man Service-Verträge?
  • Warum ist Observability Teil der Architektur?


#34. Komplexes modulares Beispielprojekt: Enterprise Commerce Platform

Dieses Kapitel ergänzt den Lernpfad um ein größeres Beispielprojekt, das typische Enterprise-Java-Bausteine zusammenführt. Es ist kein simples Demo-Projekt, sondern eine bewusst modulare Lernplattform mit mehreren Services, gemeinsamen Libraries, Maven Parent-POM, Docker Compose, Datenbanken, Kafka, API Gateway, Events und Deployment-Vorlagen.

Das Projekt liegt zusätzlich als eigener Ordner enterprise-commerce-platform/ im ZIP-Paket. Die Codeauszüge in diesem Kapitel erklären die wichtigsten Stellen; der vollständige Code befindet sich im Projektordner.

#34.1 Fachliches Szenario

Die Plattform simuliert einen kleinen Enterprise-Shop:

  • Kunden werden im customer-service verwaltet.
  • Produkte und Lagerbestand liegen im catalog-service.
  • Bestellungen werden im order-service erzeugt.
  • Zahlungen werden im payment-service autorisiert.
  • Benachrichtigungen werden im notification-service asynchron verarbeitet.
  • Externe Clients gehen über das api-gateway.

Das Szenario ist absichtlich so gewählt, dass du viele Enterprise-Themen üben kannst: Modulgrenzen, REST, Events, Datenbank pro Service, Transaktionen, Event Contracts, Build-Struktur, Observability und lokale Infrastruktur.

#34.2 Architekturübersicht

Browser / API Client
        |
        v
  api-gateway
        |
        +--> customer-service
        +--> catalog-service
        +--> order-service ----> payment-service
                   |                 |
                   +---- Kafka ------+
                            |
                            v
                   notification-service

Wichtig ist: Jeder Service besitzt sein eigenes Datenmodell. Der order-service darf nicht direkt in die Datenbank des catalog-service greifen. Stattdessen ruft er eine API auf. Nachgelagerte Reaktionen laufen über Events.

#34.3 Projektstruktur

enterprise-commerce-platform/
├── pom.xml
├── docker-compose.yml
├── docker/
│   └── postgres/init.sql
├── docs/
│   ├── architecture.md
│   └── adr/0001-service-boundaries.md
├── libs/
│   ├── shared-kernel/
│   ├── event-contracts/
│   ├── web-starter/
│   └── test-support/
├── services/
│   ├── api-gateway/
│   ├── customer-service/
│   ├── catalog-service/
│   ├── order-service/
│   ├── payment-service/
│   └── notification-service/
├── deployments/
│   └── k8s/order-service-deployment.yaml
└── scripts/
    └── smoke-test.http

Diese Struktur zeigt eine häufige Enterprise-Aufteilung:

Bereich Zweck
libs/shared-kernel technische Basistypen wie Money, DomainEvent, IdempotencyKey
libs/event-contracts stabile Event-Verträge zwischen Services
libs/web-starter gemeinsame REST-Fehlerbehandlung
libs/test-support Testcontainers- und Integrationstest-Basis
services/* fachliche Microservices
deployments/* Kubernetes- und Betriebsartefakte
docs/* Architekturentscheidungen und technische Dokumentation

#34.4 Maven Parent-POM

Die Root-POM ist Aggregator und Build-Parent. Sie definiert Module, Java-Version, Spring-Boot-Version, Spring-Cloud-BOM und zentrale Plugin-Versionen.

<project>
    <groupId>com.acme.platform</groupId>
    <artifactId>enterprise-commerce-platform</artifactId>
    <version>${revision}</version>
    <packaging>pom</packaging>

    <modules>
        <module>libs/shared-kernel</module>
        <module>libs/event-contracts</module>
        <module>libs/web-starter</module>
        <module>libs/test-support</module>
        <module>services/customer-service</module>
        <module>services/catalog-service</module>
        <module>services/payment-service</module>
        <module>services/order-service</module>
        <module>services/notification-service</module>
        <module>services/api-gateway</module>
    </modules>

    <properties>
        <revision>1.0.0-SNAPSHOT</revision>
        <java.version>21</java.version>
        <spring-boot.version>3.5.15</spring-boot.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
    </properties>
</project>

Warum das wichtig ist:

  • Alle Services verwenden dieselbe Java-Version.
  • Dependency-Versionen werden zentral verwaltet.
  • Maven Reactor baut Libraries vor Services.
  • CI/CD kann einzelne Services oder das komplette System bauen.

#34.5 Warum gemeinsame Libraries vorsichtig verwenden?

Gemeinsame Libraries sind nützlich, aber gefährlich, wenn darin Fachlogik landet. In diesem Beispiel gelten diese Regeln:

Library Erlaubt Nicht erlaubt
shared-kernel technische Value Objects, kleine Interfaces Customer-, Order- oder Payment-Fachlogik
event-contracts Event DTOs und Versionierung Datenbank-Entities oder Service-Abhängigkeiten
web-starter technische Fehlerbehandlung fachliche Controller-Logik
test-support Testcontainers-Basis produktiver Code

Beispiel Money aus shared-kernel:

public record Money(BigDecimal amount, String currency) {
    public Money {
        Objects.requireNonNull(amount, "amount must not be null");
        Objects.requireNonNull(currency, "currency must not be null");
        Currency.getInstance(currency);
        amount = amount.setScale(2, RoundingMode.HALF_UP);
        if (amount.signum() < 0) {
            throw new IllegalArgumentException("amount must be positive");
        }
    }
}

Das ist technische, wiederverwendbare Logik. Dagegen sollte eine Regel wie „Premiumkunden erhalten 10 Prozent Rabatt“ nicht in shared-kernel, sondern in den fachlich zuständigen Service.

#34.6 Event Contracts

Events sind Verträge zwischen Services. Deshalb liegen sie in libs/event-contracts und werden bewusst versioniert.

public record OrderCreatedEvent(
        String eventId,
        String aggregateId,
        String customerId,
        List<OrderLineEvent> lines,
        BigDecimal totalAmount,
        String currency,
        Instant occurredAt
) implements DomainEvent {
    @Override
    public String type() {
        return "order.created.v1";
    }
}

Lernpunkte:

  • eventId hilft bei Idempotenz und Deduplizierung.
  • aggregateId verweist auf die betroffene Bestellung.
  • type() enthält eine explizite Event-Version.
  • Events sollten möglichst additive Änderungen bekommen, damit Consumer nicht brechen.

#34.7 Customer Service

Der customer-service ist bewusst einfach gehalten. Er besitzt Kundendaten und stellt REST-Endpunkte bereit.

@RestController
@RequestMapping("/customers")
public class CustomerController {
    private final CustomerApplicationService service;

    public CustomerController(CustomerApplicationService service) {
        this.service = service;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    CustomerResponse create(@Valid @RequestBody CustomerRequest request) {
        return service.create(request);
    }

    @GetMapping("/{id}")
    CustomerResponse get(@PathVariable UUID id) {
        return service.get(id);
    }
}

Die Service-Schicht kapselt die Transaktion:

@Service
public class CustomerApplicationService {
    private final CustomerRepository repository;

    @Transactional
    public CustomerResponse create(CustomerRequest request) {
        if (repository.existsByEmail(request.email())) {
            throw new IllegalArgumentException("email already exists");
        }
        Customer customer = repository.save(new Customer(request.name(), request.email()));
        return map(customer);
    }
}

Wichtig: Der Controller enthält keine Datenbanklogik. Die Transaktion sitzt in der Anwendungsschicht.

#34.8 Catalog Service

Der catalog-service besitzt Produkte und Lagerbestand. Der order-service reserviert Bestand über eine REST-API.

public void reserve(int quantity) {
    if (quantity <= 0) throw new IllegalArgumentException("quantity must be positive");
    if (availableStock < quantity) throw new IllegalArgumentException("not enough stock for sku " + sku);
    availableStock -= quantity;
}

Das ist eine fachliche Regel und gehört deshalb in das Domain-Objekt Product, nicht in den Controller.

#34.9 Order Service als Orchestrator

Der order-service ist der komplexeste Service. Er koordiniert mehrere Schritte:

  1. Kunde existiert?
  2. Lagerbestand reservieren.
  3. Bestellung speichern.
  4. Zahlung autorisieren.
  5. OrderCreatedEvent publizieren.
@Transactional
public OrderResponse create(CreateOrderRequest request) {
    customerClient.assertExists(request.customerId());

    OrderEntity order = new OrderEntity(request.customerId());
    request.lines().forEach(line -> {
        CatalogClient.Product product = catalogClient.reserve(line.sku(), line.quantity());
        order.addLine(product.sku(), line.quantity(), product.price());
    });

    order = repository.save(order);
    paymentClient.authorize(order.id(), order.totalAmount());

    OrderCreatedEvent event = new OrderCreatedEvent(
            UUID.randomUUID().toString(),
            order.id().toString(),
            order.customerId().toString(),
            order.lines().stream()
                    .map(l -> new OrderLineEvent(l.sku(), l.quantity(), l.unitPrice()))
                    .toList(),
            order.totalAmount(),
            "EUR",
            Instant.now());

    kafkaTemplate.send("order-events", order.id().toString(), event);
    return new OrderResponse(order.id(), order.customerId(), order.totalAmount(), order.status());
}

Dieses Beispiel zeigt absichtlich eine erste, verständliche Orchestrierung. In einer produktionsnahen nächsten Ausbaustufe würdest du ergänzen:

  • Outbox Pattern statt direktem Kafka-Senden in der Transaktion.
  • Idempotency Key für POST /orders.
  • Retry und Circuit Breaker für Remote Calls.
  • Kompensationslogik, falls Zahlung fehlschlägt.
  • Contract Tests für customer-service, catalog-service und payment-service.

#34.10 Payment Service

Der payment-service ist ein Beispiel für einen fachlich separaten Service. In echten Systemen würde er Payment Provider, PSP-Callbacks und Fraud Checks integrieren. Im Lernprojekt autorisiert er eine Zahlung und publiziert ein Event.

@Transactional
public PaymentAuthorizationResponse authorize(PaymentAuthorizationRequest request) {
    Payment payment = repository.save(Payment.authorized(request.orderId(), request.amount(), request.currency()));
    kafkaTemplate.send("payment-events", request.orderId(), new PaymentAuthorizedEvent(
            UUID.randomUUID().toString(), request.orderId(), payment.id().toString(),
            request.amount(), request.currency(), Instant.now()));
    return new PaymentAuthorizationResponse(payment.id().toString(), payment.status());
}

#34.11 Notification Service als Event Consumer

Der notification-service reagiert auf OrderCreatedEvent. Er wird nicht synchron vom order-service aufgerufen. Das ist ein typisches Beispiel für Entkopplung.

@KafkaListener(topics = "order-events")
public void onOrderCreated(OrderCreatedEvent event) {
    log.info("Prepare notification for order {} and customer {}", event.aggregateId(), event.customerId());
    repository.save(new NotificationLog(event.aggregateId(), event.type()));
}

Vorteil: Wenn der Notification-Service kurz ausfällt, kann Kafka Events puffern. Der Bestellprozess muss nicht direkt von einer E-Mail-Komponente abhängen.

#34.12 API Gateway

Das API Gateway routet externe Requests auf interne Services.

spring:
  cloud:
    gateway:
      routes:
        - id: customer-service
          uri: http://localhost:8081
          predicates:
            - Path=/customers/**
        - id: catalog-service
          uri: http://localhost:8082
          predicates:
            - Path=/products/**
        - id: order-service
          uri: http://localhost:8083
          predicates:
            - Path=/orders/**

In einer nächsten Ausbaustufe kannst du hier ergänzen:

  • OAuth2 Resource Server
  • Rate Limiting
  • Request Logging
  • Correlation IDs
  • CORS-Regeln
  • API-Versionierung

#34.13 Lokale Infrastruktur mit Docker Compose

Das Beispielprojekt enthält PostgreSQL und Kafka für lokale Entwicklung.

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: acme
      POSTGRES_PASSWORD: acme
      POSTGRES_DB: platform
    ports:
      - "5432:5432"

  kafka:
    image: bitnami/kafka:3.7
    ports:
      - "9092:9092"

Start:

cd enterprise-commerce-platform
docker compose up -d postgres kafka kafka-ui
mvn clean verify
mvn -pl services/customer-service -am spring-boot:run

#34.14 Smoke-Test-Ablauf

Das Projekt enthält scripts/smoke-test.http. Der Ablauf ist:

  1. Kunde anlegen.
  2. Produkte abrufen.
  3. Bestellung mit Produktpositionen erzeugen.
  4. Kafka UI öffnen und order-events prüfen.
  5. Notification-Datenbank prüfen.

Beispiel Request:

POST http://localhost:8080/orders
Content-Type: application/json

{
  "customerId": "REPLACE_WITH_CUSTOMER_ID",
  "lines": [
    { "sku": "BOOK-SPRING", "quantity": 1 },
    { "sku": "BOOK-JAKARTA", "quantity": 1 }
  ]
}

#34.15 Kubernetes-Beispiel

Ein einfacher Deployment-Ausschnitt zeigt, wie der order-service produktionsnah betrieben werden könnte.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: order-service
          image: ghcr.io/acme/order-service:1.0.0
          ports:
            - containerPort: 8083
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8083

Das ist bewusst nur ein Startpunkt. Für echte Umgebungen fehlen noch Secrets, ConfigMaps, Resource Limits, Network Policies, Ingress, Service Account und Observability-Konfiguration.

#34.16 Erweiterungsaufgaben

Nutze das Beispielprojekt als Trainingsbasis. Baue es in Stufen aus:

#Stufe 1: Solide REST-Services

  • CRUD für Customers und Products vervollständigen.
  • Pagination einbauen.
  • Fehlerantworten mit ProblemDetail oder eigener ApiError-Struktur vereinheitlichen.
  • OpenAPI/Swagger ergänzen.

#Stufe 2: Robustere Microservices

  • Resilience4j für Timeouts, Retry und Circuit Breaker einbauen.
  • Idempotency Key für POST /orders ergänzen.
  • Outbox Pattern im order-service implementieren.
  • Dead Letter Topic für Kafka einrichten.

#Stufe 3: Security

  • Keycloak lokal ergänzen.
  • Gateway als OAuth2 Resource Server konfigurieren.
  • Service-to-Service-Kommunikation absichern.
  • Rollenmodell definieren: CUSTOMER, ADMIN, SUPPORT.

#Stufe 4: Testing

  • Testcontainers für PostgreSQL verwenden.
  • WireMock für Remote Services einsetzen.
  • Contract Tests zwischen order-service und payment-service ergänzen.
  • ArchUnit-Regeln für Schichten und Paketgrenzen schreiben.

#Stufe 5: Observability

  • Correlation ID pro Request erzeugen.
  • Micrometer-Metriken ergänzen.
  • OpenTelemetry Tracing einbauen.
  • Grafana/Prometheus/Loki in Docker Compose ergänzen.

#Stufe 6: Alternative Frameworks

Setze einen Service zusätzlich in einer anderen Technologie um:

  • catalog-service als Quarkus-Service.
  • notification-service als Micronaut-Service.
  • payment-service als Jakarta EE/MicroProfile-Service.

So lernst du nicht nur Spring Boot, sondern erkennst Framework-Unterschiede anhand derselben fachlichen Domäne.

#34.17 Lernfragen zum Beispielprojekt

  • Warum ist event-contracts ein eigenes Modul?
  • Warum darf der order-service nicht direkt in die Catalog-Datenbank schreiben?
  • Welche Risiken entstehen durch synchrone Calls im order-service?
  • Warum ist direktes Kafka-Senden innerhalb einer Datenbanktransaktion problematisch?
  • Welche Daten gehören in ein Event, welche nicht?
  • Wann wäre ein modularer Monolith besser als diese Microservice-Struktur?
  • Welche Teile würdest du zuerst produktionsreif machen?
  • Wie würdest du Security, Observability und CI/CD ergänzen?

#35. Quellen und offizielle Dokumentation

Diese Quellen wurden zur fachlichen Einordnung und Aktualisierung des erweiterten Lernpfads verwendet:

  1. Jakarta EE Specifications: https://jakarta.ee/specifications/

  2. Spring Framework Documentation: https://docs.spring.io/spring-framework/reference/index.html

  3. Spring Boot Documentation: https://docs.spring.io/spring-boot/documentation.html

  4. Spring Cloud Reference Documentation: https://docs.spring.io/spring-cloud/docs/current/reference/html/

  5. Jakarta EE Tutorial: https://jakarta.ee/learn/docs/jakartaee-tutorial/current/index.html