70 Jakarta-EE-Themen

Jakarta EE Refactoring Workbench

Alle zugehörigen Inhalte befinden sich auf dieser einen großen Seite. Kapitel und Beispiele sind standardmäßig geschlossen und lassen sich gezielt öffnen.

Fachlicher Lernpfad

Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen

Lernzielorientierter Zugang

Jakarta EE nach Enterprise-Verträgen lernen

Die vorhandenen Kapitel bleiben vollständig erhalten. Diese Orientierung gruppiert sie nach den Entscheidungen, die ein Enterprise-Team tatsächlich treffen muss.

Komponenten

CDI für explizite Abhängigkeiten und Varianten nutzen, nicht als Service-Locator.

Schnittstellen

JAX-RS auf Transportübersetzung begrenzen und Bean Validation an der richtigen Grenze einsetzen.

Transaktionen

JTA-Transaktionsgrenzen an fachlichen Use Cases ausrichten.

Persistenz

JPA-Mappings, Aggregate und Fetch-Strategien ohne Durchsickern in die Fachlogik gestalten.

Kapitel 1 CDI, Konstruktorinjektion und explizite Abhängigkeiten7 Fachthemen

CDI, JAX-RS, Bean Validation, JTA und JPA als klare Enterprise-Grenzen statt versteckter Containerkopplung.

Jakarta-EE-Themen · Abschnitt 1

Jakarta EE Refactoring Workbench

CDI, JAX-RS, Bean Validation, JTA und JPA als klare Enterprise-Grenzen statt versteckter Containerkopplung.

Fachlicher Lernpfad

Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen

Bereich 1 CDI Constructor Injection Abhängigkeiten explizit über den Konstruktor übergeben.

CDI Constructor Injection

Code Smell

Field Injection und versteckte Containerabhängigkeit

Pattern / Prinzip

Constructor Injection + Dependency Inversion

Refactoring-Pfad

Abhängigkeiten explizit über den Konstruktor übergeben.

Alternative

Producer Method für komplexe technische Erzeugung

Vorher

Jakarta-EE-Ausgangscode
@Inject CustomerRepository repository;

Nachher

Jakarta-EE-Zielcode
CdiConstructorInjectionRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

CdiConstructorInjectionRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Constructor Injection + Dependency Inversion
// Zweck: CDI-Abhängigkeiten explizit und außerhalb des Containers testbar machen.
public final class CdiConstructorInjectionRefactoring {
  public interface CustomerRepository { boolean exists(String customerId); }
  public static final class CustomerService {
    private final CustomerRepository repository;
    public CustomerService(CustomerRepository repository) { this.repository = java.util.Objects.requireNonNull(repository); }
    public boolean canCreateOrder(String customerId) { return repository.exists(customerId); }
  }
}

Bereich 2 CDI Qualifier statt String-Auswahl Fachliche Variante als typisierten Schlüssel und Registry modellieren.

CDI Qualifier statt String-Auswahl

Code Smell

Implementierungen werden über String-Schlüssel oder if/switch ausgewählt

Pattern / Prinzip

Qualifier + Strategy Registry

Refactoring-Pfad

Fachliche Variante als typisierten Schlüssel und Registry modellieren.

Alternative

CDI Alternatives für deploymentspezifischen Austausch

Vorher

Jakarta-EE-Ausgangscode
if (channel.equals("email")) { ... }

Nachher

Jakarta-EE-Zielcode
CdiQualifierStrategyRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

CdiQualifierStrategyRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Map;
// Pattern: Qualifier + Strategy Registry
// Zweck: CDI-Varianten typisiert auswählen, ohne String-basierte Fallunterscheidung.
public final class CdiQualifierStrategyRefactoring {
  public enum Channel { EMAIL, SMS }
  public interface NotificationStrategy { void send(String recipient, String text); }
  public static final class Registry {
    private final Map<Channel, NotificationStrategy> strategies;
    public Registry(Map<Channel, NotificationStrategy> strategies) { this.strategies = Map.copyOf(strategies); }
    public void send(Channel channel, String recipient, String text) {
      var strategy = strategies.get(channel); if (strategy == null) throw new IllegalArgumentException("Unsupported channel: " + channel);
      strategy.send(recipient, text);
    }
  }
}

Bereich 3 Thin JAX-RS Resource Transportdaten normalisieren und an einen frameworkfreien Use Case delegieren.

Thin JAX-RS Resource

Code Smell

REST-Ressource enthält Mapping, Fachlogik und Persistenz

Pattern / Prinzip

Thin Adapter + Application Use Case

Refactoring-Pfad

Transportdaten normalisieren und an einen frameworkfreien Use Case delegieren.

Alternative

Direkter Domain Service bei sehr kleinem Modul

Vorher

Jakarta-EE-Ausgangscode
@POST public Response create(Request r) { persist(map(r)); ... }

Nachher

Jakarta-EE-Zielcode
JaxRsThinResourceRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

JaxRsThinResourceRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Thin Adapter + Application Use Case
// Zweck: JAX-RS-Transport von fachlicher Verarbeitung trennen.
public final class JaxRsThinResourceRefactoring {
  public record CreateClaimRequest(String policyNumber, long amountCents) {}
  public record CreateClaimCommand(String policyNumber, long amountCents) {}
  public record ClaimResult(String claimId, String status) {}
  public interface CreateClaimUseCase { ClaimResult execute(CreateClaimCommand command); }
  public static ClaimResult post(CreateClaimRequest request, CreateClaimUseCase useCase) {
    if (request == null || request.policyNumber() == null || request.policyNumber().isBlank()) throw new IllegalArgumentException("policyNumber");
    return useCase.execute(new CreateClaimCommand(request.policyNumber().trim(), request.amountCents()));
  }
}

Bereich 4 ExceptionMapper als Übersetzungsgrenze Fachliche Fehler in stabile API-Fehlermodelle übersetzen und technische Ursachen intern behalten.

ExceptionMapper als Übersetzungsgrenze

Code Smell

Technische Exceptions gelangen ungefiltert bis zum HTTP-Client

Pattern / Prinzip

Exception Translation + Problem Details

Refactoring-Pfad

Fachliche Fehler in stabile API-Fehlermodelle übersetzen und technische Ursachen intern behalten.

Alternative

Result Type für erwartbare Ablehnungen

Vorher

Jakarta-EE-Ausgangscode
catch (Exception e) { return Response.serverError().entity(e.getMessage()).build(); }

Nachher

Jakarta-EE-Zielcode
JaxRsExceptionMapperRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

JaxRsExceptionMapperRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Exception Translation + Problem Details
// Zweck: Stabile API-Fehlercodes von internen Exceptions entkoppeln.
public final class JaxRsExceptionMapperRefactoring {
  public static final class CustomerNotFound extends RuntimeException { public CustomerNotFound(String id){ super(id); } }
  public record ApiProblem(int status, String code, String title, String detail) {}
  public static ApiProblem map(Throwable error) {
    if (error instanceof CustomerNotFound e) return new ApiProblem(404, "CUSTOMER_NOT_FOUND", "Kunde fehlt", e.getMessage());
    if (error instanceof IllegalArgumentException e) return new ApiProblem(400, "INVALID_REQUEST", "Ungültige Anfrage", e.getMessage());
    return new ApiProblem(500, "INTERNAL_ERROR", "Interner Fehler", "Die Anfrage konnte nicht verarbeitet werden.");
  }
}

Bereich 5 Bean Validation Boundary Strukturelle Eingabevalidierung am Adapter, Fachinvarianten im Fachobjekt halten.

Bean Validation Boundary

Code Smell

Validierung ist über Ressource, Service und Entity verteilt

Pattern / Prinzip

Validation Boundary + Value Object

Refactoring-Pfad

Strukturelle Eingabevalidierung am Adapter, Fachinvarianten im Fachobjekt halten.

Alternative

Manuelle Validator-Kette bei hochdynamischen Regeln

Vorher

Jakarta-EE-Ausgangscode
if (email == null) ... // mehrfach in mehreren Schichten

Nachher

Jakarta-EE-Zielcode
BeanValidationBoundaryRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

BeanValidationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Validation Boundary + Value Object
// Zweck: Transportvalidierung und fachliche Invarianten sauber trennen.
public final class BeanValidationBoundaryRefactoring {
  public record RegistrationRequest(String email, String displayName) {}
  public record EmailAddress(String value) {
    public EmailAddress { if (value == null || !value.matches("^[^@]+@[^@]+\\.[^@]+$")) throw new IllegalArgumentException("email"); value = value.toLowerCase(); }
  }
  public record RegisterCustomer(EmailAddress email, String displayName) {}
  public static RegisterCustomer validate(RegistrationRequest request) {
    if (request == null || request.displayName() == null || request.displayName().isBlank()) throw new IllegalArgumentException("displayName");
    return new RegisterCustomer(new EmailAddress(request.email()), request.displayName().trim());
  }
}

Bereich 6 JTA Transaction Boundary Eine Transaktion um genau einen fachlichen Use Case legen; externe I/O danach ausführen.

JTA Transaction Boundary

Code Smell

Transaktion verteilt sich über Ressource, Service und mehrere Adapter

Pattern / Prinzip

Application Service + Unit of Work

Refactoring-Pfad

Eine Transaktion um genau einen fachlichen Use Case legen; externe I/O danach ausführen.

Alternative

Saga bei serviceübergreifenden Abläufen

Vorher

Jakarta-EE-Ausgangscode
resource.create(); service.save(); publisher.send();

Nachher

Jakarta-EE-Zielcode
JtaTransactionBoundaryRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

JtaTransactionBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.function.Supplier;
// Pattern: Application Service + Unit of Work
// Zweck: JTA-Transaktion um einen vollständigen lokalen Use Case legen.
public final class JtaTransactionBoundaryRefactoring {
  public interface UnitOfWork { <T> T required(Supplier<T> work); }
  public interface OrderRepository { String save(String customerId, long amountCents); }
  public static String createOrder(String customerId, long amountCents, UnitOfWork tx, OrderRepository repository) {
    if (amountCents <= 0) throw new IllegalArgumentException("amountCents");
    return tx.required(() -> repository.save(customerId, amountCents));
  }
}

Bereich 7 JPA Repository Port Fachmodell und Persistenzmodell trennen; Adapter mappt explizit zwischen beiden.

JPA Repository Port

Code Smell

JPA Entity und EntityManager-Typen lecken in Fach- und Anwendungscode

Pattern / Prinzip

Repository Port + Data Mapper

Refactoring-Pfad

Fachmodell und Persistenzmodell trennen; Adapter mappt explizit zwischen beiden.

Alternative

Active Record für bewusst kleine CRUD-Anwendung

Vorher

Jakarta-EE-Ausgangscode
EntityManager und @Entity im Application Service

Nachher

Jakarta-EE-Zielcode
JpaRepositoryPortRefactoring.class // explizite, testbare Boundary

Kompilierbare Referenz

JpaRepositoryPortRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Optional;
// Pattern: Repository Port + Data Mapper
// Zweck: JPA und EntityManager aus dem Fachkern heraushalten.
public final class JpaRepositoryPortRefactoring {
  public record CustomerId(String value) { public CustomerId { if (value == null || value.isBlank()) throw new IllegalArgumentException("id"); } }
  public record Customer(CustomerId id, String name) {}
  public interface CustomerRepository { Optional<Customer> find(CustomerId id); void save(Customer customer); }
  public record CustomerRow(String id, String name) {}
  public static Customer toDomain(CustomerRow row) { return new Customer(new CustomerId(row.id()), row.name()); }
  public static CustomerRow toRow(Customer customer) { return new CustomerRow(customer.id().value(), customer.name()); }
}
Kapitel 2 JAX-RS, JSON-B und saubere API-Grenzen7 Fachthemen

JSON-B, JSON-P, JMS, CDI Events, Scheduler und Jakarta Security als explizite, testbare Enterprise-Grenzen.

Jakarta-EE-Themen · Abschnitt 2

Jakarta EE Refactoring Workbench

JSON-B, JSON-P, JMS, CDI Events, Scheduler und Jakarta Security als explizite, testbare Enterprise-Grenzen.

Fachlicher Lernpfad

Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen

Bereich 8 JSON-B Mapping Boundary JSON-B DTO und Domainmodell trennen; Mapping explizit und testbar halten.

JSON-B Mapping Boundary

Code Smell

JSON-B Annotationen und Transportnamen sickern in das Fachmodell

Pattern / Prinzip

DTO Mapper + Anti-Corruption Layer

Refactoring-Pfad

JSON-B DTO und Domainmodell trennen; Mapping explizit und testbar halten.

Alternative

Direktes Mapping nur bei stabilen, rein technischen DTOs

Vorher

Jakarta-EE-Ausgangscode
@JsonbProperty("customer_id") // direkt im Domainobjekt

Nachher

Jakarta-EE-Zielstruktur
JsonbMappingBoundaryRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JsonbMappingBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: DTO Mapper + Anti-Corruption Layer
// Zweck: JSON-B-Vertragsmodell und Domainmodell unabhängig weiterentwickeln.
public final class JsonbMappingBoundaryRefactoring {
  public record CustomerJson(String customer_id, String display_name) {}
  public record CustomerId(String value) { public CustomerId { if (value == null || value.isBlank()) throw new IllegalArgumentException("id"); } }
  public record Customer(CustomerId id, String displayName) {}
  public static Customer toDomain(CustomerJson dto) {
    if (dto == null || dto.display_name() == null || dto.display_name().isBlank()) throw new IllegalArgumentException("displayName");
    return new Customer(new CustomerId(dto.customer_id()), dto.display_name().trim());
  }
  public static CustomerJson toJson(Customer customer) { return new CustomerJson(customer.id().value(), customer.displayName()); }
}

Bereich 9 JSON-P Streaming Boundary JSON-P-Streaming hinter einem fachlichen Reader-Port kapseln und Datensätze schrittweise liefern.

JSON-P Streaming Boundary

Code Smell

Große JSON-Dokumente werden vollständig materialisiert und erzeugen unnötigen Speicherbedarf

Pattern / Prinzip

Streaming Parser Port + Iterator

Refactoring-Pfad

JSON-P-Streaming hinter einem fachlichen Reader-Port kapseln und Datensätze schrittweise liefern.

Alternative

JSON-B für kleine, klar begrenzte Payloads

Vorher

Jakarta-EE-Ausgangscode
var object = Json.createReader(input).readObject();

Nachher

Jakarta-EE-Zielstruktur
JsonpStreamingBoundaryRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JsonpStreamingBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Iterator;
// Pattern: Streaming Parser Port + Iterator
// Zweck: Große JSON-Payloads ohne vollständige Materialisierung verarbeiten.
public final class JsonpStreamingBoundaryRefactoring {
  public record OrderLine(String sku, int quantity) { public OrderLine { if (quantity <= 0) throw new IllegalArgumentException("quantity"); } }
  public interface JsonLineStream extends AutoCloseable, Iterator<OrderLine> { @Override void close(); }
  public interface LineConsumer { void accept(OrderLine line); }
  public static int process(JsonLineStream stream, LineConsumer consumer) {
    int count=0; try (stream) { while (stream.hasNext()) { consumer.accept(stream.next()); count++; } } return count;
  }
}

Bereich 10 JMS Producer Port Fachliches Ereignis an einen Port übergeben; JMS-Header und Destination nur im Adapter behandeln.

JMS Producer Port

Code Smell

Application Service erzeugt JMS-Nachrichten und kennt Destination, Header und Providerdetails

Pattern / Prinzip

Message Publisher Port + Adapter

Refactoring-Pfad

Fachliches Ereignis an einen Port übergeben; JMS-Header und Destination nur im Adapter behandeln.

Alternative

Transactional Outbox bei atomarer DB/Event-Anforderung

Vorher

Jakarta-EE-Ausgangscode
context.createProducer().send(queue, message);

Nachher

Jakarta-EE-Zielstruktur
JmsProducerPortRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JmsProducerPortRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
// Pattern: Message Publisher Port + Adapter
// Zweck: JMS-Providerdetails aus dem Use Case entfernen.
public final class JmsProducerPortRefactoring {
  public record OrderApproved(String orderId, Instant occurredAt) {}
  public interface EventPublisher { void publish(OrderApproved event); }
  public static final class ApproveOrderUseCase {
    private final EventPublisher publisher;
    public ApproveOrderUseCase(EventPublisher publisher) { this.publisher=java.util.Objects.requireNonNull(publisher); }
    public void approve(String orderId) { if (orderId==null || orderId.isBlank()) throw new IllegalArgumentException("orderId"); publisher.publish(new OrderApproved(orderId, Instant.now())); }
  }
}

Bereich 11 Idempotent JMS Consumer Message-ID atomar reservieren, dann genau einmal fachlich anwenden; Redelivery sicher quittieren.

Idempotent JMS Consumer

Code Smell

Redelivery führt zu mehrfacher fachlicher Verarbeitung und doppelten Seiteneffekten

Pattern / Prinzip

Idempotent Consumer + Inbox

Refactoring-Pfad

Message-ID atomar reservieren, dann genau einmal fachlich anwenden; Redelivery sicher quittieren.

Alternative

Natürlich idempotente Operation bei einfachen Zustandssetzungen

Vorher

Jakarta-EE-Ausgangscode
listener.onMessage(m) { service.book(m.getBody()); }

Nachher

Jakarta-EE-Zielstruktur
JmsIdempotentConsumerRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JmsIdempotentConsumerRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Idempotent Consumer + Inbox
// Zweck: JMS-Redelivery ohne doppelte fachliche Seiteneffekte verarbeiten.
public final class JmsIdempotentConsumerRefactoring {
  public interface Inbox { boolean reserve(String messageId); void complete(String messageId); void release(String messageId); }
  public interface Handler { void handle(String payload); }
  public static boolean consume(String messageId, String payload, Inbox inbox, Handler handler) {
    if (!inbox.reserve(messageId)) return false;
    try { handler.handle(payload); inbox.complete(messageId); return true; }
    catch (RuntimeException e) { inbox.release(messageId); throw e; }
  }
}

Bereich 12 CDI Domain Event Boundary Domain Event frameworkfrei modellieren und erst am Application-Rand auf CDI Event übersetzen.

CDI Domain Event Boundary

Code Smell

CDI Events werden direkt als Domain Events verwendet und koppeln Fachmodell an Containersemantik

Pattern / Prinzip

Domain Event + Event Bridge

Refactoring-Pfad

Domain Event frameworkfrei modellieren und erst am Application-Rand auf CDI Event übersetzen.

Alternative

Direkter CDI Event bei rein technischer, lokaler Benachrichtigung

Vorher

Jakarta-EE-Ausgangscode
event.fire(new ClaimRegistered(...)); // Domain kennt CDI

Nachher

Jakarta-EE-Zielstruktur
CdiDomainEventBoundaryRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

CdiDomainEventBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
// Pattern: Domain Event + Event Bridge
// Zweck: Fachereignis von CDI-Eventmechanik entkoppeln.
public final class CdiDomainEventBoundaryRefactoring {
  public sealed interface DomainEvent permits ClaimRegistered { Instant occurredAt(); }
  public record ClaimRegistered(String claimId, Instant occurredAt) implements DomainEvent {}
  public interface DomainEventBridge { void fire(DomainEvent event); }
  public static void register(String claimId, DomainEventBridge bridge) {
    if (claimId==null || claimId.isBlank()) throw new IllegalArgumentException("claimId");
    bridge.fire(new ClaimRegistered(claimId, Instant.now()));
  }
}

Bereich 13 Jakarta Scheduler Boundary Scheduler triggert nur einen Use Case; Zeit und Exklusivität werden über Ports kontrolliert.

Jakarta Scheduler Boundary

Code Smell

Timer-Callback enthält Fachlogik, Zeitermittlung und verteilte Sperre

Pattern / Prinzip

Scheduler Adapter + Clock Port + Lease

Refactoring-Pfad

Scheduler triggert nur einen Use Case; Zeit und Exklusivität werden über Ports kontrolliert.

Alternative

Externer Scheduler für plattformübergreifende Orchestrierung

Vorher

Jakarta-EE-Ausgangscode
@Schedule public void expire() { repository.find... }

Nachher

Jakarta-EE-Zielstruktur
JakartaSchedulerBoundaryRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JakartaSchedulerBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
// Pattern: Scheduler Adapter + Clock Port + Lease
// Zweck: Timertechnik von fachlicher periodischer Arbeit trennen.
public final class JakartaSchedulerBoundaryRefactoring {
  public interface ClockPort { Instant now(); }
  public interface Lease { boolean tryAcquire(String job, Instant until); void release(String job); }
  public interface ExpirationUseCase { int expireDueItems(Instant now); }
  public static int run(ClockPort clock, Lease lease, ExpirationUseCase useCase) {
    var now=clock.now(); if (!lease.tryAcquire("expiration", now.plusSeconds(55))) return 0;
    try { return useCase.expireDueItems(now); } finally { lease.release("expiration"); }
  }
}

Bereich 14 Jakarta Security Identity Mapping Containeridentität in typisierten Actor übersetzen und Berechtigung als fachliche Policy prüfen.

Jakarta Security Identity Mapping

Code Smell

Security Principal, Rollenstrings und Token-Claims werden direkt im Fachcode ausgewertet

Pattern / Prinzip

Identity Mapper + Authorization Policy

Refactoring-Pfad

Containeridentität in typisierten Actor übersetzen und Berechtigung als fachliche Policy prüfen.

Alternative

Method Security für einfache statische Rollenregeln

Vorher

Jakarta-EE-Ausgangscode
if (securityContext.isCallerInRole("APPROVER")) ...

Nachher

Jakarta-EE-Zielstruktur
JakartaSecurityIdentityRefactoring.class // frameworkfreie Kernlogik + Adapter

Kompilierbare Referenz

JakartaSecurityIdentityRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Set;
// Pattern: Identity Mapper + Authorization Policy
// Zweck: Jakarta-Security-Details von fachlichen Entscheidungen entkoppeln.
public final class JakartaSecurityIdentityRefactoring {
  public record Actor(String subject, Set<String> permissions) { public Actor { permissions=Set.copyOf(permissions); } }
  public interface SecurityIdentityView { String principalName(); Set<String> roles(); }
  public static Actor map(SecurityIdentityView identity) {
    var permissions=identity.roles().stream().map(r -> "ROLE_"+r.toUpperCase()).collect(java.util.stream.Collectors.toUnmodifiableSet());
    return new Actor(identity.principalName(), permissions);
  }
  public static boolean mayApprove(Actor actor, long amountCents) { return actor.permissions().contains("ROLE_APPROVER") && amountCents <= 1_000_000; }
}
Kapitel 3 Interceptor, Transaktionen und Querschnittslogik7 Fachthemen

CDI-Querschnittslogik, JPA-Nebenläufigkeit und Ladepläne sowie Jakarta-Concurrency-Grenzen als explizite, testbare Enterprise-Strukturen.

Jakarta-EE-Themen · Abschnitt 3

Jakarta EE Refactoring Workbench

CDI-Querschnittslogik, JPA-Nebenläufigkeit und Ladepläne sowie Jakarta-Concurrency-Grenzen als explizite, testbare Enterprise-Strukturen.

Fachlicher Lernpfad

Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen

Bereich 15 CDI Interceptor Boundary Querschnittslogik als expliziten Port modellieren; CDI-Interceptor delegiert nur an den Adapter.

CDI Interceptor Boundary

Code Smell

Interceptor-Annotationen und InvocationContext sickern in fachliche Services

Pattern / Prinzip

Interceptor Adapter + Decorator

Refactoring-Pfad

Querschnittslogik als expliziten Port modellieren; CDI-Interceptor delegiert nur an den Adapter.

Alternative

Expliziter Decorator bei fachlich sichtbarer Reihenfolge

Vorher

Jakarta-EE-Ausgangscode
@AroundInvoke public Object audit(InvocationContext ctx) ...

Nachher

Jakarta-EE-Zielstruktur
CdiInterceptorBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

CdiInterceptorBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
// Pattern: Interceptor Adapter + Decorator
// Zweck: CDI-Interceptor-Technik von fachlich prüfbarer Auditlogik trennen.
public final class CdiInterceptorBoundaryRefactoring {
  public record Invocation(String operation, String actor, Instant startedAt) {}
  public interface ProceedingCall<T> { T proceed() throws Exception; }
  public interface AuditPort { void succeeded(Invocation call); void failed(Invocation call, Exception error); }
  public static <T> T invoke(Invocation call, ProceedingCall<T> action, AuditPort audit) throws Exception {
    try { T result=action.proceed(); audit.succeeded(call); return result; }
    catch (Exception error) { audit.failed(call,error); throw error; }
  }
}

Bereich 16 CDI Decorator Boundary Decorator-Reihenfolge als explizite, testbare Policy-Kette modellieren.

CDI Decorator Boundary

Code Smell

Mehrere CDI-Decorator verändern Verhalten, aber Reihenfolge und Fachwirkung bleiben verborgen

Pattern / Prinzip

Decorator + Policy Chain

Refactoring-Pfad

Decorator-Reihenfolge als explizite, testbare Policy-Kette modellieren.

Alternative

Chain of Responsibility bei optionalem Abbruch

Vorher

Jakarta-EE-Ausgangscode
@Decorator class SecuredNotifier implements Notifier ...

Nachher

Jakarta-EE-Zielstruktur
CdiDecoratorBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

CdiDecoratorBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.List;
// Pattern: Decorator + Policy Chain
// Zweck: Reihenfolge fachlich relevanter Zusatzregeln explizit und testbar machen.
public final class CdiDecoratorBoundaryRefactoring {
  public record Message(String recipient, String body) {}
  public interface Notification { void send(Message message); }
  public interface MessagePolicy { Message apply(Message message); }
  public static Notification decorate(Notification target, List<MessagePolicy> policies) {
    var snapshot=List.copyOf(policies);
    return message -> { Message current=message; for (var policy:snapshot) current=policy.apply(current); target.send(current); };
  }
}

Bereich 17 JPA Optimistic Lock Boundary Version in einen fachlichen Änderungsbefehl aufnehmen und Konflikte in ein typisiertes Result übersetzen.

JPA Optimistic Lock Boundary

Code Smell

Versionskonflikte werden als technische Persistence-Exception bis zur REST-Schicht durchgereicht

Pattern / Prinzip

Optimistic Lock + Exception Translation

Refactoring-Pfad

Version in einen fachlichen Änderungsbefehl aufnehmen und Konflikte in ein typisiertes Result übersetzen.

Alternative

Pessimistische Sperre bei sehr hoher Konfliktwahrscheinlichkeit

Vorher

Jakarta-EE-Ausgangscode
entityManager.merge(order); // OptimisticLockException ungefiltert

Nachher

Jakarta-EE-Zielstruktur
JpaOptimisticLockRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JpaOptimisticLockRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Optimistic Lock + Exception Translation
// Zweck: Versionskonflikte als fachlich behandelbares Ergebnis ausdrücken.
public final class JpaOptimisticLockRefactoring {
  public record ChangeOrder(String orderId, long expectedVersion, String note) {}
  public sealed interface Result permits Updated, Conflict {}
  public record Updated(long newVersion) implements Result {}
  public record Conflict(long expectedVersion, long actualVersion) implements Result {}
  public interface OrderStore { long currentVersion(String orderId); long update(ChangeOrder command); }
  public static Result execute(ChangeOrder command, OrderStore store) {
    long actual=store.currentVersion(command.orderId());
    return actual!=command.expectedVersion() ? new Conflict(command.expectedVersion(),actual) : new Updated(store.update(command));
  }
}

Bereich 18 JPA Fetch Plan Boundary Fachlich benannte Ladeprofile am Repository-Port anbieten; JPA EntityGraph bleibt Adapterdetail.

JPA Fetch Plan Boundary

Code Smell

EntityGraph- und Fetch-Join-Details werden in Use Cases verteilt und führen zu unkontrollierten Objektgraphen

Pattern / Prinzip

Fetch Plan + Repository Port

Refactoring-Pfad

Fachlich benannte Ladeprofile am Repository-Port anbieten; JPA EntityGraph bleibt Adapterdetail.

Alternative

Projektionen für reine Leseansichten

Vorher

Jakarta-EE-Ausgangscode
em.createEntityGraph("Order.withLines") // im Service

Nachher

Jakarta-EE-Zielstruktur
JpaFetchPlanBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JpaFetchPlanBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.List;
// Pattern: Fetch Plan + Repository Port
// Zweck: Fachlich benötigte Datenform von JPA-Ladehinweisen entkoppeln.
public final class JpaFetchPlanBoundaryRefactoring {
  public enum LoadProfile { SUMMARY, WITH_LINES, WITH_AUDIT }
  public record OrderView(String id, List<String> lines, List<String> audit) {
    public OrderView { lines=List.copyOf(lines); audit=List.copyOf(audit); }
  }
  public interface OrderReader { OrderView find(String id, LoadProfile profile); }
  public static OrderView loadForPricing(String id, OrderReader reader) { return reader.find(id, LoadProfile.WITH_LINES); }
}

Bereich 19 Criteria Query Object Suchparameter in unveränderliches Query Object überführen und Adapter übersetzt es in Criteria API.

Criteria Query Object

Code Smell

Dynamische Criteria-Ausdrücke wachsen in Repository-Methoden zu schwer testbaren Verzweigungen

Pattern / Prinzip

Query Object + Specification

Refactoring-Pfad

Suchparameter in unveränderliches Query Object überführen und Adapter übersetzt es in Criteria API.

Alternative

Native Query bei stabiler, datenbankspezifischer Analyse

Vorher

Jakarta-EE-Ausgangscode
if(status!=null) predicates.add(cb.equal(root.get("status"),status));

Nachher

Jakarta-EE-Zielstruktur
JpaCriteriaQueryObjectRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JpaCriteriaQueryObjectRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
import java.util.Optional;
// Pattern: Query Object + Specification
// Zweck: Dynamische Suchabsicht unabhängig von der JPA Criteria API ausdrücken.
public final class JpaCriteriaQueryObjectRefactoring {
  public record ClaimQuery(Optional<String> status, Optional<String> customerId, Optional<Instant> createdAfter, int limit) {
    public ClaimQuery { status=status==null?Optional.empty():status; customerId=customerId==null?Optional.empty():customerId; createdAfter=createdAfter==null?Optional.empty():createdAfter; if(limit<1||limit>500) throw new IllegalArgumentException("limit"); }
  }
  public interface ClaimReader { java.util.List<String> search(ClaimQuery query); }
  public static java.util.List<String> openClaimsForCustomer(String customerId, ClaimReader reader) {
    return reader.search(new ClaimQuery(Optional.of("OPEN"),Optional.of(customerId),Optional.empty(),100));
  }
}

Bereich 20 Managed Executor Port Asynchrone Ausführung über fachlich kleinen Executor-Port kapseln; Jakarta Concurrency bleibt Adapterdetail.

Managed Executor Port

Code Smell

Use Case injiziert ManagedExecutorService direkt und vermischt Task-Scheduling mit Fachlogik

Pattern / Prinzip

Executor Port + Command

Refactoring-Pfad

Asynchrone Ausführung über fachlich kleinen Executor-Port kapseln; Jakarta Concurrency bleibt Adapterdetail.

Alternative

Synchron ausführen bei kurzen, transaktionalen Operationen

Vorher

Jakarta-EE-Ausgangscode
managedExecutor.submit(() -> service.process(id));

Nachher

Jakarta-EE-Zielstruktur
JakartaManagedExecutorRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JakartaManagedExecutorRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.concurrent.CompletionStage;
// Pattern: Executor Port + Command
// Zweck: Jakarta-Concurrency-Infrastruktur vom Use Case entkoppeln.
public final class JakartaManagedExecutorRefactoring {
  public interface AsyncExecutor { <T> CompletionStage<T> submit(java.util.concurrent.Callable<T> task); }
  public interface DocumentProcessor { String process(String documentId); }
  public static CompletionStage<String> processAsync(String documentId, AsyncExecutor executor, DocumentProcessor processor) {
    if(documentId==null||documentId.isBlank()) throw new IllegalArgumentException("documentId");
    return executor.submit(() -> processor.process(documentId));
  }
}

Bereich 21 Context Propagation Boundary Benötigten Kontext als unveränderliches Objekt erfassen und beim Task-Start explizit installieren.

Context Propagation Boundary

Code Smell

Security-, Tenant- und Correlation-Kontext wird implizit über ThreadLocal oder Containerkontext transportiert

Pattern / Prinzip

Explicit Context + Context Propagator

Refactoring-Pfad

Benötigten Kontext als unveränderliches Objekt erfassen und beim Task-Start explizit installieren.

Alternative

Scoped Values in reinem Java bei kontrollierter Laufzeit

Vorher

Jakarta-EE-Ausgangscode
executor.submit(() -> useCase.run()); // Kontext implizit

Nachher

Jakarta-EE-Zielstruktur
JakartaContextPropagationRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JakartaContextPropagationRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Map;
import java.util.concurrent.Callable;
// Pattern: Explicit Context + Context Propagator
// Zweck: Security-, Tenant- und Correlation-Daten kontrolliert über Async-Grenzen transportieren.
public final class JakartaContextPropagationRefactoring {
  public record ExecutionContext(String actor, String tenant, String correlationId, Map<String,String> baggage) {
    public ExecutionContext { baggage=Map.copyOf(baggage); }
  }
  public interface ContextScope extends AutoCloseable { @Override void close(); }
  public interface ContextInstaller { ContextScope install(ExecutionContext context); }
  public static <T> Callable<T> wrap(ExecutionContext context, ContextInstaller installer, Callable<T> task) {
    return () -> { try (var ignored=installer.install(context)) { return task.call(); } };
  }
}
Kapitel 4 Asynchrone REST-Schnittstellen und reaktive Grenzen7 Fachthemen

REST-Async, externe REST-Clients, JPA-Konvertierung und Lifecycle, JTA-After-Commit, CDI-Request-Kontext und Jakarta Mail als explizite Adaptergrenzen.

Jakarta-EE-Themen · Abschnitt 4

Jakarta EE Refactoring Workbench

REST-Async, externe REST-Clients, JPA-Konvertierung und Lifecycle, JTA-After-Commit, CDI-Request-Kontext und Jakarta Mail als explizite Adaptergrenzen.

Fachlicher Lernpfad

Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen

Bereich 22 JAX-RS Async Boundary Asynchronen Auftrag als fachlichen Handle modellieren; JAX-RS adaptiert CompletionStage nur am Rand.

JAX-RS Async Boundary

Code Smell

Asynchrone REST-Verarbeitung bindet CompletionStage und HTTP-Lifecycle direkt an den Use Case

Pattern / Prinzip

Async Result Port + Adapter

Refactoring-Pfad

Asynchronen Auftrag als fachlichen Handle modellieren; JAX-RS adaptiert CompletionStage nur am Rand.

Alternative

Synchroner Request bei kurzen, deterministischen Abläufen

Vorher

Jakarta-EE-Ausgangscode
@Suspended AsyncResponse wird tief im Service weitergereicht

Nachher

Jakarta-EE-Zielstruktur
JaxRsAsyncBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JaxRsAsyncBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.concurrent.CompletionStage;
// Pattern: Async Result Port + Adapter
// Zweck: JAX-RS-Async-Mechanik vom fachlichen Auftrag und Ergebnis trennen.
public final class JaxRsAsyncBoundaryRefactoring {
  public record JobId(String value) { public JobId { if (value==null || value.isBlank()) throw new IllegalArgumentException("job id"); } }
  public sealed interface JobResult permits Accepted, Rejected {}
  public record Accepted(JobId jobId) implements JobResult {}
  public record Rejected(String reason) implements JobResult {}
  public interface AsyncJobPort { CompletionStage<JobResult> submit(String payload); }
  public static CompletionStage<JobResult> submit(String payload, AsyncJobPort port) {
    if (payload==null || payload.isBlank()) return java.util.concurrent.CompletableFuture.completedFuture(new Rejected("empty payload"));
    return port.submit(payload.strip());
  }
}

Bereich 23 Jakarta REST Client Adapter Externes REST-Modell in einen typisierten Port und ein internes Ergebnis übersetzen.

Jakarta REST Client Adapter

Code Smell

Remote-API-DTOs, HTTP-Status und Retry-Details sickern in die Fachlogik

Pattern / Prinzip

Anti-Corruption Layer + Adapter

Refactoring-Pfad

Externes REST-Modell in einen typisierten Port und ein internes Ergebnis übersetzen.

Alternative

Direkter Client bei rein technischer Durchleitung ohne Fachlogik

Vorher

Jakarta-EE-Ausgangscode
RemoteCustomerDto wird im Domain Service verarbeitet

Nachher

Jakarta-EE-Zielstruktur
JakartaRestClientAdapterRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JakartaRestClientAdapterRefactoring.java
package com.aydinsude.workbench.jakarta;
// Pattern: Anti-Corruption Layer + Adapter
// Zweck: Externe REST-Verträge in ein stabiles internes Kundenmodell übersetzen.
public final class JakartaRestClientAdapterRefactoring {
  public record CustomerId(String value) {}
  public record CustomerProfile(CustomerId id, String displayName, boolean active) {}
  public record RemoteCustomer(String id, String fullName, String status) {}
  public interface RemoteCustomerClient { RemoteCustomer load(String id); }
  public interface CustomerProfilePort { CustomerProfile load(CustomerId id); }
  public static CustomerProfilePort adapt(RemoteCustomerClient client) {
    return id -> { var remote=client.load(id.value()); return new CustomerProfile(id, remote.fullName(), "ACTIVE".equals(remote.status())); };
  }
}

Bereich 24 JPA Attribute Converter Boundary Serialisierung und Validierung zentralisieren; der Fachkern bleibt frei von JPA-Annotationen.

JPA Attribute Converter Boundary

Code Smell

Fachliche Value Objects werden als String zerlegt und an vielen Stellen inkonsistent rekonstruiert

Pattern / Prinzip

Value Object Mapper + Attribute Converter Adapter

Refactoring-Pfad

Serialisierung und Validierung zentralisieren; der Fachkern bleibt frei von JPA-Annotationen.

Alternative

Embeddable bei mehreren separat abfragbaren Spalten

Vorher

Jakarta-EE-Ausgangscode
String currencyAmount wird manuell gesplittet

Nachher

Jakarta-EE-Zielstruktur
JpaAttributeConverterBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JpaAttributeConverterBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.math.BigDecimal;
// Pattern: Value Object Mapper + Attribute Converter Adapter
// Zweck: Persistenzformat und fachliche Money-Invariante an einer Stelle koppeln.
public final class JpaAttributeConverterBoundaryRefactoring {
  public record Money(BigDecimal amount, String currency) {
    public Money { if (amount==null || currency==null || currency.length()!=3) throw new IllegalArgumentException("money"); }
  }
  public interface AttributeCodec<T> { String encode(T value); T decode(String stored); }
  public static AttributeCodec<Money> moneyCodec() {
    return new AttributeCodec<>() {
      public String encode(Money value) { return value.amount().toPlainString()+"|"+value.currency(); }
      public Money decode(String stored) { var p=stored.split("\\|",2); return new Money(new BigDecimal(p[0]),p[1]); }
    };
  }
}

Bereich 25 JPA Entity Lifecycle Callback Boundary Callbacks auf technische Zeitstempel begrenzen; Fachregeln über explizite Domain-Methoden ausführen.

JPA Entity Lifecycle Callback Boundary

Code Smell

@PrePersist und @PostLoad enthalten fachliche Regeln, Services oder I/O

Pattern / Prinzip

Lifecycle Adapter + Domain Method

Refactoring-Pfad

Callbacks auf technische Zeitstempel begrenzen; Fachregeln über explizite Domain-Methoden ausführen.

Alternative

Entity Listener für rein technische Auditfelder

Vorher

Jakarta-EE-Ausgangscode
@PrePersist versendet E-Mail und berechnet Status

Nachher

Jakarta-EE-Zielstruktur
JpaEntityLifecycleBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JpaEntityLifecycleBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.time.Instant;
// Pattern: Lifecycle Adapter + Domain Method
// Zweck: JPA-Callbacks auf technische Metadaten begrenzen und Fachübergänge explizit machen.
public final class JpaEntityLifecycleBoundaryRefactoring {
  public record AuditStamp(Instant createdAt, Instant updatedAt) {}
  public static final class Order {
    private String status="DRAFT"; private AuditStamp audit;
    public void submit() { if (!"DRAFT".equals(status)) throw new IllegalStateException("transition"); status="SUBMITTED"; }
    public void stamp(Instant now) { audit = audit==null ? new AuditStamp(now,now) : new AuditStamp(audit.createdAt(),now); }
    public String status(){ return status; } public AuditStamp audit(){ return audit; }
  }
}

Bereich 26 JTA After-Commit Boundary Folgeaktion als Pending Event speichern und erst nach Commit über einen Adapter freigeben.

JTA After-Commit Boundary

Code Smell

Nachrichten und E-Mails werden vor erfolgreichem Commit ausgelöst oder gehen bei Rollback verloren

Pattern / Prinzip

Transaction Synchronization + Outbox

Refactoring-Pfad

Folgeaktion als Pending Event speichern und erst nach Commit über einen Adapter freigeben.

Alternative

Transactional Outbox bei dauerhaft garantierter Zustellung

Vorher

Jakarta-EE-Ausgangscode
publisher.send(event) läuft innerhalb der Schreibtransaktion

Nachher

Jakarta-EE-Zielstruktur
JtaAfterCommitBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JtaAfterCommitBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.List;
// Pattern: Transaction Synchronization + Outbox
// Zweck: Fachliche Folgeaktionen an einen erfolgreichen Commit koppeln.
public final class JtaAfterCommitBoundaryRefactoring {
  public record PendingEvent(String type, String aggregateId, String payload) {}
  public interface TransactionBoundary { void afterCommit(Runnable action); }
  public interface EventPublisher { void publish(List<PendingEvent> events); }
  public static void register(List<PendingEvent> events, TransactionBoundary tx, EventPublisher publisher) {
    var snapshot=List.copyOf(events); tx.afterCommit(() -> publisher.publish(snapshot));
  }
}

Bereich 27 CDI Request Context Snapshot Benötigte Requestdaten als unveränderliches Objekt erfassen und bewusst an den Use Case übergeben.

CDI Request Context Snapshot

Code Smell

RequestScoped-Daten werden implizit in Hintergrundaufgaben verwendet und sind dort nicht mehr aktiv

Pattern / Prinzip

Explicit Context + Snapshot

Refactoring-Pfad

Benötigte Requestdaten als unveränderliches Objekt erfassen und bewusst an den Use Case übergeben.

Alternative

Managed Context Propagation bei vollständig kontrolliertem Container-Kontext

Vorher

Jakarta-EE-Ausgangscode
Background task greift später auf @RequestScoped Bean zu

Nachher

Jakarta-EE-Zielstruktur
CdiRequestContextSnapshotRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

CdiRequestContextSnapshotRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Locale;
// Pattern: Explicit Context + Snapshot
// Zweck: Requestdaten unveränderlich erfassen und ohne aktiven CDI-Requestscope weitergeben.
public final class CdiRequestContextSnapshotRefactoring {
  public record RequestContext(String actor, String tenant, Locale locale, String correlationId) {
    public RequestContext { if (actor==null || tenant==null || correlationId==null) throw new IllegalArgumentException("context"); }
  }
  public interface RequestContextSource { String actor(); String tenant(); Locale locale(); String correlationId(); }
  public static RequestContext capture(RequestContextSource source) {
    return new RequestContext(source.actor(),source.tenant(),source.locale(),source.correlationId());
  }
}

Bereich 28 Jakarta Mail Notification Port Fachliche Nachricht modellieren; Jakarta Mail übernimmt ausschließlich Rendering und Transport.

Jakarta Mail Notification Port

Code Smell

MimeMessage, Session und Transport bestimmen Signaturen im Application Service

Pattern / Prinzip

Port and Adapter + Template

Refactoring-Pfad

Fachliche Nachricht modellieren; Jakarta Mail übernimmt ausschließlich Rendering und Transport.

Alternative

Domain Event plus externer Notification Service bei eigener Plattform

Vorher

Jakarta-EE-Ausgangscode
Application Service erzeugt MimeMessage direkt

Nachher

Jakarta-EE-Zielstruktur
JakartaMailNotificationPortRefactoring.class // frameworkfreie Kernlogik + Jakarta Adapter

Kompilierbare Referenz

JakartaMailNotificationPortRefactoring.java
package com.aydinsude.workbench.jakarta;
import java.util.Map;
// Pattern: Port and Adapter + Template
// Zweck: Fachliche Benachrichtigung von Jakarta-Mail-Rendering und Transport trennen.
public final class JakartaMailNotificationPortRefactoring {
  public record Notification(String template, String recipient, Map<String,String> variables) {
    public Notification { variables=Map.copyOf(variables); }
  }
  public interface NotificationPort { void send(Notification notification); }
  public interface TemplateRenderer { String render(String template, Map<String,String> variables); }
  public interface MailTransport { void send(String recipient, String subject, String body); }
  public static NotificationPort adapt(TemplateRenderer renderer, MailTransport transport) {
    return n -> transport.send(n.recipient(), n.template(), renderer.render(n.template(),n.variables()));
  }
}
Kapitel 5 Jakarta Batch, Jobs und Wiederanlauf7 Fachthemen

Sieben zusammenhängende Enterprise-Refactorings mit klaren Jakarta-Grenzen, Pattern-Begründung, echtem Java-21-Code und kompakten SVGs.

Bereich 29 Jakarta Batch Job Boundary Batch-Adapter ruft einen fachlichen Job-Use-Case auf.

Jakarta Batch Job Boundary

Code Smell

Batchlet enthält SQL, Fachlogik und Statuspflege.

Pattern / Prinzip

Batch Coordinator + Use Case

Refactoring-Pfad

Batch-Adapter ruft einen fachlichen Job-Use-Case auf.

Alternative

External Worker bei plattformübergreifender Orchestrierung

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaBatchJobBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Batch Coordinator + Use Case
// Zweck: Jakarta Batch Job Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaBatchJobBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaBatchJobBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 30 Chunk Processing Checkpoint Strategy Checkpoint wird explizit und restartfähig modelliert.

Chunk Processing Checkpoint Strategy

Code Smell

Reader, Processor und Writer teilen versteckte mutable Zustände.

Pattern / Prinzip

Checkpoint + Unit of Work

Refactoring-Pfad

Checkpoint wird explizit und restartfähig modelliert.

Alternative

Tasklet für kleine atomare Jobs

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

ChunkProcessingCheckpointStrategyRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Checkpoint + Unit of Work
// Zweck: Chunk Processing Checkpoint Strategy als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class ChunkProcessingCheckpointStrategyRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public ChunkProcessingCheckpointStrategyRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 31 Partitioned Batch Processing Partitionen erhalten unabhängige Schlüsselräume und Ports.

Partitioned Batch Processing

Code Smell

Partitionen teilen globale Listen und Locks.

Pattern / Prinzip

Partition Strategy + Shared Nothing

Refactoring-Pfad

Partitionen erhalten unabhängige Schlüsselräume und Ports.

Alternative

Einzelner Stream bei kleinen Datenmengen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

PartitionedBatchProcessingRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Partition Strategy + Shared Nothing
// Zweck: Partitioned Batch Processing als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class PartitionedBatchProcessingRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public PartitionedBatchProcessingRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 32 Managed Scheduler Boundary Managed Scheduler triggert Use Case mit Lease-Port.

Managed Scheduler Boundary

Code Smell

Timer enthält Fachlogik und konkurriert auf mehreren Knoten.

Pattern / Prinzip

Scheduler Adapter + Lease

Refactoring-Pfad

Managed Scheduler triggert Use Case mit Lease-Port.

Alternative

Externer Scheduler

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

ManagedSchedulerBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Scheduler Adapter + Lease
// Zweck: Managed Scheduler Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class ManagedSchedulerBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public ManagedSchedulerBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 33 Bean Validation Groups Typisierte Validierungsphasen kapseln fachliche Reihenfolge.

Bean Validation Groups

Code Smell

Gruppenstrings und Reihenfolge sind über Ressourcen verteilt.

Pattern / Prinzip

Validation Policy

Refactoring-Pfad

Typisierte Validierungsphasen kapseln fachliche Reihenfolge.

Alternative

Ein einziges DTO für einfache Grenzen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

BeanValidationGroupsRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Validation Policy
// Zweck: Bean Validation Groups als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class BeanValidationGroupsRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public BeanValidationGroupsRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 34 CDI Alternatives for Tests Alternative Implementierung wird am Composition Root gewählt.

CDI Alternatives for Tests

Code Smell

Tests ersetzen Beans über globale Containertricks.

Pattern / Prinzip

Strategy + Composition Root

Refactoring-Pfad

Alternative Implementierung wird am Composition Root gewählt.

Alternative

Pure Unit Test ohne Container

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

CDIAlternativesforTestsRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Strategy + Composition Root
// Zweck: CDI Alternatives for Tests als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class CDIAlternativesforTestsRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public CDIAlternativesforTestsRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 35 Authorization Boundary Containerrollen werden in fachliche Permissions übersetzt.

Authorization Boundary

Code Smell

Rollenstrings werden direkt im Fachcode geprüft.

Pattern / Prinzip

Identity Mapper + Policy

Refactoring-Pfad

Containerrollen werden in fachliche Permissions übersetzt.

Alternative

Statische @RolesAllowed-Regel

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

AuthorizationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Identity Mapper + Policy
// Zweck: Authorization Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class AuthorizationBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public AuthorizationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}
Kapitel 6 WebSocket, Sessions und ausgehende Ports7 Fachthemen

Sieben zusammenhängende Enterprise-Refactorings mit klaren Jakarta-Grenzen, Pattern-Begründung, echtem Java-21-Code und kompakten SVGs.

Bereich 36 Jakarta WebSocket Session Port Session wird hinter typisiertem Outbound-Port gekapselt.

Jakarta WebSocket Session Port

Code Smell

Fachlogik hält WebSocket Session und sendet direkt.

Pattern / Prinzip

Session Port + Adapter

Refactoring-Pfad

Session wird hinter typisiertem Outbound-Port gekapselt.

Alternative

SSE bei reinem Server-Push

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaWebSocketSessionPortRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Session Port + Adapter
// Zweck: Jakarta WebSocket Session Port als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaWebSocketSessionPortRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaWebSocketSessionPortRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 37 Jakarta Faces Presenter Boundary Presenter erzeugt unveränderliches View Model und ruft Use Cases.

Jakarta Faces Presenter Boundary

Code Smell

Backing Bean mischt Navigation, Persistenz und Formatierung.

Pattern / Prinzip

Presenter + View Model

Refactoring-Pfad

Presenter erzeugt unveränderliches View Model und ruft Use Cases.

Alternative

JAX-RS SPA-Frontend

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaFacesPresenterBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Presenter + View Model
// Zweck: Jakarta Faces Presenter Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaFacesPresenterBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaFacesPresenterBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 38 Jakarta Persistence Soft Delete Soft-Delete-Regel wird zentral als Repository-Policy modelliert.

Jakarta Persistence Soft Delete

Code Smell

deleted-Flag wird in jeder Query manuell ergänzt.

Pattern / Prinzip

Repository Policy + Specification

Refactoring-Pfad

Soft-Delete-Regel wird zentral als Repository-Policy modelliert.

Alternative

Physisches Löschen mit Audit

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaPersistenceSoftDeleteRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Repository Policy + Specification
// Zweck: Jakarta Persistence Soft Delete als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaPersistenceSoftDeleteRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaPersistenceSoftDeleteRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 39 Jakarta Data Repository Boundary Technisches Repository implementiert fachlichen Query-Port.

Jakarta Data Repository Boundary

Code Smell

Jakarta-Data-Repository-Typen lecken in Domain und Service.

Pattern / Prinzip

Query Port + Mapper

Refactoring-Pfad

Technisches Repository implementiert fachlichen Query-Port.

Alternative

JPA Repository Adapter

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaDataRepositoryBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Query Port + Mapper
// Zweck: Jakarta Data Repository Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaDataRepositoryBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaDataRepositoryBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 40 Jakarta Connectors Resource Adapter Resource Adapter übersetzt in kanonische Fachobjekte.

Jakarta Connectors Resource Adapter

Code Smell

Legacy-EIS-Datensätze gelangen ungefiltert in Fachcode.

Pattern / Prinzip

Resource Adapter + Anti-Corruption Layer

Refactoring-Pfad

Resource Adapter übersetzt in kanonische Fachobjekte.

Alternative

REST/SOAP Adapter bei moderner API

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaConnectorsResourceAdapterRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Resource Adapter + Anti-Corruption Layer
// Zweck: Jakarta Connectors Resource Adapter als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaConnectorsResourceAdapterRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaConnectorsResourceAdapterRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 41 Jakarta XML Binding Boundary JAXB DTOs bleiben am Rand und werden gemappt.

Jakarta XML Binding Boundary

Code Smell

JAXB-generierte Klassen werden als Domain-Modell verwendet.

Pattern / Prinzip

Mapper + Canonical Model

Refactoring-Pfad

JAXB DTOs bleiben am Rand und werden gemappt.

Alternative

JSON-B bei JSON-Verträgen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaXMLBindingBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Mapper + Canonical Model
// Zweck: Jakarta XML Binding Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaXMLBindingBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaXMLBindingBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 42 Jakarta Web Service Gateway Gateway kapselt Stub, Fault-Mapping und Idempotenz.

Jakarta Web Service Gateway

Code Smell

SOAP Stub, Faults und Header verteilen sich im Use Case.

Pattern / Prinzip

Gateway + Contract Translation

Refactoring-Pfad

Gateway kapselt Stub, Fault-Mapping und Idempotenz.

Alternative

REST Client Adapter

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaWebServiceGatewayRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Gateway + Contract Translation
// Zweck: Jakarta Web Service Gateway als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaWebServiceGatewayRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaWebServiceGatewayRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}
Kapitel 7 REST-Filter, Request-Kontext und technische Grenzen7 Fachthemen

Sieben zusammenhängende Enterprise-Refactorings zu REST-Verträgen, Persistenzgrenzen, Transaktionskompensation, CDI-Komposition und OpenAPI.

Bereich 43 Jakarta REST Filter Boundary Filter extrahiert technischen Kontext und übergibt ein typisiertes RequestContext-Objekt.

Jakarta REST Filter Boundary

Code Smell

REST Filter schreibt Header, Security und Fachentscheidungen gleichzeitig.

Pattern / Prinzip

Request Filter + Context Mapper

Refactoring-Pfad

Filter extrahiert technischen Kontext und übergibt ein typisiertes RequestContext-Objekt.

Alternative

CDI Interceptor bei reinem Methodenaufruf

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaRestFilterBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Request Filter + Context Mapper
// Zweck: Jakarta REST Filter Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaRestFilterBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaRestFilterBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 44 Jakarta REST Conditional Requests Eine ETag-Policy kapselt Vergleich, Precondition Failed und Versionsfortschreibung.

Jakarta REST Conditional Requests

Code Smell

If-Match- und Versionsprüfung wird in jeder Resource dupliziert.

Pattern / Prinzip

ETag Policy + Optimistic Concurrency

Refactoring-Pfad

Eine ETag-Policy kapselt Vergleich, Precondition Failed und Versionsfortschreibung.

Alternative

JPA Optimistic Lock bei reinem Persistenzkonflikt

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaRestConditionalRequestsRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: ETag Policy + Optimistic Concurrency
// Zweck: Jakarta REST Conditional Requests als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaRestConditionalRequestsRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaRestConditionalRequestsRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 45 Jakarta Persistence Tenant Boundary Ein unveränderlicher TenantContext wird am Adapter validiert und als Repository-Spezifikation verwendet.

Jakarta Persistence Tenant Boundary

Code Smell

Mandanten-ID wird als String durch jede Query gereicht.

Pattern / Prinzip

Tenant Context + Repository Specification

Refactoring-Pfad

Ein unveränderlicher TenantContext wird am Adapter validiert und als Repository-Spezifikation verwendet.

Alternative

Separate Datenbank pro Mandant

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaPersistenceTenantBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Tenant Context + Repository Specification
// Zweck: Jakarta Persistence Tenant Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaPersistenceTenantBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaPersistenceTenantBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 46 Jakarta Persistence Audit Trail Domain Event und Audit-Port trennen fachliche Änderung von technischer Persistenz.

Jakarta Persistence Audit Trail

Code Smell

Entity Listener schreibt direkt in Audit-Tabellen und kennt Fachdetails.

Pattern / Prinzip

Audit Port + Domain Event

Refactoring-Pfad

Domain Event und Audit-Port trennen fachliche Änderung von technischer Persistenz.

Alternative

CDC bei rein technischer Nachvollziehbarkeit

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaPersistenceAuditTrailRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Audit Port + Domain Event
// Zweck: Jakarta Persistence Audit Trail als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaPersistenceAuditTrailRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaPersistenceAuditTrailRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 47 Jakarta Transactions Compensation Boundary Ein Process Manager koordiniert lokale Transaktionen und explizite Kompensationsbefehle.

Jakarta Transactions Compensation Boundary

Code Smell

Verteilte Schritte werden in einer langen JTA-Transaktion gehalten.

Pattern / Prinzip

Compensating Command + Process Manager

Refactoring-Pfad

Ein Process Manager koordiniert lokale Transaktionen und explizite Kompensationsbefehle.

Alternative

Saga-Orchestrierung bei mehreren Services

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaTransactionsCompensationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Compensating Command + Process Manager
// Zweck: Jakarta Transactions Compensation Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaTransactionsCompensationBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaTransactionsCompensationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 48 CDI Stereotype Composition Root Ein bewusstes Stereotype bündelt technische Metadaten; der Composition Root wählt Implementierungen.

CDI Stereotype Composition Root

Code Smell

Scopes, Qualifier und Interceptoren werden inkonsistent über Klassen verteilt.

Pattern / Prinzip

Stereotype + Composition Root

Refactoring-Pfad

Ein bewusstes Stereotype bündelt technische Metadaten; der Composition Root wählt Implementierungen.

Alternative

Explizite Producer-Methoden ohne Stereotype

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

CdiStereotypeCompositionRootRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Stereotype + Composition Root
// Zweck: CDI Stereotype Composition Root als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class CdiStereotypeCompositionRootRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public CdiStereotypeCompositionRootRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}

Bereich 49 Jakarta OpenAPI Contract Boundary Stabile API-DTOs und Mapper bilden eine veröffentlichte Sprache unabhängig vom Domain-Modell.

Jakarta OpenAPI Contract Boundary

Code Smell

OpenAPI-Anmerkungen spiegeln interne Domain-Typen und ändern sich unkontrolliert.

Pattern / Prinzip

Contract Adapter + Published Language

Refactoring-Pfad

Stabile API-DTOs und Mapper bilden eine veröffentlichte Sprache unabhängig vom Domain-Modell.

Alternative

Schema-first Generator mit separatem Adapter

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.

Nachher

JakartaOpenapiContractBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.util.Objects;

// Design Pattern: Contract Adapter + Published Language
// Zweck: Jakarta OpenAPI Contract Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaOpenapiContractBoundaryRefactoring {
  public record Command(String id, String payload) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaOpenapiContractBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    return port.execute(command);
  }
}
Kapitel 8 Server-Sent Events und Event-Streaming7 Fachthemen

Sieben Enterprise-Refactorings zu Streaming, Bulk-Persistenz, Messaging, CDI-Konfiguration, Transaktionspolicy, Security Audit und versionierten OpenAPI-Verträgen.

Bereich 50 Jakarta REST SSE Boundary Ein typisierter EventStream-Port trennt Fachereignisse von SSE-Frames.

Jakarta REST SSE Boundary

Code Smell

Lang laufende Updates werden direkt in der REST Resource erzeugt.

Pattern / Prinzip

SSE Port + Event Stream Adapter

Refactoring-Pfad

Ein typisierter EventStream-Port trennt Fachereignisse von SSE-Frames.

Alternative

WebSocket bei bidirektionaler Kommunikation

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JakartaRestSseBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: SSE Port + Event Stream Adapter
// Zweck: Jakarta REST SSE Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaRestSseBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaRestSseBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 51 JPA Bulk Update Boundary Ein BulkCommand beschreibt Filter und Aenderung; der Adapter fuehrt genau ein kontrolliertes Update aus.

JPA Bulk Update Boundary

Code Smell

Massenaenderungen laden jede Entity einzeln und erzeugen N+1 Writes.

Pattern / Prinzip

Bulk Command + Persistence Adapter

Refactoring-Pfad

Ein BulkCommand beschreibt Filter und Aenderung; der Adapter fuehrt genau ein kontrolliertes Update aus.

Alternative

Batch Processing bei individueller Fachlogik

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JpaBulkUpdateBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Bulk Command + Persistence Adapter
// Zweck: JPA Bulk Update Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JpaBulkUpdateBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JpaBulkUpdateBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 52 JMS Request Reply Boundary Ein RequestReplyPort kapselt Nachricht, Korrelation, Timeout und Antworttyp.

JMS Request Reply Boundary

Code Smell

Reply-To und Correlation-ID werden als freie Strings im Fachservice behandelt.

Pattern / Prinzip

Request-Reply + Correlation Identifier

Refactoring-Pfad

Ein RequestReplyPort kapselt Nachricht, Korrelation, Timeout und Antworttyp.

Alternative

Asynchrones Domain Event ohne direkte Antwort

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JmsRequestReplyBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Request-Reply + Correlation Identifier
// Zweck: JMS Request Reply Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JmsRequestReplyBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JmsRequestReplyBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 53 CDI Producer Configuration Boundary Eine typisierte Konfiguration wird validiert und von einer fokussierten Factory verwendet.

CDI Producer Configuration Boundary

Code Smell

Producer-Methoden lesen Environment-Werte verteilt und erzeugen ungueltige Clients.

Pattern / Prinzip

Factory Method + Typed Configuration

Refactoring-Pfad

Eine typisierte Konfiguration wird validiert und von einer fokussierten Factory verwendet.

Alternative

Direkte Constructor Injection bei statischer Konfiguration

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

CdiProducerConfigurationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Factory Method + Typed Configuration
// Zweck: CDI Producer Configuration Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class CdiProducerConfigurationBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public CdiProducerConfigurationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 54 JTA Transaction Timeout Policy Eine TransactionPolicy beschreibt Dauer und Fehlerklassen; ein Executor setzt sie am Adapter um.

JTA Transaction Timeout Policy

Code Smell

Timeouts und Rollback-Regeln sind in Use Cases als Zahlen und Catch-Bloecke verteilt.

Pattern / Prinzip

Transaction Policy + Template

Refactoring-Pfad

Eine TransactionPolicy beschreibt Dauer und Fehlerklassen; ein Executor setzt sie am Adapter um.

Alternative

Saga bei verteilten Transaktionen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JtaTransactionTimeoutPolicyRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Transaction Policy + Template
// Zweck: JTA Transaction Timeout Policy als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JtaTransactionTimeoutPolicyRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JtaTransactionTimeoutPolicyRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 55 Jakarta Security Audit Boundary Ein SecurityDecorator prueft eine fachliche Permission und schreibt Entscheidungen ueber einen AuditPort.

Jakarta Security Audit Boundary

Code Smell

Berechtigungspruefung und Audit-Logging sind in jeder Resource dupliziert.

Pattern / Prinzip

Security Decorator + Audit Port

Refactoring-Pfad

Ein SecurityDecorator prueft eine fachliche Permission und schreibt Entscheidungen ueber einen AuditPort.

Alternative

Containerrolle bei rein technischer Zugriffskontrolle

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JakartaSecurityAuditBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Security Decorator + Audit Port
// Zweck: Jakarta Security Audit Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaSecurityAuditBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaSecurityAuditBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}

Bereich 56 Jakarta OpenAPI Version Boundary Ein ContractRegistry ordnet Versionen expliziten Request- und Response-Modellen zu.

Jakarta OpenAPI Version Boundary

Code Smell

Mehrere API-Versionen teilen unkontrolliert dieselben DTOs.

Pattern / Prinzip

Published Language + Contract Registry

Refactoring-Pfad

Ein ContractRegistry ordnet Versionen expliziten Request- und Response-Modellen zu.

Alternative

Separate Deployments bei vollstaendig getrennten Lebenszyklen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Konfiguration und Fachentscheidung sind vermischt.

Nachher

JakartaOpenapiVersionBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Duration;
import java.util.Objects;

// Design Pattern: Published Language + Contract Registry
// Zweck: Jakarta OpenAPI Version Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaOpenapiVersionBoundaryRefactoring {
  public record Command(String id, String payload, Duration timeout) {
    public Command { Objects.requireNonNull(id); Objects.requireNonNull(payload); Objects.requireNonNull(timeout); }
  }
  public interface Port { Result execute(Command command); }
  public record Result(String id, Status status, String detail) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaOpenapiVersionBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Result handle(Command command) {
    Objects.requireNonNull(command);
    if (command.id().isBlank()) return new Result(command.id(), Status.REJECTED, "missing id");
    if (command.timeout().isNegative() || command.timeout().isZero())
      return new Result(command.id(), Status.REJECTED, "invalid timeout");
    return port.execute(command);
  }
}
Kapitel 9 Security, OAuth2 und Identity-Mapping7 Fachthemen

Sieben praxisnahe Refactorings mit klarer Containergrenze, echtem Java-21-Code und themenspezifischen SVGs.

Bereich 57 Jakarta Security OAuth2 Integration Boundary Ein IdentityMapper übersetzt externe Claims in fachliche Principal- und Permission-Objekte.

Jakarta Security OAuth2 Integration Boundary

Code Smell

OAuth2 Claims werden direkt in Fachentscheidungen verwendet.

Pattern / Prinzip

Anti-Corruption Layer + Policy

Refactoring-Pfad

Ein IdentityMapper übersetzt externe Claims in fachliche Principal- und Permission-Objekte.

Alternative

Containerrollen bei rein technischer Zugriffskontrolle

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaSecurityOauth2IntegrationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Anti-Corruption Layer + Policy
// Zweck: Jakarta Security OAuth2 Integration Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaSecurityOauth2IntegrationBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaSecurityOauth2IntegrationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 58 CDI Typed Domain Event Bus Typisierte Domain Events werden über einen kleinen EventBus-Port publiziert.

CDI Typed Domain Event Bus

Code Smell

String-basierte Events verlieren Typen, Version und fachliche Bedeutung.

Pattern / Prinzip

Observer + Domain Event

Refactoring-Pfad

Typisierte Domain Events werden über einen kleinen EventBus-Port publiziert.

Alternative

Direkter Methodenaufruf bei synchroner lokaler Folgeaktion

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

CdiTypedDomainEventBusRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Observer + Domain Event
// Zweck: CDI Typed Domain Event Bus als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class CdiTypedDomainEventBusRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public CdiTypedDomainEventBusRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 59 JPA EntityGraph Query Boundary Ein Query Object trägt Filter und FetchPlan; der Adapter wählt ein EntityGraph.

JPA EntityGraph Query Boundary

Code Smell

Lazy Loading und Fetch Joins sind über Services verteilt.

Pattern / Prinzip

Query Object + Fetch Plan

Refactoring-Pfad

Ein Query Object trägt Filter und FetchPlan; der Adapter wählt ein EntityGraph.

Alternative

DTO Projection für reine Read Models

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JpaEntitygraphQueryBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Query Object + Fetch Plan
// Zweck: JPA EntityGraph Query Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JpaEntitygraphQueryBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JpaEntitygraphQueryBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 60 Jakarta Batch Restart Recovery Ein stabiler Checkpoint speichert fachlichen Fortschritt und erlaubt deterministischen Restart.

Jakarta Batch Restart Recovery

Code Smell

Batch-Neustarts beginnen unkontrolliert von vorn.

Pattern / Prinzip

Checkpoint + Memento

Refactoring-Pfad

Ein stabiler Checkpoint speichert fachlichen Fortschritt und erlaubt deterministischen Restart.

Alternative

Idempotente Vollwiederholung bei kleinen Datenmengen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaBatchRestartRecoveryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Checkpoint + Memento
// Zweck: Jakarta Batch Restart Recovery als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaBatchRestartRecoveryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaBatchRestartRecoveryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 61 ManagedExecutor Context Snapshot Ein unveränderlicher ContextSnapshot wird explizit an einen ExecutorPort übergeben.

ManagedExecutor Context Snapshot

Code Smell

Security- und Correlation-Kontext gehen bei Async-Aufgaben verloren.

Pattern / Prinzip

Context Object + Executor Port

Refactoring-Pfad

Ein unveränderlicher ContextSnapshot wird explizit an einen ExecutorPort übergeben.

Alternative

Synchroner Ablauf bei kurzer Laufzeit

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

ManagedexecutorContextSnapshotRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Context Object + Executor Port
// Zweck: ManagedExecutor Context Snapshot als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class ManagedexecutorContextSnapshotRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public ManagedexecutorContextSnapshotRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 62 Jakarta Mail Queue Adapter Ein NotificationPort schreibt typisierte MailCommands in eine Queue; ein Adapter versendet später.

Jakarta Mail Queue Adapter

Code Smell

E-Mail-Versand blockiert den Use Case und mischt Template, Transport und Retry.

Pattern / Prinzip

Queue + Adapter

Refactoring-Pfad

Ein NotificationPort schreibt typisierte MailCommands in eine Queue; ein Adapter versendet später.

Alternative

Direkter Versand bei unkritischen internen Tools

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaMailQueueAdapterRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Queue + Adapter
// Zweck: Jakarta Mail Queue Adapter als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaMailQueueAdapterRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaMailQueueAdapterRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 63 OpenAPI First Contract Boundary Versionierte Contract DTOs bilden eine stabile Published Language und werden per Contract Test abgesichert.

OpenAPI First Contract Boundary

Code Smell

API-Modelle entstehen nachträglich aus internen Klassen.

Pattern / Prinzip

Published Language + Contract Test

Refactoring-Pfad

Versionierte Contract DTOs bilden eine stabile Published Language und werden per Contract Test abgesichert.

Alternative

Code First bei rein internen Prototypen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

OpenapiFirstContractBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Published Language + Contract Test
// Zweck: OpenAPI First Contract Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class OpenapiFirstContractBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public OpenapiFirstContractBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}
Kapitel 10 Pagination, Cursor und skalierbare Abfragen7 Fachthemen

Sieben praxisnahe Refactorings mit klarer Containergrenze, echtem Java-21-Code und themenspezifischen SVGs.

Bereich 64 Jakarta REST Pagination Boundary Ein typisierter Cursor und QueryPort kapseln Sortierung, Limit und Fortsetzung.

Jakarta REST Pagination Boundary

Code Smell

Offset-Pagination wird bei großen Tabellen langsam und instabil.

Pattern / Prinzip

Cursor + Query Object

Refactoring-Pfad

Ein typisierter Cursor und QueryPort kapseln Sortierung, Limit und Fortsetzung.

Alternative

Offset bei kleinen administrativen Listen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaRestPaginationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Cursor + Query Object
// Zweck: Jakarta REST Pagination Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaRestPaginationBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaRestPaginationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 65 JPA Soft Delete Policy Eine DeletePolicy und Repository-Grenze erzwingen Sichtbarkeit und Löschsemantik zentral.

JPA Soft Delete Policy

Code Smell

Soft-Delete-Filter werden in jeder Query manuell wiederholt.

Pattern / Prinzip

Policy + Repository

Refactoring-Pfad

Eine DeletePolicy und Repository-Grenze erzwingen Sichtbarkeit und Löschsemantik zentral.

Alternative

Hard Delete bei rechtlich zulässigen flüchtigen Daten

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JpaSoftDeletePolicyRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Policy + Repository
// Zweck: JPA Soft Delete Policy als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JpaSoftDeletePolicyRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JpaSoftDeletePolicyRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 66 JMS Dead Letter Recovery Ein DeadLetterEnvelope bewahrt Ursache, Versuch, Korrelation und Recovery-Strategie.

JMS Dead Letter Recovery

Code Smell

Fehlgeschlagene Nachrichten landen ohne Ursache und Recovery-Kontext im DLQ.

Pattern / Prinzip

Dead Letter Channel + Recovery Strategy

Refactoring-Pfad

Ein DeadLetterEnvelope bewahrt Ursache, Versuch, Korrelation und Recovery-Strategie.

Alternative

Sofortiges Verwerfen bei nicht geschäftsrelevanten Telemetriedaten

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JmsDeadLetterRecoveryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Dead Letter Channel + Recovery Strategy
// Zweck: JMS Dead Letter Recovery als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JmsDeadLetterRecoveryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JmsDeadLetterRecoveryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 67 CDI Feature Module Boundary Ein FeatureModule bündelt explizit Ports, Adapter und Use Cases in einer Composition Root.

CDI Feature Module Boundary

Code Smell

Beans werden global entdeckt und bilden versteckte Kopplungen.

Pattern / Prinzip

Composition Root + Module

Refactoring-Pfad

Ein FeatureModule bündelt explizit Ports, Adapter und Use Cases in einer Composition Root.

Alternative

Einzelne Producer bei sehr kleinen Anwendungen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

CdiFeatureModuleBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Composition Root + Module
// Zweck: CDI Feature Module Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class CdiFeatureModuleBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public CdiFeatureModuleBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 68 JTA Outbox Transaction Boundary Use Case und OutboxWriter teilen dieselbe lokale Transaktion; Publishing erfolgt separat.

JTA Outbox Transaction Boundary

Code Smell

Datenbankänderung und Event-Publish erfolgen als Dual Write.

Pattern / Prinzip

Transactional Outbox

Refactoring-Pfad

Use Case und OutboxWriter teilen dieselbe lokale Transaktion; Publishing erfolgt separat.

Alternative

Saga bei mehreren autonomen Datenbanken

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JtaOutboxTransactionBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Transactional Outbox
// Zweck: JTA Outbox Transaction Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JtaOutboxTransactionBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JtaOutboxTransactionBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 69 Jakarta Telemetry Observation Boundary Ein ObservationDecorator kapselt Messung und hält den Use Case frameworkfrei.

Jakarta Telemetry Observation Boundary

Code Smell

Logging, Metriken und Tracing werden in Fachmethoden dupliziert.

Pattern / Prinzip

Decorator + Observation Port

Refactoring-Pfad

Ein ObservationDecorator kapselt Messung und hält den Use Case frameworkfrei.

Alternative

Interceptor für rein technische Querschnittsfunktionen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaTelemetryObservationBoundaryRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Decorator + Observation Port
// Zweck: Jakarta Telemetry Observation Boundary als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaTelemetryObservationBoundaryRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaTelemetryObservationBoundaryRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}

Bereich 70 Jakarta EE Composition Root Eine explizite Composition Root verbindet Use Cases, Ports, Adapter und Policies nachvollziehbar.

Jakarta EE Composition Root

Code Smell

Containerannotation und Fachverdrahtung sind über viele Klassen verstreut.

Pattern / Prinzip

Composition Root + Ports and Adapters

Refactoring-Pfad

Eine explizite Composition Root verbindet Use Cases, Ports, Adapter und Policies nachvollziehbar.

Alternative

Automatische Discovery bei kleinen Demoanwendungen

Vorher

gekoppelte Jakarta-EE-Implementierung
// Container-API, Infrastruktur und Fachentscheidung sind vermischt.

Nachher

JakartaEeCompositionRootRefactoring.java
package com.aydinsude.workbench.jakarta;

import java.time.Instant;
import java.util.Map;
import java.util.Objects;

// Design Pattern: Composition Root + Ports and Adapters
// Zweck: Jakarta EE Composition Root als explizite, testbare Jakarta-EE-Grenze modellieren.
public final class JakartaEeCompositionRootRefactoring {
  public record Context(String id, Map<String,String> attributes, Instant createdAt) {
    public Context { Objects.requireNonNull(id); attributes = Map.copyOf(attributes); Objects.requireNonNull(createdAt); }
  }
  public interface Port { Decision execute(Context context); }
  public record Decision(String id, Status status, String reason) {}
  public enum Status { ACCEPTED, REJECTED }

  private final Port port;
  public JakartaEeCompositionRootRefactoring(Port port) { this.port = Objects.requireNonNull(port); }
  public Decision handle(Context context) {
    Objects.requireNonNull(context);
    if (context.id().isBlank()) return new Decision(context.id(), Status.REJECTED, "id required");
    return port.execute(context);
  }
}
⌂ Cockpit