Zum Inhalt springen
DedicatedPHP Kontakt

Modularer Monolith vs. Microservices in PHP: richtig entscheiden

Technische und operative Kriterien, um zwischen der Stärkung eines PHP-Monolithen und der Ausgliederung von Services zu entscheiden, ohne unnötige Komplexität zu verlagern.

Redaktionelles Entscheidungsdiagramm zwischen einem modularen PHP-Monolithen und unabhängigen, über Verträge verbundenen Services

Die Entscheidung zwischen modularem Monolithen vs. Microservices in PHP lässt sich nicht anhand der Anzahl der Module, des Alters des Codes oder der Popularität einer Architektur treffen. Eine Unternehmensanwendung kann innerhalb eines einzigen Deployments gesund wachsen, wenn sie klare Grenzen wahrt. Umgekehrt kann eine zu frühe Aufteilung einfache interne Aufrufe in ein Netz aus Verträgen, Queues, Wiederholungsversuchen und Koordinationsproblemen verwandeln.

Die hilfreiche Frage lautet nicht „Sollten wir Microservices verwenden?“, sondern „Welche fachliche Fähigkeit muss sich unabhängig weiterentwickeln, ausfallen, bereitgestellt oder skaliert werden können, und können wir die Kosten ihres Betriebs auf diese Weise tragen?“. Die Antwort muss vom Fachbereich und vom tatsächlichen Betrieb ausgehen, nicht von einem Zielbild.

Funktionales Wachstum erfordert keine getrennten Services

Funktionales Wachstum erfordert keine getrennten Services — guía visual de DedicatedPHP

Das Hinzufügen von Integrationen, asynchronen Prozessen oder Produktbereichen bedeutet nicht, dass jeder davon einen eigenen Service haben muss. Ein Monolith kann klar abgegrenzte Module, Hintergrundjobs, Queues und Adapter für externe Systeme enthalten, ohne seine betriebliche Kohärenz zu verlieren.

Der erste Eingriff besteht meist darin, die interne Kopplung zu verringern. Wenn ein Abrechnungsmodul direkt Klassen für Bestellungen importiert, deren Tabellen verändert oder interne Regeln des Bestands kennt, wird das Problem nicht automatisch gelöst, indem Code in ein anderes Repository verschoben wird. Es verwandelt sich lediglich in Netzwerk- und Datenkopplung.

Ein modularer Monolith zielt darauf ab, dass jede Fähigkeit eine explizite interne Schnittstelle, gerichtete Abhängigkeiten und eigene Regeln hat. In PHP kann sich dies in Namespaces nach Domäne, Application Contracts beziehungsweise Anwendungsschnittstellen, schlanken Controllern, definierten Use Cases und Adaptern für Persistenz oder externe APIs ausdrücken. Alles gemeinsam bereitzustellen, bleibt mit diesen Grenzen vereinbar.

Was vor einer Architekturänderung zu analysieren ist

Identifizieren Sie vor der Technologiediskussion die fachlichen Fähigkeiten: etwa Bestellverwaltung, Katalog, Identität, Abrechnung, Dokumentenverarbeitung oder Benachrichtigungen. Eine Fähigkeit entspricht nicht zwangsläufig einer Entität oder einem Bildschirm; sie bündelt Regeln und Entscheidungen, die sich aus ähnlichen Gründen ändern sollten.

  • Verantwortlicher: Bestimmen Sie, wer die Regeln pflegt, Änderungen priorisiert und bei Incidents verantwortlich ist.
  • Daten: Identifizieren Sie, welche Informationen jede Fähigkeit erstellt und verwaltet, wer sie ändern darf und welche domänenübergreifenden Lesezugriffe sie benötigt.
  • Kritische Abläufe: Zeichnen Sie den Ablauf einer relevanten Operation, einschließlich Validierungen, externer Abhängigkeiten und asynchroner Schritte.
  • Änderungsrhythmus: Unterscheiden Sie häufige Anpassungen von punktuellen Änderungen. Die Häufigkeit allein reicht nicht aus; entscheidend ist, ob sie die Koordination von Teams oder Releases erfordert.
  • Lastprofil: Trennen Sie interaktiven Traffic von Aufgaben mit hohem CPU-, Speicher-, Speicherplatzbedarf oder vielen Aufrufen an Dritte.
  • Auswirkungen eines Ausfalls: Legen Sie fest, was geschieht, wenn eine Fähigkeit für Minuten oder Stunden beeinträchtigt ist, und ob der Kern des Geschäfts weiterarbeiten kann.

Dieses Inventar deckt Abhängigkeiten auf, die oft verborgen bleiben: gemeinsame Transaktionen, direkte Abfragen fremder Tabellen, duplizierte Regeln in Controllern und geplante Aufgaben, die mehrere Domänen aktualisieren. Sie ohne deren Auflösung auszugliedern, führt zu formal getrennten, aber funktional verflochtenen Services.

Sechs Signale für einen modularen Monolithen

Diese Signale sprechen dafür, das interne Design zu stärken, statt Verantwortlichkeiten zu verteilen:

  1. Änderungen durchqueren meist mehrere Module. Wenn eine Geschäftsfunktion koordinierte Änderungen an Bestellungen, Preisen und Abrechnung erfordert, kann eine Trennung Deployments und Verträge vervielfachen.
  2. Sofortige Konsistenz ist zentral. Wenn eine Operation eine einzige Datenbanktransaktion benötigt, um kritische Invarianten zu bewahren, fügt eine verteilte Grenze komplexe Kompensationsentscheidungen hinzu.
  3. Das Team ist klein oder teilt sich die Verantwortung. Mehrere Services erfordern Betriebsdisziplin, Bereitschaften, Pipelines, Versionen und Diagnose für jede Einheit.
  4. Die Last skaliert ähnlich. Wenn die Komponenten im gleichen Rhythmus wachsen und kein isolierbarer Engpass besteht, bietet die Trennung keinen klaren Vorteil.
  5. Die Domänengrenzen sind noch instabil. Eine Fähigkeit auszugliedern, während sich ihre Regeln, ihr Vokabular und ihre Verantwortlichkeiten ständig ändern, fixiert eine verfrühte Grenze.
  6. Observability und Automatisierung sind begrenzt. Ohne strukturierte Logs, Metriken, Traces, Alerts und wiederholbare Deployments wird jeder Netzwerksprung die Untersuchung eines Incidents kostspieliger machen.

Den Monolithen beizubehalten bedeutet nicht, globalen Code zu akzeptieren. Das Ziel ist, dass sich das Modul mit logischer Autonomie weiterentwickeln kann, auch wenn es Prozess, Repository und Release mit anderen teilt.

Sechs Signale, die einen unabhängigen Service rechtfertigen

Die Ausgliederung ist besser vertretbar, wenn mehrere dieser Bedingungen zusammenkommen, nicht wenn nur eine davon vorliegt:

  1. Es gibt eine klar abgegrenzte und verständliche Verantwortung. Der Service hat eine konkrete Aufgabe, zusammenhängende Regeln und eine eigene Domänensprache.
  2. Er kann Eigentümer seiner Daten sein. Er verwaltet seine Datenhaltung und stellt vereinbarte Operationen, Events oder Abfragen bereit, statt direkten Zugriff auf seine Tabellen zu erlauben.
  3. Er benötigt tatsächlich unabhängige Deployments. Sein Änderungszyklus muss voranschreiten können, ohne jedes Release mit dem Kern zu koordinieren.
  4. Seine Last unterscheidet sich. Ein Prozess für Konvertierung, Suche, Dateigenerierung oder rechenintensive Berechnungen kann andere Skalierung und Ressourcen benötigen.
  5. Sein Ausfall kann isoliert werden. Das System kann gezielt mit eingeschränkter Funktionalität weiterarbeiten, wenn diese Fähigkeit nicht antwortet, etwa durch Wiederholungsversuche, ausstehende Zustände oder verzögerte Verarbeitung.
  6. Es gibt ausreichende betriebliche Verantwortung. Ein Team oder Verantwortlicher kann seinen Lebenszyklus, Alerts, Incidents, Sicherheit und Kompatibilität pflegen.

Eine API macht ein Modul nicht von selbst zu einem Microservice. Unabhängigkeit hängt auch von Daten, Deployment, Betrieb und der Fähigkeit ab, Entscheidungen zu treffen, ohne von Interna einer anderen Anwendung abhängig zu sein.

Kosten, die bei der Trennung von Verantwortlichkeiten entstehen

Ein Funktionsaufruf schlägt anders fehl als ein HTTP-Aufruf, eine Nachricht in einer Queue oder eine Remote-Abfrage. Nach der Ausgliederung treten Latenz, Timeouts, Authentifizierung zwischen Services, Rate Limits, teilweise Nichtverfügbarkeit und inkompatible Versionen auf.

Auch das Konsistenzmodell ändert sich. Wenn ein Service eine Operation bestätigt und ein anderer das Event nicht empfängt oder nicht verarbeitet, muss entschieden werden, wie der Zustand erkannt, ohne doppelte Effekte wiederholt und bei Bedarf kompensiert wird. Event-Konsumenten müssen idempotent sein; beispielsweise darf die zweimalige Verarbeitung einer Nachricht weder zwei Dokumente ausstellen noch zweimal abrechnen.

Der Betrieb wird komplexer: Anfragekorrelation, verteilte Traces, Metrik-Dashboards, Log-Aufbewahrung, Secret-Management, Backup-Richtlinien und Recovery-Tests. Außerdem benötigt jeder Vertrag Kompatibilitätsregeln. Optionale Felder hinzuzufügen, ist meist weniger störend, als die Semantik zu ändern, Felder zu entfernen oder einen Status mit einer neuen Bedeutung wiederzuverwenden.

Übergangsarchitektur innerhalb von PHP

Der Weg mit dem geringsten Risiko besteht darin, vor der Ausgliederung zu modularisieren. Definieren Sie für jede Fähigkeit eine Anwendungsschicht mit Use Cases, die Commands oder Queries empfangen und klar definierte Ergebnisse zurückgeben. Verbergen Sie den Datenbankzugriff hinter Repositories oder Ports, wenn dies eine relevante Abhängigkeit darstellt; machen Sie nicht jede Klasse zu einer Abstraktion ohne Zweck.

Der Rest des Monolithen muss das Modul über seine interne öffentliche Schnittstelle verwenden, nicht über seine Entitäten oder Tabellen. Wenn asynchrone Benachrichtigungen benötigt werden, veröffentlichen Sie Domain- oder Integrations-Events von einem kontrollierten Punkt aus. Ein transaktionales Outbox-Pattern kann helfen, die Geschäftsänderung und das ausstehende Event in derselben Transaktion zu erfassen, damit ein späterer Prozess es zuverlässig zustellt.

Diese Phase ermöglicht zu prüfen, ob die Grenze real ist. Wenn die interne Schnittstelle unaufhörlich wächst, private Objekte anderer Module erfordert oder bei jedem Use Case gemeinsame Transaktionen benötigt, ist sie noch kein solider Kandidat für eine Trennung.

Wie die erste Servicegrenze definiert wird

Der erste Service sollte eine leicht erklärbare Verantwortung und eine begrenzte Abhängigkeit vom Kern haben. Dokumentieren Sie vier Elemente, bevor Sie Infrastruktur schreiben:

  • Verantwortung: Welche Entscheidungen er trifft und welche explizit außerhalb liegen.
  • API oder Events: Eingaben, Ausgaben, Fehler, Authentifizierung, Zeitlimits und Idempotenz.
  • Dateneigentümerschaft: Was er speichert, welche externen Identifikatoren er behält und welche Informationen er über Verträge abfragt.
  • Kompatibilität: Wie Produzenten und Konsumenten während Versionsänderungen koexistieren, einschließlich verspäteter Nachrichten.

Vermeiden Sie es, eine API als Spiegel der Tabellen zu entwerfen. Ein Vertrag muss Geschäftsoperationen oder -ereignisse ausdrücken, nicht Persistenzdetails offenlegen, die spätere Änderungen blockieren werden.

Beispiel: Dokumentenverarbeitung ohne Fragmentierung des Backoffice

Betrachten Sie ein PHP-Backoffice, das Vorgänge verwaltet und Dokumente generieren, validieren und speichern muss. Zu Beginn kann die Verarbeitung als internes Modul leben: Es empfängt einen Generierungsauftrag, registriert die Arbeit, führt eine asynchrone Aufgabe aus und aktualisiert einen für den Benutzer sichtbaren Status.

Die Ausgliederung wird sinnvoll, wenn die Generierung sehr unterschiedliche Ressourcen verbraucht, spezifische Konvertierungsabhängigkeiten benötigt, eigene Lastspitzen aufweist und mit einem Dokumentenauftrag funktionieren kann, der die minimal erforderlichen Daten enthält. Der Dokumentenservice sollte die Tabellen des Vorgangs nicht frei abfragen. Das Backoffice kann einen Auftrag mit einer Kennung, anwendbarer Vorlage, Datenversion und Ziel senden; das Ergebnis kommt als Event oder abfragbarer Status zurück.

Zuvor sollte klargestellt werden, dass eine Vorlage die Ausgabestruktur definiert, während sich ein Modell auf Domänendaten oder ein KI-System beziehen kann. Würde KI zur Klassifizierung von Dokumenten eingesetzt, wären ein abgegrenzter Use Case, eine Auswertung mit repräsentativen Daten, menschliche Überprüfung bei sensiblen Entscheidungen, Datenschutz, Kostenkontrolle und eine manuelle oder regelbasierte Alternative erforderlich, wenn der Anbieter ausfällt.

Prüfungen vor der Ausgliederung

Prüfungen vor der Ausgliederung — guía visual de DedicatedPHP

Behandeln Sie die Ausgliederung nicht als isoliertes technisches Deployment. Definieren Sie eine schrittweise Aktivierung für eine kontrollierte Teilmenge von Operationen, getrennt von der Ankündigung der Änderung für alle Benutzer. Behalten Sie einen Plan für Koexistenz und Rollback bei, während das Verhalten validiert wird.

  • Vertragstests zwischen Produzent und Konsument zusätzlich zu Unit- und Integrationstests.
  • Metriken für Latenz, Fehler, Wiederholungsversuche, ausstehende Nachrichten in Warteschlangen, Duplikate und Zeit bis zum Abschluss des Prozesses.
  • Korrelationskennungen, um eine Operation zwischen dem Monolithen, Queues und dem Service nachzuverfolgen.
  • Recovery-Verfahren: sichere erneute Ausführung, Abgleich von Zuständen, Backup und Wiederherstellung.
  • Explizite Regeln für funktionale Degradierung, wenn der Service nicht verfügbar ist.
  • Ein Erfolgskriterium: Welche Evidenz zeigt, dass die Ausgliederung ein konkretes Problem verringert und nicht nur Komplexität verlagert hat?

Die beste Architekturentscheidung ist diejenige, die die Weiterentwicklung des Produkts schützt, ohne eine unverhältnismäßige Plattform aufzuzwingen. Ein modularer, messbarer und klar abgegrenzter PHP-Monolith ist meist der richtige Schritt, bis eine Fähigkeit einen überprüfbaren Bedarf an Unabhängigkeit nachweist.

Möchten Sie diese Ideen in Ihrem Projekt anwenden?Lass uns über deine PHP-Plattform sprechen.
Verwandten Dienst anzeigen