Lab 01

Java Enterprise Basics

Ein Order-Slice wird von Controller-Spaghetti zu sauberer Schichtenstruktur.

SchichtenUse CaseDomainRepository-Port
SchwierigkeitEinsteiger+
Dauer45–60 Min
LernstufeStufe 6 – Praxislabor und Capstone
Arbeitsweise: Erst den Vorher-Code öffnen, dann Analyse lesen, anschließend die Nachher-Lösung und den Test vergleichen. Alle Abschnitte sind standardmäßig geschlossen.

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.

LegacyOrderController.javaJAVA
@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.

ConfirmOrderUseCase.javaJAVA
// 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);
    }
}
Order.javaJAVA
// 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.

ConfirmOrderUseCaseTest.javaJAVA
@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.
⌂ Cockpit