Drei Plattformen, drei Implementierungen derselben Business-Logik. Jeder plattformübergreifende Bug wird dreimal behoben. Jede Regeländerung wird dreimal implementiert. Jede Regression wird dreimal debuggt. Wenn Ihnen das bekannt vorkommt, haben Sie kein Code-Qualitätsproblem. Sie haben ein Architekturproblem.

Ich habe an zwei grossen plattformübergreifenden Systemen gearbeitet, bei denen das der Ausgangspunkt war: eine globale Autoverkaufsplattform für drei Kontinente und eine Smart-Energy-Plattform, die auf iOS, Android und Web lief. In beiden Fällen waren die Teams talentiert. Der Code war solide. Aber die Architektur garantierte Inkonsistenz.

Die Duplikationsfalle

Wenn man dasselbe Produkt für mehrere Plattformen baut, ist der natürliche Ansatz ein Team pro Plattform. iOS macht ihr Ding. Android macht ihres. Web erledigt den Rest. Jedes Team schreibt seine eigene Implementierung jeder Business-Regel, Validierung und jeden Workflow.

Anfangs fühlt sich das produktiv an. Jedes Team bewegt sich in eigenem Tempo, trifft eigene technische Entscheidungen und liefert nach eigenem Zeitplan. Die Probleme tauchen um Monat drei auf.

Eine Preisregel wird auf Web aktualisiert, aber nicht auf iOS. Eine Validierungsmeldung sagt auf Android eine Sache und überall sonst etwas anderes. Ein neuer Markt startet und drei Teams müssen dieselben Anforderungen unabhängig implementieren, wobei jedes die Spezifikation leicht unterschiedlich interpretiert.

Die Bugs sind nicht zufällig. Sie sind strukturell. Ihre Architektur produziert sie zuverlässig.

Warum gemeinsame Bibliotheken scheitern

Der erste Instinkt ist meistens eine gemeinsame Bibliothek. Die gemeinsame Logik extrahieren, in ein Paket packen, überall importieren. In der Theorie funktioniert das. In der Praxis erzeugt es andere Probleme.

Gemeinsame Bibliotheken schaffen Kopplung auf der Abhängigkeitsebene. Jede Plattform muss die Bibliothek synchron aktualisieren, oder man landet bei Version-Drift, der noch schwerer zu debuggen ist als die ursprüngliche Duplikation. Die Bibliothek wird zum Engpass: Wer besitzt sie? Wer reviewt Änderungen? Wer entscheidet, wann released wird?

Schlimmer noch: Gemeinsame Code-Bibliotheken setzen eine gemeinsame Runtime voraus. Eine JavaScript-Validierungsfunktion läuft nicht nativ auf iOS. Man landet entweder dabei, sich auf den kleinsten gemeinsamen Nenner zu beschränken, oder man pflegt plattformspezifische Wrapper um den gemeinsamen Code, was den Zweck zunichte macht.

Das eigentliche Problem ist nicht, dass der Code geteilt werden muss. Es ist, dass die Entscheidungen geteilt werden müssen. Welche Felder erscheinen auf diesem Screen? Welche Validierungsregeln gelten für diese Eingabe? Welche Optionen sind in diesem Markt verfügbar? Das sind Business-Entscheidungen, kein Code.

Konfigurationsgetriebene Architektur

Das Muster, das tatsächlich funktioniert, ist die Business-Logik in eine deklarative Konfigurationsschicht zu extrahieren. Statt Code zu schreiben, der Business-Regeln auf jeder Plattform implementiert, schreibt man Code, der ein gemeinsames Regelwerk interpretiert.

Man kann es sich als Trennung von "Was" und "Wie" vorstellen. Die Konfiguration definiert, was passieren soll: welche Felder anzeigen, welche Validierungen ausführen, welche Optionen anbieten. Jede Plattform entscheidet, wie sie diese Konfiguration mit ihren nativen Mitteln rendert und ausführt.

In der Praxis sieht das meist wie ein JSON- oder YAML-Schema aus, das beschreibt:

  • UI-Struktur: welche Screens existieren, welche Felder sie enthalten, wie sie geordnet sind
  • Validierungsregeln: Pflichtfelder, Format-Constraints, bedingte Logik
  • Business-Regeln: was in welchem Markt verfügbar ist, was von was abhängt
  • Lokalisierungskontext: Labels, Meldungen und Formatierung je Locale

Jede Plattform wird zum Interpreter. iOS liest dieselbe Konfiguration wie Android wie Web. Wenn sich eine Business-Regel ändert, ändert sie sich an einer Stelle. Wenn ein neuer Markt startet, ist es eine Konfigurationsänderung, kein Entwicklungsprojekt.

Das Regelwerk entwerfen

Das Schwierigste ist nicht der Bau des Interpreters. Es ist das Design des Regelwerks. Macht man es falsch, landet man bei einer Konfigurationssprache, die genauso komplex und fragil ist wie der Code, den sie ersetzt hat.

Einige Prinzipien, die sich über die Projekte, an denen ich gearbeitet habe, bewährt haben:

Konkret anfangen, später abstrahieren. Versuchen Sie nicht, am ersten Tag ein universelles Konfigurationsschema zu bauen. Beginnen Sie damit, was Ihre Plattformen tatsächlich tun. Kartieren Sie die Screens, die Felder, die Regeln. Suchen Sie nach den Mustern. Das Konfigurationsformat sollte aus echten Anforderungen entstehen, nicht aus einer Whiteboard-Übung.

Das Schema flach halten, wo möglich. Tiefes Verschachteln erzeugt Komplexität, über die man schwer nachdenken kann und die noch schwerer zu debuggen ist. Wenn ein Techniker meldet, dass eine Konfigurationsoption auf seinem Tablet nicht angezeigt wird, möchte man das Regelwerk ansehen und die Antwort in Sekunden sehen, nicht durch fünf Ebenen bedingter Vererbung navigieren.

Die Konfiguration für Menschen lesbar machen. Jemand auf der Business-Seite sollte die Konfiguration lesen und grob verstehen können, was sie sagt. Wenn Ihr Konfigurationsformat einen Entwickler zur Interpretation braucht, haben Sie den Engpass nur verschoben, nicht eliminiert.

Das Schema versionieren. Ihr Konfigurationsformat wird sich weiterentwickeln. Alte Konfigurationen müssen weiter funktionieren, während neue Fähigkeiten hinzugefügt werden. Bauen Sie Versionierung von Anfang an ein.

Wann auslagern, wann lassen

Nicht alles gehört in die Konfigurationsschicht. Die Versuchung ist, alles zu extrahieren und ein konfigurationsgetriebenes System zu schaffen, das jede mögliche Interaktion beschreibt. Das führt zu einer Konfigurationssprache, die im Grunde eine Programmiersprache mit schlechterem Tooling ist.

In die Konfiguration auslagern, wenn:

  • Die Logik plattformübergreifend wirklich gleich ist (Validierungsregeln, Business-Constraints, Datenanforderungen)
  • Business-Stakeholder sie ohne Entwicklerbeteiligung ändern müssen
  • Die Logik sich häufig ändert oder je nach Markt oder Region variiert

Im Plattform-Code lassen, wenn:

  • Es genuinen plattformspezifischen Charakter hat (Gesten, native Navigationsmuster, Barrierefreiheits-APIs)
  • Es komplexe Interaktionen umfasst, die ein deklaratives Format nicht sauber ausdrücken kann
  • Es sich selten ändert und nicht über Märkte hinweg variiert

Das Ziel ist, die Business-Entscheidungen in die Konfiguration zu legen und die Plattform-Entscheidungen im Code zu belassen. Eine saubere Trennung hier bedeutet, dass Änderungen an der Business-Logik keine Plattform-Releases erfordern und Plattform-Verbesserungen kein Business-Logik-Review brauchen.

Den Übergang schrittweise vollziehen

Man muss nicht alles auf einmal neu schreiben. Tatsächlich sollte man das nicht. Beide Projekte, an denen ich gearbeitet habe, folgten einem inkrementellen Ansatz:

Einen Bereich mit hohem Churn wählen. Finden Sie den Teil Ihrer Applikation, in dem plattformübergreifende Bugs am häufigsten auftreten und Business-Regeln sich am öftesten ändern. Hier zahlt sich konfigurationsgetriebene Architektur am schnellsten aus.

Den Interpreter zuerst auf einer Plattform bauen. Es auf Web (oder welche Plattform auch immer am schnellsten iteriert) zum Laufen bringen, das Konfigurationsformat validieren und dann den Interpreter auf andere Plattformen portieren. Die Konfiguration selbst bleibt dieselbe.

Screen für Screen migrieren. Versuchen Sie nicht, die gesamte Applikation in einem Sprint umzustellen. Einen Screen oder Funktionsbereich in die Konfiguration verschieben, validieren, dann den nächsten. Jede Migration reduziert die plattformübergreifende Bug-Oberfläche sofort.

Messen, was zählt. Plattformübergreifende Bugs vor und nach dem Wechsel verfolgen. Auf der Mercedes-Benz-Verkaufsplattform sanken plattformübergreifende Bugs nach dem Wechsel zu einem JSON-basierten Konfigurationssystem um 90 Prozent. Auf der Siemens Inverter-Plattform ermöglichte derselbe Ansatz vollständige Feature-Parität über iOS, Android und Web mit einer einzigen Source of Truth.

Worauf Sie achten sollten

Schleichende Konfigurationskomplexität. Die Konfigurationsschicht wird wachsen wollen. Jeder Sonderfall wird jemanden dazu verleiten, eine weitere Bedingung, einen weiteren Override, einen weiteren Spezialfall hinzuzufügen. Widerstehen Sie dem. Wenn Ihre Konfiguration anfängt, eine eigene Test-Suite zu brauchen, sind Sie zu weit gegangen.

Tooling-Lücken. Code hat Linter, Type-Checker und IDEs. Konfiguration hat das oft nicht. Investieren Sie früh in Validierungs-Tooling für Ihr Konfigurationsformat. Eine defekte Konfiguration sollte zur Build-Zeit laut scheitern, nicht still zur Laufzeit.

Die "nur dieses eine Mal"-Ausnahme. Jemand wird eine Regel auf einer Plattform hard-coden wollen, weil es schneller geht. Es beginnt immer mit einer Ausnahme. Innerhalb eines Quartals pflegen Sie wieder divergierende Implementierungen. Machen Sie den Konfigurationspfad zum einfachen Pfad.

Konfigurationsgetriebene Architektur geht nicht darum, plattformspezifischen Code zu eliminieren. Es geht darum, sicherzustellen, dass die Entscheidungen, die konsistent sein sollten, konsistent sind, durch Design statt durch Disziplin. Wenn Sie trennen, was das Produkt tut, von dem, wie jede Plattform es tut, hören plattformübergreifende Bugs auf, eine Unvermeidlichkeit zu sein, und werden zur Seltenheit.

Verwandter Artikel: Wo AI in der Enterprise-Architektur wirklich hilft