Schneller liefern mit kurzen Branches
Trunk-Based Development ersetzt lange Feature-Branches durch kurzlebige Zweige, die mehrmals täglich in den Main-Branch zurückfließen. Feature Flags machen es möglich, unfertigen Code sicher auszuliefern, ohne die Anwendung zu gefährden, während eine schnelle, zuverlässige CI-Pipeline jeden Commit sofort auf Regressionen prüft und den Main-Branch dauerhaft auslieferbar hält.
Inhaltsverzeichnis
- 1. Was Trunk-Based Development wirklich löst
- 2. Die Philosophie: sehr kurzlebige Branches
- 3. Feature Flags als Enabler
- 4. Feature-Flag-Patterns in der Praxis
- 5. Warum Trunk-Based Development starke CI braucht
- 6. Testdisziplin: Pre-Commit-Hooks, Reviews, kleine PRs
- 7. Trade-offs: wann Trunk-Based Development nicht passt
- 8. Migration: Schritt für Schritt zu Trunk-Based Development
- 9. Trunk-Based Development im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was Trunk-Based Development wirklich löst
Die meisten Git-Workflows mit langlebigen Feature-Branches lösen ein Problem, das sie gleichzeitig verschärfen: Isolation. Ein Branch, der eine Woche oder länger parallel zum Main-Branch existiert, driftet unweigerlich auseinander - andere Entwickler committen weiter, Abhängigkeiten ändern sich, und der Merge am Ende wird zum Risiko statt zur Formsache. Merge Hell entsteht nicht durch schlechte Entwickler, sondern durch die Zeitspanne zwischen Branch-Erstellung und Integration. Je länger diese Spanne, desto größer der Konfliktraum.
Trunk-Based Development (TBD) dreht das Prinzip um: Statt Integration möglichst spät und selten durchzuführen, wird sie so früh und so häufig wie möglich erzwungen. Jeder Entwickler committet direkt oder über sehr kurze Branches mehrmals täglich in den main-Branch, den sogenannten Trunk. Das Ergebnis ist kein Zufallsprodukt, sondern eine bewusste Verschiebung des Risikos: kleine, häufige Integrationen statt seltener, großer. Für PHP- und Magento-Teams mit mehreren Entwicklern an einem Shop-Codebase ist das ein direkter Hebel gegen die klassischen Merge-Konflikte in Layout-XML, di.xml und Composer-Lockfiles.
2. Die Philosophie: sehr kurzlebige Branches
Der Kern von Trunk-Based Development ist eine einfache Regel: Ein Branch lebt Stunden, maximal ein bis zwei Tage - niemals Wochen. Diese Regel ist keine Willkür, sondern ergibt sich direkt aus der Mathematik der Integration: Ein Branch, der zwei Stunden nach der Erstellung gemerged wird, kann kaum relevant vom Trunk abweichen. Ein Branch, der zwei Wochen lebt, hat zwangsläufig hunderte fremde Commits verpasst. Entwickler zerlegen ihre Arbeit deshalb in kleinere, unabhängig lieferbare Einheiten, statt ein komplettes Feature in einem einzigen, riesigen Branch zu entwickeln.
Praktisch bedeutet das: git pull --rebase vor jedem neuen Arbeitsblock, ein neuer Branch pro Teilaufgabe, und ein Merge zurück in den Trunk, sobald die Änderung getestet und reviewt ist - nicht erst, wenn das gesamte Feature fertig ist. Diese Disziplin fühlt sich zunächst ungewohnt an, weil sie erzwingt, große Aufgaben in kleine, in sich geschlossene Schritte zu zerlegen. Genau diese Zerlegung ist aber der eigentliche Wert: Sie macht jeden einzelnen Schritt review- und testbar, statt am Ende ein tausend Zeilen großes Diff zu prüfen, das niemand mehr vollständig durchdringt.
#!/usr/bin/env bash
# Typical short-lived branch workflow for trunk-based development
set -euo pipefail
# Always start from an up-to-date trunk
git checkout main
git pull --rebase origin main
# Create a short-lived branch for one small, self-contained change
git checkout -b feature/checkout-shipping-label-fix
# ... make the change, commit in small logical steps ...
git add src/app/code/Mironsoft/Checkout/Model/ShippingLabel.php
git commit -m "Fix shipping label rendering for split shipments"
# Rebase onto the latest trunk before opening the pull request
git fetch origin
git rebase origin/main
# Push and open a small, fast-to-review pull request
git push -u origin feature/checkout-shipping-label-fix
# After CI is green and review is approved, merge quickly
git checkout main
git pull --rebase origin main
git merge --no-ff feature/checkout-shipping-label-fix
git push origin main
# Delete the branch immediately, it has done its job
git branch -d feature/checkout-shipping-label-fix
git push origin --delete feature/checkout-shipping-label-fix
3. Feature Flags als Enabler
Kurzlebige Branches allein lösen nur die halbe Aufgabe. Die eigentliche Herausforderung bleibt: Was passiert mit einem Feature, das nach zwei Tagen Arbeit noch nicht vollständig fertig ist, aber trotzdem in den Trunk soll? Genau hier setzt das Feature Flag an. Ein Feature Flag ist eine Bedingung im Code, die entscheidet, ob ein neuer Codepfad aktiv ist oder nicht - unabhängig vom Deployment. Unfertiger Code kann so gemerged, deployed und sogar in Produktion ausgeliefert werden, ohne dass ein einziger Nutzer ihn zu sehen bekommt.
Das verändert die Beziehung zwischen Deployment und Release fundamental: Deployment bedeutet, dass Code auf dem Server liegt. Release bedeutet, dass eine Funktion für Nutzer sichtbar oder aktiv ist. Diese beiden Ereignisse sind bei Trunk-Based Development bewusst entkoppelt. Ein Magento-Team kann eine neue Checkout-Funktion über mehrere Tage in kleinen Schritten in den Trunk integrieren, während sie hinter einem Flag inaktiv bleibt - und sie erst per Konfigurationsänderung freischalten, wenn sie vollständig fertig und getestet ist. Kein Deployment, kein Rollback-Risiko, nur ein Toggle.
<?php
declare(strict_types=1);
namespace Mironsoft\Checkout\Model;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
/**
* Simple, config-driven feature flag check for the new express checkout flow.
*/
class FeatureFlags
{
private const XML_PATH_EXPRESS_CHECKOUT_ENABLED = 'mironsoft_checkout/features/express_checkout_enabled';
/**
* @param ScopeConfigInterface $scopeConfig Magento configuration reader
*/
public function __construct(
private readonly ScopeConfigInterface $scopeConfig
) {
}
/**
* Checks whether the express checkout feature is enabled for the current store.
*
* @param int|null $storeId Store view id, null for the current store
* @return bool True if the feature flag is active
*/
public function isExpressCheckoutEnabled(?int $storeId = null): bool
{
return $this->scopeConfig->isSetFlag(
self::XML_PATH_EXPRESS_CHECKOUT_ENABLED,
ScopeInterface::SCOPE_STORE,
$storeId
);
}
}
4. Feature-Flag-Patterns in der Praxis
Das einfachste Muster ist der binäre Konfigurations-Flag: ein einzelner Boolean in system.xml, ausgelesen über ScopeConfigInterface, wie im Beispiel oben. Das genügt für die meisten internen Features und erlaubt store-spezifisches Ein- und Ausschalten ohne Code-Änderung. Für riskantere Änderungen reicht ein einfacher An-aus-Schalter aber nicht aus - hier kommen gestaffelte Rollouts ins Spiel: Eine Funktion wird zunächst nur einem kleinen Prozentsatz der Kunden oder einem bestimmten Kundensegment angezeigt, bevor sie für alle aktiviert wird.
Ein praktikables Pattern für prozentbasierte Rollouts nutzt einen deterministischen Hash der Kunden- oder Session-ID, damit derselbe Nutzer bei jedem Aufruf konsistent dieselbe Variante sieht - kein zufälliges Hin-und-Her zwischen altem und neuem Verhalten. Wichtig ist außerdem, Flags nach erfolgreichem vollständigem Rollout konsequent aus dem Code zu entfernen: Ein Feature Flag ist ein temporäres Werkzeug, kein dauerhaftes Konfigurationssystem. Code-Basen mit dutzenden vergessenen Flags werden selbst zu einer Quelle von Komplexität und Testaufwand.
<?php
declare(strict_types=1);
namespace Mironsoft\Checkout\Service;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
/**
* Feature flag service supporting boolean flags and percentage-based rollouts.
*/
class FeatureFlagService
{
/**
* @param ScopeConfigInterface $scopeConfig Magento configuration reader
*/
public function __construct(
private readonly ScopeConfigInterface $scopeConfig
) {
}
/**
* Determines whether a feature is active for the given customer,
* based on a deterministic percentage rollout.
*
* @param string $flagCode Config path segment identifying the flag
* @param string $customerIdentifier Stable identifier, e.g. customer id or session id
* @return bool True if the feature is active for this customer
*/
public function isActiveForCustomer(string $flagCode, string $customerIdentifier): bool
{
$rolloutPercentage = (int) $this->scopeConfig->getValue(
sprintf('mironsoft_features/%s/rollout_percentage', $flagCode),
ScopeInterface::SCOPE_STORE
);
if ($rolloutPercentage <= 0) {
return false;
}
if ($rolloutPercentage >= 100) {
return true;
}
// Deterministic bucket: same customer always lands in the same bucket
$bucket = crc32($flagCode . $customerIdentifier) % 100;
return $bucket < $rolloutPercentage;
}
}
5. Warum Trunk-Based Development starke CI braucht
Trunk-Based Development steht und fällt mit einer Voraussetzung, die selten deutlich genug betont wird: Der Trunk muss praktisch jederzeit deploybar sein. Das ist keine optionale Best Practice, sondern die tragende Säule des gesamten Modells - ohne sie kollabiert die Praxis der häufigen Integration sofort. Wenn mehrere Entwickler mehrmals täglich in denselben Branch mergen, muss jede einzelne Änderung automatisiert auf Regressionen geprüft werden, bevor sie den Trunk erreicht. Ein manueller Review allein reicht dafür nicht aus.
Das bedeutet konkret: eine Continuous-Integration-Pipeline, die bei jedem Push läuft, in wenigen Minuten durch ist und zuverlässig zwischen echten Fehlern und Flaky Tests unterscheidet. Eine CI-Suite, die 45 Minuten braucht, bremst nicht nur die Entwicklung aus, sie verleitet Teams dazu, Reviews und Merges zu stapeln - und genau damit zurück in Richtung langlebiger Branches zu rutschen. PHPUnit-Tests, statische Analyse mit PHPStan und ein automatisierter Build-Schritt gehören deshalb in jede Pipeline, die Trunk-Based Development ernsthaft unterstützen soll. Ein roter Build blockiert in einem funktionierenden Trunk-Based-Setup jeden weiteren Merge, bis er wieder grün ist.
# .github/workflows/trunk-ci.yml
# Fast, mandatory checks on every push to main and every pull request
name: Trunk CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
fast-checks:
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
- uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
- name: Install dependencies
run: composer install --no-progress --prefer-dist
- name: Static analysis (PHPStan level 5)
run: vendor/bin/phpstan analyse app/code --level=5
- name: Coding standards
run: vendor/bin/phpcs --standard=Magento2 app/code
- name: Unit tests
run: vendor/bin/phpunit --testsuite unit --stop-on-failure
- name: Fail fast on red build
if: failure()
run: echo "Build is red, merge to main is blocked until fixed" && exit 1
6. Testdisziplin: Pre-Commit-Hooks, Reviews, kleine PRs
CI allein genügt nicht, wenn fehlerhafter Code überhaupt erst den Weg zur Pipeline findet. Ein Pre-Commit-Hook, der lokal Linting, statische Analyse und die schnellsten Unit-Tests ausführt, fängt einen großen Teil der Fehler ab, bevor sie überhaupt gepusht werden. Das reduziert nicht nur die Last auf der CI-Infrastruktur, sondern verkürzt auch die Feedback-Schleife für den Entwickler von Minuten auf Sekunden - ein entscheidender Unterschied bei mehreren Merges pro Tag.
Genauso wichtig ist die Größe der Pull Requests selbst. Ein PR mit 50 geänderten Zeilen lässt sich in zehn Minuten gründlich reviewen; ein PR mit 2000 Zeilen wird entweder tagelang liegen gelassen oder oberflächlich durchgewunken - beides untergräbt die Grundidee von Trunk-Based Development. Teams, die konsequent auf kleine PRs setzen, kombinieren das häufig mit Pair Programming oder Mob Programming für die riskanteren Codeabschnitte: Wenn zwei Entwickler eine Änderung gemeinsam schreiben, ist der nachgelagerte Review-Aufwand oft minimal, weil der Review faktisch schon während des Schreibens stattgefunden hat.
#!/usr/bin/env bash
# .git/hooks/pre-commit - fast local gate before code reaches CI
set -euo pipefail
echo "Running fast pre-commit checks..."
# Only lint and analyse staged PHP files, not the whole codebase
staged_php_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')
if [[ -z "$staged_php_files" ]]; then
echo "No PHP files staged, skipping checks"
exit 0
fi
echo "$staged_php_files" | xargs -r vendor/bin/phpcs --standard=Magento2
echo "$staged_php_files" | xargs -r php -l
vendor/bin/phpstan analyse $staged_php_files --level=5 --no-progress
echo "Pre-commit checks passed, proceeding with commit"
7. Trade-offs: wann Trunk-Based Development nicht passt
Trunk-Based Development ist kein universeller Ersatz für jeden Workflow. Teams ohne belastbare CI-Infrastruktur, ohne automatisierte Tests oder mit sehr wenig Erfahrung im gemeinsamen Umgang mit Git riskieren, dass ein instabiler Trunk zur Norm wird - genau das Gegenteil des angestrebten Ziels. Ein Team, das gerade erst lernt, saubere Commits zu schreiben und Merge-Konflikte zu lösen, profitiert oft mehr von etwas mehr Isolation, bis sich die grundlegenden Git-Fähigkeiten gefestigt haben.
Auch regulierte Branchen mit fest terminierten, auditierten Releases - etwa im Zahlungsverkehr oder Gesundheitswesen - stoßen an Grenzen: Wenn jede Produktionsänderung eine formale Freigabe durchlaufen muss, kollidiert die hohe Merge-Frequenz von Trunk-Based Development mit dem Freigabeprozess. In solchen Fällen bietet sich ein hybrider Ansatz an: Trunk-Based Development für die Entwicklung, kombiniert mit Release-Branches oder Feature Flags, die eine kontrollierte, terminierte Freischaltung erlauben, ohne auf lange Entwicklungs-Branches zurückzufallen. Der Fehler liegt selten im Prinzip selbst, sondern darin, es ohne die nötige Infrastruktur einzuführen.
8. Migration: Schritt für Schritt zu Trunk-Based Development
Der Umstieg gelingt selten über Nacht und sollte es auch nicht. Der erste Schritt ist fast immer die CI-Pipeline: Bevor Branches verkürzt werden, muss ein automatisierter, schneller Testlauf existieren, der bei jedem Push zuverlässig grün oder rot meldet. Ohne dieses Fundament produziert jeder weitere Schritt nur mehr Risiko. Parallel dazu lohnt sich ein Audit der bestehenden Testabdeckung: Wo fehlen automatisierte Tests für kritische Pfade wie Checkout oder Zahlungsabwicklung, und wo lässt sich mit vertretbarem Aufwand nachrüsten?
Danach folgt eine schrittweise Verkürzung der Branch-Lebensdauer: von zwei Wochen auf eine Woche, von einer Woche auf zwei Tage, bis Branches routinemäßig binnen 24 Stunden gemerged werden. Parallel dazu wird eine minimale Feature-Flag-Infrastruktur eingeführt - selbst ein simpler config-basierter Flag-Mechanismus wie im Beispiel oben reicht als Startpunkt. Entscheidend ist, das Team mitzunehmen: Pull-Request-Größe, Commit-Häufigkeit und Review-Geschwindigkeit sind Gewohnheiten, die sich über Wochen einspielen, nicht per Anweisung von einem Tag auf den anderen ändern.
9. Trunk-Based Development im direkten Vergleich
Für PHP- und Magento-Agenturen mit mehreren parallelen Projekten und wechselnden Teamgrößen lohnt sich der direkte Vergleich der gängigen Workflows. Keiner der drei Ansätze ist grundsätzlich überlegen - die Wahl hängt von CI-Reife, Release-Kadenz und Teamgröße ab.
| Dimension | Trunk-Based Development | Git Flow | GitHub Flow |
|---|---|---|---|
| Branch-Lebensdauer | Stunden bis 1-2 Tage | Wochen bis Monate | Tage bis 1 Woche |
| CI-Voraussetzung | Sehr hoch, zwingend | Moderat ausreichend | Hoch empfohlen |
| Release-Kadenz | Mehrmals täglich möglich | Feste Release-Zyklen | Kontinuierlich, pro PR |
| Struktur-Komplexität | Niedrig, ein Branch-Typ | Hoch, viele Branch-Typen | Niedrig bis moderat |
| Umgang mit unfertigem Code | Feature Flags im Trunk | Isolation im Feature-Branch | Meist erst mergen, wenn fertig |
| Eignung für kleine, unerfahrene Teams | Risikoreich ohne CI-Reife | Gut geeignet | Gut geeignet |
| Eignung für regulierte Releases | Nur mit Release-Flags/Branches | Sehr gut geeignet | Bedingt geeignet |
In der Praxis zeigt sich: Git Flow bleibt für Magento-Projekte mit festen, geplanten Release-Fenstern und mehreren parallel gepflegten Versionen sinnvoll. GitHub Flow ist ein solider Mittelweg für Teams mit guter, aber nicht perfekter CI-Reife. Trunk-Based Development entfaltet seinen vollen Vorteil erst, wenn CI-Pipeline, Testabdeckung und Feature-Flag-Infrastruktur bereits solide stehen - dann aber mit der höchsten erreichbaren Lieferfrequenz bei gleichzeitig geringerem Merge-Konflikt-Risiko.
Mironsoft
Git-Workflows, CI/CD-Pipelines und Feature-Flag-Architektur für Magento-Teams
Bereit für schnellere, sicherere Releases?
Wir analysieren euren Git-Workflow, bauen eine CI-Pipeline, die den Trunk zuverlässig grün hält, und führen eine Feature-Flag-Architektur ein, mit der euer Magento-Team unfertigen Code sicher ausliefern kann.
CI/CD-Setup
Schnelle, zuverlässige Pipelines mit PHPStan, PHPUnit und automatisiertem Deployment
Feature-Flag-Architektur
Config-basierte Flags und gestaffelte Rollouts für risikoarme Releases
Git-Workflow-Beratung
Migration von Git Flow zu kurzlebigen Branches, angepasst an Teamgröße und CI-Reife
10. Zusammenfassung
Trunk-Based Development löst das Kernproblem klassischer Branch-Modelle: Je länger ein Branch isoliert existiert, desto teurer wird die Integration. Kurzlebige Branches von Stunden bis maximal zwei Tagen halten den Konfliktraum klein. Feature Flags entkoppeln Deployment von Release und erlauben, unfertigen Code sicher im Trunk zu integrieren, ohne ihn für Nutzer sichtbar zu machen. Beides zusammen funktioniert aber nur mit einer schnellen, zuverlässigen CI-Pipeline, die den Trunk bei jedem Push auf Regressionen prüft, und mit Testdisziplin durch Pre-Commit-Hooks und kleine, schnell reviewbare Pull Requests.
Kein Team sollte Trunk-Based Development als reine Stilfrage einführen. Die Voraussetzungen, insbesondere eine belastbare CI-Infrastruktur und ausreichende Testabdeckung, entscheiden darüber, ob häufige Integration zu mehr Stabilität führt oder einen instabilen Trunk produziert. Für Magento-Agenturen mit mehreren Entwicklern an derselben Codebase ist der schrittweise Umstieg, beginnend bei der CI-Pipeline und endend bei einer schlanken Feature-Flag-Infrastruktur, der zuverlässigste Weg zu schnelleren, risikoärmeren Releases.
Trunk-Based Development - Das Wichtigste auf einen Blick
Kurzlebige Branches
Stunden bis maximal zwei Tage, mehrmals täglicher Merge in den Trunk statt seltener, großer Integrationen.
Feature Flags
Trennen Deployment von Release. Unfertiger Code liegt sicher im Trunk, bleibt aber für Nutzer inaktiv.
Starke CI-Pipeline
Schnelle, automatisierte Tests bei jedem Push. Ein roter Build blockiert jeden weiteren Merge.
Testdisziplin & kleine PRs
Pre-Commit-Hooks fangen Fehler lokal ab. Kleine Pull Requests bleiben schnell und gründlich reviewbar.