„Ihre Aufgabe ist es, dieses System neu zu schreiben. Es treibt unseren gesamten Betrieb an. Oh, und es ist in APL geschrieben.“ So begann meine Reise mit dieser Neufassung des Vermächtnisses „Refactoring eines 40 Jahre alten Softwareprojekts“. Für diejenigen, die mit APL nicht vertraut sind: Es handelt sich um eine Programmiersprache aus den 1960er Jahren, die für ihre einzigartige mathematische Notation und Array-Manipulationsfunktionen bekannt ist. Entwickler zu finden, die APL kennen, ist heute ungefähr so einfach wie die Suche nach einem Diskettenlaufwerk in einem modernen Computer.
Das System ist über vier Jahrzehnte gewachsen. Es begann als einfaches Bestandsverwaltungstool und entwickelte sich zu einem umfassenden ERP-System. Mehr als 460+ Datenbanktabellen. Unzählige Geschäftsregeln, die in den Code eingebettet sind. Komplexe Integrationen mit jedem Teil des Geschäftsprozesses. Das System ist das Rückgrat eines Produktionsbetriebs.
Die Mission war klar, aber entmutigend: dieses System mit .NET, PostgreSQL und React zu modernisieren.
Der Haken? Das Unternehmen musste während des Übergangs weiterlaufen. Keine Ausfallzeiten. Kein Datenverlust. Keine Unterbrechung des täglichen Betriebs.
Das war nicht nur eine technische Herausforderung. Es war eine Lektion im Umgang mit Komplexität, im Verständnis von Legacy-Geschäftsprozessen und im Umgang mit organisatorischen Dynamiken.
Hier ist also diese Geschichte und die Lektionen, die wir gelernt haben.
Anfangszustand: Das Vermächtnis verstehen
Die erste Herausforderung bestand darin, zu verstehen, wie dieses massive System tatsächlich funktioniert. Die Codebasis war über vier Jahrzehnte organisch gewachsen und wurde von einem einzigen Entwicklungsteam gepflegt. Sie waren jetzt in ihren 60ern und wollten in den Ruhestand gehen.
In die erste Codebasis-Überprüfung zu gehen, war wie das Öffnen einer Zeitkapsel. Die prägnante Syntax von APL bedeutete, dass komplexe Geschäftslogik in nur wenigen Zeilen geschrieben werden konnte. Schön, wenn man es lesen könnte.
Das ursprüngliche Team war bei der Wissensvermittlung von unschätzbarem Wert. Sie kannten jede Eigenart, jeden Sonderfall, jede Geschäftsregel, die im Laufe der Jahrzehnte hinzugekommen war. Aber es gibt nur so viel, was man aus Gesprächen lernen kann. Die Dokumentation war spärlich. Was existierte, war überholt (was man im Kopf hat, braucht man ja nicht aufschreiben). Die eigentliche Dokumentation befand sich in den Köpfen der ursprünglichen Entwickler.
Wir haben Wochen damit verbracht, die Funktionalität des Systems zu kartieren:
- Der Kernherstellungsprozess war auf 50+ Tabellen mit komplexen Abhängigkeiten verteilt
- Die Bestandsverwaltung betraf fast jeden Teil des Systems
- Über Jahrzehnte hinweg wurden benutzerdefinierte Reporting-Tools entwickelt, um spezifische Geschäftsanforderungen zu erfüllen
- Integrationspunkte mit externen Komponenten wurden durch ein Labyrinth von gespeicherten Prozeduren gehandhabt
Tabellen, die mit einfachen Schemata begannen, waren auf Hunderte von Spalten angewachsen. Einige Spalten wurden nicht mehr verwendet, konnten aber nicht entfernt werden, weil niemand sicher war, ob ein obskurer Bericht sie noch benötigte.
Eine besondere Herausforderung war die Diskrepanz zwischen den Geschäftsprozessen und deren technischer Umsetzung. Das Unternehmen würde einen einfachen Workflow beschreiben, aber die technische Implementierung würde Komplexitätsebenen aufzeigen, die über Jahre hinweg durch Grenzfälle und spezielle Anforderungen hinzugefügt wurden.
Wir brauchten einen systematischen Ansatz, um dieses Biest zu verstehen. Zunächst haben wir Geschäftsprozesse und die entsprechenden technischen Umsetzungen abgebildet. Dies half uns, die Kernbereiche zu identifizieren, die später unsere modulare Architektur beeinflussen sollten. Noch wichtiger war, dass es uns half, das wahre Ausmaß dessen zu verstehen, womit wir es zu tun hatten.
Der Konflikt zwischen Produkt und Technik
Das Management wollte schnelle Erfolge. Sie haben uns dazu gebracht, mit den einfachsten Komponenten zu beginnen. Dies führte zu Spannungen zwischen dem Produktmanagement und dem Entwicklungsteam.
Die Perspektive des Produktmanagements war einfach: Zeigen Sie dem Unternehmen Fortschritt. Sie brauchten sichtbare Ergebnisse, um die Investition in die Neufassung zu rechtfertigen. Das Unternehmen gab viel Geld aus und wollte schnell Renditen erzielen.
Das Entwicklerteam sah eine andere Realität. Wir wussten, dass wir mit peripheren Merkmalen beginnen mussten, um auf wackeligen Beinen zu bauen. Die Kerngeschäftslogik würde im Altsystem verbleiben, was jeden Integrationspunkt komplexer macht. Diese technischen Schulden würden sich im Laufe der Zeit vermehren.
Als technischer Leiter habe ich mich entschieden gegen diesen Ansatz ausgesprochen. Mein Argument war einfach: Der Kernfertigungsprozess war das Herzstück des Systems. Jedes periphere Merkmal hing davon ab. Durch die Verschiebung der Migration haben wir ein verworrenes Netz von Abhängigkeiten zwischen alten und neuen Systemen geschaffen. Jede neue Funktion, die wir migrierten, erforderte eine komplexe Synchronisierung mit dem Legacy-Kern. Wir bauten auf Treibsand.
Ich plädierte dafür, sich zuerst auf den Kernbereich zu konzentrieren. Ja, es würde länger dauern, bis die ersten Ergebnisse sichtbar werden. Aber es sollte eine solide Grundlage für alles schaffen, was folgte. Das Unternehmen müsste länger auf sichtbare Fortschritte warten, aber die Migration insgesamt wäre schneller und zuverlässiger.
Keine der beiden Seiten lag mit ihren Zielen falsch. Das Produktmanagement hatte berechtigte Bedenken, Fortschritte zu zeigen. Das Entwicklungsteam hatte berechtigte Bedenken hinsichtlich der technischen Nachhaltigkeit. Diese Fehlausrichtung führte jedoch zu Kompromissen, die sich auf den Projektzeitplan auswirkten. Bis heute glaube ich, dass wir die Migration früher abgeschlossen hätten, wenn wir mit der Kerngeschäftslogik begonnen hätten.
Softwarearchitektur: Bauen für die Zukunft
Während der Discovery-Phase identifizierten wir unterschiedliche Geschäftsbereiche innerhalb des Systems. Dies hat uns dazu veranlasst, eine modulare monolithische Architektur zu implementieren. Jedes Modul wäre in sich geschlossen, kann aber über einen gemeinsam genutzten Ereignisbus mit anderen kommunizieren.
Wichtige architektonische Entscheidungen:
- Modularer Monolith: Jedes Modul repräsentierte einen eigenen Geschäftsbereich. Dies bot bei Bedarf einen klaren Weg zu potenziellen zukünftigen Microservices.
- Asynchrone Kommunikation: Module, die über Ereignisse mit RabbitMQ kommuniziert werden. Dies reduzierte die Kopplung und verbesserte die Widerstandsfähigkeit des Systems.
- Gemeinsame Datenbank mit Grenzen: Obwohl alle Module dieselbe PostgreSQL-Datenbank verwendeten, hatte jedes seine eigenen Tabellen und Schemata. Dies hat uns geholfen, die logische Trennung zu wahren.
- Cloud-fähiges Design: Das System wurde mithilfe von Containerisierung in AWS bereitgestellt. Eine Jenkins-Pipeline ermöglichte Bereitstellungen in mehreren Umgebungen in wenigen Minuten.
Die Herausforderung der Datensynchronisierung
Die bidirektionale Datensynchronisierung war komplexer als ursprünglich erwartet. Hier ist der Grund, warum wir bestehende CDC-Lösungen (Change Data Capture) wie Debezium nicht verwenden konnten:
- Komplexe Transformationen: Viele Legacytabellen erforderten Daten aus mehreren neuen Tabellen. Dies war keine einfache Eins-zu-Eins-Zuordnung, bei der sich CDC-Tools auszeichnen.
- Synchronisierte Geschäftslogik: Der Synchronisierungsprozess, der erforderlich ist, um Geschäftsregeln während der Transformation anzuwenden. Dies ging über das hinaus, was die meisten Replikationstools bieten.
- Bidirektionale Anforderungen: Wir mussten in beide Richtungen synchronisieren und gleichzeitig Endlosschleifen vermeiden. Das Altsystem blieb die Quelle der Wahrheit für nicht migrierte Komponenten.
Wir haben eine benutzerdefinierte Lösung mit RabbitMQ für den Nachrichtentransport entwickelt. Das hat zwar für uns funktioniert, aber die Lektion bleibt: Evaluieren Sie vorhandene Tools gründlich, bevor Sie benutzerdefinierte Lösungen entwickeln. Auch wenn Sie sie nicht vollständig verwenden können, können Sie aus ihren Ansätzen wertvolle Muster lernen.
Wichtige technische Lektionen
- Modulare Architektur zahlt sich aus: Durch den modularen monolithischen Ansatz wurde das System leichter verständlich und wartungsfreundlich. Jedes Modul hatte klare Grenzen und Verantwortlichkeiten.
- Investieren Sie in die Automatisierung der Bereitstellung: Die CI/CD-Pipeline war von entscheidender Bedeutung. Es ermöglichte uns, sicher und häufig zu implementieren und das Risiko jeder Änderung zu reduzieren.
- Nachrichtenbasierte Integration: Die asynchrone Kommunikation zwischen den Modulen bot die Flexibilität, die für die schrittweise Migration erforderlich war.
- Komplexität der Datensynchronisierung: Unterschätzen Sie nicht die Komplexität der Datensynchronisierung bei Legacymigrationen. Unabhängig davon, ob vorhandene Tools verwendet werden oder benutzerdefinierte Lösungen erstellt werden, wird dies eine große Herausforderung sein.
Der Faktor Mensch
Technische Herausforderungen sind nur ein Teil der Geschichte. Der Erfolg von Legacy-Rewrites hängt stark von der Verwaltung verschiedener Stakeholder ab:
- Das Produktmanagement muss Fortschritte sehen
- Entwicklungsteams brauchen Zeit, um die Dinge richtig zu machen
- Das Geschäft muss weiterlaufen
- Das Legacy-Team muss Wissen transferieren
Es kann schwierig sein, die richtige Balance zwischen diesen konkurrierenden Bedürfnissen zu finden.
Wir haben mehrere Ansätze gefunden, die geholfen haben:
- Regelmäßige Stakeholder-Treffen, bei denen jede Gruppe Bedenken äußern konnte
- Transparente Projektverfolgung für alle Beteiligten sichtbar
- Klare Kommunikation über technische Entscheidungen und deren Auswirkungen auf das Geschäft
- Feier von technischen und geschäftlichen Meilensteinen
- Dokumentation sowohl des fachlichen als auch des institutionellen Wissens
Ergebnisse, auf die es ankommt
Ich kann nicht oft genug betonen, wie wichtig es war, das Wissen zu dokumentieren, das in vier Jahrzehnten Betrieb des Altsystems erworben wurde. Als das ursprüngliche Team in den Ruhestand ging, hatten wir einen umfassenden Satz von Dokumenten, in denen jede Geschäftsregel und jeder Grenzfall erläutert wurde.
Nun floriert das System. Die Cloud-Infrastruktur bietet Zuverlässigkeit und Skalierbarkeit. Die modulare monolithische Architektur macht es wartbar. Die automatisierte Bereitstellungspipeline ermöglicht schnelle Updates.
Aber die Reise hat uns wertvolle Lektionen darüber gelehrt, wie man technische Anforderungen mit geschäftlichem Druck in Einklang bringt. Der Erfolg bei der Neufassung von Legacy-Versionen erfordert mehr als nur technische Exzellenz. Es erfordert ein Verständnis der Geschäftsdomäne, den Umgang mit den Erwartungen der Stakeholder und pragmatische Architekturentscheidungen.
Die Softwarearchitektur ist wichtig, aber auch der menschliche Faktor. Planen Sie für beides beim Refactoring eines 40 Jahre alten Softwareprojekts.
Ein weiterer Beitrag: 10 Open-Source-Projekte für Entwickler