Der Spickzettel zum Quarkus-Training — von der ersten Anwendung bis zum Native Image. Schwerpunkt sind die Entscheidungen, die man beim Bauen eines Dienstes trifft, und die Stellen, an denen es später hakt.
Ausführungs- und Build-Varianten
| Modus | Start / Speicher | Einsatz |
|---|---|---|
| JVM-Mode | ~1 s / moderat | Entwicklung, klassische Deployments |
| AOT-Caching (Leyden) | schneller / moderat | JVM-Start beschleunigen (Preview) |
| Native (GraalVM) | ~0,01 s / minimal | Serverless, Scale-to-zero |
| Build-Variante | Startzeit | Speicher | Build-Dauer |
|---|---|---|---|
| Fast-JAR | ~1 s | moderat | kurz |
| Native | ~0,01 s | minimal | lang |
| Leyden-AOT | schnell | moderat | mittel |
quarkus create app de.hco:kurs-api --extension=rest,rest-jackson
quarkus dev # r = Tests, d = Dev UI, q = beenden
quarkus build --native
quarkus image build docker
„Native immer besser” stimmt nicht: längere Builds, GraalVM-Fallstricke, und für die Entwicklung ist der JVM-Mode völlig ausreichend. Native kommt am Ende.
Modul: Grundlagen & erste Anwendung
CDI, Persistenz, Konfiguration
@ApplicationScoped
public class KursService {
@Inject KursRepository repo;
}
@Entity
public class Kurs { @Id @GeneratedValue public Long id; public String titel; }
@ApplicationScoped
public class KursRepository implements PanacheRepository<Kurs> { }
| Aspekt | Active Record | Repository |
|---|---|---|
| Query-Ort | auf der Entity | eigene Klasse |
| Kopplung | eng | lose |
| Testbarkeit | mäßig | gut, mockbar |
| Passt für | einfache CRUD | größere Domänen |
Konfiguration typisiert, Eingaben validiert:
@ConfigMapping(prefix = "kurs")
interface KursConfig { int maxPlaetze(); }
public record NeuerKurs(@NotBlank String titel, @Min(1) int plaetze) { }
Modul: Kernkonzepte: DI, Persistenz, Konfiguration, Tests
Reaktiv oder Virtual Thread
@GET
public Uni<List<Kurs>> alle() { return repo.listAllAsync(); }
@GET @Path("/sync")
@RunOnVirtualThread
public List<Kurs> alleSync() { return repo.listAll(); }
Uni und Multi sind lazy — ohne Abonnent passiert nichts. Und ein
blockierender Aufruf auf dem Event Loop quittiert sich mit „blocked event loop
thread”.
Microservice-Bausteine
@RegisterRestClient(configKey = "katalog")
public interface KatalogClient {
@GET @Path("/kurse")
@Retry(maxRetries = 2)
@Timeout(2000)
List<Kurs> alle();
}
@Retry ohne @Timeout ist die klassische Falle: Hängende Aufrufe stauen sich,
statt abgebrochen zu werden. Die Basis-URL gehört in die Konfiguration, nicht in
den Code.
@Readiness
public class DbCheck implements HealthCheck {
public HealthCheckResponse call() { return HealthCheckResponse.up("db"); }
}
@RolesAllowed("admin")
@DELETE @Path("/{id}")
public void loeschen(Long id) { }
Modul: Reaktives, Microservices & Deployment
Typische Fallen
- Schreiben ohne
@Transactional— keine Persistenz oder eine Exception. drop-and-createin Produktion löscht Daten. Stattdessen Flyway.- Lazy Loading außerhalb der Transaktion →
LazyInitializationException. @Validvergessen — die Constraints greifen dann schlicht nicht.- Profil-Präfix falsch (
%prodstatt%productionoder umgekehrt) — der Wert wird kommentarlos ignoriert. - Liveness und Readiness verwechselt → Pod-Neustarts oder Fehlrouting.
- Reflection ohne Registrierung bricht im Native Image, nicht vorher.
- Kein Docker/Podman aktiv → Dev Services und Testcontainers starten nicht.
- Secrets in
application.propertiesstatt in Umgebungsvariablen.