Zum Inhalt

Evaluierung: Datenbank-Migrationsstrategie — Neues PostgreSQL-Schema für v2

Kontext

Die v2-Entwicklung (Laravel + Nuxt 3) teilt sich aktuell eine einzige MySQL-Datenbank mit dem Legacy-System (Yii 1). Beide Systeme lesen und schreiben in dieselben 64+ Tabellen. Das erzeugt erhebliche Einschränkungen:

  • Schema ist eingefroren: Spalten dürfen nicht umbenannt werden (würde Yii brechen)
  • Kryptische Spaltennamen: METTRACK-Codes wie I4201, C2303, K4601 statt serial_number, next_cal_date, company
  • Laufzeit-Mapping-Layer: HasFieldMapping-Trait + field_configuration-Tabelle übersetzen zwischen API-Namen und DB-Codes → Komplexität + Performance-Overhead
  • Keine Schema-Freiheit: Laravel darf nur 1 eigene Tabelle (personal_access_tokens) anlegen
  • Konflikte: DMS-Inbox-Verarbeitung kann nicht parallel laufen; Report-Engine existiert doppelt
  • Legacy-Ballast: MD5/SHA1-Passwort-Upgrade, V1CompatResponse-Middleware

Diese Evaluierung prüft, ob ein neues PostgreSQL-Schema die v2-Entwicklung vereinfachen würde.


A. Vorteile eines neuen PostgreSQL-Schemas

A.1 Wegfallende Komplexität

Komponente Aktuell Nach Migration
HasFieldMapping-Trait Jedes Model braucht Runtime-Mapping aus field_configuration Entfällt — Spalten heißen direkt serial_number etc.
FieldMappingService Lädt + cached Mappings pro Tabelle Entfällt
field_configuration-Tabelle 100+ Einträge, shared mit Yii, read-only für Laravel Entfällt (oder wird zu einer optionalen Admin-Konfig)
V1CompatResponse-Middleware Transformiert v2 → v1 JSON-Format Entfällt nach v1-Abschaltung
Legacy-Passwort-Handling MD5/SHA1-Erkennung + Upgrade in User.php Entfällt — einmalige Migration auf bcrypt
DB-Konventionen in CLAUDE.md "NEVER rename DB columns", "Only ONE new table" Entfällt — freie Schema-Evolution

A.2 Bessere Datenmodellierung

  • Lesbare Spalten: inventory.serial_number statt inventory.I4202
  • Echte Datentypen: BOOLEAN statt TINYINT(1), native UUID, TIMESTAMPTZ, JSONB
  • Proper Foreign Keys: Echte FK-Constraints statt lose String-Referenzen
  • Normalisierung: Custom-Fields könnten in JSONB oder EAV-Tabellen statt 25+ Spalten pro Tabelle
  • Schema-Evolution: Laravel-Migrations ohne Rücksicht auf Yii

A.3 PostgreSQL-spezifische Vorteile

  • JSONB: Flexible custom fields mit Indexierung (GIN-Index)
  • Partial Indexes: z.B. Index nur auf aktive Geräte
  • CTEs/Window Functions: Bessere Auswertungen (Kalibrierhistorie, Fälligkeitsberichte)
  • Full-Text Search: Native tsvector/tsquery statt MySQL FULLTEXT
  • LISTEN/NOTIFY: Echtzeit-Events ohne Polling
  • Array-Typen: Multi-Value-Felder ohne Junction-Tabellen

B. Schema-Design-Prinzipien (Vorschlag)

B.1 Namenskonventionen

  • Tabellen: snake_case, Plural (inventories, calibrations, customers)
  • Spalten: snake_case, beschreibend (serial_number, next_calibration_date)
  • Primary Keys: id als UUID (nativ)
  • Foreign Keys: {tabelle_singular}_id (z.B. customer_id, inventory_id)
  • Timestamps: created_at, updated_at (Laravel-Standard)

B.2 Kern-Tabellen (Beispiel-Redesign)

inventories (statt inventory mit 80+ Ixxxx-Spalten)
├── id                  UUID PK     (statt MTAG VARCHAR)
├── asset_number        VARCHAR     (statt I4201)
├── serial_number       VARCHAR     (statt I4202)
├── description         TEXT        (statt I4203)
├── manufacturer        VARCHAR     (statt I4206)
├── model               VARCHAR     (statt I4207)
├── status              SMALLINT    (statt I4211)
├── cal_interval_months INTEGER     (statt I4220)
├── customer_id         UUID FK     (statt ktag VARCHAR)
├── parent_id           UUID FK     (statt I4217 → I4201 Lookup)
├── custom_fields       JSONB       (statt I4244-I4259, 15+ Spalten)
├── created_at          TIMESTAMPTZ
├── updated_at          TIMESTAMPTZ
└── deleted_at          TIMESTAMPTZ (Soft Deletes)

B.3 Custom Fields via JSONB

Statt 25+ fester Custom-Spalten pro Tabelle (I4244-I4259) ein JSONB-Feld:

{
  "custom_text_1": "Wert",
  "custom_date_1": "2026-01-15",
  "custom_decimal_1": 42.5
}
Flexibler, unbegrenzte Custom Fields, durchsuchbar via GIN-Index.


C. Migrationsstrategie

Option 1: Graduelle Migration (empfohlen)

Phase 1: Neue PostgreSQL-DB parallel aufsetzen
  └─ Laravel bekommt zweite DB-Connection (`pgsql`)
  └─ Neue Tabellen nur noch in Postgres
  └─ Bestehende Tabellen bleiben in MySQL (Yii liest/schreibt weiter)

Phase 2: Tabelle-für-Tabelle migrieren
  └─ ETL-Script: MySQL → Postgres (METTRACK-Codes → lesbare Namen)
  └─ Laravel-Model auf neue Tabelle umstellen
  └─ HasFieldMapping für migrierte Tabellen entfernen
  └─ Dual-Read: Fallback auf MySQL falls nötig

Phase 3: v1-Abschaltung
  └─ Yii-System abschalten / read-only setzen
  └─ V1CompatResponse-Middleware entfernen
  └─ MySQL-DB archivieren

Vorteil: Kein Big-Bang, schrittweise testbar, Rollback pro Tabelle möglich.

Option 2: Big-Bang-Migration

  • Komplett-Export MySQL → Postgres zu einem Stichtag
  • Yii wird gleichzeitig abgeschaltet
  • Risiko: Erfordert vollständige v2-Feature-Parität vor Umstellung

Empfehlung: Option 1 (graduell)


D. Auswirkungen auf bestehenden Code

D.1 Laravel — Was sich ändert

Datei/Bereich Änderung
config/database.php Neue pgsql-Connection als Default
app/Models/*.php Neue Tabellennamen, lesbare Spalten, native Typen
app/Models/Concerns/HasFieldMapping.php Entfällt komplett
app/Services/FieldMapping/ Entfällt komplett
database/migrations/ Neue Migrations für Postgres-Schema
app/Http/Resources/Api/V2/ Vereinfacht (kein toApiArray() mehr, direkte Attribute)
app/Http/Middleware/V1CompatResponse.php Entfällt nach v1-Abschaltung
app/Models/User.php Legacy-Passwort-Logik entfernen (Zeilen 151-227)
database/seeders/ Anpassung auf neues Schema
tests/ Anpassung auf neue Spaltennamen

D.2 Frontend-v2 — Minimaler Impact

  • Frontend spricht nur mit Laravel API (/api/v2/)
  • API-Feldnamen bleiben identisch (sind bereits lesbar: serial_number etc.)
  • Keine Änderungen nötig, solange API-Contract gleich bleibt

D.3 Neue Artisan-Commands (benötigt)

  • php artisan migrate:from-legacy — ETL-Command für Datentransfer MySQL → Postgres
  • Mapping-Tabelle: METTRACK-Code → neuer Spaltenname (einmalig)

E. Risiken und Herausforderungen

Risiko Schwere Mitigation
Datenverlust bei Migration Hoch Checksummen, Validierungs-Queries, Rollback-Plan
v1-Feature noch nicht in v2 Mittel Feature-Parität-Matrix erstellen vor Phase 3
Downtime bei Umstellung Mittel Dual-Write in Phase 2 minimiert Downtime
MetTeam-Integration (nAssetUID etc.) Mittel FK-Mapping-Tabelle für externe IDs beibehalten
Custom Fields Migration Niedrig JSONB ist flexibler als feste Spalten
Team-Einarbeitung PostgreSQL Niedrig Laravel abstrahiert 95% der DB-Unterschiede

F. Empfohlener Phasenplan

Phase 1 — Fundament

  1. PostgreSQL in Docker-Setup integrieren
  2. Neues Schema-Design für Kerntabellen finalisieren
  3. Erste Laravel-Migration für Postgres erstellen
  4. ETL-Command-Grundgerüst (migrate:from-legacy)

Phase 2 — Kern-Migration

  1. inventories migrieren (wichtigste Tabelle, 80+ Spalten → ~20 + JSONB)
  2. customers migrieren
  3. calibrations + results migrieren
  4. users migrieren (inkl. Passwort-Upgrade auf bcrypt)
  5. Je Tabelle: Model anpassen, HasFieldMapping entfernen, Tests aktualisieren

Phase 3 — Neben-Tabellen

  1. Alle verbleibenden Tabellen migrieren
  2. V1CompatResponse-Middleware entfernen
  3. FieldMappingService + HasFieldMapping-Trait entfernen
  4. field_configuration-Tabelle entfernen oder zu Admin-Config umbauen

Phase 4 — Abschluss

  1. Yii read-only setzen oder abschalten
  2. MySQL-DB archivieren
  3. Dokumentation aktualisieren
  4. Performance-Tests und Optimierung

G. Fazit

Eine PostgreSQL-Migration würde die v2-Entwicklung erheblich vereinfachen:

  • ~30% weniger Code durch Wegfall von FieldMapping-Layer, V1Compat, Legacy-Passwort-Handling
  • Deutlich bessere Developer Experience durch lesbare Spaltennamen
  • Mehr Flexibilität durch eigenes Schema ohne Yii-Rücksicht
  • Zukunftssichere Basis mit PostgreSQL-Features (JSONB, native UUID, Partial Indexes)

Der graduelle Ansatz (Option 1) minimiert das Risiko und erlaubt parallelen Betrieb während der Übergangsphase. Das Frontend-v2 wäre von der Migration nicht betroffen.


Betroffene Dateien (Referenz)

  • laravel/app/Models/Concerns/HasFieldMapping.php — Entfällt nach Migration
  • laravel/app/Services/FieldMapping/FieldMappingService.php — Entfällt nach Migration
  • laravel/app/Http/Middleware/V1CompatResponse.php — Entfällt nach v1-Abschaltung
  • laravel/app/Models/User.php (Zeilen 151-227) — Legacy-PW-Logik entfällt
  • laravel/config/database.php — pgsql als Default
  • laravel/CLAUDE.md — DB-Konventionen aktualisieren
  • laravel/database/migrations/ — Neues Migrations-Set
  • laravel/app/Models/*.php (90 Models) — Schrittweise anpassen