Zum Hauptinhalt springen

Case Study: Migration großer C#- und TypeScript-Codebasen ohne Entwicklungsstopp

Erfahrungsbericht aus mehreren Enterprise-Projekten

Diese Case Study beschreibt die kontrollierte Migration großer C#- und TypeScript-Codebasen auf neue Framework- und Sprachversionen, ohne die laufende Entwicklung zu stoppen.

In mehreren Projekten habe ich bestehende Anwendungen migriert – teilweise von kleineren Lösungen mit 10 Projekten, teilweise von großen Monolithen mit über 200 Projekten.

Die zentrale Anforderung war dabei stets:

  • Keine Unterbrechung der laufenden Entwicklung
  • Kein Big-Bang-Rewrite
  • Minimierung von Projektrisiken
  • Kontrollierte, nachvollziehbare Migration

Die Migrationen habe ich bislang eigenverantwortlich durchgeführt – mit einer klar strukturierten, phasenweisen Vorgehensweise.


Grundprinzip: Funktionale Identität vor Modernisierung

Die wichtigste Entscheidung in meinen Migrationen war:

In der ersten Phase wird nicht refaktoriert.
Es wird ausschließlich funktionale Identität hergestellt.

Warum?

Wenn während der Migration gleichzeitig Refactoring oder Logikänderungen stattfinden, ist später nicht mehr nachvollziehbar, ob ein unerwünschter Nebeneffekt durch:

  • eine fehlerhafte Übertragung
  • eine Framework-Änderung
  • oder eine neue Logik nach dem Refactoring

entstanden ist.

Deshalb ist die erste Phase immer eine 1:1-Übertragung der bestehenden Funktionalität.


Architektur der Migration

1️⃣ Isolierte Migrations-Codebasis

Die Migration erfolgt in einer separaten Codebasis:

  • als Draft-Branch (z. B. migration/net10)
  • oder als separates Repository

Das bestehende Team-Repository bleibt unverändert und produktiv nutzbar.

Während ich migriere, arbeitet das Team weiterhin im ursprünglichen Repository.


2️⃣ Phase 1 – Übertragung 1:1

Vorgehen:

  • Neues Projekt auf Basis des aktuellen Templates erstellen
  • DTOs, POCOs und Domänenobjekte 1:1 kopieren
  • Schrittweise so lange übernehmen, bis das neue Projekt kompiliert

Breaking Changes werden:

  • temporär auskommentiert
  • mit TODO dokumentiert
  • ggf. durch Mocks oder Platzhalter ersetzt

In dieser Phase gilt:

  • Keine strukturellen Änderungen
  • Keine Modernisierung
  • Keine Verzeichnisumstrukturierung

Der Compiler dient als Migrationswerkzeug.

Ziel: Kompilierbarkeit bei funktionaler Gleichheit.


3️⃣ Laufende Synchronisierung mit dem Team

Während der Migration:

  • arbeitet das Team im ursprünglichen Repository weiter
  • ich synchronisiere regelmäßig deren Änderungen in die Migrations-Codebasis

Dadurch entsteht kein großer Merge-Konflikt am Ende.
Der finale Delta bleibt minimal.

info

Die kontinuierliche Synchronisierung ist entscheidend, um die Migration ohne Entwicklungsstopp zu ermöglichen.


4️⃣ Phase 2 – Konsolidierung & Auflösung der TODOs

Erst nachdem alle relevanten Objekte übertragen wurden:

  • werden TODO-Markierungen systematisch beseitigt
  • werden Breaking Changes sauber implementiert
  • wird Code an neue Sprach- oder Framework-Version angepasst

Neue Sprachfeatures (z. B. strict mode in TypeScript oder neue C#-Features) werden nicht erzwungen, solange sie massive Compile-Fehler verursachen würden.
Stattdessen werden sie dokumentiert und später kontrolliert aktiviert.


5️⃣ Refactoring & Struktur-Anpassung

Erst nach stabiler Kompilierung:

  • Anpassung der Verzeichnisstruktur
  • Refactoring
  • kosmetische Verbesserungen
  • Modernisierung von APIs
  • Reduktion technischer Schuld

Damit bleibt klar unterscheidbar:

  • Was war reine Migration?
  • Was ist bewusste Verbesserung?

6️⃣ API-Contracts & Datenbank

In der beschriebenen Code-Migration:

  • bleiben API-Contracts unverändert
  • bleiben Datenbank-Entities unverändert
  • werden weder CI/CD-Pipeline noch Datenbank-Struktur migriert

Diese Themen werden als separate Migration betrachtet.

Dadurch bleibt die Migration technisch kontrollierbar und klar abgegrenzt.


7️⃣ Validierung: Tests & Dry-Run-Strategie

Zur Absicherung der Migration nutze ich:

  • Unit-Tests
  • Integrationstests
  • gezielte manuelle Tests

Ein wichtiger Bestandteil ist die Dry-Run-Strategie.

Dabei wird das System um spezielle Testfunktionen erweitert:

  • Erzwingen bestimmter Systemparameter, z.B. das aktuelle Datum in der Zukunft
  • Simulation von Edge-Cases
  • Erweiterte UI-Funktionen für Tester
  • Ausführung kritischer Prozesse ohne Persistierung in der Datenbank

So lassen sich Grenzszenarien testen, ohne produktive Daten zu verändern.

Das ermöglicht:

  • realitätsnahe Tests
  • Validierung komplexer Prozesse
  • Minimierung von Risiko vor Umschaltung

8️⃣ Finalisierung & Umschaltung

Wenn die Migration abgeschlossen ist:

  1. Änderungsstopp im ursprünglichen Repository
  2. Letzte Synchronisierung
  3. Finaler Build und Tests
  4. Atomare Umschaltung auf die neue Codebasis (meist übers Wochenende)

Am Montag arbeiten alle Entwickler mit dem neuen Repository.

Falls Probleme auftreten:

  • wird temporär wieder das alte Repository genutzt
  • die Finalisierung wird am nächsten Wochenende wiederholt

Diese Vorgehensweise ermöglicht faktisch eine kontrollierte Zero-Downtime-Migration auf Code-Ebene, da der laufende Entwicklungsprozess bis zum finalen Cut nicht gestoppt wird.


Optional: Paralleler Betrieb

Wenn Infrastruktur vorhanden ist, kann die neue Version:

  • parallel betrieben
  • auf separater Umgebung validiert
  • schrittweise aktiviert

In vielen Fällen erfolgte die Bereitstellung zunächst im Dev-Umfeld und wurde nach erfolgreicher Validierung übernommen.


Ergebnis meiner Migrationen

Mit dieser Strategie konnte ich:

  • Codebasen mit 10 bis über 200 Projekten migrieren
  • parallele Weiterentwicklung im Team ermöglichen
  • Risiken kontrollieren
  • Migration und Modernisierung sauber trennen
  • Umschaltung atomar durchführen
  • Rückfalloptionen offenhalten

Wichtiger Hinweis

Die hier beschriebenen Schritte sind kein allgemeingültiges Kochrezept.

Jede Migration:

  • hat eigene Abhängigkeiten
  • eigene Risiken
  • eigene Architektur-Besonderheiten

Eine erfolgreiche Migration erfordert immer:

  • individuelle Analyse
  • klare Zieldefinition
  • strukturierte Planung

Gerne unterstütze ich bei der Bewertung und Planung komplexer Code-Migrationen.