meldestelle/docs/clients/visionen/ProjectArchitecture_StructureGuide.md
StefanMo 034892e890
chore(MP-23): network DI client, frontend architecture guards, detekt & ktlint setup, docs, ping DI factory (#21)
* chore(MP-21): snapshot pre-refactor state (Epic 1)

* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template

* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert

* MP-23 Epic 3: Gradle/Build Governance zentralisieren
2025-11-30 23:14:00 +01:00

8.2 KiB

🏗 Project Architecture & Structure Guide

"Code is liability. Structure is asset." Wir bauen dieses System nicht für den schnellsten Start, sondern für die Wartbarkeit über Jahre, Offline-Fähigkeit und Skalierbarkeit über mehrere Teams hinweg.


1. Die Große Übersicht: The Monorepo Strategy

Wir organisieren Backend und Frontend in einem einzigen Repository (Monorepo).

Warum Monorepo? (Decision Record)

  • Alternative: Getrennte Repositories für Backend, Web-Frontend, Desktop-App.
  • Problem dabei: "Version Hell". Backend ändert API v1 zu v2, aber Frontend-Repo ist noch auf v1. Refactorings über die ganze Kette sind schmerzhaft.
  • Unsere Entscheidung: Monorepo.
    • Atomic Commits: Ein Pull Request enthält Backend-Änderungen UND die dazugehörige Frontend-Anpassung.
    • Single Versioning: Wir nutzen gradle/libs.versions.toml als einzige Quelle der Wahrheit für Library-Versionen (z.B. Kotlin Version) über das gesamte System hinweg.

2. Der "Deep Dive" in die Ordnerstruktur

Hier ist der detaillierte Aufriss unseres Dateisystems. Jeder Ordner hat einen spezifischen architektonischen Zweck.

/my-project-root
│
├── ⚙️ docker-compose.yml       <-- Die lokale "Cloud". Startet DBs, Gateway & Services.
├── 📄 settings.gradle.kts      <-- Definiert die Module (Frontend & Backend).
├── 📂 gradle
│   └── libs.versions.toml      <-- 🛑 STOP! Hier werden Versionen definiert. Nirgendwo sonst.
│
├── 📂 backend                  <-- ARCHITEKTUR: Hexagonal / DDD
│   ├── 📂 gateway              <-- Der "Türsteher". Routing & Auth-Check.
│   ├── 📂 discovery            <-- Das "Telefonbuch" (Consul/Service Registry).
│   └── 📂 services             <-- Die Business Logic (Microservices)
│       ├── 📂 inventory-service
│       │   ├── 📄 Dockerfile   <-- Jedes Service ist ein isolierter Container!
│       │   └── 📂 src/main/kotlin/.../domain  <-- Reine Logik, kein Spring!
│       └── 📂 auth-service
│
└── 📂 frontend                 <-- ARCHITEKTUR: Kotlin Multiplatform (KMP)
    │
    ├── 📂 shells               <-- 💡 CONCEPT: "The Assembler"
    │   │   Das sind die ausführbaren Anwendungen. Sie enthalten KEINE Logik.
    │   │   Sie "kleben" nur Features zusammen und konfigurieren DI.
    │   │
    │   ├── 📂 warehouse-app    <-- Desktop-App (Windows/Linux) für Lageristen
    │   │   └── build.gradle.kts (bindet :features:inventory ein)
    │   └── 📂 admin-portal     <-- Web-App (JS/Wasm) für Management
    │       └── build.gradle.kts (bindet alle Features ein)
    │
    ├── 📂 features             <-- 💡 CONCEPT: "Vertical Slices" (Micro-Frontends)
    │   │   Hier passiert die Arbeit. Ein Feature gehört einem Team.
    │   │
    │   ├── 📂 inventory-feature
    │   │   ├── 📂 src/commonMain
    │   │   │   ├── 📂 api      <-- Public Interface (Der Vertrag nach außen)
    │   │   │   ├── 📂 ui       <-- Screens & Components (Internal)
    │   │   │   └── 📂 data     <-- Repository & SSoT (Internal)
    │   │   └── build.gradle.kts
    │   └── 📂 auth-feature
    │
    └── 📂 core                 <-- 💡 CONCEPT: "Shared Foundation"
        │   Code, der sich selten ändert, aber überall genutzt wird.
        │
        ├── 📂 design-system    <-- UI-Baukasten (Farben, Typo, Buttons)
        ├── 📂 network          <-- HTTP Clients & Auth-Interceptor
        ├── 📂 local-db         <-- SQLDelight Schemas (Die Offline-Wahrheit)
        └── 📂 auth             <-- OAuth2 Logik (Browser Bridge für Desktop)

3. Architectural Decision Records (ADRs)

Warum haben wir das so gebaut? Hier sind die Antworten auf die "Warum nicht X?" Fragen.

ADR 001: Kotlin Multiplatform vs. Electron / Web-Wrapper

  • Kontext: Wir brauchen eine Web-App UND eine Desktop-App.
  • Entscheidung: Wir nutzen Kotlin Multiplatform (Compose).
  • Begründung:
    • Performance: Electron braucht pro App ~200MB RAM (Chromium Instanz). Unsere Desktop-Apps (Lager, Kasse) laufen auf schwacher Hardware. JVM/Native ist effizienter.
    • Type Safety: Wir teilen Business-Logik (Validation, SSoT) zwischen Web und Desktop. Mit JS/Electron müssten wir Logik duplizieren oder transpilen.
    • Offline: Echte SQL-Datenbank (SQLite) Integration ist in nativem Code robuster als im Browser-Storage.

ADR 002: Multiple App Shells vs. One "Super-App"

  • Kontext: Wir haben Lagerarbeiter, Kassierer und Manager.
  • Entscheidung: Wir bauen pro Rolle eine eigene "Shell" (Executable).
  • Begründung:
    • Security (Web): "Tree Shaking". Wenn der Code für "Admin-User-Löschen" gar nicht erst in der warehouse-app.js enthalten ist, kann er auch nicht gehackt werden.
    • Focus (Desktop): Die Lager-App startet schneller und hat weniger Bugs, weil sie den Code für das Rechnungswesen gar nicht lädt.
    • Flexibilität: Wir können Features wiederverwenden. Das Feature auth-feature ist in ALLEN Apps, inventory-feature nur in zweien.

ADR 003: Single Source of Truth (SSoT) via Database

  • Kontext: Desktop-Apps werden in Hallen mit schlechtem WLAN genutzt.
  • Entscheidung: Database First Architecture.
  • Begründung:
    • Klassisch (UI -> API -> UI) führt zu weißen Screens und Ladekreisen bei Netzschwankungen.
    • Wir nutzen UI -> Local DB <- Sync -> API.
    • Das UI zeigt immer Daten an (auch wenn sie 10 Minuten alt sind). Der User kann arbeiten. Sync passiert transparent im Hintergrund.

ADR 004: Docker für alles (außer Desktop Runtime)

  • Kontext: "Bei mir läuft's aber..." Probleme.
  • Entscheidung: Das gesamte Backend + Web-Frontend Build-Pipeline läuft in Docker.
  • Begründung:
    • Die docker-compose.yml ist die Wahrheit.
    • Für die Desktop-Entwicklung nutzen wir Gradle lokal, aber der Server, gegen den entwickelt wird, läuft im Container. Das garantiert Identität zwischen Dev und Prod.

4. Guidelines: Wo gehört mein Code hin?

Wenn du neuen Code schreibst, stelle dir diese Fragen:

Q1: Ist es Business Logik (z.B. "Preis berechnen")?

  • ➡️ Gehört in /backend/services/.../domain (Server-Side Validierung ist Pflicht).
  • ➡️ UND optional in /frontend/features/.../domain (für schnelle UI-Feedback, aber Server hat das letzte Wort).

Q2: Ist es ein UI-Element (z.B. "Runder Button")?

  • ➡️ Gehört in /frontend/core/design-system.
  • 🛑 Stop! Baue keine Custom Buttons in deinem Feature-Ordner. Nutze das Design System. Wenn etwas fehlt, erweitere das Design System.

Q3: Ich brauche Daten von einem anderen Service.

  • Szenario: Im "Checkout" (Kasse) brauche ich den Produktnamen aus dem "Inventory".
  • Falsch: CheckoutService ruft InventoryService Datenbank direkt ab.
  • Richtig (Backend): CheckoutService ruft InventoryService via REST/gRPC über das Gateway.
  • Richtig (Frontend): Das Checkout-Feature kennt das Inventory-Feature nicht. Es bekommt nur eine productId. Wenn es Details anzeigen muss, nutzt es entweder ein eigenes minimales Datenmodell oder fragt das Backend.

Q4: Auth Token Handling

  • Niemals: httpClient.header("Authorization", token) manuell aufrufen.
  • Immer: Nutze den konfigurierten Client aus dem DI-Container: get(named("apiClient")). Die Architektur kümmert sich um Refresh und Injection.

5. Das "Mental Model" für Entwickler

Stell dir unsere App wie einen Lego-Baukasten vor.

  1. Core (Platte): Das Fundament (Auth, Network, Design). Muss immer da sein.
  2. Features (Steine): Bunte Bausteine (Inventory, Cart, Profile). Sie berühren sich seitlich nicht (keine direkten Abhängigkeiten).
  3. Shells (Modelle): Das fertige Haus.
  • Haus A (Admin Portal) nutzt alle Steine.
  • Haus B (Lager App) nutzt nur die grünen Steine (Inventory).

Dein Job als Entwickler ist es meistens, einen neuen Stein (Feature) zu bauen oder einen bestehenden zu verbessern. Du musst dich selten um das Fundament oder das fertige Haus kümmern.