Loslegen
Javaneer
Zurück zur Stufe
Modul 9·Abschlussprojekt: BookVault bauen

Ereignisse & Produktionsreife

Die letzten Schichten: ein Domänenereignis beim Ausleihen, dann Observability, virtuelle Threads und ein Container-Image, die BookVault deploybar machen.

16 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

BookVault is secured and tested. The final layers make it decoupled and deployable: a domain event on borrow, then the observability, virtual threads, and container packaging that turn a working app into one you can run in production.

A domain event on borrow

When a book is borrowed, other things should happen - notify the member, update a dashboard - but the borrow shouldn't depend on them. So the service publishes an event rather than calling a notifier directly:

@Transactional
public Loan borrow(String isbn, Long memberId) {
    // ... reserve a copy, save the loan ...
    events.publishEvent(new BookBorrowedEvent(isbn, memberId, loan.getId()));
    return loan;
}

// a listener reacts AFTER the transaction commits, so nobody is notified about a rolled-back borrow
@TransactionalEventListener
@Async
void onBorrow(BookBorrowedEvent event) { notifier.send(event); }

@TransactionalEventListener(AFTER_COMMIT) ensures the notification only fires once the borrow is truly committed; @Async keeps it off the request thread. This in-process event is also the seam a future microservices split would follow - the same event becomes a broker message between services.

Observability: see it running

You can't operate what you can't see. Actuator exposes health (with liveness/readiness probes an orchestrator uses to route and restart), and Micrometer exposes metrics - JVM, HTTP, and your own:

// a domain metric, visible at /actuator/prometheus for scraping
loansCreated = Counter.builder("bookvault.loans.created")
    .description("Number of book loans created").register(meterRegistry);
// ...
loansCreated.increment();   // on each successful borrow

Virtual threads: scale the simple way

BookVault is blocking, imperative code - and with Java 21 virtual threads that scales to high concurrency without a rewrite. One property, and every request runs on a cheap virtual thread that doesn't tie up an OS thread while it waits on I/O:

spring:
  threads:
    virtual:
      enabled: true

Package it: a container

Finally, package the app as an immutable container image and inject configuration at runtime - the same image runs in every environment (twelve-factor). BookVault ships a layered Dockerfile so dependency layers cache and only the small app layer rebuilds:

docker build -t bookvault .
docker run -p 8080:8080 -e JWT_SECRET=... -e SPRING_PROFILES_ACTIVE=prod bookvault
Fitting out a house before people move in

The structure stands, but before anyone lives there you add smoke detectors (health checks), meters (metrics), efficient plumbing (virtual threads), and you pack everything into a moving container so it arrives intact anywhere. These production layers don't change what BookVault does - they make it safe to actually live in and easy to move in.

Production layers don't change behavior - they change operability

Events, metrics, virtual threads, and containers leave BookVault's features identical. What they change is whether you can run it responsibly: recover from failure, see what's happening, handle load, and deploy reproducibly. That's the difference between 'it works on my machine' and 'it runs in production'.

Why publish an event instead of calling the notifier?

BookVault's borrow could just call notifier.send(...) directly. Explain two advantages of publishing a BookBorrowedEvent with an @TransactionalEventListener(AFTER_COMMIT) instead, including what happens if the transaction rolls back - and how this positions BookVault for a future service split.

What do the final production layers add to BookVault?

Key takeaways

  • Borrow publishes a BookBorrowedEvent instead of calling a notifier, decoupling the operation from its consumers.
  • @TransactionalEventListener(AFTER_COMMIT) fires only after the transaction commits, and @Async keeps it off the request thread - no notifications for rolled-back borrows.
  • That in-process event is the seam a future microservices split would follow, becoming a broker message.
  • Actuator health probes and Micrometer metrics (including a custom loans counter) make the running app observable.
  • Java 21 virtual threads scale BookVault's blocking code to high concurrency with a single property.
  • A layered container image with runtime-injected config makes deployment immutable and reproducible - these layers add operability, not features.
War diese Lektion hilfreich?
Diese Seite auf GitHub bearbeiten