0Pricing
PHP Academy · Lektion

Vom Monolithen zu Microservices

Entscheiden, was aufgeteilt wird und wie Servicegrenzen verlaufen

Vom Monolithen zu Microservices ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Vom Monolithen zu Microservices

Microservices sind eine organisatorische und betriebliche Entscheidung, kein Standardweg. Durch die Aufteilung eines PHP-Monolithen tauschen Sie die Einfachheit von Aufrufen innerhalb eines Prozesses gegen Netzwerkaufrufe, verteilte Daten und komplexere Deployments ein. Aus den falschen Gründen umgesetzt, wird dadurch alles langsamer.

In dieser Lektion geht es um den schwierigsten Teil: das Festlegen von Servicegrenzen. Sind die Schnittstellen richtig gewählt, ist der Rest hauptsächlich Plumbing; sind sie falsch, bauen Sie einen verteilten Monolithen — mit allen Kosten, aber ohne Nutzen.

Warum (und warum nicht) aufteilen

Gute Gründe für eine Aufteilung:

  • Unabhängige Bereitstellung und Verantwortung durch Teams.
  • Unabhängige Skalierung stark ausgelasteter Subsysteme.
  • Isolation von Technologien und Laufzeitumgebungen sowie Begrenzung von Fehlerauswirkungen.

Schlechte Gründe: „Der Code ist unübersichtlich“ (refaktorieren Sie zuerst den Monolithen) oder das Verfolgen eines Trends. Wenn zwei Services immer gemeinsam bereitgestellt werden müssen oder eine Datenbanktabelle gemeinsam nutzen, handelt es sich um einen Service mit zwei Gesichtern.

Bounded Contexts

Domain-Driven Design bietet das präziseste Werkzeug für Grenzen: den Bounded Context. Innerhalb eines Contexts haben Begriffe genau eine Bedeutung. „Customer“ in Billing (das Ziel einer Rechnung) unterscheidet sich von „Customer“ in Support (der Verfasser eines Tickets).

Jeder Bounded Context ist ein guter Kandidat für einen Service. Die Grenzen folgen der Geschäftssprache und der tatsächlichen Kommunikation in der Organisation — Conway's Law in der Praxis.

Hohe Kohäsion, geringe Kopplung

Eine gute Servicegrenze hält Dinge, die sich gemeinsam ändern, zusammen und verschiebt Dinge, die sich unabhängig ändern, nach außen. Bewerten Sie eine geplante Aufteilung anhand folgender Kriterien:

  • Wie viele Features erfordern Änderungen an zwei Services gleichzeitig? (Das sollten wenige sein.)
  • Wie gesprächig ist das Aufrufmuster zwischen ihnen? (Es sollte grobgranular sein.)

Wenn die Implementierung eines Features ständig eine Grenze überschreitet, liegt die Grenze an der falschen Stelle.

<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
    $product = $catalogApi->get($item->productId); // one call PER item!
    $names[] = $product['name'];
}

// Coarse-grained: one batch call across the boundary
$ids   = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";

Eine Datenbank pro Service

Die unverhandelbare Regel lautet: Jeder Service besitzt seine Daten, und kein anderer Service greift auf seine Tabellen zu. Gemeinsame Datenbanken stellen die Kopplung wieder her, die Sie durch die Aufteilung vermeiden wollten — eine Schemaänderung bringt unabhängige Services zum Ausfall.

Das bedeutet, dass serviceübergreifende Abfragen, die im Monolithen ein SQL-JOIN waren, zu API-Aufrufen oder replizierten Lesemodellen werden. Das ist der Preis — und genau darum geht es.

<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
        FROM orders o JOIN customers c ON c.id = o.customer_id';

// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id);          // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";

Das Strangler-Fig-Pattern

Schreiben Sie einen Monolithen niemals in einem Big Bang neu. Das Strangler-Fig-Pattern extrahiert schrittweise: Leiten Sie einen Teil des Datenverkehrs über eine Fassade oder einen Proxy an einen neuen Service weiter, erweitern Sie diesen und entfernen Sie den alten Codepfad erst, wenn der neue ihn vollständig ersetzt.

Ein Reverse Proxy (oder API-Gateway) sitzt davor und entscheidet für jede Route, ob der alte Monolith oder der neue Service angesprochen wird. Die Migration erfolgt Fähigkeit für Fähigkeit, wobei jede Änderung jederzeit auslieferbar bleibt.

<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
    $migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
    foreach ($migrated as $prefix) {
        if (str_starts_with($path, $prefix)) {
            return 'http://billing-svc' . $path;
        }
    }
    return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";

Extrahieren eines Moduls

Eine pragmatische Reihenfolge für die Extraktion:

  1. Finden Sie ein Modul mit wenigen eingehenden Abhängigkeiten und klarer Datenverantwortung.
  2. Kapseln Sie seine Aufrufe innerhalb des Prozesses zunächst im Monolithen hinter einer Schnittstelle.
  3. Verschieben Sie die von ihm verwalteten Daten in ein eigenes Schema bzw. eine eigene Datenbank.
  4. Ersetzen Sie die Implementierung der Schnittstelle durch einen Netzwerk-Client.
  5. Leiten Sie den Datenverkehr über die Fassade um und löschen Sie den alten Code.

Wenn Sie zunächst im Monolithen die Aufrufe hinter einer Schnittstelle kapseln, verringern Sie das Risiko beim Schritt zum Netzwerk.

<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
    public function generate(string $orderId): string; // returns invoice id
}

// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
    public function generate(string $orderId): string { return 'inv-1'; }
}

Konsistenz verteilter Daten

Sobald die Daten aufgeteilt sind, verlieren Sie transaktionsübergreifende ACID-Transaktionen zwischen Services. Akzeptieren Sie eventuelle Konsistenz: Services veröffentlichen Ereignisse über ihre eigenen Daten, und andere Services erstellen daraus lokale Lesemodelle.

Der Orders-Service fragt Customers nicht bei jeder Anfrage ab — er verwaltet eine kleine Projektion (nur die benötigten Felder), die durch CustomerUpdated-Ereignisse aktualisiert wird. Dadurch entfallen eine Laufzeitabhängigkeit und ein zusätzlicher Latenzsprung.

<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
    $db->prepare(
        'INSERT INTO customer_read_model (id, email)
         VALUES (:id, :email)
         ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
    )->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}

Die betriebliche Realität

Microservices verlagern die Komplexität vom Code in den Betrieb. Bevor Sie aufteilen, benötigen Sie:

  • Zentralisiertes Logging und verteiltes Tracing (Korrelations-IDs über alle Aufrufschritte hinweg).
  • CI/CD pro Service und unabhängig versionierte Deployments.
  • Health-Checks, Timeouts, Retries und Circuit Breaker bei jedem Aufruf.
  • Contract-Tests, damit ein Provider einen Konsumenten nicht unbemerkt beschädigen kann.

Wenn Ihr Team einen einzelnen Service nicht zuverlässig betreiben kann, wird der Betrieb von zehn Services zehnmal schlimmer.

Services richtig dimensionieren

„Micro“ ist irreführend — dimensionieren Sie Services nach Geschäftsfähigkeit und Verantwortung eines Teams, nicht nach der Anzahl der Codezeilen. Bei zu feiner Aufteilung (Nanoservices) führt ein einzelnes Feature zu einer Flut von Netzwerkaufrufen; bei zu grober Aufteilung sind Sie wieder bei einem Monolithen.

Eine sinnvolle Faustregel: Ein Service sollte von einem Team verantwortet werden können, eigenständig bereitstellbar sein und seine zentralen Anwendungsfälle erfüllen können, ohne synchron eine lange Kette vieler anderer Services aufzurufen.

Die Alternative: der modulare Monolith

Bevor Sie auf verteilte Systeme umsteigen, sollten Sie den modularen Monolithen in Betracht ziehen: eine einzige bereitstellbare Anwendung, die intern jedoch in Module mit klaren Grenzen und eigenen Schemata aufgeteilt ist. Die Module kommunizieren ausschließlich über veröffentlichte Schnittstellen — kein modulübergreifender Zugriff auf Tabellen.

Sie erhalten klare Grenzen und einfache Refaktorierung ohne den Aufschlag verteilter Systeme. Wenn ein Modul tatsächlich unabhängig skaliert oder verantwortet werden muss, ist es bereits so strukturiert, dass es mithilfe des Strangler-Patterns herausgelöst werden kann. Für die meisten Teams ist dies der richtige erste Schritt.

<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing;          // owns billing_* tables
interface BillingFacade {
    public function invoiceForOrder(string $orderId): string;
}

namespace App\Sales;            // owns sales_* tables
final class Checkout {
    public function __construct(private \App\Billing\BillingFacade $billing) {}
    // Sales never SELECTs from billing_* directly - only via the facade.
}

Kurztest

Eine schlechte Aufteilung erkennen.

Zusammenfassung

Servicegrenzen richtig festlegen:

  • Teilen Sie wegen Bereitstellbarkeit, Skalierung und Verantwortung auf — nicht, weil der Code unübersichtlich ist.
  • Richten Sie die Grenzen an Bounded Contexts aus; streben Sie hohe Kohäsion und geringe Kopplung an.
  • Eine Datenbank pro Service — keine gemeinsamen Tabellen; ersetzen Sie JOINs durch APIs oder Lesemodelle.
  • Migrieren Sie schrittweise mit dem Strangler-Fig-Pattern.
  • Akzeptieren Sie eventuelle Konsistenz und investieren Sie in den Betrieb, bevor Sie horizontal skalieren.

Als Nächstes: Wie diese Services tatsächlich miteinander kommunizieren — REST und gRPC.

Häufig gestellte Fragen

Ist die Lektion „Vom Monolithen zu Microservices“ kostenlos?

Ja — der vollständige Text von „Vom Monolithen zu Microservices“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Vom Monolithen zu Microservices“?

Entscheiden, was aufgeteilt wird und wie Servicegrenzen verlaufen Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.

Wie lange dauert die Lektion „Vom Monolithen zu Microservices“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Vom Monolithen zu Microservices
  2. Servicekommunikation: REST und gRPC
  3. API-Gateways und Service Discovery
  4. Resilienz: Circuit Breaker und Retries
← Zurück zu PHP Academy