* 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
156 lines
8.2 KiB
Markdown
156 lines
8.2 KiB
Markdown
# 🏗 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.
|
|
|
|
```text
|
|
/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.
|