Platform Engineering und Internal Developer Platform: Konzepte und Anwendung
1. Überblick
Wenn die Anzahl der Services wächst, muss jedes Team selbst CI/CD, Infrastruktur, Logging, Sicherheit und Dokumentation verwalten. Sie verschwenden viel Zeit mit repetitiven Aufgaben statt mit der Entwicklung von Funktionen. Platform Engineering löst dieses Problem, indem es diese Aufgaben in einer Internal Developer Platform (IDP) zusammenführt, die Entwickler selbstbedienend nutzen können.
Nach diesem Artikel wirst du das Konzept verstehen und wissen, warum es Aufmerksamkeit erhält. Du wirst auch einen kleinen “Golden Path” mit Spring Boot Starter, Service Catalog und einer einfachen Policy Check aufbauen können. Am Ende des Artikels findest du Fälle, in denen die Investition in eine Platform nicht sinnvoll ist.
2. Was ist Platform Engineering?
Platform Engineering ist der Prozess, bei dem ein spezialisiertes Team (Platform Team) eine Reihe von Tools, Prozessen und internen Diensten aufbaut und betreibt. Die Produktteams (Stream-aligned Teams) nutzen diese, um Services zu bauen, bereitzustellen und zu betreiben, ohne die zugrunde liegenden Infrastrukturschichten verstehen zu müssen.
2.1. Internal Developer Platform
Die IDP ist das Produkt, das das Platform Team erschafft. Sie ist normalerweise kein einzelnes Tool. Es ist eine Integrationssicht auf mehrere bestehende Systeme: Git, CI, Kubernetes, Cloud, Secret Manager, Observability. Entwickler interagieren mit dieser Schicht über ein Portal, CLI, Templates oder nur eine Konfigurationsdatei im Repository.
2.2. Drei Kernideen
- Golden Path (befestigte Straße): der empfohlene und unterstützte Weg, um etwas Häufiges zu tun, zum Beispiel „einen neuen REST Service erstellen”. Wer diesen Weg geht, ist schnell und sicher. Andere Wege sind möglich, aber das Team trägt dafür die Verantwortung.
- Self-Service: Entwickler erstellen Services, Datenbanken oder Test-Umgebungen selbst, ohne ein Ticket zu öffnen und auf andere zu warten.
- Platform as a Product: interne Entwickler sind Kunden. Das Platform Team interviewt sie, misst die Nutzung und priorisiert sein Backlog wie ein echtes Produktteam.
2.3. Unterschied zu DevOps?
DevOps ist eine Kultur: das Team, das Code schreibt, trägt auch Verantwortung für den Betrieb (“you build it, you run it”). Platform Engineering ersetzt DevOps nicht. Es ist eine Möglichkeit, DevOps in großem Maßstab zu realisieren. Statt jedes Team zum Kubernetes-, Terraform- und IAM-Experten zu machen, bündelt das Platform Team dieses Wissen in benutzerfreundliche Abstraktionen.
3. Warum wird dieses Thema gerade relevant?
Es gibt mehrere praktische Gründe, die unabhängig von einem bestimmten Tool sind:
- Steigende kognitive Last: Ein moderner Service braucht möglicherweise ein Dockerfile, Kubernetes-Manifest, Pipeline, Dashboard, Alert, Sicherheitsrichtlinien und Secret-Konfiguration. Jede Komponente hat viele Möglichkeiten.
- Jedes Team seine eigene Art: Ohne gemeinsamen Standard protokolliert jeder Service die Logs anders und benennt Metriken unterschiedlich. Bei einem Incident ist es schwierig zu sehen, welches Team für einen Service verantwortlich ist.
- Sicherheits- und Compliance-Anforderungen: Dependency Scanning, Artifact-Signierung, Zugriffskontrolle. Diese Dinge lassen sich leicht einheitlich durchsetzen, wenn sie im Golden Path eingebettet sind.
- Langsames Onboarding: Neue Mitarbeiter müssen lernen: “Wo erstelle ich einen Service, wie stelle ich ihn bereit, wo sehe ich Logs”. Eine gute Platform beantwortet diese Fragen an einem Ort.
4. Typische Komponenten einer IDP
| Komponente | Rolle |
|---|---|
| Service Catalog | Liste der Services, Owner, Dokumentation, Abhängigkeiten |
| Software Template | Neue Projekte nach Standard mit nur wenigen Parametern erstellen |
| Gemeinsame CI/CD | Pipeline mit vordefiniertem Build, Test, Scan, Deploy |
| Self-Service Infrastruktur | Datenbank, Queue, Bucket über deklarative Konfiguration erstellen |
| Standard Observability | Log, Metric, Trace automatisch verfügbar beim Service-Start |
| Guardrail / Policy | Automatische Überprüfung obligatorischer Regeln |
Du brauchst nicht alle sofort. Die folgenden Abschnitte zeigen drei kleine Teile: einen Spring Boot Starter für den Golden Path, einen Catalog Descriptor und eine Policy Check.
5. Golden Path mit Spring Boot Starter
Der günstigste Weg, Java Services zu standardisieren, ist ein interner Starter zu veröffentlichen. Services, die diesen Starter hinzufügen, erhalten automatisch Standard-Metadaten: Name, Team-Besitzer, Kritikalität. Die Metadaten werden über den Actuator angezeigt.
5.1. Abhängigkeiten
Der interne Starter braucht diese Dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Actuator ist als optional gekennzeichnet. Services, die keinen Actuator nutzen, können den Starter trotzdem verwenden.
5.2. Properties und Descriptor
package dev.nonla.platform;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("platform.service")
public record PlatformServiceProperties(String name, String owner, String tier) {
public PlatformServiceProperties {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("platform.service.name is required");
}
if (owner == null || owner.isBlank()) {
throw new IllegalArgumentException("platform.service.owner is required");
}
if (tier == null || tier.isBlank()) {
tier = "standard";
}
}
}
package dev.nonla.platform;
public record ServiceDescriptor(String name, String owner, String tier) {
}
Der kompakte Konstruktor des Record macht zwei Dinge:
- Erzwingt die Deklaration von
nameundowner. Wenn eine der beiden fehlt, startet die Anwendung nicht. Dies ist ein Guardrail: es gibt keine “verwaisten” Services. - Setzt
tierauf den Standardwertstandard, falls nicht deklariert.
5.3. Auto-Konfiguration
package dev.nonla.platform;
import java.util.Map;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
@EnableConfigurationProperties(PlatformServiceProperties.class)
public class PlatformServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ServiceDescriptor serviceDescriptor(PlatformServiceProperties props) {
return new ServiceDescriptor(props.name(), props.owner(), props.tier());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(InfoContributor.class)
static class ActuatorConfiguration {
@Bean
InfoContributor platformInfoContributor(ServiceDescriptor descriptor) {
return builder -> builder.withDetail("platform", Map.of(
"name", descriptor.name(),
"owner", descriptor.owner(),
"tier", descriptor.tier()));
}
}
}
Erklärung der einzelnen Teile:
@ConditionalOnMissingBeanermöglicht einem Service, seinen eigenenServiceDescriptorzu definieren. Dies ist ein „Fluchtweg” aus dem Golden Path, wenn nötig.- Die verschachtelte Klasse
ActuatorConfigurationwird nur geladen, wennInfoContributorauf dem Classpath ist. Auf diese Weise bekommt ein Service ohne Actuator keineClassNotFoundException.
Damit Spring Boot 3 diese Auto-Konfiguration findet, registrierst du sie in der Datei src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
dev.nonla.platform.PlatformServiceAutoConfiguration
Im Service, z.B. order-service, musst du nur den Starter hinzufügen und ein paar Zeilen konfigurieren:
platform.service.name=order-service
platform.service.owner=team-checkout
platform.service.tier=critical
management.endpoints.web.exposure.include=health,info
management.info.env.enabled=false
Wenn du GET /actuator/info aufrufst, erhältst du:
{"platform":{"name":"order-service","owner":"team-checkout","tier":"critical"}}
Jetzt können Überwachungstools oder Catalog diesen Endpoint abfragen, um zu sehen, welches Team welchen Service besitzt.
5.4. Test des Starter-Verhaltens
Das Platform Team muss den Starter gründlich testen. Ein Fehler hier wirkt sich auf jeden Service aus, der ihn nutzt. ApplicationContextRunner hilft, Auto-Konfigurationen schnell zu testen, ohne den ganzen Web Server zu starten:
package dev.nonla.platform;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class PlatformServiceAutoConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PlatformServiceAutoConfiguration.class));
@Test
void givenNameAndOwner_whenStarting_thenDescriptorUsesDefaultTier() {
runner.withPropertyValues(
"platform.service.name=order-service",
"platform.service.owner=team-checkout")
.run(ctx -> {
assertThat(ctx).hasSingleBean(ServiceDescriptor.class)
.hasSingleBean(InfoContributor.class);
assertThat(ctx.getBean(ServiceDescriptor.class).tier())
.isEqualTo("standard");
});
}
@Test
void givenMissingOwner_whenStarting_thenContextFails() {
runner.withPropertyValues("platform.service.name=order-service")
.run(ctx -> {
assertThat(ctx).hasFailed();
assertThat(ctx.getStartupFailure())
.rootCause()
.hasMessageContaining("platform.service.owner");
});
}
}
Der erste Test bestätigt den Standardwert und die Registrierung des InfoContributor. Der zweite Test bestätigt das Guardrail: Wenn owner fehlt, startet der Context nicht, und die Fehlermeldung zeigt genau, welche Eigenschaft fehlt.
6. Service Catalog und Guardrail
6.1. Catalog Descriptor im Repository
Die meisten IDPs speichern Service-Metadaten direkt im Repository neben dem Code. Hier ist ein Descriptor im typischen Stil von bekannten Open-Source-Portalen:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: Verarbeitet Bestellungen und Zahlungen
tags:
- java
- spring-boot
spec:
type: service
lifecycle: production
owner: team-checkout
system: checkout
dependsOn:
- resource:orders-db
providesApis:
- order-api
Die Speicherung dieser Datei in Git hat zwei Vorteile. Metadaten werden zusammen mit dem Code überprüft, und sie geraten nicht aus der Realität wie eine vergessene Wiki-Seite. Das Portal muss nur diese Datei lesen, um Besitzer, Abhängigkeiten und APIs des Service anzuzeigen.
6.2. Einfache Policy Check
Guardrails benötigen nicht unbedingt eine komplexe Policy Engine. Für den Anfang reicht Code, der in CI läuft, um fehlerhafte Konfigurationen zu verhindern:
package dev.nonla.platform.policy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ServicePolicy {
private static final Set<String> ALLOWED_TIERS = Set.of("critical", "standard", "experimental");
public record ServiceSpec(String name, String owner, String tier, int replicas) {
}
public List<String> violations(ServiceSpec spec) {
List<String> errors = new ArrayList<>();
if (!spec.name().matches("[a-z][a-z0-9-]{2,40}")) {
errors.add("name must be kebab-case, 3-41 chars");
}
if (!spec.owner().startsWith("team-")) {
errors.add("owner must be a team, not a person");
}
if (!ALLOWED_TIERS.contains(spec.tier())) {
errors.add("unknown tier: " + spec.tier());
}
if ("critical".equals(spec.tier()) && spec.replicas() < 2) {
errors.add("critical services need at least 2 replicas");
}
return errors;
}
}
Mit Input new ServiceSpec("Order_Service", "alice", "critical", 1) gibt die Funktion zurück:
[name must be kebab-case, 3-41 chars, owner must be a team, not a person, critical services need at least 2 replicas]
Der wichtige Punkt ist, dass Fehlermeldungen klar sagen, was zu reparieren ist. Gute Guardrails leiten Entwickler an, Fehler zu beheben, statt sie nur zu blockieren.
7. Praktische Anwendung: Wo fängst du an?
- Befrage Entwickler zuerst. Frag, wo sie die meiste Zeit verschwenden: beim Erstellen eines Services, beim Warten auf eine Umgebung, oder beim Debuggen von Pipelines. Priorisiere das größte Schmerz, nicht das interessanteste Tool.
- Starte mit einer “dünnsten lebensfähigen Platform”. Manchmal reichen ein Template-Repository, eine gemeinsame Pipeline und eine Dokumentationsseite. Ein Portal kann später kommen.
- Wähle einen einzigen Golden Path. Zum Beispiel “REST Service Spring Boot auf Kubernetes”. Mach diesen Weg gut, dann kannst du dich erweitern.
- Messe. Verfolge, wie viele Teams die Platform nutzen, die Zeit vom Repository-Erstellen bis zum ersten Deployment, und regelmäßiges Feedback von Entwicklern. Wenn niemand sie freiwillig nutzt, ist das ein Signal, dass das Produkt noch nicht stimmt.
- Halte einen Ausweg offen. Der Golden Path sollte der einfachste, nicht der einzig erzwungene Weg sein. Wie in Abschnitt 5.3 ist
@ConditionalOnMissingBeanein kleines Beispiel dieses Prinzips. - Versionierung und Deprecation klar. Starter, Templates und gemeinsame Pipelines sind APIs. Breaking Changes müssen kommuniziert und mit einem Upgrade-Plan versehen werden.
8. Häufige Fehler und wann du keine Platform brauchst
Häufige Fehler:
- Portal vor Inhalten bauen. Ein schönes Portal mit leerem Catalog und veralteten Templates wird schnell vergessen.
- Das Ops-Team in Platform-Team umbenennen. Wenn Entwickler noch immer ein Ticket öffnen und warten müssen, ist das keine Self-Service.
- Zu viel Abstraktion. Kubernetes komplett zu verbergen hindert Entwickler am Debuggen bei Problemen. Die Platform sollte die kognitive Last reduzieren, nicht die Sicht auf tiefere Schichten entfernen.
- Mit Regeln erzwingen statt Wert bieten. Wenn Leute nur wegen einer Order die Platform nutzen, hat sie das Problem nicht wirklich gelöst.
- Ein Starter, der “alles macht”. Ein Starter, der zu viele Dependencies zieht und zu viel implizite Konfiguration hat, verursacht Versionskonflikte und wird schwer zu aktualisieren.
Hinweis: Platform Engineering hat erhebliche Fixkosten. Du brauchst Menschen zum Aufbau, Betrieb und Support der Platform selbst.
Wann solltest du keine separate IDP aufbauen:
- Das Unternehmen ist klein, nur wenige Teams und wenige Services. Eine gute README mit gemeinsamer Pipeline reicht oft aus.
- Das Produkt sucht noch nach Product-Market Fit und die Architektur ändert sich ständig. Zu frühe Standardisierung friert Entscheidungen ein, die bald wieder ändern.
- Cloud-managed Services decken die Bedürfnisse bereits ab. Eine eigene Abstraktionsschicht darüber zu bauen bedeutet nur zusätzliche Wartung.
- Es gibt niemanden, der Product Owner für die Platform sein kann. Ohne jemanden, der auf Entwickler hört, wird die Platform zu einer Sammlung von Tools, die keiner nutzen will.
9. Fazit
Platform Engineering ist kein neues Tool. Es ist eine Organisationsmethode: ein Team sieht die Developer Experience intern als Produkt an, bündelt dann Infrastruktur-Wissen in Golden Paths und Self-Service. Du hast drei kleine Puzzleteile gesehen: einen Spring Boot Starter, der Metadaten standardisiert mit Tests, einen Catalog Descriptor im Repository und eine Policy Check in CI.
Starte klein, miss ehrlich, und erweitere nur, wenn Entwickler die Platform wirklich nutzen. Wenn deine Organisation noch jung ist, können eine gute Template und eine gemeinsame Pipeline schon große Vorteile bringen, ohne dass eine vollständige Platform nötig ist.