Komponenten
CDI für explizite Abhängigkeiten und Varianten nutzen, nicht als Service-Locator.
Alle zugehörigen Inhalte befinden sich auf dieser einen großen Seite. Kapitel und Beispiele sind standardmäßig geschlossen und lassen sich gezielt öffnen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Die vorhandenen Kapitel bleiben vollständig erhalten. Diese Orientierung gruppiert sie nach den Entscheidungen, die ein Enterprise-Team tatsächlich treffen muss.
CDI für explizite Abhängigkeiten und Varianten nutzen, nicht als Service-Locator.
JAX-RS auf Transportübersetzung begrenzen und Bean Validation an der richtigen Grenze einsetzen.
JTA-Transaktionsgrenzen an fachlichen Use Cases ausrichten.
JPA-Mappings, Aggregate und Fetch-Strategien ohne Durchsickern in die Fachlogik gestalten.
CDI, JAX-RS, Bean Validation, JTA und JPA als klare Enterprise-Grenzen statt versteckter Containerkopplung.
CDI, JAX-RS, Bean Validation, JTA und JPA als klare Enterprise-Grenzen statt versteckter Containerkopplung.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Field Injection und versteckte Containerabhängigkeit
Constructor Injection + Dependency Inversion
Abhängigkeiten explizit über den Konstruktor übergeben.
Producer Method für komplexe technische Erzeugung
Jakarta-EE-Ausgangscode@Inject CustomerRepository repository;Jakarta-EE-ZielcodeCdiConstructorInjectionRefactoring.class // explizite, testbare BoundaryCdiConstructorInjectionRefactoring.javapackage 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); }
}
}Implementierungen werden über String-Schlüssel oder if/switch ausgewählt
Qualifier + Strategy Registry
Fachliche Variante als typisierten Schlüssel und Registry modellieren.
CDI Alternatives für deploymentspezifischen Austausch
Jakarta-EE-Ausgangscodeif (channel.equals("email")) { ... }Jakarta-EE-ZielcodeCdiQualifierStrategyRefactoring.class // explizite, testbare BoundaryCdiQualifierStrategyRefactoring.javapackage 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);
}
}
}REST-Ressource enthält Mapping, Fachlogik und Persistenz
Thin Adapter + Application Use Case
Transportdaten normalisieren und an einen frameworkfreien Use Case delegieren.
Direkter Domain Service bei sehr kleinem Modul
Jakarta-EE-Ausgangscode@POST public Response create(Request r) { persist(map(r)); ... }Jakarta-EE-ZielcodeJaxRsThinResourceRefactoring.class // explizite, testbare BoundaryJaxRsThinResourceRefactoring.javapackage 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()));
}
}Technische Exceptions gelangen ungefiltert bis zum HTTP-Client
Exception Translation + Problem Details
Fachliche Fehler in stabile API-Fehlermodelle übersetzen und technische Ursachen intern behalten.
Result Type für erwartbare Ablehnungen
Jakarta-EE-Ausgangscodecatch (Exception e) { return Response.serverError().entity(e.getMessage()).build(); }Jakarta-EE-ZielcodeJaxRsExceptionMapperRefactoring.class // explizite, testbare BoundaryJaxRsExceptionMapperRefactoring.javapackage 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.");
}
}Validierung ist über Ressource, Service und Entity verteilt
Validation Boundary + Value Object
Strukturelle Eingabevalidierung am Adapter, Fachinvarianten im Fachobjekt halten.
Manuelle Validator-Kette bei hochdynamischen Regeln
Jakarta-EE-Ausgangscodeif (email == null) ... // mehrfach in mehreren SchichtenJakarta-EE-ZielcodeBeanValidationBoundaryRefactoring.class // explizite, testbare BoundaryBeanValidationBoundaryRefactoring.javapackage 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());
}
}Transaktion verteilt sich über Ressource, Service und mehrere Adapter
Application Service + Unit of Work
Eine Transaktion um genau einen fachlichen Use Case legen; externe I/O danach ausführen.
Saga bei serviceübergreifenden Abläufen
Jakarta-EE-Ausgangscoderesource.create(); service.save(); publisher.send();Jakarta-EE-ZielcodeJtaTransactionBoundaryRefactoring.class // explizite, testbare BoundaryJtaTransactionBoundaryRefactoring.javapackage 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));
}
}JPA Entity und EntityManager-Typen lecken in Fach- und Anwendungscode
Repository Port + Data Mapper
Fachmodell und Persistenzmodell trennen; Adapter mappt explizit zwischen beiden.
Active Record für bewusst kleine CRUD-Anwendung
Jakarta-EE-AusgangscodeEntityManager und @Entity im Application ServiceJakarta-EE-ZielcodeJpaRepositoryPortRefactoring.class // explizite, testbare BoundaryJpaRepositoryPortRefactoring.javapackage 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()); }
}JSON-B, JSON-P, JMS, CDI Events, Scheduler und Jakarta Security als explizite, testbare Enterprise-Grenzen.
JSON-B, JSON-P, JMS, CDI Events, Scheduler und Jakarta Security als explizite, testbare Enterprise-Grenzen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
JSON-B Annotationen und Transportnamen sickern in das Fachmodell
DTO Mapper + Anti-Corruption Layer
JSON-B DTO und Domainmodell trennen; Mapping explizit und testbar halten.
Direktes Mapping nur bei stabilen, rein technischen DTOs
Jakarta-EE-Ausgangscode@JsonbProperty("customer_id") // direkt im DomainobjektJakarta-EE-ZielstrukturJsonbMappingBoundaryRefactoring.class // frameworkfreie Kernlogik + AdapterJsonbMappingBoundaryRefactoring.javapackage 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()); }
}Große JSON-Dokumente werden vollständig materialisiert und erzeugen unnötigen Speicherbedarf
Streaming Parser Port + Iterator
JSON-P-Streaming hinter einem fachlichen Reader-Port kapseln und Datensätze schrittweise liefern.
JSON-B für kleine, klar begrenzte Payloads
Jakarta-EE-Ausgangscodevar object = Json.createReader(input).readObject();Jakarta-EE-ZielstrukturJsonpStreamingBoundaryRefactoring.class // frameworkfreie Kernlogik + AdapterJsonpStreamingBoundaryRefactoring.javapackage 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;
}
}Application Service erzeugt JMS-Nachrichten und kennt Destination, Header und Providerdetails
Message Publisher Port + Adapter
Fachliches Ereignis an einen Port übergeben; JMS-Header und Destination nur im Adapter behandeln.
Transactional Outbox bei atomarer DB/Event-Anforderung
Jakarta-EE-Ausgangscodecontext.createProducer().send(queue, message);Jakarta-EE-ZielstrukturJmsProducerPortRefactoring.class // frameworkfreie Kernlogik + AdapterJmsProducerPortRefactoring.javapackage 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())); }
}
}Redelivery führt zu mehrfacher fachlicher Verarbeitung und doppelten Seiteneffekten
Idempotent Consumer + Inbox
Message-ID atomar reservieren, dann genau einmal fachlich anwenden; Redelivery sicher quittieren.
Natürlich idempotente Operation bei einfachen Zustandssetzungen
Jakarta-EE-Ausgangscodelistener.onMessage(m) { service.book(m.getBody()); }Jakarta-EE-ZielstrukturJmsIdempotentConsumerRefactoring.class // frameworkfreie Kernlogik + AdapterJmsIdempotentConsumerRefactoring.javapackage 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; }
}
}CDI Events werden direkt als Domain Events verwendet und koppeln Fachmodell an Containersemantik
Domain Event + Event Bridge
Domain Event frameworkfrei modellieren und erst am Application-Rand auf CDI Event übersetzen.
Direkter CDI Event bei rein technischer, lokaler Benachrichtigung
Jakarta-EE-Ausgangscodeevent.fire(new ClaimRegistered(...)); // Domain kennt CDIJakarta-EE-ZielstrukturCdiDomainEventBoundaryRefactoring.class // frameworkfreie Kernlogik + AdapterCdiDomainEventBoundaryRefactoring.javapackage 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()));
}
}Timer-Callback enthält Fachlogik, Zeitermittlung und verteilte Sperre
Scheduler Adapter + Clock Port + Lease
Scheduler triggert nur einen Use Case; Zeit und Exklusivität werden über Ports kontrolliert.
Externer Scheduler für plattformübergreifende Orchestrierung
Jakarta-EE-Ausgangscode@Schedule public void expire() { repository.find... }Jakarta-EE-ZielstrukturJakartaSchedulerBoundaryRefactoring.class // frameworkfreie Kernlogik + AdapterJakartaSchedulerBoundaryRefactoring.javapackage 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"); }
}
}Security Principal, Rollenstrings und Token-Claims werden direkt im Fachcode ausgewertet
Identity Mapper + Authorization Policy
Containeridentität in typisierten Actor übersetzen und Berechtigung als fachliche Policy prüfen.
Method Security für einfache statische Rollenregeln
Jakarta-EE-Ausgangscodeif (securityContext.isCallerInRole("APPROVER")) ...Jakarta-EE-ZielstrukturJakartaSecurityIdentityRefactoring.class // frameworkfreie Kernlogik + AdapterJakartaSecurityIdentityRefactoring.javapackage 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; }
}CDI-Querschnittslogik, JPA-Nebenläufigkeit und Ladepläne sowie Jakarta-Concurrency-Grenzen als explizite, testbare Enterprise-Strukturen.
CDI-Querschnittslogik, JPA-Nebenläufigkeit und Ladepläne sowie Jakarta-Concurrency-Grenzen als explizite, testbare Enterprise-Strukturen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Interceptor-Annotationen und InvocationContext sickern in fachliche Services
Interceptor Adapter + Decorator
Querschnittslogik als expliziten Port modellieren; CDI-Interceptor delegiert nur an den Adapter.
Expliziter Decorator bei fachlich sichtbarer Reihenfolge
Jakarta-EE-Ausgangscode@AroundInvoke public Object audit(InvocationContext ctx) ...Jakarta-EE-ZielstrukturCdiInterceptorBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterCdiInterceptorBoundaryRefactoring.javapackage 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; }
}
}Mehrere CDI-Decorator verändern Verhalten, aber Reihenfolge und Fachwirkung bleiben verborgen
Decorator + Policy Chain
Decorator-Reihenfolge als explizite, testbare Policy-Kette modellieren.
Chain of Responsibility bei optionalem Abbruch
Jakarta-EE-Ausgangscode@Decorator class SecuredNotifier implements Notifier ...Jakarta-EE-ZielstrukturCdiDecoratorBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterCdiDecoratorBoundaryRefactoring.javapackage 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); };
}
}Versionskonflikte werden als technische Persistence-Exception bis zur REST-Schicht durchgereicht
Optimistic Lock + Exception Translation
Version in einen fachlichen Änderungsbefehl aufnehmen und Konflikte in ein typisiertes Result übersetzen.
Pessimistische Sperre bei sehr hoher Konfliktwahrscheinlichkeit
Jakarta-EE-AusgangscodeentityManager.merge(order); // OptimisticLockException ungefiltertJakarta-EE-ZielstrukturJpaOptimisticLockRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJpaOptimisticLockRefactoring.javapackage 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));
}
}EntityGraph- und Fetch-Join-Details werden in Use Cases verteilt und führen zu unkontrollierten Objektgraphen
Fetch Plan + Repository Port
Fachlich benannte Ladeprofile am Repository-Port anbieten; JPA EntityGraph bleibt Adapterdetail.
Projektionen für reine Leseansichten
Jakarta-EE-Ausgangscodeem.createEntityGraph("Order.withLines") // im ServiceJakarta-EE-ZielstrukturJpaFetchPlanBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJpaFetchPlanBoundaryRefactoring.javapackage 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); }
}Dynamische Criteria-Ausdrücke wachsen in Repository-Methoden zu schwer testbaren Verzweigungen
Query Object + Specification
Suchparameter in unveränderliches Query Object überführen und Adapter übersetzt es in Criteria API.
Native Query bei stabiler, datenbankspezifischer Analyse
Jakarta-EE-Ausgangscodeif(status!=null) predicates.add(cb.equal(root.get("status"),status));Jakarta-EE-ZielstrukturJpaCriteriaQueryObjectRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJpaCriteriaQueryObjectRefactoring.javapackage 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));
}
}Use Case injiziert ManagedExecutorService direkt und vermischt Task-Scheduling mit Fachlogik
Executor Port + Command
Asynchrone Ausführung über fachlich kleinen Executor-Port kapseln; Jakarta Concurrency bleibt Adapterdetail.
Synchron ausführen bei kurzen, transaktionalen Operationen
Jakarta-EE-AusgangscodemanagedExecutor.submit(() -> service.process(id));Jakarta-EE-ZielstrukturJakartaManagedExecutorRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJakartaManagedExecutorRefactoring.javapackage 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));
}
}Security-, Tenant- und Correlation-Kontext wird implizit über ThreadLocal oder Containerkontext transportiert
Explicit Context + Context Propagator
Benötigten Kontext als unveränderliches Objekt erfassen und beim Task-Start explizit installieren.
Scoped Values in reinem Java bei kontrollierter Laufzeit
Jakarta-EE-Ausgangscodeexecutor.submit(() -> useCase.run()); // Kontext implizitJakarta-EE-ZielstrukturJakartaContextPropagationRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJakartaContextPropagationRefactoring.javapackage 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(); } };
}
}REST-Async, externe REST-Clients, JPA-Konvertierung und Lifecycle, JTA-After-Commit, CDI-Request-Kontext und Jakarta Mail als explizite Adaptergrenzen.
REST-Async, externe REST-Clients, JPA-Konvertierung und Lifecycle, JTA-After-Commit, CDI-Request-Kontext und Jakarta Mail als explizite Adaptergrenzen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Asynchrone REST-Verarbeitung bindet CompletionStage und HTTP-Lifecycle direkt an den Use Case
Async Result Port + Adapter
Asynchronen Auftrag als fachlichen Handle modellieren; JAX-RS adaptiert CompletionStage nur am Rand.
Synchroner Request bei kurzen, deterministischen Abläufen
Jakarta-EE-Ausgangscode@Suspended AsyncResponse wird tief im Service weitergereichtJakarta-EE-ZielstrukturJaxRsAsyncBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJaxRsAsyncBoundaryRefactoring.javapackage 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());
}
}Remote-API-DTOs, HTTP-Status und Retry-Details sickern in die Fachlogik
Anti-Corruption Layer + Adapter
Externes REST-Modell in einen typisierten Port und ein internes Ergebnis übersetzen.
Direkter Client bei rein technischer Durchleitung ohne Fachlogik
Jakarta-EE-AusgangscodeRemoteCustomerDto wird im Domain Service verarbeitetJakarta-EE-ZielstrukturJakartaRestClientAdapterRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJakartaRestClientAdapterRefactoring.javapackage 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())); };
}
}Fachliche Value Objects werden als String zerlegt und an vielen Stellen inkonsistent rekonstruiert
Value Object Mapper + Attribute Converter Adapter
Serialisierung und Validierung zentralisieren; der Fachkern bleibt frei von JPA-Annotationen.
Embeddable bei mehreren separat abfragbaren Spalten
Jakarta-EE-AusgangscodeString currencyAmount wird manuell gesplittetJakarta-EE-ZielstrukturJpaAttributeConverterBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJpaAttributeConverterBoundaryRefactoring.javapackage 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]); }
};
}
}@PrePersist und @PostLoad enthalten fachliche Regeln, Services oder I/O
Lifecycle Adapter + Domain Method
Callbacks auf technische Zeitstempel begrenzen; Fachregeln über explizite Domain-Methoden ausführen.
Entity Listener für rein technische Auditfelder
Jakarta-EE-Ausgangscode@PrePersist versendet E-Mail und berechnet StatusJakarta-EE-ZielstrukturJpaEntityLifecycleBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJpaEntityLifecycleBoundaryRefactoring.javapackage 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; }
}
}Nachrichten und E-Mails werden vor erfolgreichem Commit ausgelöst oder gehen bei Rollback verloren
Transaction Synchronization + Outbox
Folgeaktion als Pending Event speichern und erst nach Commit über einen Adapter freigeben.
Transactional Outbox bei dauerhaft garantierter Zustellung
Jakarta-EE-Ausgangscodepublisher.send(event) läuft innerhalb der SchreibtransaktionJakarta-EE-ZielstrukturJtaAfterCommitBoundaryRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJtaAfterCommitBoundaryRefactoring.javapackage 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));
}
}RequestScoped-Daten werden implizit in Hintergrundaufgaben verwendet und sind dort nicht mehr aktiv
Explicit Context + Snapshot
Benötigte Requestdaten als unveränderliches Objekt erfassen und bewusst an den Use Case übergeben.
Managed Context Propagation bei vollständig kontrolliertem Container-Kontext
Jakarta-EE-AusgangscodeBackground task greift später auf @RequestScoped Bean zuJakarta-EE-ZielstrukturCdiRequestContextSnapshotRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterCdiRequestContextSnapshotRefactoring.javapackage 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());
}
}MimeMessage, Session und Transport bestimmen Signaturen im Application Service
Port and Adapter + Template
Fachliche Nachricht modellieren; Jakarta Mail übernimmt ausschließlich Rendering und Transport.
Domain Event plus externer Notification Service bei eigener Plattform
Jakarta-EE-AusgangscodeApplication Service erzeugt MimeMessage direktJakarta-EE-ZielstrukturJakartaMailNotificationPortRefactoring.class // frameworkfreie Kernlogik + Jakarta AdapterJakartaMailNotificationPortRefactoring.javapackage 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()));
}
}Sieben zusammenhängende Enterprise-Refactorings mit klaren Jakarta-Grenzen, Pattern-Begründung, echtem Java-21-Code und kompakten SVGs.
Batchlet enthält SQL, Fachlogik und Statuspflege.
Batch Coordinator + Use Case
Batch-Adapter ruft einen fachlichen Job-Use-Case auf.
External Worker bei plattformübergreifender Orchestrierung
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaBatchJobBoundaryRefactoring.javapackage 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);
}
}
Reader, Processor und Writer teilen versteckte mutable Zustände.
Checkpoint + Unit of Work
Checkpoint wird explizit und restartfähig modelliert.
Tasklet für kleine atomare Jobs
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.ChunkProcessingCheckpointStrategyRefactoring.javapackage 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);
}
}
Partitionen teilen globale Listen und Locks.
Partition Strategy + Shared Nothing
Partitionen erhalten unabhängige Schlüsselräume und Ports.
Einzelner Stream bei kleinen Datenmengen
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.PartitionedBatchProcessingRefactoring.javapackage 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);
}
}
Timer enthält Fachlogik und konkurriert auf mehreren Knoten.
Scheduler Adapter + Lease
Managed Scheduler triggert Use Case mit Lease-Port.
Externer Scheduler
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.ManagedSchedulerBoundaryRefactoring.javapackage 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);
}
}
Gruppenstrings und Reihenfolge sind über Ressourcen verteilt.
Validation Policy
Typisierte Validierungsphasen kapseln fachliche Reihenfolge.
Ein einziges DTO für einfache Grenzen
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.BeanValidationGroupsRefactoring.javapackage 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);
}
}
Tests ersetzen Beans über globale Containertricks.
Strategy + Composition Root
Alternative Implementierung wird am Composition Root gewählt.
Pure Unit Test ohne Container
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.CDIAlternativesforTestsRefactoring.javapackage 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);
}
}
Rollenstrings werden direkt im Fachcode geprüft.
Identity Mapper + Policy
Containerrollen werden in fachliche Permissions übersetzt.
Statische @RolesAllowed-Regel
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.AuthorizationBoundaryRefactoring.javapackage 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);
}
}
Sieben zusammenhängende Enterprise-Refactorings mit klaren Jakarta-Grenzen, Pattern-Begründung, echtem Java-21-Code und kompakten SVGs.
Fachlogik hält WebSocket Session und sendet direkt.
Session Port + Adapter
Session wird hinter typisiertem Outbound-Port gekapselt.
SSE bei reinem Server-Push
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaWebSocketSessionPortRefactoring.javapackage 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);
}
}
Backing Bean mischt Navigation, Persistenz und Formatierung.
Presenter + View Model
Presenter erzeugt unveränderliches View Model und ruft Use Cases.
JAX-RS SPA-Frontend
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaFacesPresenterBoundaryRefactoring.javapackage 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);
}
}
deleted-Flag wird in jeder Query manuell ergänzt.
Repository Policy + Specification
Soft-Delete-Regel wird zentral als Repository-Policy modelliert.
Physisches Löschen mit Audit
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaPersistenceSoftDeleteRefactoring.javapackage 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);
}
}
Jakarta-Data-Repository-Typen lecken in Domain und Service.
Query Port + Mapper
Technisches Repository implementiert fachlichen Query-Port.
JPA Repository Adapter
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaDataRepositoryBoundaryRefactoring.javapackage 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);
}
}
Legacy-EIS-Datensätze gelangen ungefiltert in Fachcode.
Resource Adapter + Anti-Corruption Layer
Resource Adapter übersetzt in kanonische Fachobjekte.
REST/SOAP Adapter bei moderner API
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaConnectorsResourceAdapterRefactoring.javapackage 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);
}
}
JAXB-generierte Klassen werden als Domain-Modell verwendet.
Mapper + Canonical Model
JAXB DTOs bleiben am Rand und werden gemappt.
JSON-B bei JSON-Verträgen
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaXMLBindingBoundaryRefactoring.javapackage 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);
}
}
SOAP Stub, Faults und Header verteilen sich im Use Case.
Gateway + Contract Translation
Gateway kapselt Stub, Fault-Mapping und Idempotenz.
REST Client Adapter
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaWebServiceGatewayRefactoring.javapackage 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);
}
}
Sieben zusammenhängende Enterprise-Refactorings zu REST-Verträgen, Persistenzgrenzen, Transaktionskompensation, CDI-Komposition und OpenAPI.
REST Filter schreibt Header, Security und Fachentscheidungen gleichzeitig.
Request Filter + Context Mapper
Filter extrahiert technischen Kontext und übergibt ein typisiertes RequestContext-Objekt.
CDI Interceptor bei reinem Methodenaufruf
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaRestFilterBoundaryRefactoring.javapackage 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);
}
}
If-Match- und Versionsprüfung wird in jeder Resource dupliziert.
ETag Policy + Optimistic Concurrency
Eine ETag-Policy kapselt Vergleich, Precondition Failed und Versionsfortschreibung.
JPA Optimistic Lock bei reinem Persistenzkonflikt
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaRestConditionalRequestsRefactoring.javapackage 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);
}
}
Mandanten-ID wird als String durch jede Query gereicht.
Tenant Context + Repository Specification
Ein unveränderlicher TenantContext wird am Adapter validiert und als Repository-Spezifikation verwendet.
Separate Datenbank pro Mandant
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaPersistenceTenantBoundaryRefactoring.javapackage 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);
}
}
Entity Listener schreibt direkt in Audit-Tabellen und kennt Fachdetails.
Audit Port + Domain Event
Domain Event und Audit-Port trennen fachliche Änderung von technischer Persistenz.
CDC bei rein technischer Nachvollziehbarkeit
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaPersistenceAuditTrailRefactoring.javapackage 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);
}
}
Verteilte Schritte werden in einer langen JTA-Transaktion gehalten.
Compensating Command + Process Manager
Ein Process Manager koordiniert lokale Transaktionen und explizite Kompensationsbefehle.
Saga-Orchestrierung bei mehreren Services
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaTransactionsCompensationBoundaryRefactoring.javapackage 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);
}
}
Scopes, Qualifier und Interceptoren werden inkonsistent über Klassen verteilt.
Stereotype + Composition Root
Ein bewusstes Stereotype bündelt technische Metadaten; der Composition Root wählt Implementierungen.
Explizite Producer-Methoden ohne Stereotype
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.CdiStereotypeCompositionRootRefactoring.javapackage 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);
}
}
OpenAPI-Anmerkungen spiegeln interne Domain-Typen und ändern sich unkontrolliert.
Contract Adapter + Published Language
Stabile API-DTOs und Mapper bilden eine veröffentlichte Sprache unabhängig vom Domain-Modell.
Schema-first Generator mit separatem Adapter
gekoppelte Jakarta-EE-Implementierung// Container-API, Fachentscheidung und Seiteneffekt sind vermischt.JakartaOpenapiContractBoundaryRefactoring.javapackage 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);
}
}
Sieben Enterprise-Refactorings zu Streaming, Bulk-Persistenz, Messaging, CDI-Konfiguration, Transaktionspolicy, Security Audit und versionierten OpenAPI-Verträgen.
Lang laufende Updates werden direkt in der REST Resource erzeugt.
SSE Port + Event Stream Adapter
Ein typisierter EventStream-Port trennt Fachereignisse von SSE-Frames.
WebSocket bei bidirektionaler Kommunikation
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JakartaRestSseBoundaryRefactoring.javapackage 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);
}
}
Massenaenderungen laden jede Entity einzeln und erzeugen N+1 Writes.
Bulk Command + Persistence Adapter
Ein BulkCommand beschreibt Filter und Aenderung; der Adapter fuehrt genau ein kontrolliertes Update aus.
Batch Processing bei individueller Fachlogik
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JpaBulkUpdateBoundaryRefactoring.javapackage 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);
}
}
Reply-To und Correlation-ID werden als freie Strings im Fachservice behandelt.
Request-Reply + Correlation Identifier
Ein RequestReplyPort kapselt Nachricht, Korrelation, Timeout und Antworttyp.
Asynchrones Domain Event ohne direkte Antwort
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JmsRequestReplyBoundaryRefactoring.javapackage 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);
}
}
Producer-Methoden lesen Environment-Werte verteilt und erzeugen ungueltige Clients.
Factory Method + Typed Configuration
Eine typisierte Konfiguration wird validiert und von einer fokussierten Factory verwendet.
Direkte Constructor Injection bei statischer Konfiguration
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.CdiProducerConfigurationBoundaryRefactoring.javapackage 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);
}
}
Timeouts und Rollback-Regeln sind in Use Cases als Zahlen und Catch-Bloecke verteilt.
Transaction Policy + Template
Eine TransactionPolicy beschreibt Dauer und Fehlerklassen; ein Executor setzt sie am Adapter um.
Saga bei verteilten Transaktionen
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JtaTransactionTimeoutPolicyRefactoring.javapackage 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);
}
}
Berechtigungspruefung und Audit-Logging sind in jeder Resource dupliziert.
Security Decorator + Audit Port
Ein SecurityDecorator prueft eine fachliche Permission und schreibt Entscheidungen ueber einen AuditPort.
Containerrolle bei rein technischer Zugriffskontrolle
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JakartaSecurityAuditBoundaryRefactoring.javapackage 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);
}
}
Mehrere API-Versionen teilen unkontrolliert dieselben DTOs.
Published Language + Contract Registry
Ein ContractRegistry ordnet Versionen expliziten Request- und Response-Modellen zu.
Separate Deployments bei vollstaendig getrennten Lebenszyklen
gekoppelte Jakarta-EE-Implementierung// Container-API, Konfiguration und Fachentscheidung sind vermischt.JakartaOpenapiVersionBoundaryRefactoring.javapackage 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);
}
}
Sieben praxisnahe Refactorings mit klarer Containergrenze, echtem Java-21-Code und themenspezifischen SVGs.
OAuth2 Claims werden direkt in Fachentscheidungen verwendet.
Anti-Corruption Layer + Policy
Ein IdentityMapper übersetzt externe Claims in fachliche Principal- und Permission-Objekte.
Containerrollen bei rein technischer Zugriffskontrolle
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaSecurityOauth2IntegrationBoundaryRefactoring.javapackage 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);
}
}
String-basierte Events verlieren Typen, Version und fachliche Bedeutung.
Observer + Domain Event
Typisierte Domain Events werden über einen kleinen EventBus-Port publiziert.
Direkter Methodenaufruf bei synchroner lokaler Folgeaktion
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.CdiTypedDomainEventBusRefactoring.javapackage 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);
}
}
Lazy Loading und Fetch Joins sind über Services verteilt.
Query Object + Fetch Plan
Ein Query Object trägt Filter und FetchPlan; der Adapter wählt ein EntityGraph.
DTO Projection für reine Read Models
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JpaEntitygraphQueryBoundaryRefactoring.javapackage 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);
}
}
Batch-Neustarts beginnen unkontrolliert von vorn.
Checkpoint + Memento
Ein stabiler Checkpoint speichert fachlichen Fortschritt und erlaubt deterministischen Restart.
Idempotente Vollwiederholung bei kleinen Datenmengen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaBatchRestartRecoveryRefactoring.javapackage 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);
}
}
Security- und Correlation-Kontext gehen bei Async-Aufgaben verloren.
Context Object + Executor Port
Ein unveränderlicher ContextSnapshot wird explizit an einen ExecutorPort übergeben.
Synchroner Ablauf bei kurzer Laufzeit
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.ManagedexecutorContextSnapshotRefactoring.javapackage 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);
}
}
E-Mail-Versand blockiert den Use Case und mischt Template, Transport und Retry.
Queue + Adapter
Ein NotificationPort schreibt typisierte MailCommands in eine Queue; ein Adapter versendet später.
Direkter Versand bei unkritischen internen Tools
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaMailQueueAdapterRefactoring.javapackage 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);
}
}
API-Modelle entstehen nachträglich aus internen Klassen.
Published Language + Contract Test
Versionierte Contract DTOs bilden eine stabile Published Language und werden per Contract Test abgesichert.
Code First bei rein internen Prototypen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.OpenapiFirstContractBoundaryRefactoring.javapackage 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);
}
}
Sieben praxisnahe Refactorings mit klarer Containergrenze, echtem Java-21-Code und themenspezifischen SVGs.
Offset-Pagination wird bei großen Tabellen langsam und instabil.
Cursor + Query Object
Ein typisierter Cursor und QueryPort kapseln Sortierung, Limit und Fortsetzung.
Offset bei kleinen administrativen Listen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaRestPaginationBoundaryRefactoring.javapackage 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);
}
}
Soft-Delete-Filter werden in jeder Query manuell wiederholt.
Policy + Repository
Eine DeletePolicy und Repository-Grenze erzwingen Sichtbarkeit und Löschsemantik zentral.
Hard Delete bei rechtlich zulässigen flüchtigen Daten
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JpaSoftDeletePolicyRefactoring.javapackage 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);
}
}
Fehlgeschlagene Nachrichten landen ohne Ursache und Recovery-Kontext im DLQ.
Dead Letter Channel + Recovery Strategy
Ein DeadLetterEnvelope bewahrt Ursache, Versuch, Korrelation und Recovery-Strategie.
Sofortiges Verwerfen bei nicht geschäftsrelevanten Telemetriedaten
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JmsDeadLetterRecoveryRefactoring.javapackage 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);
}
}
Beans werden global entdeckt und bilden versteckte Kopplungen.
Composition Root + Module
Ein FeatureModule bündelt explizit Ports, Adapter und Use Cases in einer Composition Root.
Einzelne Producer bei sehr kleinen Anwendungen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.CdiFeatureModuleBoundaryRefactoring.javapackage 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);
}
}
Datenbankänderung und Event-Publish erfolgen als Dual Write.
Transactional Outbox
Use Case und OutboxWriter teilen dieselbe lokale Transaktion; Publishing erfolgt separat.
Saga bei mehreren autonomen Datenbanken
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JtaOutboxTransactionBoundaryRefactoring.javapackage 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);
}
}
Logging, Metriken und Tracing werden in Fachmethoden dupliziert.
Decorator + Observation Port
Ein ObservationDecorator kapselt Messung und hält den Use Case frameworkfrei.
Interceptor für rein technische Querschnittsfunktionen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaTelemetryObservationBoundaryRefactoring.javapackage 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);
}
}
Containerannotation und Fachverdrahtung sind über viele Klassen verstreut.
Composition Root + Ports and Adapters
Eine explizite Composition Root verbindet Use Cases, Ports, Adapter und Policies nachvollziehbar.
Automatische Discovery bei kleinen Demoanwendungen
gekoppelte Jakarta-EE-Implementierung// Container-API, Infrastruktur und Fachentscheidung sind vermischt.JakartaEeCompositionRootRefactoring.javapackage 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);
}
}