Java Enterprise Basics
Ein Order-Slice wird von Controller-Spaghetti zu sauberer Schichtenstruktur.
Workshop-Slice
Dieses Lab zeigt einen konkreten Enterprise-Fehler mit Vorher/Nachher-Code. Die Beispiele sind bewusst fachlich benannt, damit du Architekturentscheidungen und nicht nur Syntax übst.
Lernziel
Du erkennst die Bausteine einer Enterprise-Java-Anwendung und trennst HTTP, Anwendung, Domain und Persistenz sauber voneinander.
Ausgangslage
Ein alter Order-Endpunkt mischt HTTP, SQL, fachliche Regeln und technische Fehlerbehandlung in einer Methode. Für kleine Demos wirkt das schnell, in Enterprise-Systemen erzeugt es aber kaum testbare Logik und schwer wartbare Änderungen.
Vorher: problematischer Code
Der Controller entscheidet fachlich, baut SQL zusammen und liefert technische Fehlermeldungen direkt an Clients zurück.
@RestController
class LegacyOrderController {
private final DataSource dataSource;
@PostMapping("/orders/{id}/confirm")
ResponseEntity<String> confirm(@PathVariable String id) throws Exception {
try (Connection c = dataSource.getConnection()) {
var rs = c.createStatement().executeQuery(
"select status,total_amount from orders where id='" + id + "'");
if (!rs.next()) {
return ResponseEntity.status(404).body("order not found: " + id);
}
if ("CONFIRMED".equals(rs.getString("status"))) {
return ResponseEntity.status(500).body("already confirmed");
}
c.createStatement().executeUpdate(
"update orders set status='CONFIRMED' where id='" + id + "'");
return ResponseEntity.ok("ok");
}
}
}
Analyse: Was ist daran schlecht?
- SQL-Injection-Risiko durch String-Konkatenation.
- Fachregel „nicht doppelt bestätigen“ ist nicht testbar, weil sie im Controller steckt.
- HTTP-Status 500 wird für einen fachlichen Konflikt missbraucht.
- Controller kennt Datenbankdetails und verletzt klare Schichtengrenzen.
Nachher: bessere Lösung
Die Zielversion trennt Adapter, Application Service, Domain und Repository-Port. Der Controller ruft nur den Use Case auf.
// Pattern: Application Service / Use Case
// Zweck: Koordiniert einen fachlichen Anwendungsfall ohne HTTP- oder SQL-Details.
public final class ConfirmOrderUseCase {
private final OrderRepository orders;
public ConfirmOrderUseCase(OrderRepository orders) {
this.orders = orders;
}
public void confirm(OrderId id) {
Order order = orders.findById(id).orElseThrow(() -> new OrderNotFound(id));
order.confirm();
orders.save(order);
}
}
// Pattern: Domain Model
// Zweck: Fachliche Invarianten liegen im Aggregat, nicht im Controller.
public final class Order {
private final OrderId id;
private OrderStatus status;
public void confirm() {
if (status == OrderStatus.CONFIRMED) {
throw new OrderAlreadyConfirmed(id);
}
this.status = OrderStatus.CONFIRMED;
}
}
Test / Prüfnachweis
Dieser Abschnitt zeigt, wie du die Verbesserung nachweist. Es ist bewusst kein reiner Happy-Path-Test, sondern prüft ein Risiko aus dem Vorher-Teil.
@Test
void cannotConfirmAlreadyConfirmedOrder() {
var repository = new InMemoryOrderRepository(
Order.confirmed(new OrderId("O-1001"))
);
var useCase = new ConfirmOrderUseCase(repository);
assertThrows(OrderAlreadyConfirmed.class,
() -> useCase.confirm(new OrderId("O-1001")));
}
Typische Fehler
- Controller übernimmt Domainlogik.
- Repository gibt technische JPA-Entities direkt bis zur API weiter.
- Fachliche Exceptions werden zu allgemeinen RuntimeExceptions.
Deep-Learning-Bezug
Die Links führen zum ausführlichen Inhalt; die Deep-Learning-Seite bleibt nur die Lernlandkarte und kopiert den Inhalt nicht doppelt.
Prüfcheckliste
- Controller enthält keine SQL-Details.
- Fachregel ist im Domain-Modell testbar.
- Use Case ist ohne Webcontainer ausführbar.
- Fehler werden später über einen API-Fehlervertrag gemappt.