Hinweis: Diese Version enthält zusätzliche Erklärungen zu Lernstrategie, Agenten-Workflow, Java-Architektur, Frontend-Stackwahl und allen Beispielprojekten. Jeder Code-, Befehl-, SQL-, JSON-, YAML- und Prompt-Block hat einen Copy-Button oben rechts.

Ausführlicher Lernpfad: Codex, Claude Code, Java Enterprise & Frontend-Stacks

Stand: 02.07.2026
Ziel: Du lernst, wie du Codex und Claude Code sinnvoll als Coding-Agenten nutzt, wie du Java-Enterprise-Projekte sauber startest und wie du moderne Frontend-Stacks aufsetzt.


Inhaltsverzeichnis

  1. Zielbild und Lernstrategie
  2. Grundsetup für Entwickler
  3. Codex Lernpfad
  4. Claude Code Lernpfad
  5. Codex vs. Claude Code: Wann welches Tool?
  6. AI-Coding-Workflow für echte Projekte
  7. Java Enterprise Lernpfad
  8. Java Enterprise Projekt starten
  9. Backend-Architektur für Enterprise-Projekte
  10. Frontend Lernpfad
  11. Frontend-Stacks erstellen
  12. Fullstack-Integration: Java Backend + Frontend
  13. Testing, Security, Observability und DevOps
  14. 12-Wochen-Roadmap
  15. Masterprojekt
  16. Beispielprojekt: Customer Order Portal
  17. Beispielprojekt: Erweiterter HTML Manager
  18. Beispielprojekt: Ticket & Approval Workflow
  19. Beispielprojekt: WordPress Pizza-Restaurant mit Lieferung, SEO, Sicherheit und App
  20. Prompt-Bibliothek für Codex und Claude Code
  21. Checklisten
  22. Quellen und offizielle Doku

1. Zielbild und Lernstrategie

Du willst nicht nur Framework-Namen kennen, sondern Projekte sauber starten können. Der Lernpfad hat deshalb drei Ebenen:

Ebene Was du lernst Ergebnis
Tool-Ebene Codex und Claude Code bedienen, sicher prompten, Änderungen prüfen Du kannst Coding-Agenten kontrolliert im Repo einsetzen.
Backend-Ebene Java, Spring Boot, Jakarta EE, Quarkus/Micronaut, Datenbank, Security, Tests Du kannst ein Enterprise-Backend produktionsnah starten.
Frontend-Ebene TypeScript, React/Vite, Angular, Next.js, Vue, UI, State, API-Integration Du kannst moderne Frontends sauber aufsetzen.

Wichtiges Prinzip: AI-Tools ersetzen nicht deine Architekturentscheidung. Sie beschleunigen Recherche, Boilerplate, Refactoring, Tests und Migrationen. Du bleibst verantwortlich für Fachlogik, Sicherheit, Datenmodell, Architektur und Review.

Erklärung: Wie du diesen Lernpfad praktisch benutzt

Dieser Lernpfad ist nicht als reine Liste von Technologien gedacht. Er ist eher wie ein Bauplan: Du lernst zuerst die Grundwerkzeuge, dann baust du kleine Module, danach verbindest du Backend, Frontend, Datenbank, Sicherheit, Tests und Deployment. So vermeidest du den typischen Fehler, zehn Frameworks gleichzeitig anzufangen, aber kein vollständiges Projekt sauber fertigzustellen.

Die wichtigste Denkweise: Jedes Thema sollte am Ende in einem kleinen lauffähigen Ergebnis landen. Bei Java bedeutet das zum Beispiel nicht nur „Spring Boot lesen“, sondern einen REST-Endpunkt bauen, Daten speichern, Fehler sauber behandeln und Tests schreiben. Bei Frontend bedeutet es nicht nur „React oder Angular installieren“, sondern Formulare, API-Aufrufe, Ladezustände, Fehlerzustände und responsive Layouts umzusetzen. Bei Codex und Claude Code bedeutet es nicht nur „Prompt schreiben“, sondern Aufgaben klein schneiden, Änderungen prüfen, Tests ausführen und den Git-Diff verstehen.

Frage Gute Lernentscheidung
„Was soll ich zuerst lernen?“ Erst Git, Terminal, Java, TypeScript, HTTP und Datenbank-Grundlagen. Danach Frameworks.
„Soll ich Spring Boot, Quarkus oder Jakarta EE nehmen?“ Für den Einstieg meistens Spring Boot. Quarkus/Micronaut später für Cloud-native oder spezielle Performance-Ziele. Jakarta EE lernen, um Enterprise-Standards zu verstehen.
„React oder Angular?“ React/Vite für leichte, flexible Frontends. Angular für große Teams mit klarer Struktur und vielen eingebauten Konventionen.
„Wann AI-Agenten einsetzen?“ Für Analyse, Boilerplate, Tests, Refactoring, Dokumentation und wiederholbare Änderungen. Kritische Architektur- und Security-Entscheidungen immer selbst prüfen.

Merksatz: Ein guter Entwickler erkennt nicht nur, welcher Code funktioniert, sondern warum er funktioniert, wo er brechen kann und wie man ihn später sicher erweitert.

Empfohlene Reihenfolge

  1. Git, Terminal, Java, Node.js und IDE sicher beherrschen.
  2. Ein kleines Backend mit Spring Boot erstellen.
  3. Ein kleines Frontend mit React/Vite oder Angular erstellen.
  4. Beide über REST/OpenAPI verbinden.
  5. Codex und Claude Code für konkrete Aufgaben einsetzen: Analyse, Tests, Refactoring, Feature-Implementierung.
  6. Danach fortgeschrittene Enterprise-Themen lernen: Auth, Observability, CI/CD, Container, Kubernetes, Microservices, Eventing.

2. Grundsetup für Entwickler

2.1 Betriebssystem und Terminal

Nutze eine Umgebung, in der du Shell-Kommandos zuverlässig ausführen kannst.

  • macOS/Linux: Terminal, zsh/bash, Homebrew oder Paketmanager.
  • Windows: PowerShell plus optional WSL2. Für Java- und Frontend-Projekte ist WSL2 oft angenehmer, weil viele Tools Linux-first dokumentiert sind.
  • Git: Pflicht. Jede Agenten-Aufgabe startet und endet mit einem Git-Checkpoint.
git --version
git config --global user.name "Dein Name"
git config --global user.email "deine.email@example.com"

2.2 Java Setup

Für Enterprise-Projekte nimm eine LTS-Version. Aktuell sinnvoll:

  • Java 25 LTS für neue Projekte, wenn dein Team/Hosting es unterstützt.
  • Java 21 LTS für Organisationen, die langsamer migrieren oder noch auf bestehende Plattformen achten müssen.
java --version
javac --version

Empfohlene Tools:

  • Maven oder Gradle
  • IntelliJ IDEA oder VS Code
  • Docker Desktop oder Podman
  • HTTP Client: IntelliJ HTTP Client, curl, httpie, Postman oder Bruno
  • Datenbank lokal: PostgreSQL über Docker Compose

2.3 Node.js und Frontend Setup

Für Frontend-Projekte nimm eine LTS-Version von Node.js. Nutze am besten einen Version Manager:

# Beispiel mit nvm
nvm install --lts
nvm use --lts
node --version
npm --version

Alternativen:

  • pnpm für schnelle Monorepos
  • npm workspaces für einfache Multi-Package-Repositories
  • corepack zur Verwaltung von Paketmanagern
corepack enable

2.4 IDE Setup

Mindest-Extensions:

  • Java: Java Extension Pack oder IntelliJ Java-Unterstützung
  • Spring Boot: Spring Tools, Spring Boot Dashboard
  • Frontend: ESLint, Prettier, TypeScript, Tailwind CSS IntelliSense
  • Docker: Docker Extension
  • AI Tools: Codex IDE Extension, Claude Code Extension oder CLI

2.5 Repository-Grundregeln

Vor jeder AI-Agent-Aufgabe:

git status
git checkout -b feature/kurze-beschreibung
git add . && git commit -m "checkpoint before ai task"

Nach jeder AI-Agent-Aufgabe:

git diff
git status
mvn test
npm test
npm run build
git add . && git commit -m "implement feature xyz"

3. Codex Lernpfad

Erklärung: Warum Codex und Claude Code nicht wie normale Chatbots genutzt werden sollten

Ein Coding-Agent arbeitet am besten, wenn du ihm eine konkrete Aufgabe im Kontext eines Repositories gibst. Statt allgemein zu fragen „Baue mir ein Backend“, solltest du erklären, welches Modul geändert werden soll, welche Regeln gelten, welche Tests existieren und welches Ergebnis du erwartest. Je präziser der Arbeitsrahmen ist, desto kleiner wird das Risiko, dass der Agent zu viel umbaut oder Architekturentscheidungen erfindet.

Schlechte Aufgabe:

Baue mir eine komplette Enterprise-App.

Gute Aufgabe:

Analysiere das bestehende Spring-Boot-Projekt. Ergänze nur im Modul customer eine POST-Route zum Anlegen eines Kunden. Verwende vorhandene DTO-Konventionen, schreibe einen Service-Test und ändere keine Security-Konfiguration. Zeige mir am Ende alle geänderten Dateien und erkläre die wichtigsten Entscheidungen.

Warum ist die zweite Aufgabe besser? Sie begrenzt den Arbeitsbereich, nennt die Technologie, definiert die erwartete Änderung, fordert Tests und verhindert unnötige Umbauten. Genau so solltest du Codex und Claude Code in echten Projekten einsetzen: kleine, überprüfbare Schritte statt riesige unkontrollierte Prompts.

Agenten-Regel Erklärung
Immer mit Git-Status starten So siehst du, ob schon lokale Änderungen existieren.
Eine Aufgabe pro Prompt Vermischt du API, UI, Security und Deployment, wird der Diff schwer prüfbar.
Tests verlangen Ohne Test erzeugt der Agent oft Code, der syntaktisch gut aussieht, aber fachlich nicht abgesichert ist.
Diff lesen Du musst verstehen, ob der Agent Nebenwirkungen eingebaut hat.
Keine Secrets einfügen API-Keys, Passwörter und Tokens gehören nie in Prompts oder Codebeispiele.

3.1 Was ist Codex?

Codex ist ein Coding-Agent von OpenAI. Er kann lokal in deinem Projektverzeichnis arbeiten, Code lesen, Dateien ändern und Kommandos ausführen. Du kannst ihn über CLI, IDE oder Cloud-Oberflächen verwenden, abhängig von deinem Plan und Setup.

3.2 Installation

macOS/Linux:

curl -fsSL https://chatgpt.com/codex/install.sh | sh
codex

Windows PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
codex

npm oder Homebrew:

npm install -g @openai/codex
brew install --cask codex

Beim ersten Start meldest du dich mit ChatGPT-Account oder API-Key an.

3.3 Erste Aufgaben mit Codex

Starte im Projektordner:

cd mein-projekt
codex

Gute erste Prompts:

Analysiere dieses Projekt. Erkläre mir die Architektur, die wichtigsten Ordner und wie ich es lokal starte.
Finde kleine Bugs oder inkonsistente Patterns. Ändere noch nichts. Gib mir zuerst einen Plan.
Schreibe Tests für den Auth-Service. Führe die Tests aus und behebe nur Fehler, die direkt mit deinen Tests zusammenhängen.

3.4 Codex Skill-Level

Level 1: Lesen und erklären

Ziel: Du lernst, Codex als Codebase-Erklärer zu nutzen.

Aufgaben:

  • Projektstruktur erklären lassen.
  • Startbefehle identifizieren lassen.
  • Datenfluss durch Controller, Service und Repository erklären lassen.
  • Teststrategie des Projekts zusammenfassen lassen.

Prompt:

Erkläre die Codebase aus Sicht eines neuen Entwicklers. Fokus: Starten, Architektur, wichtigste Module, Risiken. Keine Dateien ändern.

Level 2: Kleine Änderungen

Ziel: Codex darf kleine, reversible Änderungen durchführen.

Aufgaben:

  • README aktualisieren.
  • Lint-Fehler beheben.
  • DTO-Feld ergänzen.
  • Logging verbessern.
  • Kleine Unit Tests schreiben.

Prompt:

Füge im bestehenden Stil ein Feld `status` zum Order-DTO hinzu. Passe Mapping, Tests und Dokumentation an. Mache keine Architekturänderungen.

Level 3: Feature-Implementierung

Ziel: Codex implementiert ein kleines Feature Ende-zu-Ende.

Beispiel: Customer Notes in CRM-App.

Prompt:

Implementiere Customer Notes:
- Backend: Entity, Repository, Service, REST Controller, Validation, Tests
- Datenbank: Flyway Migration
- API: OpenAPI aktualisieren
- Frontend: Liste, Formular, API Client, Fehleranzeige
Arbeite in kleinen Schritten. Erstelle zuerst einen Plan und warte auf Review.

Level 4: Refactoring

Ziel: Codex darf bestehende Struktur verbessern, aber nur mit Tests.

Prompt:

Refactore den Payment-Service in kleinere Methoden. Verhalten darf sich nicht ändern. Schreibe oder erweitere Tests vor der Änderung. Zeige mir danach die wichtigsten Diffs.

Level 5: Migrationen

Ziel: Codex hilft bei Framework- und Versionsmigrationen.

Beispiele:

  • Java 21 zu Java 25 prüfen.
  • Spring Boot Major Upgrade vorbereiten.
  • Angular Update durchführen.
  • JavaScript zu TypeScript migrieren.

Prompt:

Untersuche, was für ein Upgrade auf die aktuelle Spring-Boot-Version nötig wäre. Ändere noch nichts. Erstelle eine Migrationsliste mit Risiken, betroffenen Dateien und Testplan.

3.5 Codex Best Practices

  • Immer im Git-Branch arbeiten.
  • Niemals große Aufgaben ohne Plan starten.
  • Erst Analyse, dann Änderung.
  • Secrets niemals in Prompts schreiben.
  • Nach jeder Änderung Tests laufen lassen.
  • Diffs manuell prüfen.
  • Agent nicht direkt auf Produktion oder echte Kundendaten loslassen.
  • Aufgaben klein schneiden: ein Feature, ein Bug, ein Refactoring.

3.6 Codex Einsatz in Java-Projekten

Gute Codex-Aufgaben:

  • Spring Boot Controller + Service + Test generieren.
  • Flyway Migration schreiben.
  • Integrationstest mit Testcontainers ergänzen.
  • Security-Konfiguration erklären.
  • Circular Dependencies finden.
  • N+1 Query-Probleme suchen.
  • OpenAPI-Dokumentation aktualisieren.

Schlechtere Aufgaben:

  • Unklare „mach alles besser“-Prompts.
  • Große Microservice-Zerlegung ohne Architekturkontext.
  • Security-Änderungen ohne Review.
  • Datenbankmigrationen ohne Backup- und Rollback-Plan.

4. Claude Code Lernpfad

4.1 Was ist Claude Code?

Claude Code ist ein agentisches Coding-Tool von Anthropic. Es kann Codebases lesen, Dateien editieren, Kommandos ausführen und sich in Terminal, IDE, Desktop, Web und Tooling integrieren.

4.2 Installation

macOS/Linux/WSL:

curl -fsSL https://claude.ai/install.sh | bash
claude

Windows PowerShell:

irm https://claude.ai/install.ps1 | iex
claude

Homebrew:

brew install --cask claude-code

WinGet:

winget install Anthropic.ClaudeCode

Beim ersten Start meldest du dich an:

claude

In einer laufenden Session kannst du dich neu anmelden:

/login

4.3 Erste Aufgaben mit Claude Code

Erkläre dieses Repository. Welche Module gibt es, wie starte ich Backend und Frontend, welche Tests existieren?
Erstelle einen Plan für ein neues Feature: Task-Kommentare. Nenne betroffene Dateien, Datenmodell, API und Tests. Noch nichts ändern.
Implementiere nur den Backend-Teil aus deinem Plan. Schreibe Tests und führe sie aus.

4.4 Claude Code in VS Code

Die VS-Code-Erweiterung ist praktisch, wenn du Änderungen lieber visuell prüfst:

  • Dateien und Zeilenbereiche per @file.ts#5-10 referenzieren.
  • Plan prüfen, bevor Änderungen akzeptiert werden.
  • Diffs side-by-side ansehen.
  • Mehrere Sessions parallel führen.
  • Bei Bedarf CLI im integrierten Terminal nutzen.

4.5 Claude Code Skill-Level

Level 1: Orientierung

  • Projekt erklären lassen.
  • Architekturdiagramm als Text erstellen lassen.
  • „Wie starte ich lokal?“ dokumentieren lassen.

Level 2: Navigation

  • Relevante Dateien zu einem Feature finden.
  • Call-Flow erklären lassen.
  • Fehler anhand Stacktrace lokalisieren lassen.

Level 3: Umsetzung

  • Tests schreiben.
  • Kleine Features bauen.
  • UI-Komponenten ergänzen.
  • API-Client anpassen.

Level 4: Agentische Workflows

  • Issue aus Ticket-System lesen.
  • Pull Request vorbereiten.
  • CI-Fehler analysieren.
  • Migrationsplan schreiben.

Level 5: Tool-Integration über MCP

Claude Code kann über MCP mit externen Tools verbunden werden, zum Beispiel Issue Tracker, Datenbanken, Monitoring, Figma oder interne APIs. Das ist stark, aber sicherheitskritisch. Verbinde nur Tools, denen du vertraust, und gib nur minimale Rechte.

4.6 Claude Code Best Practices

  • Bei großen Aufgaben zuerst einen Plan verlangen.
  • Diffs in der IDE prüfen.
  • Tests immer ausführen lassen.
  • Für Architekturfragen klare Randbedingungen geben.
  • Für MCP: Least Privilege, getrennte Tokens, Audit Logs.
  • Für Datenbanken: Nur lokale/dev Datenbanken verbinden, niemals ungeprüft Produktion.

5. Codex vs. Claude Code: Wann welches Tool?

Situation Codex Claude Code
Terminal-basierte Arbeit im Repo Sehr gut Sehr gut
IDE-Diff und visuelle Review Sehr gut mit IDE Extension Sehr gut mit VS-Code-Extension
Lange Analyse großer Codebases Sehr gut Sehr gut
Plan-first Umsetzung Sehr gut Sehr gut
MCP-/Tool-Ökosystem Gut, je nach Setup Sehr stark über Claude Code MCP
OpenAI/ChatGPT-Workflow Sehr passend Nicht relevant
Anthropic/Claude-Team-Workflow Nicht relevant Sehr passend
Java Backend Boilerplate Gut Gut
Frontend-Komponenten Gut Gut
Sicherheitskritische Änderungen Nur mit Review Nur mit Review

Praktische Empfehlung: Lerne beide. Nutze sie nicht religiös. Entscheidend ist dein Workflow:

  • Für schnelle lokale Aufgaben: CLI.
  • Für UI-Diffs: IDE Extension.
  • Für externe Tool-Kontexte: MCP/Connectors, aber mit strenger Rechtevergabe.
  • Für Architektur: erst Plan, dann kleine Schritte.

6. AI-Coding-Workflow für echte Projekte

6.1 Der sichere Standardablauf

1. Git-Branch erstellen
2. Agent soll Codebase analysieren
3. Agent soll Plan erstellen
4. Plan prüfen
5. Agent implementiert kleinen Teil
6. Tests ausführen
7. Diff prüfen
8. Commit
9. Nächster kleiner Teil

6.2 Aufgaben richtig schneiden

Schlecht:

Baue mir eine komplette Enterprise-App.

Gut:

Implementiere im bestehenden Spring-Boot-Projekt einen read-only REST-Endpunkt GET /api/customers/{id}. Nutze bestehende Patterns, schreibe Unit- und Integrationstest, ändere keine Security-Konfiguration.

6.3 Agenten-Kontext geben

Guter Kontext enthält:

  • Ziel des Features
  • Akzeptanzkriterien
  • betroffene Schichten
  • Nicht-Ziele
  • Testanforderungen
  • Coding-Standards
  • Sicherheitsgrenzen

Beispiel:

Ziel: Benutzer können Bestellungen nach Status filtern.
Akzeptanzkriterien:
- GET /api/orders?status=OPEN gibt nur offene Bestellungen zurück
- Ungültiger Status liefert 400
- Integrationstest mit Testdaten
Nicht-Ziele:
- Keine UI-Änderung
- Keine Änderung an Auth
Stil:
- Bestehende Controller/Service/Repository-Struktur beibehalten

6.4 Review-Fragen nach Agentenarbeit

Nach jeder Änderung fragst du:

  • Wurden unnötige Dateien geändert?
  • Wurde Fachlogik verändert, obwohl nur Refactoring geplant war?
  • Sind Tests sinnvoll oder nur oberflächlich?
  • Gibt es Security-Probleme?
  • Gibt es neue Abhängigkeiten?
  • Sind Fehlermeldungen und Validierung sauber?
  • Funktioniert Build lokal?

7. Java Enterprise Lernpfad

7.1 Was bedeutet Java Enterprise heute?

Java Enterprise bedeutet heute nicht nur „Java EE“. Es meint produktionsfähige Unternehmenssoftware mit:

  • stabiler JVM-Plattform
  • Backend-Frameworks wie Spring Boot, Jakarta EE, Quarkus oder Micronaut
  • Datenbanken und Transaktionen
  • Security und Identity Provider
  • REST, GraphQL oder Messaging APIs
  • Tests, Observability, CI/CD
  • Container und Cloud Deployment

7.2 Priorisierte Frameworks

Framework Wann lernen? Einsatz
Spring Boot zuerst Standard für viele Enterprise-Backends, sehr großes Ökosystem
Jakarta EE danach Standardisierte Enterprise-APIs, App-Server, Portabilität
Quarkus danach Cloud-native, Kubernetes, schnelle Dev-Loops, GraalVM-freundlich
Micronaut danach Microservices, geringe Startzeit, Compile-Time DI
Helidon/Open Liberty optional Jakarta/MicroProfile-orientierte Enterprise-Stacks

7.3 Java-Grundlagen für Enterprise

Du solltest sicher beherrschen:

  • Klassen, Interfaces, Records, Enums
  • Collections und Streams
  • Exceptions und Error Handling
  • Generics
  • Concurrency-Grundlagen
  • Optional und Null-Safety Patterns
  • Date/Time API
  • Maven/Gradle
  • JUnit 5, AssertJ, Mockito
  • HTTP, JSON, REST
  • SQL und Transaktionen

7.4 Enterprise-Konzepte

Lerne diese Konzepte parallel zur Praxis:

  • Layered Architecture
  • Hexagonal Architecture / Ports and Adapters
  • Domain-Driven Design Basics
  • DTO vs Entity
  • Validation
  • Transactions
  • Security: Authentication vs Authorization
  • OAuth2/OIDC/JWT
  • Idempotenz
  • Pagination und Filtering
  • Caching
  • Observability: Logs, Metrics, Traces
  • CI/CD und Deployment Pipelines

8. Java Enterprise Projekt starten

Erklärung: Warum ein Java-Enterprise-Projekt mehr ist als ein Controller

Viele Anfänger starten mit einem Controller und schreiben dort direkt Datenbankzugriffe, Validierung und Geschäftslogik hinein. Das funktioniert für kleine Demos, wird aber schnell unwartbar. Ein Enterprise-Projekt trennt Verantwortlichkeiten: Der Controller nimmt HTTP-Anfragen an, DTOs beschreiben Eingabe und Ausgabe, der Service enthält Fachlogik, das Repository spricht mit der Datenbank, und Tests sichern das Verhalten ab.

Diese Trennung klingt am Anfang nach mehr Arbeit. Der Vorteil kommt später: Du kannst einzelne Teile austauschen, besser testen, Fehler sauberer behandeln und Sicherheitsregeln zentraler durchsetzen. In größeren Teams ist diese Struktur auch wichtig, damit neue Entwickler schnell verstehen, wo welche Logik hingehört.

Schicht Aufgabe Typischer Fehler
Controller HTTP-Endpunkte, Statuscodes, Request/Response Geschäftslogik direkt im Controller schreiben
DTO Daten nach außen und innen begrenzen Entity direkt an das Frontend zurückgeben
Service Fachlogik, Transaktionen, Regeln Datenbankdetails mit UI-Regeln vermischen
Repository Persistenz und Queries Komplexe Fachentscheidungen in SQL/Repository verstecken
Migration Datenbankschema versionieren Tabellen manuell in der DB erstellen
Test Verhalten absichern Nur Happy Path testen

Praktischer Grundsatz: Wenn du eine Änderung später erklären musst, sollte klar sein, in welcher Schicht sie liegt. Eine neue Validierungsregel gehört meist in DTO/Service. Eine neue URL gehört in den Controller. Eine neue Tabelle gehört in Migration und Entity. Eine neue Darstellung gehört ins Frontend.

8.1 Entscheidungsbaum

Nimm Spring Boot, wenn:

  • du schnell produktiv sein willst,
  • du viele Beispiele und Libraries brauchst,
  • du REST APIs, JPA, Security, Batch, Messaging oder Actuator brauchst,
  • Teammitglieder unterschiedliche Erfahrungslevel haben.

Nimm Jakarta EE/Open Liberty/WildFly/Payara, wenn:

  • Standard-APIs und App-Server-Portabilität wichtig sind,
  • dein Unternehmen bereits App-Server betreibt,
  • du Jakarta REST, CDI, JPA, Bean Validation und Transactions standardnah nutzen willst.

Nimm Quarkus, wenn:

  • Kubernetes, Container, schnelle Startzeit und Dev Mode wichtig sind,
  • du cloud-native Java bauen willst,
  • du MicroProfile/Jakarta-nahe APIs magst.

Nimm Micronaut, wenn:

  • Compile-Time Dependency Injection,
  • Microservices,
  • geringe Reflection-Nutzung,
  • schnelle Startup-Zeiten wichtig sind.

8.2 Spring Boot Projekt mit Spring Initializr

Empfohlene Auswahl:

  • Project: Maven oder Gradle
  • Language: Java
  • Java: 25 oder 21 LTS
  • Packaging: Jar
  • Dependencies:
    • Spring Web
    • Spring Validation
    • Spring Data JPA
    • PostgreSQL Driver
    • Spring Security
    • OAuth2 Resource Server
    • Actuator
    • Flyway Migration
    • Testcontainers
    • Lombok optional

Beispiel mit curl:

curl https://start.spring.io/starter.zip \
  -d dependencies=web,validation,data-jpa,postgresql,security,oauth2-resource-server,actuator,flyway \
  -d type=maven-project \
  -d language=java \
  -d javaVersion=25 \
  -d bootVersion=4.1.0 \
  -d groupId=com.example \
  -d artifactId=enterprise-api \
  -o enterprise-api.zip

unzip enterprise-api.zip -d enterprise-api
cd enterprise-api
./mvnw spring-boot:run

Wenn die gewünschte Spring-Boot-Version in deinem Unternehmen festgelegt ist, nutze diese statt blind die neueste Version.

8.3 Alternative: Quarkus Projekt starten

quarkus create app com.example:enterprise-api \
  --extension='rest-jackson,hibernate-orm-panache,jdbc-postgresql,flyway,smallrye-openapi,smallrye-health'
cd enterprise-api
quarkus dev

Oder mit Maven Plugin:

mvn io.quarkus.platform:quarkus-maven-plugin:create \
  -DprojectGroupId=com.example \
  -DprojectArtifactId=enterprise-api \
  -Dextensions='rest-jackson,hibernate-orm-panache,jdbc-postgresql,flyway,smallrye-openapi'

8.4 Alternative: Micronaut Projekt starten

mn create-app com.example.enterprise-api \
  --features=data-jdbc,postgres,flyway,security-jwt,management,openapi,testcontainers \
  --build=maven \
  --lang=java
cd enterprise-api
./mvnw mn:run

8.5 Empfohlene Spring-Boot-Projektstruktur

enterprise-api/
  src/main/java/com/example/enterprise/
    EnterpriseApiApplication.java
    config/
      SecurityConfig.java
      OpenApiConfig.java
    customer/
      Customer.java
      CustomerController.java
      CustomerService.java
      CustomerRepository.java
      CustomerMapper.java
      dto/
        CustomerRequest.java
        CustomerResponse.java
    order/
      Order.java
      OrderController.java
      OrderService.java
      OrderRepository.java
      dto/
    common/
      error/
        ApiError.java
        GlobalExceptionHandler.java
      pagination/
      validation/
  src/main/resources/
    application.yml
    db/migration/
      V1__init.sql
  src/test/java/com/example/enterprise/
    customer/
    order/

8.6 Minimaler Controller-Service-Repository Flow

@RestController
@RequestMapping("/api/customers")
class CustomerController {
  private final CustomerService service;

  CustomerController(CustomerService service) {
    this.service = service;
  }

  @GetMapping("/{id}")
  CustomerResponse getById(@PathVariable UUID id) {
    return service.getById(id);
  }
}
@Service
class CustomerService {
  private final CustomerRepository repository;
  private final CustomerMapper mapper;

  CustomerService(CustomerRepository repository, CustomerMapper mapper) {
    this.repository = repository;
    this.mapper = mapper;
  }

  @Transactional(readOnly = true)
  CustomerResponse getById(UUID id) {
    return repository.findById(id)
      .map(mapper::toResponse)
      .orElseThrow(() -> new NotFoundException("Customer not found"));
  }
}

8.7 Konfiguration mit application.yml

spring:
  application:
    name: enterprise-api
  datasource:
    url: jdbc:postgresql://localhost:5432/enterprise
    username: enterprise
    password: enterprise
  jpa:
    hibernate:
      ddl-auto: validate
    open-in-view: false
  flyway:
    enabled: true

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

8.8 Lokale Datenbank mit Docker Compose

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: enterprise
      POSTGRES_USER: enterprise
      POSTGRES_PASSWORD: enterprise
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:

Start:

docker compose up -d
./mvnw spring-boot:run

8.9 AI-Prompt zum Erstellen eines Spring Features

Du bist Senior Java/Spring Boot Entwickler.
Implementiere ein Customer-Modul im bestehenden Projekt.
Anforderungen:
- Entity: Customer mit id, name, email, status, createdAt
- REST: POST /api/customers, GET /api/customers/{id}, GET /api/customers
- Validation: email gültig, name nicht leer
- Persistence: Spring Data JPA
- Migration: Flyway SQL
- Error Handling: vorhandenen GlobalExceptionHandler nutzen
- Tests: Service Unit Tests und Controller Integration Test
Regeln:
- Bestehende Projektstruktur beibehalten
- Keine unnötigen Dependencies hinzufügen
- Erst Plan zeigen, dann implementieren

9. Backend-Architektur für Enterprise-Projekte

9.1 Layered Architecture

Klassisch und verständlich:

Controller -> Service -> Repository -> Database

Gut für:

  • CRUD APIs
  • kleine bis mittlere Teams
  • schnelle Produktentwicklung

Risiko:

  • Fachlogik verteilt sich schnell zwischen Service, Entity und Controller.
  • Bei wachsender Komplexität wird es schwer, Domänenregeln sauber zu halten.

9.2 Hexagonal Architecture

Bessere Trennung zwischen Domain und Infrastruktur:

Inbound Adapter: REST Controller
Application Service: Use Cases
Domain: Entities, Value Objects, Rules
Outbound Port: Repository Interface
Outbound Adapter: JPA Repository

Beispielstruktur:

customer/
  domain/
    Customer.java
    CustomerStatus.java
  application/
    CreateCustomerUseCase.java
    GetCustomerUseCase.java
  port/in/
    CreateCustomerCommand.java
  port/out/
    CustomerRepositoryPort.java
  adapter/in/web/
    CustomerController.java
  adapter/out/jpa/
    JpaCustomerRepository.java
    CustomerJpaEntity.java

Gut für:

  • komplexe Domänen
  • langfristige Wartung
  • Tests ohne Datenbank
  • Austausch von Infrastruktur

9.3 Modularer Monolith

Für viele Enterprise-Projekte ist ein modularer Monolith besser als sofort Microservices.

app/
  customer-module
  order-module
  billing-module
  notification-module

Regeln:

  • Module kommunizieren über definierte APIs.
  • Keine wilden Cross-Imports.
  • Pro Modul eigene Tests.
  • Gemeinsame „common“-Module klein halten.

9.4 Microservices erst später

Nimm Microservices erst, wenn du wirklich brauchst:

  • unabhängige Deployments
  • getrennte Skalierung
  • getrennte Teams
  • klare Domänengrenzen
  • robuste DevOps-Reife

Sonst erzeugst du nur verteilte Komplexität: Netzwerkfehler, Datenkonsistenz, Observability, Deployment-Abhängigkeiten, Versionierung.

9.5 Datenmodell und Migrationen

Regeln:

  • Keine ddl-auto=create in Enterprise.
  • Schemaänderungen über Flyway oder Liquibase.
  • Jede Migration muss vorwärtskompatibel geplant werden.
  • Datenmigrationen separat testen.
  • Rollback-Strategie dokumentieren.

9.6 API Design

REST-Konventionen:

GET    /api/customers
GET    /api/customers/{id}
POST   /api/customers
PUT    /api/customers/{id}
PATCH  /api/customers/{id}/status
DELETE /api/customers/{id}

Achte auf:

  • konsistente Fehlerantworten
  • Pagination
  • Sorting
  • Filtering
  • Versionierung
  • OpenAPI-Dokumentation
  • Idempotenz bei kritischen Operationen

10. Frontend Lernpfad

10.1 Reihenfolge

  1. HTML, CSS, JavaScript
  2. TypeScript
  3. Browser APIs und HTTP
  4. Komponentenmodell
  5. Routing
  6. Forms und Validation
  7. State Management
  8. API Clients
  9. Testing
  10. Performance und Accessibility
  11. Build, Deployment und Monitoring

10.2 TypeScript zuerst ernst nehmen

Enterprise-Frontend ohne TypeScript ist riskant. Lerne:

  • primitive Typen
  • Interfaces und Types
  • Union Types
  • Generics
  • Utility Types
  • Type Guards
  • unknown statt any
  • DTO-Typen aus OpenAPI generieren

Beispiel:

export type CustomerStatus = 'ACTIVE' | 'INACTIVE' | 'BLOCKED';

export interface CustomerResponse {
  id: string;
  name: string;
  email: string;
  status: CustomerStatus;
  createdAt: string;
}

10.3 Frontend-Framework-Auswahl

Stack Wann nehmen? Typischer Einsatz
React + Vite flexibel, schnell, viele Libraries SPAs, Dashboards, interne Tools
Angular großes Team, klare Struktur, Enterprise-Standards große Unternehmensanwendungen
Next.js React mit SSR/SSG, App Router, Fullstack-Optionen Web-Apps, Portale, SEO, SaaS
Vue + Vite leichter Einstieg, gute DX kleinere bis mittlere Apps
Svelte/SvelteKit kompakt, performant moderne Apps, wenn Team Erfahrung hat

10.4 Enterprise-Frontend-Bausteine

  • Routing
  • Layout-System
  • Design System oder UI Library
  • Auth Guard
  • API Client
  • Error Handling
  • Toasts/Notifications
  • Forms
  • Tabellen mit Pagination/Filtering
  • Internationalisierung
  • Accessibility
  • Unit Tests
  • E2E Tests
  • Build Pipeline

11. Frontend-Stacks erstellen

Erklärung: Wie du den passenden Frontend-Stack auswählst

Frontend-Stacks unterscheiden sich weniger darin, ob sie „gut“ oder „schlecht“ sind, sondern darin, welche Art von Projekt und Team sie unterstützen. Für ein kleines Dashboard ist React mit Vite sehr schnell und flexibel. Für ein großes internes Enterprise-Portal mit vielen Formularen, Routing-Regeln und klaren Team-Konventionen kann Angular sehr angenehm sein. Next.js lohnt sich, wenn SEO, serverseitiges Rendering, Content-Seiten oder Fullstack-Routing wichtig sind. Vue ist gut, wenn du eine leicht zugängliche, komponentenbasierte Lösung möchtest.

Situation Empfehlung Warum
Kleines bis mittleres SPA React + Vite Schnell eingerichtet, flexibel, großer Ökosystem-Vorteil
Großes Enterprise-Frontend Angular Klare Struktur, CLI, Routing, Forms, DI und Team-Konventionen
SEO-starke Website/App Next.js Server Rendering, Routing und Content-Struktur sind stärker integriert
Einfacher Einstieg mit Komponenten Vue Gut lesbar, geringe Einstiegshürde
Designsystem für mehrere Apps React oder Angular + Storybook Komponenten können dokumentiert und wiederverwendet werden

Wichtig: Die Stack-Wahl löst nicht automatisch UX-Probleme. Du brauchst trotzdem gute Formularführung, Ladezustände, Fehlertexte, Barrierefreiheit, responsive Layouts und saubere API-Verträge.

11.1 React + Vite + TypeScript

npm create vite@latest enterprise-web -- --template react-ts
cd enterprise-web
npm install
npm run dev

Empfohlene Dependencies:

npm install react-router-dom @tanstack/react-query zod axios
npm install -D vitest @testing-library/react @testing-library/jest-dom eslint prettier

Struktur:

enterprise-web/
  src/
    app/
      App.tsx
      router.tsx
      providers.tsx
    features/
      customers/
        api/customerApi.ts
        components/CustomerForm.tsx
        pages/CustomerListPage.tsx
        types.ts
    shared/
      api/httpClient.ts
      components/
      errors/
      config/env.ts
    main.tsx

Minimaler API Client:

import axios from 'axios';

export const httpClient = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080/api',
});

React Query Beispiel:

import { useQuery } from '@tanstack/react-query';
import { httpClient } from '../../shared/api/httpClient';

export function useCustomers() {
  return useQuery({
    queryKey: ['customers'],
    queryFn: async () => {
      const response = await httpClient.get('/customers');
      return response.data;
    },
  });
}

11.2 Angular Enterprise App

npm install -g @angular/cli
ng new enterprise-web --routing --style=scss
cd enterprise-web
ng serve

Empfohlene Struktur:

src/app/
  core/
    auth/
    http/
    guards/
  shared/
    ui/
    pipes/
  features/
    customers/
      pages/
      components/
      data-access/
      models/
  app.routes.ts

Service-Beispiel:

@Injectable({ providedIn: 'root' })
export class CustomerApiService {
  private readonly http = inject(HttpClient);
  private readonly baseUrl = '/api/customers';

  getCustomers(): Observable<CustomerResponse[]> {
    return this.http.get<CustomerResponse[]>(this.baseUrl);
  }
}

Angular passt besonders gut, wenn dein Team klare Konventionen, Dependency Injection, Router, Forms, HTTP und Testing in einem Framework möchte.

11.3 Next.js App

npx create-next-app@latest enterprise-portal
cd enterprise-portal
npm run dev

Typische Struktur:

app/
  layout.tsx
  page.tsx
  customers/
    page.tsx
components/
lib/
  api.ts

Nimm Next.js, wenn du brauchst:

  • SSR oder SSG
  • SEO
  • serverseitige Datenladung
  • Auth im Web-Portal
  • Landing Pages plus App-Funktionen

11.4 Vue + Vite + TypeScript

npm create vue@latest enterprise-web
cd enterprise-web
npm install
npm run dev

Gute Wahl, wenn:

  • du schnellen Einstieg willst,
  • das Team Vue kennt,
  • die App weniger komplex ist als ein großes Angular-Projekt,
  • du trotzdem TypeScript, Router und State sauber einsetzen willst.

11.5 UI Libraries

Framework Libraries
React MUI, Chakra UI, Ant Design, shadcn/ui, Tailwind CSS
Angular Angular Material, PrimeNG, NG-ZORRO, Tailwind CSS
Vue Vuetify, PrimeVue, Naive UI, Tailwind CSS

Enterprise-Regel: Nimm nicht zu viele UI Libraries gleichzeitig. Entscheide dich früh für Design Tokens, Spacing, Farben, Form Controls und Tabellenverhalten.

11.6 Frontend AI-Prompt

Du bist Senior Frontend Entwickler.
Erstelle im bestehenden React/Vite/TypeScript Projekt ein Customer Feature.
Anforderungen:
- Route /customers
- Tabelle mit Name, Email, Status
- Lade Daten über vorhandenen httpClient
- Loading, Empty State, Error State
- Komponente testbar halten
- Keine neue UI Library hinzufügen
- Erst Plan, dann Umsetzung

12. Fullstack-Integration: Java Backend + Frontend

12.1 Empfohlene Architektur lokal

Browser -> Frontend Dev Server :5173
Frontend -> Backend API :8080
Backend -> PostgreSQL :5432

12.2 CORS im Backend

Für lokale Entwicklung:

@Configuration
class WebConfig {
  @Bean
  WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
          .allowedOrigins("http://localhost:5173")
          .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
          .allowedHeaders("*");
      }
    };
  }
}

Für Produktion CORS restriktiver setzen.

12.3 API Contract mit OpenAPI

Backend generiert OpenAPI, Frontend generiert Types:

npm install -D openapi-typescript
npx openapi-typescript http://localhost:8080/v3/api-docs -o src/shared/api/schema.ts

Vorteile:

  • Frontend DTOs passen zum Backend.
  • Breaking Changes werden schneller sichtbar.
  • Weniger manuelle Typfehler.

12.4 Proxy statt CORS im Frontend

Vite Beispiel:

export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:8080',
    },
  },
});

Dann kann das Frontend /api/customers nutzen.

12.5 Fullstack-Monorepo

enterprise-app/
  backend/
    pom.xml
    src/
  frontend/
    package.json
    src/
  docker-compose.yml
  _intern/sources/_intern\sources\README.md

Root README sollte enthalten:

# Start DB
docker compose up -d

# Start backend
cd backend && ./mvnw spring-boot:run

# Start frontend
cd frontend && npm run dev

12.6 CI Pipeline Beispiel

name: build
on: [push, pull_request]
jobs:
  backend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '25'
      - run: cd backend && ./mvnw test
  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
      - run: cd frontend && npm ci
      - run: cd frontend && npm test
      - run: cd frontend && npm run build

13. Testing, Security, Observability und DevOps

13.1 Backend Testing

Testtyp Tool Ziel
Unit Test JUnit, AssertJ, Mockito Fachlogik schnell testen
Slice Test @WebMvcTest, @DataJpaTest einzelne Spring-Schicht testen
Integration Test Spring Boot Test, Testcontainers API + DB realitätsnah testen
Contract Test OpenAPI, Pact Backend/Frontend Vertrag prüfen
E2E Playwright/Cypress User Flow prüfen

13.2 Frontend Testing

  • Unit Tests mit Vitest/Jest
  • Component Tests mit Testing Library
  • E2E Tests mit Playwright
  • Accessibility Checks
  • Visual Regression optional

13.3 Security Basics

Backend:

  • OAuth2/OIDC statt eigener Passwortlogik, wenn möglich
  • JWT validieren, nicht nur decodieren
  • Rollen und Berechtigungen serverseitig prüfen
  • Input Validation
  • Rate Limiting für kritische Endpunkte
  • Audit Logging
  • Secrets über Vault/Cloud Secret Manager

Frontend:

  • Tokens sicher behandeln
  • Keine Secrets im Frontend
  • XSS vermeiden
  • CSRF je nach Auth-Strategie beachten
  • Auth Guards nur UX, nicht Sicherheit

13.4 Observability

Mindestens:

  • strukturierte Logs
  • Health Checks
  • Metrics über Actuator/Micrometer
  • Tracing mit OpenTelemetry
  • Error Tracking
  • Dashboards und Alerts

13.5 DevOps Basics

  • Dockerfile für Backend und Frontend
  • Docker Compose für lokale Infrastruktur
  • CI mit Tests und Build
  • Security Scans für Dependencies
  • Migrations-Check in Pipeline
  • Deployment: Kubernetes, Cloud Run, ECS, Azure Container Apps oder klassische VM, je nach Unternehmen

14. 12-Wochen-Roadmap

Woche 1: Tooling und Grundlagen

  • Git Branching, Commits, Diffs
  • Java LTS installieren
  • Node LTS installieren
  • IDE einrichten
  • Codex und Claude Code installieren
  • Erstes Repo analysieren lassen

Ergebnis: Du kannst lokale Projekte starten und Agenten kontrolliert nutzen.

Woche 2: Java und Spring Boot Basics

  • Spring Boot Projekt erstellen
  • Controller, Service, Repository
  • DTOs und Validation
  • Fehlerbehandlung
  • PostgreSQL via Docker

Ergebnis: CRUD Backend läuft lokal.

Woche 3: Tests und Datenbankmigrationen

  • JUnit 5
  • Mockito
  • Spring Boot Integration Tests
  • Testcontainers
  • Flyway

Ergebnis: Backend ist testbar und DB-Schema versioniert.

Woche 4: Security

  • Spring Security Basics
  • OAuth2 Resource Server
  • JWT
  • Rollen und Berechtigungen
  • CORS

Ergebnis: API ist abgesichert.

Woche 5: TypeScript und React/Vite

  • TypeScript Grundlagen
  • React Komponenten
  • Router
  • API Client
  • React Query

Ergebnis: Frontend ruft Backend auf.

Woche 6: Angular Alternative

  • Angular CLI
  • Components
  • Services
  • Router
  • Reactive Forms
  • HTTP Client

Ergebnis: Du kannst entscheiden, ob React oder Angular besser passt.

Woche 7: Fullstack Contract

  • OpenAPI generieren
  • Frontend Types generieren
  • API Fehler anzeigen
  • Pagination
  • Loading/Error/Empty States

Ergebnis: Frontend und Backend sind vertraglich verbunden.

Woche 8: Architektur

  • Modularer Monolith
  • Hexagonal Architecture
  • DDD Basics
  • Module Boundaries
  • Refactoring mit Codex/Claude Code

Ergebnis: Projekt bleibt wartbar.

Woche 9: Quarkus und Micronaut Überblick

  • Quarkus Dev Mode
  • Micronaut DI
  • MicroProfile Basics
  • Vergleich mit Spring Boot

Ergebnis: Du kennst Alternativen und deren Einsatzfälle.

Woche 10: Observability und DevOps

  • Actuator
  • Logs
  • Metrics
  • OpenTelemetry Grundidee
  • Dockerfile
  • CI Pipeline

Ergebnis: App ist deploy- und beobachtbar.

Woche 11: Agentische Entwicklung

  • Codex für Tests und Refactoring
  • Claude Code für Multi-File Features
  • MCP/Tool-Integration verstehen
  • PR Review mit Agenten

Ergebnis: Du nutzt AI-Agenten produktiv und sicher.

Woche 12: Abschlussprojekt

  • Eigenes Fullstack-Projekt bauen
  • README, Architekturdiagramm, Tests, CI
  • Agenten-Prompts dokumentieren
  • Code Review durchführen

Ergebnis: Portfolio-fähiges Enterprise-Projekt.


15. Masterprojekt

Baue ein kleines, aber realistisches Enterprise-System: Customer Order Management.

15.1 Funktionen

Backend:

  • Customer CRUD
  • Order CRUD
  • Order Status Workflow
  • Auth mit Rollen: ADMIN, USER
  • PostgreSQL
  • Flyway Migrationen
  • OpenAPI
  • Actuator
  • Tests

Frontend:

  • Login-Seite oder Auth-Mock
  • Customer-Liste
  • Customer-Details
  • Order-Liste
  • Order erstellen
  • Status ändern
  • Error/Loading/Empty States

DevOps:

  • Docker Compose
  • CI Pipeline
  • README
  • Architekturdiagramm als Mermaid

15.2 Phasen

Phase 1: Backend Skeleton

Erstelle ein Spring-Boot-Projekt für Customer Order Management.
Nutze Java 25, Maven, PostgreSQL, Flyway, Validation, Actuator.
Implementiere noch keine Features. Richte nur Struktur, Docker Compose, README und Health Endpoint ein.

Phase 2: Customer Modul

Implementiere Customer CRUD mit Tests und Flyway Migration.
Nutze die bestehende Projektstruktur.
Erstelle zuerst einen Plan.

Phase 3: Order Modul

Implementiere Order CRUD mit Beziehung zu Customer.
Achte auf Transaktionen, Validierung und sinnvolle Fehlercodes.

Phase 4: Frontend

Erstelle ein React/Vite/TypeScript Frontend.
Baue Customer List, Customer Details und Order List.
Nutze vorhandenen API Client und Loading/Error States.

Phase 5: Integration

Verbinde Frontend und Backend über OpenAPI-generierte Typen.
Prüfe alle API Calls und erstelle eine kurze Dokumentation.

Phase 6: Qualität

Analysiere das Projekt auf Testlücken, Security-Risiken, fehlende Fehlerbehandlung und schlechte Architekturgrenzen.
Erstelle zuerst eine Liste, ändere noch nichts.

16. Beispielprojekt: Customer Order Portal

Dieses Beispielprojekt ist ein kompletter, realistischer Startpunkt für ein Java-Enterprise-Projekt mit Frontend. Du kannst es als Portfolio-Projekt, Lernprojekt oder als Template für echte Kundenprojekte verwenden.

Projektidee: Ein internes Portal, in dem Mitarbeitende Kunden verwalten und Bestellungen anlegen. Das Backend liefert eine REST API, das Frontend konsumiert diese API, und PostgreSQL speichert die Daten.

16.1 Ziel und Lernnutzen

Erklärung: Warum dieses Beispielprojekt gut zum Lernen ist

Das Customer Order Portal ist ein ideales erstes Enterprise-Beispiel, weil es typische Bausteine enthält, ohne fachlich zu kompliziert zu werden. Kunden, Bestellungen, Produkte und Statuswerte kommen in fast jedem Unternehmenssystem vor. Dadurch lernst du nicht nur Syntax, sondern echte Muster: Datenmodellierung, REST-API, Validierung, Fehlerbehandlung, Frontend-Listen, Formulare und Tests.

Du solltest dieses Projekt nicht als „fertige Produktionssoftware“ sehen, sondern als Trainingsbasis. Erst wird ein kleiner vertikaler Schnitt gebaut: Datenbanktabelle, Entity, DTO, Service, Controller, API-Client und UI-Liste. Danach kannst du denselben Schnitt für neue Module wiederholen. Genau diese Wiederholung ist wertvoll, weil Enterprise-Software oft aus vielen ähnlichen, aber fachlich unterschiedlichen Modulen besteht.

Lernpunkt Warum er wichtig ist
Monorepo-Struktur Backend und Frontend bleiben gemeinsam dokumentiert und versioniert.
Flyway-Migrationen Datenbankschema ist reproduzierbar und teamfähig.
DTOs statt Entities Du kontrollierst, welche Daten nach außen gehen.
Service-Schicht Fachlogik bleibt unabhängig vom HTTP-Controller.
API-Client im Frontend Frontend-Komponenten müssen nicht wissen, wie URLs genau gebaut werden.
Tests Änderungen können später sicherer refaktoriert werden.

Typische Erweiterung: Baue zuerst Kunden, dann Bestellungen, dann Produkte. Danach erst Authentifizierung, Rollen und Reporting. So bleibt die Komplexität kontrollierbar.

Nach diesem Projekt kannst du:

  • ein Spring-Boot-Backend sauber initialisieren,
  • eine typische Enterprise-Struktur mit Controller, Service, Repository, DTOs und Migrationen aufbauen,
  • ein React/Vite-Frontend oder alternativ ein Angular-Frontend starten,
  • Backend und Frontend über REST verbinden,
  • lokale Infrastruktur mit Docker Compose starten,
  • Tests ergänzen,
  • Codex oder Claude Code gezielt für Analyse, Implementierung, Tests und Refactoring einsetzen.

16.2 Fachlicher Scope

Version 1: Minimal lauffähig

  • Kunden anlegen
  • Kunden auflisten
  • Kunden-Details anzeigen
  • Bestellung für Kunden anlegen
  • Bestellungen auflisten
  • Gesundheitscheck über Actuator

Version 2: Enterprise-Ausbau

  • Login/Auth mit Rollen
  • Status-Workflow für Orders
  • Suche, Filter, Pagination
  • OpenAPI-Dokumentation
  • Audit-Felder
  • Testcontainers
  • CI Pipeline
  • Dockerfile für Backend und Frontend

16.3 Technologie-Stack

Bereich Empfehlung Warum
Backend Java 21 oder 25 + Spring Boot Enterprise-Standard, viele Integrationen, schnelle API-Entwicklung
Build Maven einfach, gut dokumentiert, sehr verbreitet
Datenbank PostgreSQL produktionsnaher SQL-Standard
Migrationen Flyway Datenbankschema versionieren
API REST + DTOs klarer Einstieg, leicht testbar
Frontend 1 React + Vite + TypeScript schnell, flexibel, sehr beliebt
Frontend 2 Angular + TypeScript starkes Enterprise-Framework mit klarer Struktur
Dev Services Docker Compose lokale Datenbank reproduzierbar starten
AI Tools Codex + Claude Code Boilerplate, Tests, Refactoring und Codebase-Analyse beschleunigen

16.4 Repository-Struktur

Empfohlen ist ein Monorepo mit getrenntem Backend und Frontend:

customer-order-portal/
  _intern/sources/_intern\sources\README.md
  docs/
    architecture.md
    api.md
    prompts.md
  backend/
    pom.xml
    compose.yaml
    src/
      main/
        java/com/example/portal/
          CustomerOrderPortalApplication.java
          common/
            error/
              ApiError.java
              GlobalExceptionHandler.java
            validation/
          customer/
            Customer.java
            CustomerStatus.java
            CustomerRepository.java
            CustomerService.java
            CustomerController.java
            dto/
              CustomerCreateRequest.java
              CustomerResponse.java
          order/
            Order.java
            OrderStatus.java
            OrderRepository.java
            OrderService.java
            OrderController.java
            dto/
              OrderCreateRequest.java
              OrderResponse.java
        resources/
          application.yml
          db/migration/
            V1__init.sql
      test/
        java/com/example/portal/
          customer/
          order/
  frontend-react/
    package.json
    vite.config.ts
    src/
      main.tsx
      App.tsx
      api/
        client.ts
        customers.ts
        orders.ts
      features/
        customers/
          CustomerList.tsx
          CustomerDetail.tsx
          CustomerCreateForm.tsx
        orders/
          OrderList.tsx
          OrderCreateForm.tsx
      shared/
        Loading.tsx
        ErrorMessage.tsx
  frontend-angular/
    angular.json
    package.json
    src/app/
      core/api/
      features/customers/
      features/orders/

16.5 Projekt initialisieren

Variante A: Backend über Spring Initializr erzeugen

Öffne Spring Initializr im Browser oder nutze curl.

mkdir customer-order-portal
cd customer-order-portal
mkdir docs
curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.5.0 \
  -d baseDir=backend \
  -d groupId=com.example \
  -d artifactId=customer-order-portal \
  -d name=customer-order-portal \
  -d packageName=com.example.portal \
  -d javaVersion=21 \
  -d dependencies=web,validation,data-jpa,postgresql,flyway,actuator,docker-compose \
  -o backend.zip
unzip backend.zip
rm backend.zip

Hinweis: Passe bootVersion bei Bedarf an die aktuell stabile Version an. Für Enterprise-Teams ist wichtiger, dass alle Teammitglieder dieselbe Version verwenden und sie im Repository dokumentiert ist.

Variante B: React/Vite-Frontend erzeugen

cd customer-order-portal
npm create vite@latest frontend-react -- --template react-ts
cd frontend-react
npm install
npm run dev

Variante C: Angular-Frontend erzeugen

cd customer-order-portal
npm install -g @angular/cli
ng new frontend-angular --routing --style=scss
cd frontend-angular
ng serve

16.6 Backend: compose.yaml

Lege diese Datei in backend/compose.yaml an:

services:
  postgres:
    image: postgres:16
    container_name: customer-order-postgres
    environment:
      POSTGRES_DB: customer_order
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d customer_order"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres-data:

Start:

cd backend
docker compose up -d
./mvnw spring-boot:run

16.7 Backend: application.yml

spring:
  application:
    name: customer-order-portal
  datasource:
    url: jdbc:postgresql://localhost:5432/customer_order
    username: app
    password: app
  jpa:
    hibernate:
      ddl-auto: validate
    open-in-view: false
    properties:
      hibernate:
        format_sql: true
  flyway:
    enabled: true
  docker:
    compose:
      lifecycle-management: start-only

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

logging:
  level:
    org.hibernate.SQL: info

16.8 Backend: Datenbank-Migration

Datei: backend/src/main/resources/db/migration/V1__init.sql

create table customers (
  id uuid primary key,
  name varchar(160) not null,
  email varchar(200) not null unique,
  status varchar(40) not null,
  created_at timestamp with time zone not null
);

create table orders (
  id uuid primary key,
  customer_id uuid not null references customers(id),
  title varchar(200) not null,
  amount numeric(12, 2) not null,
  status varchar(40) not null,
  created_at timestamp with time zone not null
);

create index idx_orders_customer_id on orders(customer_id);
create index idx_orders_status on orders(status);

16.9 Backend: Domain-Modell Customer

Datei: backend/src/main/java/com/example/portal/customer/CustomerStatus.java

package com.example.portal.customer;

public enum CustomerStatus {
  ACTIVE,
  INACTIVE
}

Datei: backend/src/main/java/com/example/portal/customer/Customer.java

package com.example.portal.customer;

import jakarta.persistence.*;
import java.time.OffsetDateTime;
import java.util.UUID;

@Entity
@Table(name = "customers")
public class Customer {

  @Id
  private UUID id;

  @Column(nullable = false, length = 160)
  private String name;

  @Column(nullable = false, unique = true, length = 200)
  private String email;

  @Enumerated(EnumType.STRING)
  @Column(nullable = false, length = 40)
  private CustomerStatus status;

  @Column(nullable = false)
  private OffsetDateTime createdAt;

  protected Customer() {
  }

  public Customer(String name, String email) {
    this.id = UUID.randomUUID();
    this.name = name;
    this.email = email;
    this.status = CustomerStatus.ACTIVE;
    this.createdAt = OffsetDateTime.now();
  }

  public UUID getId() { return id; }
  public String getName() { return name; }
  public String getEmail() { return email; }
  public CustomerStatus getStatus() { return status; }
  public OffsetDateTime getCreatedAt() { return createdAt; }

  public void deactivate() {
    this.status = CustomerStatus.INACTIVE;
  }
}

16.10 Backend: DTOs

Datei: CustomerCreateRequest.java

package com.example.portal.customer.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CustomerCreateRequest(
  @NotBlank @Size(max = 160) String name,
  @NotBlank @Email @Size(max = 200) String email
) {}

Datei: CustomerResponse.java

package com.example.portal.customer.dto;

import com.example.portal.customer.CustomerStatus;
import java.time.OffsetDateTime;
import java.util.UUID;

public record CustomerResponse(
  UUID id,
  String name,
  String email,
  CustomerStatus status,
  OffsetDateTime createdAt
) {}

16.11 Backend: Repository, Service, Controller

Datei: CustomerRepository.java

package com.example.portal.customer;

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;

public interface CustomerRepository extends JpaRepository<Customer, UUID> {
  Optional<Customer> findByEmail(String email);
  boolean existsByEmail(String email);
}

Datei: CustomerService.java

package com.example.portal.customer;

import com.example.portal.customer.dto.CustomerCreateRequest;
import com.example.portal.customer.dto.CustomerResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

@Service
public class CustomerService {

  private final CustomerRepository repository;

  public CustomerService(CustomerRepository repository) {
    this.repository = repository;
  }

  @Transactional
  public CustomerResponse create(CustomerCreateRequest request) {
    if (repository.existsByEmail(request.email())) {
      throw new IllegalArgumentException("Email already exists");
    }
    Customer customer = new Customer(request.name(), request.email());
    return toResponse(repository.save(customer));
  }

  @Transactional(readOnly = true)
  public List<CustomerResponse> findAll() {
    return repository.findAll().stream().map(this::toResponse).toList();
  }

  @Transactional(readOnly = true)
  public CustomerResponse findById(UUID id) {
    return repository.findById(id)
      .map(this::toResponse)
      .orElseThrow(() -> new IllegalArgumentException("Customer not found"));
  }

  private CustomerResponse toResponse(Customer customer) {
    return new CustomerResponse(
      customer.getId(),
      customer.getName(),
      customer.getEmail(),
      customer.getStatus(),
      customer.getCreatedAt()
    );
  }
}

Datei: CustomerController.java

package com.example.portal.customer;

import com.example.portal.customer.dto.CustomerCreateRequest;
import com.example.portal.customer.dto.CustomerResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

  private final CustomerService service;

  public CustomerController(CustomerService service) {
    this.service = service;
  }

  @PostMapping
  @ResponseStatus(HttpStatus.CREATED)
  public CustomerResponse create(@Valid @RequestBody CustomerCreateRequest request) {
    return service.create(request);
  }

  @GetMapping
  public List<CustomerResponse> findAll() {
    return service.findAll();
  }

  @GetMapping("/{id}")
  public CustomerResponse findById(@PathVariable UUID id) {
    return service.findById(id);
  }
}

16.12 Backend: Fehlerbehandlung

Datei: common/error/ApiError.java

package com.example.portal.common.error;

import java.time.OffsetDateTime;

public record ApiError(
  OffsetDateTime timestamp,
  int status,
  String error,
  String message,
  String path
) {}

Datei: common/error/GlobalExceptionHandler.java

package com.example.portal.common.error;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import java.time.OffsetDateTime;

@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(IllegalArgumentException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  ApiError handleIllegalArgument(IllegalArgumentException ex, HttpServletRequest request) {
    return new ApiError(
      OffsetDateTime.now(),
      400,
      "Bad Request",
      ex.getMessage(),
      request.getRequestURI()
    );
  }

  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  ApiError handleValidation(MethodArgumentNotValidException ex, HttpServletRequest request) {
    String message = ex.getBindingResult().getFieldErrors().stream()
      .findFirst()
      .map(error -> error.getField() + " " + error.getDefaultMessage())
      .orElse("Validation failed");

    return new ApiError(
      OffsetDateTime.now(),
      400,
      "Validation Error",
      message,
      request.getRequestURI()
    );
  }
}

16.13 Backend: Order-Modul als Agenten-Aufgabe

Implementiere das Order-Modul bewusst mit Codex oder Claude Code, statt alles manuell zu schreiben. Dadurch trainierst du agentisches Arbeiten.

Du bist Senior Java/Spring-Boot-Entwickler.
Implementiere ein Order-Modul passend zur bestehenden Customer-Struktur.

Fachliche Anforderungen:
- Entity Order mit id, customer, title, amount, status, createdAt
- Status: DRAFT, CONFIRMED, CANCELLED
- POST /api/orders
- GET /api/orders
- GET /api/orders/{id}
- GET /api/customers/{customerId}/orders
- OrderCreateRequest mit customerId, title, amount
- amount muss positiv sein
- title darf nicht leer sein

Technische Anforderungen:
- JPA Beziehung ManyToOne zu Customer
- Flyway Migration für orders ist bereits vorhanden, prüfe sie
- DTOs statt Entities in der API
- Service mit Transaktionen
- Controller Tests oder Integration Tests
- bestehende Fehlerbehandlung nutzen

Regeln:
- Erstelle zuerst einen Plan
- Ändere keine Architektur ohne Begründung
- Führe Tests aus
- Zeige am Ende geänderte Dateien und offene Risiken

16.14 Backend lokal testen

cd backend
docker compose up -d
./mvnw test
./mvnw spring-boot:run

Health Check:

curl http://localhost:8080/actuator/health

Kunden anlegen:

curl -X POST http://localhost:8080/api/customers \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada Lovelace","email":"ada@example.com"}'

Kunden abrufen:

curl http://localhost:8080/api/customers

16.15 React/Vite-Frontend: API Client

Datei: frontend-react/.env

VITE_API_BASE_URL=http://localhost:8080

Datei: frontend-react/src/api/client.ts

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";

export async function apiRequest<T>(path: string, options: RequestInit = {}): Promise<T> {
  const response = await fetch(`${API_BASE_URL}${path}`, {
    headers: {
      "Content-Type": "application/json",
      ...(options.headers ?? {}),
    },
    ...options,
  });

  if (!response.ok) {
    const errorBody = await response.json().catch(() => null);
    throw new Error(errorBody?.message ?? `Request failed with ${response.status}`);
  }

  return response.json() as Promise<T>;
}

Datei: frontend-react/src/api/customers.ts

import { apiRequest } from "./client";

export type CustomerStatus = "ACTIVE" | "INACTIVE";

export type Customer = {
  id: string;
  name: string;
  email: string;
  status: CustomerStatus;
  createdAt: string;
};

export type CustomerCreateRequest = {
  name: string;
  email: string;
};

export function getCustomers() {
  return apiRequest<Customer[]>("/api/customers");
}

export function createCustomer(data: CustomerCreateRequest) {
  return apiRequest<Customer>("/api/customers", {
    method: "POST",
    body: JSON.stringify(data),
  });
}

16.16 React/Vite-Frontend: Customer List

Datei: frontend-react/src/features/customers/CustomerList.tsx

import { useEffect, useState } from "react";
import { Customer, getCustomers } from "../../api/customers";

export function CustomerList() {
  const [customers, setCustomers] = useState<Customer[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    getCustomers()
      .then(setCustomers)
      .catch((err: unknown) => setError(err instanceof Error ? err.message : "Unknown error"))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <p>Lade Kunden...</p>;
  if (error) return <p role="alert">Fehler: {error}</p>;
  if (customers.length === 0) return <p>Keine Kunden vorhanden.</p>;

  return (
    <section>
      <h2>Kunden</h2>
      <ul>
        {customers.map((customer) => (
          <li key={customer.id}>
            <strong>{customer.name}</strong> — {customer.email} — {customer.status}
          </li>
        ))}
      </ul>
    </section>
  );
}

Datei: frontend-react/src/App.tsx

import { CustomerList } from "./features/customers/CustomerList";

export default function App() {
  return (
    <main style={{ maxWidth: 960, margin: "40px auto", fontFamily: "system-ui" }}>
      <h1>Customer Order Portal</h1>
      <CustomerList />
    </main>
  );
}

Start:

cd frontend-react
npm run dev

16.17 Angular-Alternative: API Service

Falls du Angular statt React nutzen willst, starte so:

cd frontend-angular
ng generate interface core/api/customer
ng generate service core/api/customers
ng generate component features/customers/customer-list

Datei: frontend-angular/src/app/core/api/customer.ts

export type CustomerStatus = 'ACTIVE' | 'INACTIVE';

export interface Customer {
  id: string;
  name: string;
  email: string;
  status: CustomerStatus;
  createdAt: string;
}

export interface CustomerCreateRequest {
  name: string;
  email: string;
}

Datei: frontend-angular/src/app/core/api/customers.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Customer, CustomerCreateRequest } from './customer';

@Injectable({ providedIn: 'root' })
export class CustomersService {
  private readonly baseUrl = 'http://localhost:8080/api/customers';

  constructor(private readonly http: HttpClient) {}

  getCustomers(): Observable<Customer[]> {
    return this.http.get<Customer[]>(this.baseUrl);
  }

  createCustomer(request: CustomerCreateRequest): Observable<Customer> {
    return this.http.post<Customer>(this.baseUrl, request);
  }
}

16.18 CORS für lokale Entwicklung

Für lokale Entwicklung kann das Frontend auf Port 5173 oder 4200 laufen, während das Backend auf 8080 läuft. Ergänze im Backend eine kontrollierte CORS-Konfiguration.

Datei: backend/src/main/java/com/example/portal/common/WebConfig.java

package com.example.portal.common;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**")
      .allowedOrigins("http://localhost:5173", "http://localhost:4200")
      .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
      .allowedHeaders("*");
  }
}

Für Produktion solltest du CORS nicht pauschal öffnen. Erlaube nur konkrete Domains deiner Anwendung.

16.19 Test-Beispiel für Backend

Datei: backend/src/test/java/com/example/portal/customer/CustomerControllerTest.java

package com.example.portal.customer;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class CustomerControllerTest {

  @Autowired
  MockMvc mockMvc;

  @Test
  void createsCustomer() throws Exception {
    mockMvc.perform(post("/api/customers")
        .contentType(MediaType.APPLICATION_JSON)
        .content("""
          {"name":"Grace Hopper","email":"grace@example.com"}
          """))
      .andExpect(status().isCreated())
      .andExpect(jsonPath("$.name").value("Grace Hopper"))
      .andExpect(jsonPath("$.email").value("grace@example.com"));
  }
}

16.20 README Vorlage

Datei: _intern/sources/_intern\sources\README.md

# Customer Order Portal

Fullstack-Beispielprojekt mit Java/Spring Boot Backend und React/Vite oder Angular Frontend.

## Voraussetzungen

- Java 21 oder 25
- Maven Wrapper
- Node.js LTS
- Docker oder Podman

## Backend starten

```bash
cd backend
docker compose up -d
./mvnw spring-boot:run
```

Backend läuft auf: http://localhost:8080

## React Frontend starten

```bash
cd frontend-react
npm install
npm run dev
```

React läuft auf: http://localhost:5173

## Angular Frontend starten

```bash
cd frontend-angular
npm install
ng serve
```

Angular läuft auf: http://localhost:4200

## Wichtige Endpunkte

- GET /actuator/health
- POST /api/customers
- GET /api/customers
- GET /api/customers/{id}
- POST /api/orders
- GET /api/orders

## Tests

```bash
cd backend
./mvnw test
```

16.21 Architekturdiagramm als Mermaid

Datei: docs/architecture.md

flowchart LR
  Browser[Browser]
  React[React/Vite Frontend]
  Angular[Angular Frontend]
  API[Spring Boot REST API]
  Service[Service Layer]
  Repo[Spring Data JPA]
  DB[(PostgreSQL)]

  Browser --> React
  Browser --> Angular
  React --> API
  Angular --> API
  API --> Service
  Service --> Repo
  Repo --> DB

16.22 Codex/Claude Code Aufgabenplan für dieses Projekt

Nutze diese Reihenfolge, damit der Agent nicht zu viel auf einmal verändert.

Aufgabe 1: Projektanalyse

Analysiere dieses Repository.
Erkläre Struktur, Startbefehle, Datenfluss, Risiken und fehlende Teile.
Ändere keine Dateien.

Aufgabe 2: Backend Order-Modul

Implementiere das Order-Modul gemäß docs/api.md.
Erstelle zuerst einen Plan.
Danach implementiere Entity, Repository, Service, Controller, DTOs, Migration und Tests.
Führe ./mvnw test aus.

Aufgabe 3: Frontend Create Form

Ergänze im React-Frontend ein CustomerCreateForm.
Anforderungen:
- name und email Eingaben
- clientseitige einfache Validierung
- POST /api/customers nutzen
- nach Erfolg Liste aktualisieren
- Loading und Error State anzeigen
- keine externe Form-Library hinzufügen

Aufgabe 4: Refactoring

Prüfe das Backend auf Duplikate, schlechte Fehlerbehandlung und fehlende Tests.
Gib zuerst eine priorisierte Liste.
Ändere erst nach meiner Freigabe Dateien.

Aufgabe 5: Dokumentation

Aktualisiere _intern/sources/_intern\sources\README.md und docs/architecture.md.
Beschreibe Startbefehle, API-Endpunkte, Architektur, Tests und bekannte Einschränkungen.
Keine Implementierungsdateien ändern.

16.23 Abnahmekriterien

Das Beispielprojekt gilt als fertig, wenn diese Punkte erfüllt sind:

16.24 Erweiterungen für echte Enterprise-Projekte

Wenn die Basis läuft, erweitere in dieser Reihenfolge:

  1. OpenAPI: API dokumentieren und Frontend-Typen generieren.
  2. Security: Spring Security, JWT/OAuth2 oder Keycloak integrieren.
  3. Pagination: Listen-Endpunkte nicht unbegrenzt liefern.
  4. Testcontainers: echte PostgreSQL-Integrationstests statt Mock-Datenbank.
  5. CI: GitHub Actions oder GitLab CI für Build, Test und Lint.
  6. Observability: strukturierte Logs, Metrics, Tracing.
  7. Docker Images: Backend und Frontend containerisieren.
  8. Deployment: zuerst Docker Compose, später Kubernetes oder Cloud Runtime.

17. Beispielprojekt: Erweiterter HTML Manager

Dieses Beispiel ist ein ausgebauter HTML-/Content-Manager für interne Enterprise-Anwendungen. Die Idee: Fachbereiche können HTML-Bausteine, Landingpages, E-Mail-Templates oder Dokumentationsseiten pflegen, versionieren, prüfen und veröffentlichen, ohne dass jedes Mal ein Deployment nötig ist.

Der HTML Manager ist absichtlich praxisnah, weil er typische Enterprise-Themen kombiniert: Rollen, Freigabeprozess, Versionierung, Preview, XSS-Sicherheit, Audit-Log, Validierung, API, Frontend-Editor und Deployment.

17.1 Ziel des Projekts

Erklärung: Warum ein HTML Manager anspruchsvoller ist als er aussieht

Ein HTML Manager wirkt auf den ersten Blick wie ein einfacher Editor. In Wirklichkeit berührt er viele Enterprise-Themen: Rollen, Versionierung, Freigaben, sichere Vorschau, XSS-Schutz, Änderungsverlauf und Publishing. Besonders wichtig ist, dass gespeicherter HTML-Code niemals ungeprüft im Admin-Bereich oder auf öffentlichen Seiten ausgegeben wird. Sobald Benutzer HTML speichern dürfen, musst du klar definieren, welche Tags und Attribute erlaubt sind.

Das Projekt trainiert deshalb eine andere Art von Denken als ein normales CRUD-Modul. Du speicherst nicht nur Daten, sondern potenziell ausführbaren Inhalt. Genau deshalb gehören Sanitizing, Preview-Sandbox, Rechteprüfung und Audit-Log zum Kern des Designs.

Risiko Erklärung Schutzmaßnahme
XSS Benutzer speichert schädliches Script im HTML Allowlist, Sanitizing, CSP, Sandbox-Iframe
Falsche Veröffentlichung Entwurf geht ohne Review live Statusmodell Draft → Review → Published
Unklare Änderungen Niemand weiß, wer was geändert hat Versionierung und Audit-Log
Rechte-Missbrauch Redakteur ändert Systembereiche Rollen und Berechtigungen pro Aktion
Kaputte Seiten Ungültiges HTML beschädigt Layout Preview, Validierung, Rollback

Merksatz: Content-Management ist nicht nur Editor-UI. Es ist ein kontrollierter Veröffentlichungsprozess.

Bereich Beschreibung
Fachlicher Zweck HTML-Seiten und Templates erstellen, bearbeiten, prüfen und veröffentlichen
Backend Spring Boot, REST API, PostgreSQL, Flyway, Validation, Security, Audit
Frontend React/Vite oder Angular, Editor, Live Preview, Version History, Publish Flow
AI-Agenten-Fokus Codex/Claude Code erstellen Module, Tests, Refactorings und Security Checks

17.2 Rollen und Berechtigungen

Rolle Rechte
AUTHOR Entwürfe erstellen und bearbeiten
REVIEWER Entwürfe prüfen, Kommentare schreiben, ablehnen
PUBLISHER geprüfte Versionen veröffentlichen
ADMIN Kategorien, Rollen und globale Einstellungen verwalten

17.3 Hauptfunktionen

  1. HTML-Seite erstellen.
  2. Slug, Titel, Sprache, Kategorie und Status verwalten.
  3. HTML-Inhalt speichern und versionieren.
  4. Live Preview in sicherem Sandbox-Iframe anzeigen.
  5. HTML serverseitig bereinigen oder blockieren.
  6. Statusfluss: DRAFT -> IN_REVIEW -> APPROVED -> PUBLISHED.
  7. Audit-Log für Änderungen, Freigaben und Veröffentlichungen.
  8. Suche nach Titel, Slug, Kategorie und Status.
  9. Rollback auf ältere Version.
  10. Export als HTML-Datei oder JSON.

17.4 Empfohlener Stack

Schicht Option A Option B
Backend Spring Boot REST Quarkus REST
Datenbank PostgreSQL MariaDB
Migration Flyway Liquibase
Frontend React + Vite Angular
Editor <textarea> für Start Monaco Editor später
Preview Sandbox-Iframe getrennte Preview-Route
Sicherheit Spring Security + Rollen Gateway + JWT
Tests JUnit, Testcontainers, Playwright JUnit, Cypress

17.5 Projektstruktur

html-manager/
├─ backend/
│  ├─ src/main/java/com/example/htmlmanager/
│  │  ├─ HtmlManagerApplication.java
│  │  ├─ pages/
│  │  │  ├─ HtmlPage.java
│  │  │  ├─ HtmlPageStatus.java
│  │  │  ├─ HtmlPageRepository.java
│  │  │  ├─ HtmlPageService.java
│  │  │  ├─ HtmlPageController.java
│  │  │  ├─ dto/
│  │  │  │  ├─ CreateHtmlPageRequest.java
│  │  │  │  ├─ UpdateHtmlPageRequest.java
│  │  │  │  └─ HtmlPageResponse.java
│  │  ├─ versions/
│  │  │  ├─ HtmlPageVersion.java
│  │  │  └─ HtmlPageVersionRepository.java
│  │  ├─ audit/
│  │  │  ├─ AuditEvent.java
│  │  │  └─ AuditService.java
│  │  └─ security/
│  │     └─ SecurityConfig.java
│  └─ src/main/resources/db/migration/
│     ├─ V1__create_html_pages.sql
│     └─ V2__create_html_page_versions.sql
├─ frontend-react/
│  ├─ src/api/htmlPagesApi.ts
│  ├─ src/features/html-pages/HtmlPageList.tsx
│  ├─ src/features/html-pages/HtmlPageEditor.tsx
│  ├─ src/features/html-pages/HtmlPreview.tsx
│  └─ src/features/html-pages/VersionHistory.tsx
└─ docker-compose.yml

17.6 Datenmodell

CREATE TABLE html_pages (
  id UUID PRIMARY KEY,
  slug VARCHAR(160) NOT NULL UNIQUE,
  title VARCHAR(240) NOT NULL,
  category VARCHAR(120) NOT NULL,
  language VARCHAR(12) NOT NULL DEFAULT 'de',
  status VARCHAR(40) NOT NULL,
  current_version INTEGER NOT NULL DEFAULT 1,
  created_by VARCHAR(160) NOT NULL,
  created_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL
);

CREATE TABLE html_page_versions (
  id UUID PRIMARY KEY,
  page_id UUID NOT NULL REFERENCES html_pages(id),
  version INTEGER NOT NULL,
  html_content TEXT NOT NULL,
  plain_text_summary TEXT,
  change_note VARCHAR(500),
  created_by VARCHAR(160) NOT NULL,
  created_at TIMESTAMP NOT NULL,
  UNIQUE(page_id, version)
);

CREATE TABLE html_page_audit_events (
  id UUID PRIMARY KEY,
  page_id UUID NOT NULL REFERENCES html_pages(id),
  action VARCHAR(80) NOT NULL,
  actor VARCHAR(160) NOT NULL,
  message VARCHAR(800),
  created_at TIMESTAMP NOT NULL
);

17.7 Backend: Status-Enum

package com.example.htmlmanager.pages;

public enum HtmlPageStatus {
    DRAFT,
    IN_REVIEW,
    APPROVED,
    PUBLISHED,
    ARCHIVED
}

17.8 Backend: Entity

package com.example.htmlmanager.pages;

import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;

@Entity
@Table(name = "html_pages")
public class HtmlPage {

    @Id
    private UUID id;

    @Column(nullable = false, unique = true, length = 160)
    private String slug;

    @Column(nullable = false, length = 240)
    private String title;

    @Column(nullable = false, length = 120)
    private String category;

    @Column(nullable = false, length = 12)
    private String language;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 40)
    private HtmlPageStatus status;

    @Column(nullable = false)
    private Integer currentVersion;

    @Column(nullable = false, length = 160)
    private String createdBy;

    @Column(nullable = false)
    private Instant createdAt;

    @Column(nullable = false)
    private Instant updatedAt;

    protected HtmlPage() {}

    public HtmlPage(String slug, String title, String category, String language, String createdBy) {
        this.id = UUID.randomUUID();
        this.slug = slug;
        this.title = title;
        this.category = category;
        this.language = language;
        this.status = HtmlPageStatus.DRAFT;
        this.currentVersion = 1;
        this.createdBy = createdBy;
        this.createdAt = Instant.now();
        this.updatedAt = Instant.now();
    }

    public void submitForReview() {
        if (status != HtmlPageStatus.DRAFT) {
            throw new IllegalStateException("Nur Entwürfe können zur Prüfung eingereicht werden.");
        }
        status = HtmlPageStatus.IN_REVIEW;
        updatedAt = Instant.now();
    }

    public void approve() {
        if (status != HtmlPageStatus.IN_REVIEW) {
            throw new IllegalStateException("Nur geprüfte Entwürfe können freigegeben werden.");
        }
        status = HtmlPageStatus.APPROVED;
        updatedAt = Instant.now();
    }

    public void publish() {
        if (status != HtmlPageStatus.APPROVED) {
            throw new IllegalStateException("Nur freigegebene Seiten können veröffentlicht werden.");
        }
        status = HtmlPageStatus.PUBLISHED;
        updatedAt = Instant.now();
    }

    public UUID getId() { return id; }
    public String getSlug() { return slug; }
    public String getTitle() { return title; }
    public String getCategory() { return category; }
    public String getLanguage() { return language; }
    public HtmlPageStatus getStatus() { return status; }
    public Integer getCurrentVersion() { return currentVersion; }
    public Instant getCreatedAt() { return createdAt; }
    public Instant getUpdatedAt() { return updatedAt; }
}

17.9 Backend: DTOs

package com.example.htmlmanager.pages.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record CreateHtmlPageRequest(
    @NotBlank @Pattern(regexp = "^[a-z0-9-]+$") @Size(max = 160)
    String slug,

    @NotBlank @Size(max = 240)
    String title,

    @NotBlank @Size(max = 120)
    String category,

    @NotBlank @Size(max = 12)
    String language,

    @NotBlank @Size(max = 200_000)
    String htmlContent,

    @Size(max = 500)
    String changeNote
) {}
package com.example.htmlmanager.pages.dto;

import java.time.Instant;
import java.util.UUID;

public record HtmlPageResponse(
    UUID id,
    String slug,
    String title,
    String category,
    String language,
    String status,
    Integer currentVersion,
    Instant updatedAt
) {}

17.10 Backend: Repository

package com.example.htmlmanager.pages;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.UUID;

public interface HtmlPageRepository extends JpaRepository<HtmlPage, UUID> {
    Optional<HtmlPage> findBySlug(String slug);
    Page<HtmlPage> findByTitleContainingIgnoreCaseOrSlugContainingIgnoreCase(
        String title,
        String slug,
        Pageable pageable
    );
}

17.11 Backend: Service mit Sicherheitsgrenzen

Für den Start kann der Service HTML zunächst nur grob blockieren. In echten Projekten brauchst du eine geprüfte HTML-Sanitizing-Strategie, serverseitige Validierung, CSP und Security Review.

package com.example.htmlmanager.pages;

import com.example.htmlmanager.pages.dto.CreateHtmlPageRequest;
import com.example.htmlmanager.pages.dto.HtmlPageResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class HtmlPageService {

    private final HtmlPageRepository repository;

    public HtmlPageService(HtmlPageRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public HtmlPageResponse create(CreateHtmlPageRequest request, String actor) {
        assertHtmlIsAcceptable(request.htmlContent());

        repository.findBySlug(request.slug()).ifPresent(existing -> {
            throw new IllegalArgumentException("Slug ist bereits vergeben: " + request.slug());
        });

        HtmlPage page = new HtmlPage(
            request.slug(),
            request.title(),
            request.category(),
            request.language(),
            actor
        );

        HtmlPage saved = repository.save(page);
        return toResponse(saved);
    }

    @Transactional
    public HtmlPageResponse submitForReview(java.util.UUID id) {
        HtmlPage page = repository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("HTML-Seite nicht gefunden"));
        page.submitForReview();
        return toResponse(page);
    }

    private void assertHtmlIsAcceptable(String html) {
        String lower = html.toLowerCase();
        if (lower.contains("<script") || lower.contains("javascript:")) {
            throw new IllegalArgumentException("Unsicheres HTML wurde blockiert.");
        }
    }

    private HtmlPageResponse toResponse(HtmlPage page) {
        return new HtmlPageResponse(
            page.getId(),
            page.getSlug(),
            page.getTitle(),
            page.getCategory(),
            page.getLanguage(),
            page.getStatus().name(),
            page.getCurrentVersion(),
            page.getUpdatedAt()
        );
    }
}

17.12 Backend: REST Controller

package com.example.htmlmanager.pages;

import com.example.htmlmanager.pages.dto.CreateHtmlPageRequest;
import com.example.htmlmanager.pages.dto.HtmlPageResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.UUID;

@RestController
@RequestMapping("/api/html-pages")
public class HtmlPageController {

    private final HtmlPageService service;

    public HtmlPageController(HtmlPageService service) {
        this.service = service;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public HtmlPageResponse create(
        @Valid @RequestBody CreateHtmlPageRequest request,
        Principal principal
    ) {
        String actor = principal != null ? principal.getName() : "local-dev";
        return service.create(request, actor);
    }

    @PostMapping("/{id}/submit")
    public HtmlPageResponse submit(@PathVariable UUID id) {
        return service.submitForReview(id);
    }
}

17.13 Frontend: API Client

// src/api/htmlPagesApi.ts
export type HtmlPage = {
  id: string;
  slug: string;
  title: string;
  category: string;
  language: string;
  status: string;
  currentVersion: number;
  updatedAt: string;
};

export type CreateHtmlPageRequest = {
  slug: string;
  title: string;
  category: string;
  language: string;
  htmlContent: string;
  changeNote?: string;
};

const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";

export async function createHtmlPage(payload: CreateHtmlPageRequest): Promise<HtmlPage> {
  const response = await fetch(`${API_BASE}/api/html-pages`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    throw new Error(`HTML-Seite konnte nicht erstellt werden: ${response.status}`);
  }

  return response.json();
}

17.14 Frontend: Editor mit Preview

// src/features/html-pages/HtmlPageEditor.tsx
import { useMemo, useState } from "react";
import { createHtmlPage } from "../../api/htmlPagesApi";
import { HtmlPreview } from "./HtmlPreview";

export function HtmlPageEditor() {
  const [title, setTitle] = useState("Neue Landingpage");
  const [slug, setSlug] = useState("neue-landingpage");
  const [category, setCategory] = useState("Marketing");
  const [htmlContent, setHtmlContent] = useState("<h1>Hallo Enterprise</h1><p>Startinhalt</p>");
  const [message, setMessage] = useState<string | null>(null);

  const canSave = useMemo(() => title.trim() && slug.trim() && htmlContent.trim(), [title, slug, htmlContent]);

  async function handleSave() {
    setMessage(null);
    const created = await createHtmlPage({
      title,
      slug,
      category,
      language: "de",
      htmlContent,
      changeNote: "Initiale Version",
    });
    setMessage(`Gespeichert: ${created.slug}`);
  }

  return (
    <main className="editor-layout">
      <section className="editor-panel">
        <h1>HTML Manager</h1>

        <label>
          Titel
          <input value={title} onChange={(event) => setTitle(event.target.value)} />
        </label>

        <label>
          Slug
          <input value={slug} onChange={(event) => setSlug(event.target.value)} />
        </label>

        <label>
          Kategorie
          <input value={category} onChange={(event) => setCategory(event.target.value)} />
        </label>

        <label>
          HTML Inhalt
          <textarea
            rows={18}
            value={htmlContent}
            onChange={(event) => setHtmlContent(event.target.value)}
          />
        </label>

        <button disabled={!canSave} onClick={handleSave}>Speichern</button>
        {message && <p role="status">{message}</p>}
      </section>

      <section className="preview-panel">
        <h2>Live Preview</h2>
        <HtmlPreview html={htmlContent} />
      </section>
    </main>
  );
}

17.15 Frontend: sichere Preview im Sandbox-Iframe

// src/features/html-pages/HtmlPreview.tsx
export function HtmlPreview({ html }: { html: string }) {
  const document = `
<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>
    body { font-family: system-ui, sans-serif; padding: 24px; line-height: 1.6; }
    img { max-width: 100%; height: auto; }
  </style>
</head>
<body>${html}</body>
</html>`;

  return (
    <iframe
      title="HTML Preview"
      sandbox=""
      srcDoc={document}
      style={{ width: "100%", minHeight: 520, border: "1px solid #ddd", borderRadius: 12 }}
    />
  );
}

17.16 CSS für das Editor-Layout

.editor-layout {
  display: grid;
  grid-template-columns: minmax(360px, 520px) 1fr;
  gap: 24px;
  padding: 24px;
}

.editor-panel,
.preview-panel {
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  padding: 20px;
  background: #fff;
}

label {
  display: grid;
  gap: 6px;
  margin-bottom: 14px;
  font-weight: 700;
}

input,
textarea {
  width: 100%;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  padding: 10px 12px;
  font: inherit;
}

textarea {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

button {
  border: 0;
  border-radius: 999px;
  padding: 10px 16px;
  font-weight: 800;
  cursor: pointer;
}

@media (max-width: 960px) {
  .editor-layout {
    grid-template-columns: 1fr;
  }
}

17.17 Angular-Alternative: Feature-Struktur

src/app/features/html-pages/
├─ html-pages.routes.ts
├─ data-access/
│  └─ html-pages-api.service.ts
├─ ui/
│  ├─ html-preview.component.ts
│  └─ status-badge.component.ts
└─ pages/
   ├─ html-page-list.component.ts
   └─ html-page-editor.component.ts
// src/app/features/html-pages/data-access/html-pages-api.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";

export type HtmlPage = {
  id: string;
  slug: string;
  title: string;
  category: string;
  language: string;
  status: string;
  currentVersion: number;
  updatedAt: string;
};

@Injectable({ providedIn: "root" })
export class HtmlPagesApiService {
  private readonly baseUrl = "http://localhost:8080/api/html-pages";

  constructor(private readonly http: HttpClient) {}

  create(payload: unknown) {
    return this.http.post<HtmlPage>(this.baseUrl, payload);
  }

  submit(id: string) {
    return this.http.post<HtmlPage>(`${this.baseUrl}/${id}/submit`, {});
  }
}

17.18 AI-Aufgaben für Codex oder Claude Code

Analysiere das HTML-Manager-Projekt.
Erstelle einen Plan für ein Versionierungsmodul.
Anforderungen:
- Jede Änderung am HTML-Inhalt erzeugt eine neue Version.
- Versionen sind unveränderlich.
- Es gibt eine Endpoint-Liste für alle Versionen einer Seite.
- Es gibt einen Rollback-Endpunkt.
- Tests mit Service- und Controller-Ebene.
Ändere noch keine Dateien, bevor du den Plan erklärt hast.
Implementiere im Frontend eine Version-History-Seitenleiste.
Regeln:
- React + TypeScript verwenden.
- Ladezustand, Fehlerzustand und Empty State darstellen.
- Beim Klick auf eine Version wird die Preview aktualisiert.
- Keine neue UI Library einführen.
- Danach kurze Diff-Zusammenfassung geben.
Führe ein Security Review für den HTML Manager durch.
Fokus:
- XSS-Risiken
- Preview-Sandbox
- serverseitige Sanitization
- Content Security Policy
- Rollenmodell
- Audit-Log
Erstelle Findings mit Schweregrad und konkretem Fix.
Ändere noch keine Dateien.

17.19 Abnahmekriterien

17.20 Erweiterungen

Erweiterung Nutzen
Asset Manager Bilder, CSS-Dateien und Downloads zentral verwalten
Template Variablen Platzhalter wie {{customerName}} oder {{date}} einsetzen
Mehrsprachigkeit Pro Seite mehrere Sprachversionen pflegen
Preview-URLs Review-Link für Fachbereich generieren
Scheduled Publishing Veröffentlichung zu bestimmtem Zeitpunkt planen
Export Statische HTML-Dateien für CDN oder Intranet erzeugen
Rechte pro Kategorie Marketing, Legal, HR getrennt verwalten
AI Review Agent prüft Barrierefreiheit, Tonalität und defekte Links

18. Beispielprojekt: Ticket & Approval Workflow

Dieses zweite Beispiel ist ein internes Service-Desk- und Freigabeportal. Es passt sehr gut zu Enterprise-Teams, weil fast jede Organisation ähnliche Abläufe hat: Anfrage erstellen, zuständige Person finden, Prüfung durchführen, genehmigen oder ablehnen, Status verfolgen und alles auditierbar dokumentieren.

18.1 Ziel des Projekts

Erklärung: Warum Workflows in Enterprise-Projekten so wichtig sind

Viele Unternehmensprozesse sind keine einfachen „Speichern und fertig“-Abläufe. Ein Ticket, Antrag oder Freigabeprozess durchläuft mehrere Zustände. Genau hier entstehen häufig Fehler: Benutzer überspringen Schritte, Zustände werden falsch gesetzt, Berechtigungen sind unklar oder niemand kann später nachvollziehen, warum eine Entscheidung getroffen wurde.

Das Ticket-&-Approval-Projekt zeigt dir, wie man solche Prozesse sauber modelliert. Der wichtigste Teil ist nicht das Formular, sondern die Statusmaschine. Sie definiert, welche Übergänge erlaubt sind: Ein neues Ticket darf zum Beispiel von OPEN nach IN_REVIEW, aber nicht direkt nach APPROVED, wenn ein Review notwendig ist. Diese Regeln gehören in die Fachlogik, nicht nur in die UI. Die UI darf Buttons ausblenden, aber das Backend muss die Regel wirklich erzwingen.

Konzept Erklärung
Statusmaschine Legt fest, welche Zustandswechsel erlaubt sind.
Audit-Log Dokumentiert, wer wann welche Entscheidung getroffen hat.
Rollen Bestimmt, wer erstellen, prüfen, genehmigen oder ablehnen darf.
Kommentare Erklären Entscheidungen und Rückfragen.
Benachrichtigungen Informieren Beteiligte über notwendige Aktionen.

Praxisregel: Alles, was fachlich wichtig ist, muss im Backend geschützt werden. Das Frontend ist nur die Bedienoberfläche, nicht die Sicherheitsgrenze.

Bereich Beschreibung
Fachlicher Zweck Tickets, Anfragen und Freigaben verwalten
Backend Spring Boot, PostgreSQL, REST, Security, Workflow-Service
Frontend Angular oder React mit Dashboard, Listen, Detailseite und Formularen
Enterprise-Fokus Rollen, Statusmaschine, Audit, SLA, Kommentare, Anhänge
AI-Agenten-Fokus Workflow-Regeln, Tests, UI-Komponenten, Refactoring

18.2 Beispiel-Use-Cases

  1. Mitarbeiter erstellt eine Anfrage, zum Beispiel „VPN-Zugang“, „Software-Freigabe“ oder „Budget-Antrag“.
  2. System weist Anfrage einer Kategorie und einem Team zu.
  3. Reviewer prüft die Anfrage.
  4. Approver genehmigt oder lehnt ab.
  5. Antragsteller sieht Status und Kommentare.
  6. Audit-Log zeigt alle Statuswechsel.
  7. SLA-Anzeige markiert verspätete Tickets.

18.3 Statusmodell

NEW
  -> TRIAGED
  -> IN_PROGRESS
  -> WAITING_FOR_APPROVAL
  -> APPROVED
  -> DONE

Alternative Pfade:
NEW -> REJECTED
WAITING_FOR_APPROVAL -> REJECTED
IN_PROGRESS -> CANCELLED

18.4 Projektstruktur

ticket-approval/
├─ backend/
│  ├─ src/main/java/com/example/workflow/
│  │  ├─ WorkflowApplication.java
│  │  ├─ tickets/
│  │  │  ├─ Ticket.java
│  │  │  ├─ TicketStatus.java
│  │  │  ├─ TicketPriority.java
│  │  │  ├─ TicketRepository.java
│  │  │  ├─ TicketService.java
│  │  │  ├─ TicketController.java
│  │  │  └─ dto/
│  │  ├─ comments/
│  │  ├─ approvals/
│  │  ├─ audit/
│  │  └─ security/
│  ├─ src/main/resources/db/migration/
│  └─ src/test/java/com/example/workflow/
├─ frontend-angular/
│  ├─ src/app/features/tickets/
│  ├─ src/app/features/dashboard/
│  └─ src/app/shared/
└─ docker-compose.yml

18.5 Datenbank-Migration

CREATE TABLE tickets (
  id UUID PRIMARY KEY,
  title VARCHAR(240) NOT NULL,
  description TEXT NOT NULL,
  category VARCHAR(120) NOT NULL,
  priority VARCHAR(40) NOT NULL,
  status VARCHAR(60) NOT NULL,
  requester VARCHAR(160) NOT NULL,
  assignee VARCHAR(160),
  due_at TIMESTAMP,
  created_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL
);

CREATE TABLE ticket_comments (
  id UUID PRIMARY KEY,
  ticket_id UUID NOT NULL REFERENCES tickets(id),
  author VARCHAR(160) NOT NULL,
  body TEXT NOT NULL,
  created_at TIMESTAMP NOT NULL
);

CREATE TABLE approval_steps (
  id UUID PRIMARY KEY,
  ticket_id UUID NOT NULL REFERENCES tickets(id),
  approver VARCHAR(160) NOT NULL,
  decision VARCHAR(60),
  decision_note VARCHAR(800),
  decided_at TIMESTAMP
);

CREATE TABLE ticket_audit_events (
  id UUID PRIMARY KEY,
  ticket_id UUID NOT NULL REFERENCES tickets(id),
  actor VARCHAR(160) NOT NULL,
  action VARCHAR(80) NOT NULL,
  old_status VARCHAR(60),
  new_status VARCHAR(60),
  created_at TIMESTAMP NOT NULL
);

18.6 Backend: Status und Priorität

package com.example.workflow.tickets;

public enum TicketStatus {
    NEW,
    TRIAGED,
    IN_PROGRESS,
    WAITING_FOR_APPROVAL,
    APPROVED,
    REJECTED,
    DONE,
    CANCELLED
}
package com.example.workflow.tickets;

public enum TicketPriority {
    LOW,
    NORMAL,
    HIGH,
    CRITICAL
}

18.7 Backend: Entity mit Statusübergängen

package com.example.workflow.tickets;

import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;

@Entity
@Table(name = "tickets")
public class Ticket {

    @Id
    private UUID id;

    @Column(nullable = false, length = 240)
    private String title;

    @Column(nullable = false)
    private String description;

    @Column(nullable = false, length = 120)
    private String category;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 40)
    private TicketPriority priority;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 60)
    private TicketStatus status;

    @Column(nullable = false, length = 160)
    private String requester;

    @Column(length = 160)
    private String assignee;

    private Instant dueAt;

    @Column(nullable = false)
    private Instant createdAt;

    @Column(nullable = false)
    private Instant updatedAt;

    protected Ticket() {}

    public Ticket(String title, String description, String category, TicketPriority priority, String requester) {
        this.id = UUID.randomUUID();
        this.title = title;
        this.description = description;
        this.category = category;
        this.priority = priority;
        this.requester = requester;
        this.status = TicketStatus.NEW;
        this.createdAt = Instant.now();
        this.updatedAt = Instant.now();
    }

    public void triage(String assignee) {
        requireStatus(TicketStatus.NEW);
        this.assignee = assignee;
        this.status = TicketStatus.TRIAGED;
        this.updatedAt = Instant.now();
    }

    public void startWork() {
        requireStatus(TicketStatus.TRIAGED);
        this.status = TicketStatus.IN_PROGRESS;
        this.updatedAt = Instant.now();
    }

    public void requestApproval() {
        requireStatus(TicketStatus.IN_PROGRESS);
        this.status = TicketStatus.WAITING_FOR_APPROVAL;
        this.updatedAt = Instant.now();
    }

    public void approve() {
        requireStatus(TicketStatus.WAITING_FOR_APPROVAL);
        this.status = TicketStatus.APPROVED;
        this.updatedAt = Instant.now();
    }

    public void reject() {
        if (status != TicketStatus.NEW && status != TicketStatus.WAITING_FOR_APPROVAL) {
            throw new IllegalStateException("Ticket kann in diesem Status nicht abgelehnt werden: " + status);
        }
        this.status = TicketStatus.REJECTED;
        this.updatedAt = Instant.now();
    }

    private void requireStatus(TicketStatus expected) {
        if (status != expected) {
            throw new IllegalStateException("Erwarteter Status: " + expected + ", aktueller Status: " + status);
        }
    }

    public UUID getId() { return id; }
    public String getTitle() { return title; }
    public TicketStatus getStatus() { return status; }
    public TicketPriority getPriority() { return priority; }
    public String getRequester() { return requester; }
    public String getAssignee() { return assignee; }
    public Instant getUpdatedAt() { return updatedAt; }
}

18.8 Backend: Create Request und Response

package com.example.workflow.tickets.dto;

import com.example.workflow.tickets.TicketPriority;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record CreateTicketRequest(
    @NotBlank @Size(max = 240)
    String title,

    @NotBlank @Size(max = 5_000)
    String description,

    @NotBlank @Size(max = 120)
    String category,

    @NotNull
    TicketPriority priority
) {}
package com.example.workflow.tickets.dto;

import java.time.Instant;
import java.util.UUID;

public record TicketResponse(
    UUID id,
    String title,
    String status,
    String priority,
    String requester,
    String assignee,
    Instant updatedAt
) {}

18.9 Backend: Service

package com.example.workflow.tickets;

import com.example.workflow.tickets.dto.CreateTicketRequest;
import com.example.workflow.tickets.dto.TicketResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Service
public class TicketService {

    private final TicketRepository repository;

    public TicketService(TicketRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public TicketResponse create(CreateTicketRequest request, String requester) {
        Ticket ticket = new Ticket(
            request.title(),
            request.description(),
            request.category(),
            request.priority(),
            requester
        );
        return toResponse(repository.save(ticket));
    }

    @Transactional
    public TicketResponse triage(UUID id, String assignee) {
        Ticket ticket = findTicket(id);
        ticket.triage(assignee);
        return toResponse(ticket);
    }

    @Transactional
    public TicketResponse approve(UUID id) {
        Ticket ticket = findTicket(id);
        ticket.approve();
        return toResponse(ticket);
    }

    private Ticket findTicket(UUID id) {
        return repository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("Ticket nicht gefunden"));
    }

    private TicketResponse toResponse(Ticket ticket) {
        return new TicketResponse(
            ticket.getId(),
            ticket.getTitle(),
            ticket.getStatus().name(),
            ticket.getPriority().name(),
            ticket.getRequester(),
            ticket.getAssignee(),
            ticket.getUpdatedAt()
        );
    }
}

18.10 Backend: Controller

package com.example.workflow.tickets;

import com.example.workflow.tickets.dto.CreateTicketRequest;
import com.example.workflow.tickets.dto.TicketResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.UUID;

@RestController
@RequestMapping("/api/tickets")
public class TicketController {

    private final TicketService service;

    public TicketController(TicketService service) {
        this.service = service;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public TicketResponse create(@Valid @RequestBody CreateTicketRequest request, Principal principal) {
        String requester = principal != null ? principal.getName() : "local-user";
        return service.create(request, requester);
    }

    @PostMapping("/{id}/triage")
    public TicketResponse triage(@PathVariable UUID id, @RequestParam String assignee) {
        return service.triage(id, assignee);
    }

    @PostMapping("/{id}/approve")
    public TicketResponse approve(@PathVariable UUID id) {
        return service.approve(id);
    }
}

18.11 Frontend: Angular Dashboard-Idee

features/tickets/
├─ pages/
│  ├─ ticket-list-page.component.ts
│  ├─ ticket-detail-page.component.ts
│  └─ create-ticket-page.component.ts
├─ ui/
│  ├─ ticket-status-badge.component.ts
│  ├─ ticket-priority-badge.component.ts
│  └─ ticket-kanban-board.component.ts
└─ data-access/
   └─ tickets-api.service.ts
// tickets-api.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";

export type Ticket = {
  id: string;
  title: string;
  status: string;
  priority: string;
  requester: string;
  assignee?: string;
  updatedAt: string;
};

@Injectable({ providedIn: "root" })
export class TicketsApiService {
  private readonly baseUrl = "http://localhost:8080/api/tickets";

  constructor(private readonly http: HttpClient) {}

  create(payload: unknown) {
    return this.http.post<Ticket>(this.baseUrl, payload);
  }

  triage(id: string, assignee: string) {
    return this.http.post<Ticket>(`${this.baseUrl}/${id}/triage?assignee=${encodeURIComponent(assignee)}`, {});
  }

  approve(id: string) {
    return this.http.post<Ticket>(`${this.baseUrl}/${id}/approve`, {});
  }
}

18.12 Frontend: React Kanban-Variante

type Ticket = {
  id: string;
  title: string;
  status: string;
  priority: string;
  assignee?: string;
};

const columns = ["NEW", "TRIAGED", "IN_PROGRESS", "WAITING_FOR_APPROVAL", "APPROVED", "DONE"];

export function TicketKanbanBoard({ tickets }: { tickets: Ticket[] }) {
  return (
    <section className="kanban-board">
      {columns.map((column) => {
        const items = tickets.filter((ticket) => ticket.status === column);
        return (
          <article key={column} className="kanban-column">
            <h2>{column}</h2>
            {items.length === 0 && <p>Keine Tickets</p>}
            {items.map((ticket) => (
              <div key={ticket.id} className="ticket-card">
                <strong>{ticket.title}</strong>
                <span>{ticket.priority}</span>
                <small>{ticket.assignee ?? "nicht zugewiesen"}</small>
              </div>
            ))}
          </article>
        );
      })}
    </section>
  );
}

18.13 Test für Statuslogik

package com.example.workflow.tickets;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class TicketTest {

    @Test
    void shouldMoveFromNewToTriaged() {
        Ticket ticket = new Ticket("VPN Zugang", "Bitte VPN freischalten", "IT", TicketPriority.NORMAL, "aydin");

        ticket.triage("it-agent-1");

        assertThat(ticket.getStatus()).isEqualTo(TicketStatus.TRIAGED);
        assertThat(ticket.getAssignee()).isEqualTo("it-agent-1");
    }

    @Test
    void shouldNotApproveWithoutApprovalState() {
        Ticket ticket = new Ticket("Budget", "Neuer Monitor", "Finance", TicketPriority.HIGH, "aydin");

        assertThatThrownBy(ticket::approve)
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("WAITING_FOR_APPROVAL");
    }
}

18.14 AI-Aufgaben für Codex oder Claude Code

Implementiere für das Ticket-Projekt einen sauberen Workflow-Service.
Regeln:
- Erlaubte Statusübergänge zentral definieren.
- Ungültige Übergänge mit fachlicher Fehlermeldung ablehnen.
- AuditEvent bei jedem Übergang speichern.
- Unit Tests für alle erlaubten und verbotenen Übergänge schreiben.
- Keine Controller-Logik duplizieren.
Erstelle ein Angular Ticket-Dashboard.
Anforderungen:
- Tickets nach Status gruppieren.
- Filter für Kategorie, Priorität und Assignee.
- Loading, Error und Empty State.
- Klick auf Ticket öffnet Detailansicht.
- API Service zentral verwenden.
- Komponenten klein und testbar halten.
Führe ein Architekturreview für das Ticket & Approval Projekt durch.
Bewerte:
- Domänenmodell
- Statusmaschine
- Auditierbarkeit
- Rollen und Berechtigungen
- Testabdeckung
- Erweiterbarkeit für SLA und Benachrichtigungen
Gib konkrete Verbesserungen mit Aufwand und Risiko.

18.15 Abnahmekriterien

18.16 Enterprise-Erweiterungen

Erweiterung Beschreibung
SLA Engine Fälligkeit abhängig von Kategorie und Priorität berechnen
Benachrichtigungen E-Mail, Slack/Teams oder In-App Notifications
Anhänge Dateien sicher hochladen und virenprüfen
Mandantenfähigkeit Tickets pro Organisation oder Kunde trennen
Reporting Durchlaufzeiten, Engpässe und Team-Auslastung messen
Outbox Pattern Statusereignisse zuverlässig an andere Systeme senden
SSO Login über Unternehmens-Identity-Provider
Berechtigungen Sichtbarkeit nach Team, Rolle und Kategorie einschränken

19. Beispielprojekt: WordPress Pizza-Restaurant mit Lieferung, SEO, Sicherheit und App

Dieses Beispielprojekt ist ein professionelles WordPress-Projekt für ein Pizza-Restaurant mit Online-Bestellung, lokaler Lieferung, Abholung, responsivem Design, PWA-App und optionaler nativer App. Budget spielt hier keine Rolle. Deshalb wird nicht der billigste Weg gewählt, sondern ein stabiler, performanter und sicherer Enterprise-Ansatz.

Projektname: Pizza Delivery Pro
Ziel: Eine schnelle, suchmaschinenoptimierte und sichere Website für ein Pizza-Restaurant, auf der Kunden Pizza, Getränke und Menüs bestellen, Lieferzeit wählen, online bezahlen und die Seite als App nutzen können.

19.1 Zielbild

Erklärung: Warum WordPress für dieses Projekt sinnvoll sein kann

Für ein Pizza-Restaurant ist Geschwindigkeit bei Umsetzung, Bedienbarkeit und lokaler Sichtbarkeit entscheidend. WordPress ist hier sinnvoll, weil Inhalte, Speisekarte, Angebote, Blog, Landingpages und rechtliche Seiten sehr schnell gepflegt werden können. WooCommerce ergänzt Warenkorb, Checkout, Zahlungen und Bestellungen. Das Restaurant-Team kann später Preise, Öffnungszeiten, Aktionen und Lieferinformationen ändern, ohne jedes Mal Entwickler zu brauchen.

Der Nachteil: WordPress-Projekte werden oft langsam oder unsicher, wenn zu viele Plugins, schlechte Themes oder ungeprüfte Erweiterungen genutzt werden. Deshalb setzt dieses Beispiel auf eine Premium-Architektur: schlankes Theme, kontrollierte Plugin-Auswahl, WAF, Backups, Staging, Updates, Security-Monitoring und klare Rollen. Das Ziel ist nicht „irgendeine schöne Website“, sondern eine bestellfähige, schnelle und sichere Verkaufsplattform.

Entscheidung Warum sie wichtig ist
Bricks statt überladenes Nischen-Theme Bessere Kontrolle über Layout, Performance und WooCommerce-Templates.
WooCommerce Standardisierte Produkt-, Warenkorb-, Checkout- und Bestelllogik.
Food-Ordering-Plugin Restaurant-spezifische Optionen wie Extras, Lieferzeiten und Abholung.
PWA zuerst App-ähnliches Erlebnis ohne sofort native iOS/Android-Entwicklung.
Cloudflare + WordPress-Härtung Schutz auf Netzwerk-, Login-, Datei- und Anwendungsebene.
Local SEO Restaurantkunden suchen häufig lokal und mobil.

Merksatz: Bei einem Restaurant-Projekt ist die Website nicht nur Visitenkarte. Sie ist Vertriebskanal, Bestellsystem, SEO-Landingpage und Kundenbindungswerkzeug zugleich.

Bereich Ziel
Website Moderne Restaurant-Website mit Menü, Liefergebiet, Aktionen, Bewertungen, Blog und lokalen Landingpages
Shop WooCommerce für Warenkorb, Checkout, Coupons, Zahlungen und Bestellverwaltung
Lieferung Lieferzonen, Mindestbestellwert, Abholoption, Zeitfenster, Öffnungszeiten, Feiertage
Theme Performance-first Theme, kein überladenes ThemeForest-Template
SEO Local SEO, strukturierte Daten, schnelle Ladezeiten, Stadtteilseiten, Pizza-Menü-Seiten
Sicherheit WAF, Malware-Scan, 2FA, Backups, Rollenmodell, Updates, Monitoring, Staging
Responsive Mobile-first Layout für Bestellungen auf Smartphone, Tablet und Desktop
App PWA als erste App-Stufe, optional native iOS/Android App über WordPress/WooCommerce APIs

19.2 Beste Theme-Entscheidung

Erklärung: Theme-Entscheidung im Detail

Die Theme-Wahl beeinflusst langfristig fast alles: Ladezeit, Wartbarkeit, Designfreiheit, WooCommerce-Templates, Barrierefreiheit und Update-Risiko. Ein fertiges Pizza-Theme sieht oft sofort passend aus, aber es bringt manchmal alte Page-Builder, viele Demo-Assets oder starre Layouts mit. Das kann später problematisch werden, wenn du Checkout, Produktkarten, Lieferzonen oder Landingpages individuell optimieren willst.

Bricks ist hier die Premium-Empfehlung, weil du ein eigenes Designsystem aufbauen kannst: Farben, Abstände, Buttons, Karten, Produkt-Layouts, Header, Footer und responsive Regeln werden einmal sauber definiert und dann wiederverwendet. Für SEO und Conversion ist das besser als ein rein dekoratives Theme. Ein Pizza-Restaurant braucht nicht nur hübsche Bilder, sondern klare Bestellwege, schnelle Seiten, gute mobile Bedienung und zuverlässige WooCommerce-Templates.

Wann Astra Pro trotzdem besser sein kann: Wenn das Projekt extrem schnell online gehen muss, das Team wenig technische Erfahrung hat und ein Starter-Template bevorzugt. Astra ist dann der pragmatische Weg. Bei „Budget spielt keine Rolle“ und maximaler Kontrolle ist Bricks plus individuelles Designsystem aber die stärkere Wahl.

Empfehlung ohne Budgetlimit: Bricks Builder als Theme/Visual Site Builder mit eigenem Pizza-Designsystem.

Warum nicht einfach irgendein fertiges „Pizza Theme“?

  • Viele Nischen-Themes sehen am Anfang gut aus, bringen aber zu viel CSS, JS und Plugin-Abhängigkeiten mit.
  • Für SEO, Core Web Vitals, Sicherheit und Wartbarkeit ist ein schlankes Theme mit sauberer Struktur besser.
  • Mit Bricks kannst du Header, Footer, WooCommerce-Templates, Produktseiten, Checkout-Layouts und Landingpages gezielt bauen.
  • Das Design wird individuell für die Marke erstellt: nicht wie tausend andere Pizza-Websites.

Alternative, wenn sehr schnelle Umsetzung wichtiger ist: Astra Pro mit Restaurant-/Food-Starter-Template und WooCommerce. Astra ist gut, wenn du ein bewährtes, leicht verständliches Theme mit vielen Vorlagen willst.

Entscheidung für dieses Projekt:

Theme: Bricks Builder
Designsystem: Custom Pizza Brand System
Shop: WooCommerce
Food Ordering: Orderable Pro oder WooCommerce Restaurant Ordering
SEO: Yoast SEO Premium + Yoast Local SEO + WooCommerce SEO
Security: Cloudflare WAF + Wordfence Premium/Care + serverseitige Härtung
Performance: Managed WordPress Hosting + CDN + Object Cache + Bildoptimierung
App: PWA zuerst, native App optional über WooCommerce/WordPress REST API

19.3 Architektur

Erklärung: Wie die Architektur zusammenspielt

Die Architektur trennt mehrere Verantwortlichkeiten. Cloudflare sitzt vor der Website und schützt sowie beschleunigt den Traffic. WordPress verwaltet Inhalte, Seiten, Medien und Benutzer. WooCommerce übernimmt Produkte, Warenkorb, Checkout und Bestellungen. Das Food-Ordering-Plugin ergänzt Restaurantfunktionen wie Extras, Lieferzeitfenster und Abholung. Der Payment Provider verarbeitet Zahlungen, damit keine sensiblen Kartendaten im eigenen System gespeichert werden müssen.

Für die Küche ist ein klares Order-Dashboard wichtig. Dort zählt nicht Design, sondern Geschwindigkeit: neue Bestellung sehen, Status ändern, Bon drucken, Lieferzeit abschätzen. Für Kunden zählt dagegen die mobile Bestellstrecke: schnell Menü öffnen, Pizza konfigurieren, Lieferadresse prüfen, bezahlen, Bestätigung bekommen.

Wichtig: Nicht jede Seite darf gleich gecacht werden. Startseite, Blog und Speisekarte können stark profitieren. Warenkorb, Checkout, Konto und personalisierte Bestellseiten müssen vorsichtig behandelt werden, weil sonst falsche Warenkorb- oder Kundendaten angezeigt werden könnten.

flowchart LR
  Customer[Mobile/Desktop Kunde]
  PWA[PWA / Website]
  WP[WordPress]
  WC[WooCommerce]
  Food[Food Ordering Plugin]
  Pay[Payment Provider]
  DB[(Managed DB)]
  CDN[Cloudflare CDN/WAF]
  App[Native App optional]
  Kitchen[Küche / Order Dashboard]

  Customer --> CDN
  CDN --> PWA
  PWA --> WP
  WP --> WC
  WC --> Food
  WC --> Pay
  WP --> DB
  Food --> Kitchen
  App --> WP
  App --> WC

19.4 Empfohlener Premium-Stack

Kategorie Empfehlung Zweck
Hosting Managed WordPress Hosting mit Staging, Backups, Redis/Object Cache Stabilität, Performance, schnelle Wiederherstellung
CDN/WAF Cloudflare Pro/Business/Enterprise DDoS-Schutz, WAF, Caching, Bot-Schutz
Theme Bricks Builder Schnelle, individuelle, responsive WordPress-Seiten
Shop WooCommerce Produkte, Warenkorb, Checkout, Zahlungen, Bestellungen
Food Ordering Orderable Pro oder WooCommerce Restaurant Ordering Restaurant-Menü, Extras, Liefer-/Abhollogik
Delivery Slots Order Delivery Date oder Delivery Time Slots Lieferzeiten und Abholzeiten
SEO Yoast SEO Premium + Yoast Local SEO + WooCommerce SEO Local SEO, Schema, Produkt-SEO, interne Verlinkung
Security Wordfence Premium/Care oder vergleichbarer Managed-Security-Service Firewall, Malware-Scan, Login-Schutz, Incident Response
Backup BlogVault, Jetpack VaultPress oder Hosting-Backup plus Offsite-Backup Wiederherstellung nach Fehler oder Angriff
Performance ShortPixel/Imagify, Perfmatters, Redis Object Cache Bilder, Assets, Datenbank und Cache optimieren
Analytics Google Search Console, GA4, Matomo optional SEO- und Conversion-Auswertung
E-Mail SMTP-Dienst wie Postmark, Brevo oder Mailgun Zuverlässige Bestell- und Kundenmails

19.5 Funktionsumfang

  1. Startseite mit Hero, Liefergebiet, CTA „Jetzt bestellen“.
  2. Online-Speisekarte mit Kategorien: Pizza, Pasta, Salate, Desserts, Getränke, Menüs.
  3. Produktoptionen: Größe, Teig, Extra-Käse, Schärfe, Zutaten entfernen, Zusatzbeläge.
  4. Liefergebiet nach PLZ oder Radius.
  5. Mindestbestellwert pro Lieferzone.
  6. Lieferkosten nach Zone oder Entfernung.
  7. Abholung mit Uhrzeit-Auswahl.
  8. Lieferung mit Zeitfenster-Auswahl.
  9. Online-Zahlung plus Barzahlung bei Lieferung, falls gewünscht.
  10. Gutscheine, Mittagsmenü, Familienmenü, Studentenrabatt.
  11. Kundenkonto optional, Gast-Checkout aktiviert.
  12. Küche-Dashboard für neue Bestellungen.
  13. Automatische E-Mail/SMS/WhatsApp-Benachrichtigung optional.
  14. Bewertungsbereich und Google-Business-Verknüpfung.
  15. Blog für lokale SEO-Inhalte.
  16. PWA-Installation auf Smartphone.
  17. Optional native App für iOS und Android.

19.6 Seitenstruktur für SEO

/
/bestellen/
/speisekarte/
/speisekarte/pizza/
/speisekarte/pasta/
/speisekarte/getraenke/
/angebote/
/liefergebiet/
/liefergebiet/wien-1010/
/liefergebiet/wien-1020/
/liefergebiet/wien-1030/
/abholung/
/ueber-uns/
/kontakt/
/blog/
/blog/beste-pizza-in-wien/
/blog/pizza-lieferung-wien-abends/
/datenschutz/
/impressum/
/agb/

19.7 SEO-Strategie

Erklärung: SEO bei einem Pizza-Lieferdienst

SEO für ein Restaurant ist stark lokal. Ein Kunde sucht meistens nicht allgemein nach „Pizza“, sondern nach „Pizza bestellen in meiner Nähe“, „Pizza Lieferung Wien 1020“ oder „Pizzeria offen jetzt“. Deshalb braucht das Projekt eine klare lokale Struktur: konsistente Name-Adresse-Telefon-Daten, Google Business Profile, Öffnungszeiten, Liefergebiet, Produktseiten und echte lokale Inhalte.

Stadtteilseiten sind nur dann sinnvoll, wenn sie echten Nutzen haben. Eine Seite für „Pizza Lieferung Wien 1020“ sollte also nicht nur denselben Text mit anderer PLZ enthalten. Sie sollte Lieferbedingungen, ungefähre Lieferzeit, Mindestbestellwert, beliebte Produkte, lokale Hinweise und klare Bestell-CTA enthalten. Sonst wirkt sie wie eine Doorway Page und bringt langfristig wenig.

SEO-Element Erklärung
LocalBusiness/Restaurant Schema Hilft Suchmaschinen, Restaurantdaten strukturiert zu verstehen.
Produktdaten Jede Pizza kann als kaufbares Produkt mit Preis und Angebot verstanden werden.
Interne Verlinkung Führt Nutzer und Suchmaschinen von Startseite zu Speisekarte, Angeboten und Liefergebieten.
Core Web Vitals Mobile Geschwindigkeit beeinflusst Nutzererlebnis und Conversion.
Bewertungen Vertrauen und lokale Sichtbarkeit werden gestärkt.
FAQ Klärt Lieferzeit, Zahlung, Allergene, Mindestbestellwert und Abholung.

Praxisziel: SEO soll nicht nur Besucher bringen, sondern Bestellungen. Deshalb müssen Ranking-Seiten immer mit einer klaren Bestellmöglichkeit verbunden sein.

SEO-Bereich Umsetzung
Local SEO Google Business Profile, identische NAP-Daten, Öffnungszeiten, Telefonnummer, Adresse, Liefergebiet
Strukturierte Daten Restaurant, LocalBusiness, PostalAddress, OpeningHoursSpecification, Menu, Product, Offer
Keyword-Cluster „Pizza Lieferung + Stadt“, „Pizza bestellen + Stadtteil“, „beste Pizza + Stadt“, „Pizzeria in der Nähe“
Landingpages Eigene Seiten pro Liefergebiet, aber mit echtem lokalen Inhalt statt dupliziertem Text
Produkt-SEO Jede Pizza mit Name, Beschreibung, Zutaten, Allergenen, Bild, Preis, Varianten
Performance Mobile LCP optimieren, Bilder komprimieren, Fonts lokal, unnötige Plugins vermeiden
Content Blog über Aktionen, Zutaten, lokale Events, Lieferzeiten, Pizza-Arten
Interne Links Startseite → Speisekarte → Kategorien → Bestseller → Liefergebiet
Conversion Sticky „Jetzt bestellen“-Button auf Mobile, klare Mindestbestellwerte, schnelle Checkout-Strecke

19.8 Beispiel: JSON-LD für Restaurant

Werte wie Adresse, Telefon, Öffnungszeiten und URL musst du im echten Projekt ersetzen.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Restaurant",
  "name": "Pizza Delivery Pro",
  "image": "https://example.com/wp-content/uploads/pizza-margherita.jpg",
  "url": "https://example.com/",
  "telephone": "+43-1-1234567",
  "servesCuisine": ["Pizza", "Italian", "Pasta"],
  "priceRange": "€€",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Beispielstraße 12",
    "addressLocality": "Wien",
    "postalCode": "1010",
    "addressCountry": "AT"
  },
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
      "opens": "11:00",
      "closes": "23:00"
    },
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Saturday", "Sunday"],
      "opens": "12:00",
      "closes": "23:30"
    }
  ],
  "hasMenu": "https://example.com/speisekarte/",
  "acceptsReservations": false,
  "areaServed": ["Wien 1010", "Wien 1020", "Wien 1030"]
}
</script>

19.9 Responsive Design-System

Erklärung: Responsive und App-ähnliche Bedienung

Bei einem Pizza-Lieferdienst kommt ein großer Teil der Bestellungen über Smartphones. Deshalb muss Mobile nicht nur „irgendwie passen“, sondern zuerst geplant werden. Ein Desktop-Layout mit drei Spalten funktioniert auf dem Handy nicht automatisch gut. Mobile Nutzer wollen schnell Kategorien wechseln, Produkte konfigurieren, den Warenkorb sehen und ohne lange Formulare bestellen.

Die Sticky-Bestellleiste ist deshalb kein Deko-Element, sondern ein Conversion-Werkzeug. Sie zeigt dem Nutzer jederzeit, dass er bestellen kann und wie er zum Warenkorb kommt. Produktkarten müssen große Buttons haben, Optionen dürfen nicht versteckt sein, und die PLZ-Prüfung sollte früh erfolgen. Es ist frustrierend, wenn ein Kunde erst am Ende erfährt, dass seine Adresse nicht beliefert wird.

Für die App-Strategie gilt: Eine PWA ist der beste erste Schritt, weil sie schnell verfügbar ist, installierbar wirkt und keine App-Store-Freigabe braucht. Eine native App lohnt sich erst, wenn es echte Stammkunden-Funktionen gibt: Push-Nachrichten, Favoriten, Reorder, Loyalty, Gutscheine oder Fahrertracking.

:root {
  --color-tomato: #d62828;
  --color-cheese: #fcbf49;
  --color-basil: #2a9d8f;
  --color-crust: #f4a261;
  --color-ink: #1f2937;
  --color-surface: #fffaf0;
  --radius-card: 18px;
  --shadow-card: 0 16px 40px rgba(31, 41, 55, 0.12);
  --font-heading: "Inter", system-ui, sans-serif;
  --font-body: "Inter", system-ui, sans-serif;
}

.pizza-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 1rem;
}

@media (min-width: 640px) {
  .pizza-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .pizza-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.mobile-order-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.85rem 1rem;
  background: var(--color-tomato);
  color: white;
}

@media (min-width: 900px) {
  .mobile-order-bar {
    display: none;
  }
}

19.10 Mobile UX-Regeln

Bereich Regel
Hero Ein CTA: „Jetzt bestellen“, nicht mehrere konkurrierende Buttons
Menü Kategorien als horizontale Sticky-Tabs
Produktkarte Bild, Name, Preis, Bestseller-Badge, „Hinzufügen“-Button
Checkout So wenig Felder wie möglich, Gastbestellung erlauben
Lieferadresse PLZ zuerst prüfen, dann Adresse erfassen
Warenkorb Sticky Summary auf Mobile
Geschwindigkeit Keine schweren Slider, keine Autoplay-Videos auf Startseite
Barrierefreiheit Buttons groß genug, Kontrast prüfen, Fokus-Stile sichtbar

19.11 Lokale Entwicklungsumgebung mit Docker

services:
  wordpress:
    image: wordpress:php8.3-apache
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: pizza_wp
    volumes:
      - ./wp-content:/var/www/html/wp-content
    depends_on:
      - db

  db:
    image: mariadb:11
    environment:
      MYSQL_DATABASE: pizza_wp
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - db_data:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin:latest
    ports:
      - "8081:80"
    environment:
      PMA_HOST: db
    depends_on:
      - db

volumes:
  db_data:

Start:

mkdir pizza-delivery-pro
cd pizza-delivery-pro
mkdir wp-content
# docker-compose.yml anlegen
docker compose up -d

WordPress lokal öffnen:

http://localhost:8080

19.12 WooCommerce Produktmodell für Pizza

Produkt Typ Varianten/Optionen
Pizza Margherita Variables Product 26 cm, 32 cm, Familie
Pizza Salami Variables Product Größe, extra Käse, scharf
Pizza Vegetaria Variables Product Größe, glutenfrei optional
Pizza Diavolo Variables Product Größe, Schärfegrad
Getränke Simple Product Cola, Fanta, Wasser
Menü 1 Bundle/Composite Pizza + Getränk + Dessert

Empfohlene Kategorien:

Pizza
Pasta
Salate
Desserts
Getränke
Menüs
Angebote
Vegetarisch
Scharf
Bestseller

19.13 Checkout-Logik

Erklärung: Warum Checkout-Logik geschäftskritisch ist

Der Checkout entscheidet direkt über Umsatz. Jede unnötige Hürde kann Bestellungen abbrechen lassen. Für ein Pizza-Restaurant sind drei Dinge besonders wichtig: Lieferfähigkeit, korrekte Zeitfenster und transparente Kosten. Der Kunde sollte früh sehen, ob seine PLZ beliefert wird, welcher Mindestbestellwert gilt und wann die Bestellung ungefähr ankommt.

Die Regeln dürfen nicht nur im Frontend stehen. Ein Benutzer könnte Frontend-Prüfungen umgehen oder ein Fehler könnte falsche Werte senden. Deshalb müssen Lieferzone, Mindestbestellwert, Öffnungszeiten, Liefergebühren und verfügbare Zeitfenster serverseitig geprüft werden. Das Frontend hilft nur bei der Bedienung; die Wahrheit liegt im Backend beziehungsweise in WooCommerce/Plugin-Regeln.

Regel Warum sie serverseitig geprüft werden muss
Lieferzone Sonst werden Bestellungen außerhalb des Gebiets angenommen.
Mindestbestellwert Sonst entstehen unrentable Lieferungen.
Lieferzeitfenster Sonst bestellt jemand außerhalb der Küchenkapazität.
Zahlungsart Nicht jede Zahlungsart sollte für jede Zone/Bestellung erlaubt sein.
Coupon Rabatte dürfen nicht falsch kombiniert werden.

Optimierung: Gast-Checkout aktivieren, aber Kundenkonto nach der Bestellung anbieten. So wird der erste Kauf nicht blockiert, aber Stammkunden können später schneller bestellen.

Kunde öffnet /bestellen
→ PLZ prüfen
→ Lieferzone bestimmen
→ Mindestbestellwert anzeigen
→ Produkte hinzufügen
→ Lieferzeit oder Abholzeit wählen
→ Adresse bestätigen
→ Zahlung wählen
→ Bestellung abschicken
→ Küche bekommt Bestellung
→ Kunde bekommt Bestellbestätigung

Regeln:

Wenn PLZ nicht im Liefergebiet:
  Abholung anbieten

Wenn Warenkorb < Mindestbestellwert:
  Differenz anzeigen

Wenn Restaurant geschlossen:
  Vorbestellung für nächsten Slot anbieten

Wenn Küche ausgelastet:
  nächste freie Lieferzeit anzeigen

19.14 App-Strategie

Phase 1: PWA

  • Website installierbar auf Smartphone.
  • App-Icon auf Homescreen.
  • Schneller Zugriff auf Speisekarte und Bestellung.
  • Offline-Fallback für Menü-Seiten.
  • Push-Benachrichtigung nur, wenn sauber umgesetzt und rechtlich geprüft.

Phase 2: Native App optional

  • React Native oder Flutter.
  • WordPress REST API für Inhalte.
  • WooCommerce REST API oder Store API für Produkte, Warenkorb und Bestellung.
  • Eigene App-Funktionen: Reorder, Favoriten, Push, Loyalty-System, Gutschein-Wallet.

Empfehlung: Erst PWA bauen und echte Nutzungsdaten sammeln. Native App erst erstellen, wenn wiederkehrende Kunden, Loyalty und Push-Marketing wirklich relevant sind.

19.15 PWA Manifest

Datei: app.webmanifest

{
  "id": "/?source=pwa",
  "name": "Pizza Delivery Pro",
  "short_name": "PizzaPro",
  "description": "Pizza online bestellen mit Lieferung und Abholung.",
  "start_url": "/bestellen/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "background_color": "#fffaf0",
  "theme_color": "#d62828",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/wp-content/uploads/app-icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/wp-content/uploads/app-icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "shortcuts": [
    {
      "name": "Jetzt bestellen",
      "url": "/bestellen/",
      "description": "Pizza bestellen"
    },
    {
      "name": "Angebote",
      "url": "/angebote/",
      "description": "Aktuelle Pizza-Angebote"
    }
  ]
}

In WordPress einbinden:

add_action('wp_head', function () {
    echo '<link rel="manifest" href="/app.webmanifest">' . PHP_EOL;
    echo '<meta name="theme-color" content="#d62828">' . PHP_EOL;
});

19.16 Service Worker Minimalbeispiel

Datei: service-worker.js

const CACHE_NAME = 'pizza-delivery-pro-v1';
const OFFLINE_URLS = [
  '/',
  '/speisekarte/',
  '/bestellen/',
  '/wp-content/uploads/app-icon-192.png'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(OFFLINE_URLS))
  );
});

self.addEventListener('fetch', event => {
  if (event.request.method !== 'GET') return;

  event.respondWith(
    fetch(event.request).catch(() => caches.match(event.request).then(response => {
      return response || caches.match('/speisekarte/');
    }))
  );
});

Registrierung:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('/service-worker.js');
  });
}
</script>

19.17 Headless App API-Idee

Für eine spätere native App oder ein eigenes React/Next.js-Frontend kannst du WordPress und WooCommerce als Backend verwenden.

GET /wp-json/wp/v2/pages
GET /wp-json/wp/v2/posts
GET /wp-json/wc/store/v1/products
GET /wp-json/wc/store/v1/products/categories
POST /wp-json/wc/store/v1/cart/add-item
GET /wp-json/wc/store/v1/cart
POST /wp-json/wc/store/v1/checkout

Beispiel: Produkte für die App abrufen

export async function loadPizzaProducts() {
  const response = await fetch('https://example.com/wp-json/wc/store/v1/products?category=pizza');

  if (!response.ok) {
    throw new Error('Produkte konnten nicht geladen werden');
  }

  return response.json();
}

19.18 Sicherheitskonzept

Erklärung: Security als Schichtenmodell

WordPress-Sicherheit funktioniert am besten in mehreren Schichten. Keine einzelne Maßnahme reicht allein aus. Cloudflare kann Angriffe filtern, aber schützt nicht vor einem unsicheren Admin-Passwort. Wordfence kann Malware erkennen, aber ersetzt keine Backups. Backups helfen bei Wiederherstellung, verhindern aber keinen Angriff. Deshalb kombiniert das Konzept Netzwerk-Schutz, sichere Logins, minimale Rechte, vertrauenswürdige Plugins, Updates, Monitoring und Restore-Tests.

Besonders wichtig ist das Rollenmodell. Der Restaurantbesitzer braucht nicht dauerhaft Administratorrechte, um Bestellungen zu sehen. Küchenpersonal braucht keine Plugin-Installation. Marketing braucht keinen Zugriff auf Security-Einstellungen. Je weniger Rechte ein Konto hat, desto kleiner ist der Schaden bei Passwortverlust oder Fehlbedienung.

Sicherheitsfrage Richtige Denkweise
„Haben wir Backups?“ Wichtig, aber nur ein getesteter Restore zählt.
„Sind Plugins aktuell?“ Updates zuerst in Staging testen, dann Produktion aktualisieren.
„Ist Wordfence installiert?“ Gut, aber zusätzlich WAF, 2FA und Rechte prüfen.
„Kann jeder Admin sein?“ Nein, Rollen nach Aufgabe trennen.
„Brauchen wir CSP?“ Ja, aber vorsichtig mit Report-Only starten, weil Payment/Analytics externe Skripte nutzen.

Notfallregel: Vor jedem größeren Update Backup erstellen, Staging testen, Change dokumentieren und Rückrollplan kennen.

Ebene Maßnahme
DNS/CDN Cloudflare mit WAF, Bot-Schutz, Rate Limiting, DDoS-Schutz
Hosting Managed Hosting, isolierte Umgebung, tägliche Backups, Staging
WordPress Auto-Updates für Minor Releases, Update-Prozess für Plugins/Themes
Login 2FA für Admins, starke Passwörter, Login-Limits, keine geteilten Accounts
Rollen Admin nur für technische Verantwortliche, Shop Manager für Bestellungen
Plugins Nur notwendige Plugins, Premium-Plugins aus vertrauenswürdigen Quellen
Dateien Schreibrechte begrenzen, Plugin-/Theme-Editor deaktivieren
Malware Wordfence Scan, serverseitige Malware-Prüfung, externe Uptime-Checks
Backups Offsite-Backups, Restore-Test monatlich
Zahlungen Keine Kreditkartendaten selbst speichern; seriöse Payment Provider nutzen
Datenschutz DSGVO-konforme Cookie-/Consent-Lösung, Auftragsverarbeitungsverträge prüfen

19.19 wp-config.php Härtung

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', false);
define('FORCE_SSL_ADMIN', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
define('EMPTY_TRASH_DAYS', 7);
define('WP_POST_REVISIONS', 10);

// Debug niemals in Produktion sichtbar lassen.
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);

19.20 Security Headers

Beispiel für Nginx:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self)" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Content Security Policy vorsichtig einführen, weil WordPress, WooCommerce, Payment Provider, Maps, Analytics und Consent-Tools externe Skripte verwenden können.

Start mit Report-Only:

add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; connect-src 'self' https:; frame-src https:;" always;

19.21 Performance-Plan

Erklärung: Performance ohne kaputten Warenkorb

Performance-Optimierung bei WooCommerce ist anders als bei einer reinen Content-Website. Eine normale Blogseite kann aggressiv gecacht werden. Ein Warenkorb oder Checkout ist dynamisch und benutzerspezifisch. Wenn du hier falsch cachst, sehen Kunden im schlimmsten Fall falsche Warenkörbe oder Checkout-Zustände. Deshalb muss die Caching-Strategie zwischen öffentlichen Seiten und transaktionalen Seiten unterscheiden.

Öffentliche Seiten wie Startseite, Liefergebiet, Blog und Speisekarte können über Page Cache und CDN sehr schnell werden. Produktseiten können teilweise gecacht werden, solange Warenkorb-Fragmente und Preise korrekt bleiben. Cart, Checkout, Mein Konto und Bestellbestätigung sollten aus dem aggressiven Cache ausgeschlossen werden. Object Cache hilft zusätzlich, Datenbankabfragen schneller zu machen, ohne komplette Seiten falsch auszuliefern.

Messpunkte vor Launch: Mobile LCP, CLS, Checkout-Ladezeit, Server-TTFB, Bildgrößen, Plugin-Anzahl, Datenbank-Autoload-Optionen und WooCommerce-Logs. Performance ist nicht nur ein SEO-Thema, sondern beeinflusst direkt die Bestellquote.

Thema Umsetzung
Bilder WebP/AVIF, korrekte Bildgrößen, Lazy Loading
Fonts Lokale Fonts, maximal 1–2 Familien, font-display: swap
CSS/JS Nur benötigte Bricks-/Plugin-Assets laden, keine schweren Slider
Cache Page Cache für nicht-personalisierte Seiten, Object Cache für Datenbank
Warenkorb Cart/Checkout nicht aggressiv cachen
CDN Statische Assets über CDN ausliefern
Datenbank Autoloaded Options prüfen, Transients bereinigen
Monitoring Core Web Vitals, Uptime, Error Logs, WooCommerce Status Logs

19.22 SEO-Contentplan für 8 Wochen

Woche Inhalt
1 Startseite, Speisekarte, Bestellseite, Kontakt, Liefergebiet
2 Produkttexte für Top-20-Pizzen optimieren
3 Stadtteilseiten für wichtigste Liefergebiete erstellen
4 Blogartikel: „Beste Pizza in [Stadt] bestellen“
5 Blogartikel: „Pizza Lieferung am Abend in [Stadt]“
6 Angebotsseite für Familienmenüs und Mittagsmenü
7 FAQ-Seite zu Lieferzeiten, Allergenen, Zahlung, Abholung
8 Schema, interne Links, Search Console, Ranking-Review

19.23 Projektstruktur für Dokumentation

pizza-delivery-pro/
├─ docs/
│  ├─ brief.md
│  ├─ seo-keywords.md
│  ├─ security-checklist.md
│  ├─ content-plan.md
│  ├─ app-strategy.md
│  └─ launch-checklist.md
├─ wp-content/
│  ├─ themes/
│  │  └─ bricks-child-pizza/
│  │     ├─ style.css
│  │     ├─ functions.php
│  │     └─ assets/
│  ├─ plugins/
│  └─ uploads/
├─ docker-compose.yml
└─ _intern/sources/_intern\sources\README.md

19.24 README-Vorlage

# Pizza Delivery Pro

WordPress/WooCommerce-Projekt für ein Pizza-Restaurant mit Lieferung, Abholung, SEO, Security und PWA.

## Stack

- WordPress
- WooCommerce
- Bricks Builder
- Orderable Pro oder WooCommerce Restaurant Ordering
- Yoast SEO Premium, Local SEO, WooCommerce SEO
- Cloudflare WAF
- Wordfence Premium/Care
- PWA Manifest + Service Worker

## Lokaler Start

```bash
docker compose up -d
```

Website:

```text
http://localhost:8080
```

phpMyAdmin:

```text
http://localhost:8081
```

## Launch-Check

- [ ] SSL aktiv
- [ ] Cloudflare aktiv
- [ ] WAF-Regeln aktiv
- [ ] Admin 2FA aktiv
- [ ] Backups getestet
- [ ] WooCommerce Checkout getestet
- [ ] Zahlung getestet
- [ ] Lieferzonen getestet
- [ ] Schema geprüft
- [ ] Search Console verbunden
- [ ] PWA installierbar

19.25 Codex/Claude-Code Prompts

Du bist Senior WordPress/WooCommerce Architekt. Analysiere dieses Pizza-Restaurant-Projekt und erstelle eine technische Umsetzungsliste für Theme, WooCommerce, Lieferung, SEO, Security und PWA. Teile die Arbeit in kleine GitHub-Issues mit Akzeptanzkriterien auf.
Erstelle für dieses WordPress-Projekt ein Sicherheitskonzept. Berücksichtige Cloudflare WAF, Wordfence, Rollen, 2FA, Plugin-Auswahl, Backups, Restore-Test, Staging, Payment-Sicherheit und DSGVO. Gib konkrete technische Maßnahmen und Prüfpunkte aus.
Erstelle eine SEO-Struktur für ein Pizza-Restaurant mit Lieferung in Wien. Baue Seitenstruktur, Keyword-Cluster, LocalBusiness/Restaurant Schema, interne Verlinkung, Blogplan und Produktbeschreibungsregeln. Vermeide Doorway Pages und duplizierte Stadtteiltexte.
Erstelle ein responsives UI-Konzept für eine mobile Pizza-Bestellseite. Fokus: Sticky Warenkorb, schnelle Kategorie-Navigation, Produktoptionen, PLZ-Prüfung, Mindestbestellwert, Lieferzeit-Auswahl und barrierearme Buttons.
Erstelle eine PWA-Strategie für diese WordPress/WooCommerce-Seite. Liefere Manifest, Service-Worker-Plan, Cache-Regeln, Offline-Fallback, App-Icons, Install-Hinweise und Grenzen gegenüber einer nativen App.

19.26 Abnahmekriterien

19.27 Erweiterungen

Erweiterung Nutzen
Loyalty-System Punkte, Stempelkarte, Gutscheine
Reorder-Funktion Stammkunden bestellen schneller erneut
Küchenmonitor Bessere operative Bestellabwicklung
Fahrer-Modul Lieferstatus und Tourenplanung
Multilocation Mehrere Filialen mit eigener Lieferzone
Native App Push, Login, Favoriten, Gutschein-Wallet
Headless Frontend Next.js oder Nuxt für maximale Frontend-Kontrolle
CRM-Anbindung Newsletter, Geburtstagsgutscheine, Segmentierung

20. Prompt-Bibliothek für Codex und Claude Code

17.1 Projektanalyse

Analysiere dieses Repository für einen neuen Entwickler.
Erkläre:
- Projektziel
- Architektur
- Startbefehle
- wichtigste Module
- Teststrategie
- Risiken
Ändere keine Dateien.

17.2 Feature Plan

Ich möchte Feature X implementieren.
Erstelle zuerst einen technischen Plan mit:
- betroffene Dateien
- API Änderungen
- Datenmodell
- Tests
- Risiken
- Reihenfolge der Umsetzung
Ändere noch keine Dateien.

17.3 Backend Feature

Implementiere Feature X im Spring-Boot-Projekt.
Regeln:
- bestehende Architektur beibehalten
- DTOs nicht direkt als Entities verwenden
- Validation ergänzen
- Fehlerfälle behandeln
- Unit- und Integrationstests schreiben
- Tests ausführen
- keine unnötigen Dependencies

17.4 Frontend Feature

Implementiere Feature X im Frontend.
Regeln:
- TypeScript strikt verwenden
- vorhandene Komponenten/Styles nutzen
- Loading, Error und Empty State berücksichtigen
- API Client zentral verwenden
- Tests ergänzen
- keine neue UI Library ohne Begründung

17.5 Bugfix

Hier ist der Fehler/Stacktrace: ...
Finde die Ursache.
Erkläre zuerst Hypothesen und betroffene Dateien.
Ändere erst nach dem Plan.
Schreibe einen Regressionstest.

17.6 Refactoring

Refactore diesen Bereich, ohne Verhalten zu ändern.
Vorgehen:
- vorhandene Tests prüfen
- fehlende Tests ergänzen
- kleine Schritte
- keine public API ändern
- am Ende Diff zusammenfassen

17.7 Security Review

Führe ein Security Review für dieses Modul durch.
Fokus:
- Authentifizierung
- Autorisierung
- Input Validation
- Secrets
- Logging sensibler Daten
- SQL/Injection Risiken
Erstelle Findings mit Schweregrad und Fix-Vorschlag. Ändere noch nichts.

17.8 Architekturreview

Prüfe die Architektur dieses Projekts.
Bewerte:
- Modulgrenzen
- Abhängigkeiten
- Fachlogik-Ort
- Testbarkeit
- Datenbankzugriffe
- API Konsistenz
Gib konkrete Verbesserungsvorschläge mit Aufwand und Risiko.

21. Checklisten

18.1 Neues Java Enterprise Projekt

18.2 Neues Frontend Projekt

18.3 AI-Agent Aufgabe

18.4 Pull Request


22. Quellen und offizielle Doku

WordPress, WooCommerce, SEO, Security und App

AI Coding Tools

Java und Backend

Frontend

Codex- und Coding-Agents-Familie

Lernwerk, Praxis und verbindliche Regeln bleiben bewusst getrennt:

  • Coding-Agents-Bibliothek
  • Enterprise-Praxispaket
  • Zentrale Regelbibliothek
↑ Inhalt