Shared Kernel Pattern für Microservices mit Symfony
AI generated
SF
{ }
Symfony · Shared Kernel · Microservices · Composer
Shared Kernel Pattern für Microservices
Geteilter Code, ohne Services fest zu verkoppeln

Das Shared Kernel Pattern loest ein Problem, das in fast jeder Symfony Microservice Landschaft entsteht: mehrere Services brauchen dieselben Value Objects, Fehlercodes oder Event Vertraege. Dieser Artikel zeigt, wie ein Shared Kernel als eigenes, versioniertes Composer Paket aufgebaut wird, was hineingehoert, was draussen bleiben muss, und wie Contract Tests eine schleichende Kopplung verhindern.

19 Min. Lesezeit Composer Paket · Versionierung · Contract Tests · Bounded Context Symfony 7.x · PHP 8.3+

1. Welches Problem das Shared Kernel Pattern loest

Sobald eine Symfony Microservice Landschaft aus mehr als zwei oder drei Services besteht, taucht fast zwangslaeufig dieselbe Frage auf: Wo lebt der Code, den mehrere Services gemeinsam brauchen? Ein Shared Kernel Pattern beantwortet das mit einem bewusst kleinen, gemeinsam gepflegten Codeanteil, der als eigenes versioniertes Paket in jeden beteiligten Service eingebunden wird. Ohne diese explizite Loesung landet geteilter Code entweder redundant in jedem Service, was zu Inkonsistenzen fuehrt, oder er wird per Copy Paste synchron gehalten, was auf Dauer garantiert auseinanderdriftet.

Der Begriff Shared Kernel stammt aus dem strategischen Domain Driven Design und beschreibt genau diesen bewusst kleinen, gemeinsam vereinbarten Ausschnitt zwischen zwei oder mehr Bounded Contexts. Wichtig ist dabei die Betonung auf klein: Ein Shared Kernel Pattern, das zu einem heimlichen zweiten Domain Modell anwaechst, unterlaeuft genau die Unabhaengigkeit, die Microservices eigentlich bringen sollten. Die folgenden Abschnitte zeigen, wie die Grenze in der Praxis gezogen wird und wie ein Shared Kernel technisch als Symfony taugliches Composer Paket umgesetzt wird.

2. Was in den Shared Kernel gehoert, und was nicht

Die wichtigste Entscheidung beim Shared Kernel Pattern ist die Abgrenzung des Inhalts. Geeignet sind reine Value Objects ohne Verhalten, die in mehreren Services identisch interpretiert werden muessen, etwa eine CustomerId Klasse, ein Money Value Object oder standardisierte Fehlercode Enums fuer die API Kommunikation zwischen Services. Diese Bausteine aendern sich selten und haben keine eigene Geschaeftslogik, die an einen einzelnen Service gebunden ist.

Nicht geeignet fuer ein Shared Kernel Pattern sind Aggregate, Repository Implementierungen oder irgendetwas mit Doctrine Abhaengigkeit. Sobald ein geteiltes Objekt eigene Geschaeftsregeln durchsetzt oder eine Datenbank Anbindung braucht, gehoert es in genau einen Service, und andere Services sprechen diesen Service ueber eine API an, statt den Code direkt zu importieren. Diese Regel klingt einfach, wird in der Praxis aber regelmaessig verletzt, wenn ein Team unter Zeitdruck schnell eine Entity Klasse in den Shared Kernel schiebt, um Doppelarbeit zu vermeiden, und damit zwei Services an dieselbe Implementierung fesselt.


<?php

declare(strict_types=1);

namespace Mironsoft\SharedKernel\ValueObject;

// Shared Kernel content — pure value object, zero framework dependencies
final readonly class CustomerId
{
    private function __construct(public string $value) {}

    public static function fromString(string $value): self
    {
        if (!preg_match('/^cust_[a-z0-9]{16}$/', $value)) {
            throw new \InvalidArgumentException("Invalid CustomerId format: {$value}");
        }

        return new self($value);
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }
}

3. Der Shared Kernel als eigenes Composer Paket

Technisch wird ein Shared Kernel Pattern in einer PHP Microservice Landschaft am saubersten als eigenes privates Composer Paket umgesetzt, etwa unter dem Namen mironsoft/shared-kernel, gehostet in einem eigenen Git Repository oder ueber ein privates Packagist Satis Repository. Jeder Symfony Service, der Teile davon braucht, deklariert eine Abhaengigkeit mit einer festen Versionsbeschraenkung in seiner composer.json, genau wie bei jedem anderen Drittanbieter Paket.

Dieser Ansatz macht das Shared Kernel Pattern explizit sichtbar: Wer sich die composer.json eines Service ansieht, erkennt sofort, welche geteilten Bausteine dieser Service tatsaechlich nutzt. Das ist ein wesentlicher Unterschied zu einem Monorepo Ansatz, bei dem geteilter Code oft implizit ueber relative Pfade eingebunden wird und die tatsaechliche Abhaengigkeit im Code verschwindet, bis ein Refactoring ueberraschend mehrere Services gleichzeitig betrifft.


{
  "name": "mironsoft/order-service",
  "require": {
    "php": "^8.3",
    "symfony/framework-bundle": "^7.1",
    "mironsoft/shared-kernel": "^2.4"
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://satis.mironsoft.internal"
    }
  ]
}

4. Versionierung ohne Breaking Changes fuer alle Services

Weil mehrere Services gleichzeitig vom Shared Kernel Pattern abhaengen, aber unterschiedliche Deployment Zyklen haben, muss das Paket strikt Semantic Versioning einhalten. Ein Patch Release aendert nichts an der oeffentlichen Schnittstelle, ein Minor Release fuegt nur additive Funktionalitaet hinzu, und ein Major Release signalisiert Breaking Changes, denen jeder Service erst nach eigener Anpassung folgen muss. Ohne diese Disziplin bricht ein einzelnes Update des Shared Kernel potenziell die halbe Microservice Landschaft gleichzeitig.

In der Praxis bewaehrt sich beim Shared Kernel Pattern eine Uebergangsphase: Ein neues Major Release wird veroeffentlicht, aber die alte Major Version bleibt fuer einen definierten Zeitraum parallel gepflegt und bekommt weiterhin Sicherheits Patches. Services migrieren nacheinander, nicht gleichzeitig, und ein Dashboard oder eine einfache Tabelle zeigt sichtbar, welche Version jeder Service gerade nutzt. Diese Transparenz verhindert, dass ein vergessener Service Monate spaeter mit einer laengst veralteten Shared Kernel Version in Produktion laeuft.

5. Event Vertraege als stabiler Teil des Shared Kernel

Neben Value Objects ist der zweite haeufige Bestandteil eines Shared Kernel Pattern die Struktur von Integration Events, also den Nachrichten, die Services ueber einen Message Broker austauschen. Statt dass jeder Service seine eigene Interpretation eines OrderPlacedEvent pflegt, definiert der Shared Kernel eine einzige kanonische PHP Klasse mit allen Feldern, die konsumierende Services garantiert erwarten koennen, inklusive einer expliziten Event Version im Payload.

Diese kanonische Struktur im Shared Kernel Pattern verhindert das haeufigste Integrationsproblem in Event Driven Microservice Landschaften: unterschiedliche Interpretation desselben fachlichen Ereignisses durch verschiedene Teams. Wichtig ist trotzdem, dass die Event Klasse selbst keine Geschaeftslogik enthaelt, nur Datenstruktur und Validierung des Formats, damit der Shared Kernel ein reiner Vertrag bleibt und nicht heimlich zu einem zweiten Domain Modell wird, das die eigentliche Domain Schicht der einzelnen Services untergraebt.


<?php

declare(strict_types=1);

namespace Mironsoft\SharedKernel\Event;

// Shared Kernel content — canonical event contract, no business logic
final readonly class OrderPlacedEvent
{
    public function __construct(
        public string $eventVersion,
        public string $orderId,
        public string $customerId,
        public int $totalAmountInCents,
        public string $currency,
        public \DateTimeImmutable $occurredAt,
    ) {}

    public static function currentVersion(): string
    {
        return '2.0';
    }
}

6. Contract Tests gegen stillschweigende Abweichungen

Ein Shared Kernel Pattern ohne automatisierte Absicherung erodiert genauso wie jede andere Architekturregel, die nur auf Disziplin beruht. Contract Tests loesen dieses Problem, indem der produzierende Service in seiner Test Suite garantiert, dass jedes tatsaechlich gesendete Event dem Schema des Shared Kernel entspricht, waehrend konsumierende Services in ihrer eigenen Test Suite pruefen, dass sie mit dem aktuellen Vertrag noch korrekt umgehen koennen. Werkzeuge wie Pact eignen sich fuer diese Art von Vertragstest zwischen Services.

Der praktische Nutzen von Contract Tests im Shared Kernel Pattern zeigt sich vor allem in der CI Pipeline: Ein Team, das versehentlich ein Feld aus einem Event entfernt, ohne die Major Version zu erhoehen, bekommt das sofort als fehlgeschlagenen Contract Test gemeldet, lange bevor der Fehler in Produktion bei einem anderen Team auftaucht und dort schwer nachvollziehbar debuggt werden muss.

7. Governance: wer darf den Shared Kernel aendern?

Weil ein Shared Kernel Pattern per Definition von mehreren Teams gemeinsam genutzt wird, braucht es eine klare Governance dafuer, wer Aenderungen vorschlagen und mergen darf. Bewaehrt hat sich ein Modell mit einer kleinen Gruppe von Codeowners aus verschiedenen Teams, die jede Pull Request am Shared Kernel gemeinsam reviewen, und eine feste Regel, dass Breaking Changes vorab in einem kurzen Architektur Meeting angekuendigt werden, statt einfach gemerged zu werden.

Ohne diese Governance neigt ein Shared Kernel Pattern dazu, entweder zu stagnieren, weil niemand Verantwortung uebernimmt, oder unkontrolliert zu wachsen, weil jedes Team eigene Wuensche unabgestimmt hinzufuegt. Beide Extreme untergraben den eigentlichen Zweck des Patterns: einen bewusst kleinen, stabilen, gemeinsam vereinbarten Kern zu haben, auf den sich alle beteiligten Services verlassen koennen.

8. Wann sich ein Shared Kernel nicht lohnt

Das Shared Kernel Pattern ist keine automatische Empfehlung fuer jede Microservice Landschaft. Bei Services, die von komplett unterschiedlichen Teams mit unterschiedlichen Release Rhythmen betrieben werden, kann selbst eine kleine gemeinsame Abhaengigkeit zu einem Koordinationsengpass werden, weil jede Aenderung am Shared Kernel eine Abstimmung ueber Teamgrenzen hinweg erfordert. In solchen Faellen ist Duplizierung eines kleinen Value Objects in mehreren Services oft die pragmatischere Wahl als eine gemeinsame Abhaengigkeit.

Ein zweites Warnsignal beim Shared Kernel Pattern ist Groesse: Wenn das Paket ueber die Zeit auf mehrere hundert Klassen anwaechst, ist es faktisch kein Shared Kernel mehr, sondern eine verdeckte gemeinsame Bibliothek, die genau die Kopplung zurueckbringt, die eine Microservice Architektur eigentlich vermeiden sollte. Ein regelmaessiger Blick auf die Groesse und den Umfang des Pakets gehoert deshalb zur Pflege des Patterns dazu, nicht nur die technische Versionierung.

9. Shared Kernel im Vergleich zu Alternativen

Es gibt mehrere Strategien, um Code zwischen Symfony Microservices zu teilen. Die folgende Tabelle vergleicht das Shared Kernel Pattern mit den gaengigsten Alternativen.

Strategie Kopplung Konsistenz Passt am besten fuer
Copy Paste Duplizierung Sehr niedrig Driftet garantiert auseinander Sehr kleine, seltene Value Objects
Shared Kernel Paket Niedrig, versioniert Hoch, kontrolliert Value Objects und Event Vertraege
Monorepo mit direkten Imports Hoch, implizit Sehr hoch Teams unter demselben Deployment Rhythmus
Schema Registry fuer Events Niedrig, sprachunabhaengig Hoch Polyglotte Landschaften ohne gemeinsames PHP

Fuer reine PHP Symfony Landschaften bietet das Shared Kernel Pattern als Composer Paket meist den besten Kompromiss aus Konsistenz und geringer Kopplung. Bei polyglotten Systemen mit anderen Sprachen neben PHP ersetzt oder ergaenzt eine sprachunabhaengige Schema Registry fuer die Event Vertraege das Composer Paket.

Mironsoft

Symfony Microservices, geteilte Vertraege und Architektur Governance

Geteilter Code zwischen Services ausser Kontrolle?

Wir schneiden einen sauberen Shared Kernel aus bestehendem Code heraus, richten Versionierung und Contract Tests ein und definieren die Governance, damit euer geteilter Code klein, stabil und wirklich gemeinsam getragen bleibt.

Shared Kernel Extraktion

Bestehenden verstreuten Code in ein sauberes Composer Paket ueberfuehren

Contract Tests

Vertragstests zwischen Services einrichten und in die CI Pipeline integrieren

Governance Modell

Codeowner Prozess und Versionierungsregeln fuer teamuebergreifenden Code

10. Zusammenfassung

Das Shared Kernel Pattern loest ein reales Problem in jeder Symfony Microservice Landschaft: gemeinsame Value Objects und Event Vertraege brauchen einen stabilen, versionierten Ort, statt in jedem Service dupliziert oder implizit synchron gehalten zu werden. Als eigenes Composer Paket mit striktem Semantic Versioning, Contract Tests und klarer Codeowner Governance bleibt der geteilte Code klein, stabil und fuer alle beteiligten Teams verlaesslich.

Der wichtigste Erfolgsfaktor ist Disziplin bei der Abgrenzung: Nur reine Datenstrukturen ohne Geschaeftslogik und ohne Framework Abhaengigkeit gehoeren in den Shared Kernel. Sobald Aggregate, Repositories oder Doctrine Mapping hineinrutschen, verwandelt sich das Pattern in eine versteckte gemeinsame Bibliothek, die genau die Kopplung zurueckbringt, die eine Microservice Architektur eigentlich vermeiden soll. Wer diese Grenze konsequent verteidigt, bekommt mit dem Shared Kernel Pattern echten Nutzen bei minimaler Kopplung.

Shared Kernel Pattern für Symfony Microservices — Das Wichtigste auf einen Blick

Inhalt

Reine Value Objects und Event Vertraege, niemals Aggregate, Repositories oder Doctrine Mapping.

Verteilung

Eigenes privates Composer Paket mit striktem Semantic Versioning fuer alle beteiligten Services.

Absicherung

Contract Tests zwischen produzierenden und konsumierenden Services in der CI Pipeline.

Governance

Codeowner aus mehreren Teams, Breaking Changes vorab angekuendigt, Groesse regelmaessig geprueft.

11. FAQ: Shared Kernel Pattern für Symfony Microservices

1Was ist ein Shared Kernel im DDD?
Ein bewusst kleiner, gemeinsam vereinbarter Codeausschnitt zwischen Bounded Contexts, meist Value Objects und Event Vertraege.
2Duerfen Aggregate hinein?
Nein, Aggregate bleiben an genau einen Service gebunden, andere sprechen ihn ueber eine API an.
3Wie wird er technisch verteilt?
Als eigenes privates Composer Paket ueber ein Satis oder Private Packagist Repository.
4Wie verhindert man Breaking Changes fuer alle?
Semantic Versioning plus Uebergangsphase mit paralleler Pflege der alten Major Version.
5Was sind Contract Tests hierbei?
Automatisierte Tests, die Event Schema Konformitaet zwischen produzierenden und konsumierenden Services pruefen.
6Wer sollte ihn pflegen duerfen?
Eine kleine Codeowner Gruppe aus den beteiligten Teams mit abgestimmten Breaking Changes.
7Wann lohnt er sich nicht?
Bei stark abweichenden Release Rhythmen zwischen Teams, dann ist Duplizierung oft pragmatischer.
8Wie gross darf er werden?
So klein wie moeglich, sonst wird er zu einer verdeckten Bibliothek mit hoher Kopplung.
9Passt das Pattern zu polyglotten Systemen?
Ein PHP Paket funktioniert nur fuer PHP, sonst braucht es eine sprachunabhaengige Schema Registry.
10Unterschied zu einer Utility Library?
Strategisch begrenzt auf Domain Vertraege zwischen Bounded Contexts, mit expliziter Governance statt beliebiger Helfer.