Wenn semantische Versionierung und Automatisierung zusammentreffen! 🤔

Die Verwaltung von Softwareversionen ist eine der grundlegenden Herausforderungen in der modernen Softwareentwicklung. Ohne ein durchdachtes Versionierungskonzept droht das gefĂĽrchtete „Dependency Hell“ – ein Zustand, in dem Abhängigkeiten zwischen Softwarekomponenten zu unvorhersehbaren Fehlern und Inkompatibilitäten fĂĽhren. Dieser Beitrag zeigt, wie wir die semantische Versionierung in unseren GitLab CI/CD-Pipelines automatisiert haben, mit dem Ziel die Protokollierung, Nachverfolgbarkeit und Konsistenz ĂĽber alle Deployment-Artefakte hinweg zu gewährleisten – von OCI-Images bis hin zu Programm-Bibliotheken.

Das Problem „Dependency Hell“

In der Welt des Software-Managements existiert ein gefĂĽrchteter Ort namens „Dependency Hell“. Je größer ein System wächst und je mehr Pakete integriert werden, desto wahrscheinlicher ist es, dass man sich eines Tages an diesem Abgrund der Verzweiflung wiederfindet.

Ein anschauliches Beispiel liefert AndrĂ© Monteiro in seinem Blog: Eine NestJS-Anwendung, die wochenlang problemlos lief, stĂĽrzte plötzlich nach einem neuen Build ab â€“ nur weil eine transitive Abhängigkeit (das ws-Paket) die UnterstĂĽtzung fĂĽr Node 6 in Version 7 eingestellt hatte, ohne dass die ĂĽbergeordneten Pakete ihre Versionsnummern entsprechend angepasst hatten.

Solche Szenarien verdeutlichen, warum semantische Versionierung nicht nur eine Best Practice, sondern eine Notwendigkeit fĂĽr professionelle Softwareentwicklung ist.

Was ist Semantische Versionierung ?

Semantische Versionierung (Semantic Versioning) ist ein Konzept von Tom Preston-Werner, MitgrĂĽnder von GitHub und Erfinder von Gravatar, fĂĽr die Versionsnummernvergabe von Programmen und Softwarekomponenten. Die vollständige Spezifikation ist unter semver.org verfĂĽgbar. Eine Versionsnummer besteht aus drei Teilen: MAJOR.MINOR.PATCH (z. B. 1.10.24). Zusätzlich verwenden wir optional einen Build‑Indikator als Pre‑Release‑Suffix -bN (z. B. 1.10.24-b1). Hinweis: Die SemVer‑Spezifikation notiert Build‑Metadaten mit einem + (z. B. 1.10.24+0041). Wir weichen hier bewusst leicht ab und verwenden -bN; die Bedeutung (interner Buildzähler) bleibt gleich.


1.2.3-b4
│ │ │ └── Build (bN) – interner Buildzähler
│ │ └── Patch
│ └─── Minor
└──── Major

Die Regeln der semantischen Versionierung

VersionstypBeschreibungBeispiel
PATCHFehlerkorrekturen, abwärtskompatibel1.0.0 → 1.0.1
MINORNeue Funktionen, abwärtskompatibel1.0.0 → 1.1.0
MAJORInkompatible API-Ă„nderungen, Breaking Changes1.0.0 → 2.0.0
BUILDEntwicklungsfortschritt, interne Builds1.0.0 → 1.0.0-b1


Hauptversionsnummer (Major Release):
 Eine Ă„nderung der MAJOR-Versionszahl bedeutet, dass es Ă„nderungen an der Art und Weise gibt, wie das Programm, Skript oder die Softwarekomponente zu verwenden ist â€“ zum Beispiel Ă„nderungen der Eingabeparameter oder Breaking Changes in der API.

Nebenversionsnummer (Minor Release): Eine Ă„nderung der MINOR-Versionszahl bedeutet eine Erweiterung der Funktionen ohne Beeinträchtigung der Kompatibilität zu frĂĽheren Versionen. Es gibt neue Funktionen, aber die alten Funktionen funktionieren wie bisher.

Revisionsnummer (Patch Release): Eine Ă„nderung der PATCH-Versionszahl bedeutet eine Fehlerkorrektur, die voll kompatibel zu frĂĽheren Versionen ist. Hier hat sich nur intern etwas geändert.

Buildnummer: Die Buildnummer kennzeichnet den Fortschritt der Entwicklungsarbeit in Einzelschritten und wird beispielsweise bei jedem Kompilieren des Codes um eins erhöht.

Warum ist die automatische Versionierung besser ?

Die manuelle Vergabe von Versionsnummern ist fehleranfällig und zeitaufwändig. Entwickler vergessen häufig, die Version zu aktualisieren, oder wählen inkonsistente Versionsnummern. Durch die Automatisierung der Versionsnummern-Generierung zum Build-Zeitpunkt minimieren bzw. verhindern wir die falsche und fehlende Verwendung der Versionsnummer.

Jedes Deployment-Artefakt muss eine semantische Versionsnummer verwenden. Nur so kann eine Traceability und Nachverfolgbarkeit gewährleistet werden. Wir verwenden hierzu Tags im Commit-Statement, aus denen die semantische Versionsnummer abgeleitet wird.

Vorteile der automatischen Versionierung

  • Konsistenz: Alle Artefakte folgen demselben Versionierungsschema
  • Nachverfolgbarkeit: Jede Version kann eindeutig einem Commit zugeordnet werden
  • Fehlerreduktion: Keine manuellen Fehler bei der Versionsvergabe
  • Zeitersparnis: Entwickler mĂĽssen sich nicht um Versionsnummern kĂĽmmern
  • Rollback-Fähigkeit: Eindeutige Identifikation fĂĽr Rollbacks

Unsere Implementierung: Commit-basierte Versionierung

ĂĽr unsere Deployment-Artefakte nutzen wir Build-Pipelines unseres Sourcecode-Managementsystems GitLab. Die Versionsnummer wird während des Builds ĂĽber Commit-Kommentare bestimmt und die Versionsnummern-Vergabe automatisch durchgefĂĽhrt.

Tagging-Convention zur automatischen Vergabe von Versionsnummern

Keyword im CommitVersionsänderungBeispiel
majorMAJOR +1, MINOR=0, PATCH=01.0.0 → 2.0.0
featureMINOR +1, PATCH=01.0.0 → 1.1.0
patchbugfixfixPATCH +11.0.0 → 1.0.1
(kein Keyword)BUILD +11.0.0 → 1.0.0-b1

Ein Commit mit der Nachricht feature: Neue Exportfunktion hinzugefĂĽgt fĂĽhrt automatisch zu einer Minor-Version-Erhöhung. Ein Commit mit bugfix: Fehler in der Datumsvalidierung behoben erhöht die Patch-Version.

Artefakt-Typen und ihre Versionierung

Semantische Versionierung ist nicht auf einen Artefakt-Typ beschränkt. Wir setzen sie fĂĽr verschiedene Deployment-Artefakte ein:

OCI-Images fĂĽr Microservices

FĂĽr containerisierte Microservices (z. B. data-importer, data-receiver) durchläuft unsere CI/CD-Pipeline folgende Schritte:

  1. Version Calculation Stage:
    Das version-manager.sh-Skript liest die aktuelle Version aus der release-notes.json, analysiert die Commit-Nachricht auf Keywords und berechnet die neue Versionsnummer.
  2. Build Stage:  Das OCI-Image wird mit der neuen Versionsnummer gebaut und als v{VERSION}-{BRANCH} getaggt.
  3. Publish Stage: Das OCI-Image wird in die GitLab Container Registry gepusht.
  4. Update Stage:  Das update-main-ci.sh-Skript aktualisiert die Versionsvariablen in der .gitlab-ci.yml und die Release Notes werden automatisch ergänzt.
  5. Commit Stage: Die Ă„nderungen werden automatisch committet mit einer Nachricht wie chore: update data-importer version to v1.0.1.

JAR-Bibliotheken fĂĽr Maven-Projekte

FĂĽr Java-Bibliotheken, die als JAR-Dateien verteilt werden (z. B. invoice-transformer), ist die semantische Versionierung besonders wichtig. Diese Bibliotheken werden von anderen Projekten als Abhängigkeiten eingebunden â€“ genau hier entsteht das „Dependency Hell“, wenn Versionen nicht korrekt gepflegt werden.

Warum ist die Versionierung von JAR-Artefakten so wichtig?

  1. Maven-Abhängigkeitsauflösung: Maven verwendet Versionsnummern, um die richtige Version einer Bibliothek zu laden. Ohne eindeutige Versionen können Konflikte entstehen.
  2. Reproduzierbare Builds: Mit einer festen Versionsnummer kann ein Build jederzeit reproduziert werden.
  3. Kompatibilitätsprüfung: Entwickler können anhand der Versionsnummer erkennen, ob ein Update Breaking Changes enthält.
  4. Package Registry Integration: GitLab Package Registry organisiert Pakete nach Versionen – ohne semantische Versionierung ist eine saubere Paketverwaltung unmöglich.

Pipeline-Ablauf fĂĽr JAR-Artefakte:

  1. Version Calculation:  Identisch zu OCI-Images â€“ das version-manager.sh-Skript berechnet die neue Version.
  2. Build Package: Das update-pom-version.sh-Skript aktualisiert die Version in der pom.xml, dann wird mit mvn clean package das JAR gebaut.
  3. Publish to Registry: Das JAR wird mit mvn deploy in die GitLab Package Registry hochgeladen:
  4. Update Config: Die aktualisierte pom.xml, das JAR und die Abhängigkeiten werden ins Repository committet.
mvn deploy:deploy-file \
  -DgroupId=com.example.invoice \
  -DartifactId=invoice-transformer \
  -Dversion=${INVOICE_PACKAGE_VERSION} \
  -Dpackaging=jar \
  -Dfile=target/invoice-transformer.jar \
  -Durl=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven

Beispiel einer versionierten pom.xml:

<groupId>com.example.invoice</groupId>
<artifactId>invoice-transformer</artifactId>
<version>1.2.0</version>  <!-- Automatisch aktualisiert -->
<packaging>jar</packaging>

Verwendung in anderen Projekten:

<dependency>
    <groupId>com.example.invoice</groupId>
    <artifactId>invoice-transformer</artifactId>
    <version>1.2.0</version>
</dependency>

Release Notes und Kompatibilitätsinformationen

Besonders wertvoll ist die automatische Dokumentation in der release-notes.json. Jeder Build erzeugt einen Eintrag mit Version, Build-Datum, Commit-Nachricht und optional Kompatibilitätsinformationen:

{
  "version": "v1.0.0",
  "buildAt": "2025-10-23",
  "releaseNotes": "Major first release\n- Logging to nfs-share\n- Version management",
  "compatibilityInfos": {
    "PostgreSQL": "v3.5.3-b1",
    "Kafka": "v3.6.1-b1",
    "Zookeeper": "v3.8.3-b1"
  }
}

Entwickler können Kompatibilitätsinformationen direkt im Commit-Kommentar angeben:


feature: Neue Datenbankanbindung Compatibility: - data-receiver: v1.2.0 or higher - PostgreSQL: v16.0 or higher

Diese Informationen werden automatisch geparst und in der JSON-Datei gespeichert.

Warum Kompatibilitätsinformationen unverzichtbar sind

In verteilten Systemen mit mehreren Microservices ist die Dokumentation von Kompatibilitäten kritisch. Ein typisches Szenario verdeutlicht dies:

Das Szenario: Ein verteiltes System besteht aus mehreren Komponenten, die ĂĽber einen Message Broker miteinander kommunizieren:

Das Problem: Bei einem Deployment wurde eine neue Version des API-Service ausgerollt, die ein geändertes Nachrichtenformat fĂĽr den Message Broker verwendete. Der Consumer war jedoch noch auf die alte Version konfiguriert und konnte die neuen Nachrichten nicht verarbeiten. Gleichzeitig war der Producer auf eine Version aktualisiert worden, die mit dem alten Consumer-Format inkompatibel war.

Die Folge:

  • Nachrichten wurden nicht mehr korrekt verarbeitet
  • Fehler traten erst zur Laufzeit auf – nicht beim Build
  • Die Fehlersuche war zeitaufwändig, da unklar war, welche Versionen miteinander kompatibel sind

Die Lösung: Durch konsequente Dokumentation der Kompatibilitäten in den Release Notes kann vor einem Deployment geprĂĽft werden, ob alle Komponenten zueinander passen. Hätte das Deployment-Team die Release Notes des neuen API-Service geprĂĽft, wäre sofort ersichtlich gewesen, dass Consumer und Producer ebenfalls aktualisiert werden mĂĽssen:

{
  "version": "v2.0.0",
  "buildAt": "2025-10-28",
  "releaseNotes": "Major: Neues Nachrichtenformat fĂĽr Message Broker",
  "compatibilityInfos": {
    "database": "v3.5.0 or higher",
    "message-broker": "v3.6.0 or higher",
    "api-service": "v2.0.0",
    "consumer": "v2.0.0 or higher",
    "producer": "v2.0.0 or higher",
    "data-importer": "v1.5.0 or higher"
  }
}

Best Practices für Kompatibilitätsinformationen

  1. Immer dokumentieren
    Bei jeder Änderung, die andere Komponenten betrifft, Kompatibilität angeben
  2. Mindestversionen angeben
    Nicht nur „kompatibel mit“, sondern „v1.2.0 or higher“
  3. Breaking Changes markieren
    Bei MAJOR-Versionen explizit auf Inkompatibilitäten hinweisen
  4. Infrastruktur einbeziehen
    Auch Message Broker, Datenbanken, Cache-Systeme etc. dokumentieren
  5. Vor Deployment prĂĽfen
    Release Notes aller zu deployenden Komponenten vergleichen

Helm Values und Versionskonsistenz

In Kubernetes-Deployments mit Helm werden die Image-Versionen in values.yaml definiert. Die Kompatibilitätsinformationen aus den Release Notes helfen dabei, konsistente Deployments zu gewährleisten:

# deployment/values.yaml
api-service:
  app:
    image: registry.example.com/myproject/api-service:v2.0.0
 
consumer:
  app:
    image: registry.example.com/myproject/consumer:v2.0.0
 
producer:
  app:
    image: registry.example.com/myproject/producer:v2.0.0
 
# Alle Versionen mĂĽssen laut Release Notes kompatibel sein!

Vor jedem Deployment sollte geprĂĽft werden:

  1. Welche Version wird aktuell deployed?
  2. Welche Versionen laufen bereits in der Zielumgebung?
  3. Sind alle Versionen laut Release Notes kompatibel?

Tipp: Automatisierte KompatibilitätsprĂĽfungen können in die CI/CD-Pipeline integriert werden, um vor dem Deployment zu warnen, wenn inkompatible Versionen kombiniert werden sollen.

Abfrage der Versionsnummer zur Laufzeit

Um die Version eines Deployment-Artefaktes abzufragen, stellen wir eine dedizierte, standardisierte API bereit. FĂĽr Microservices in einer Cloud-nativen Architektur erfolgt die Abfrage ĂĽber den Endpunkt /version:

{
  "version": "1.0.2",
  "build": "abc12345",
  "commit_hash": "a1b2c3d4e5f67890abcdef1234567890"
}

Anforderungen an die Versionsabfrage

AnforderungBeschreibung
EinheitlichkeitDie Abfrage muss fĂĽr jede Deployment-Artefakt-Kategorie identisch sein
Öffentlich zugänglichDie Abfrage muss ohne Authentifizierung möglich sein
KonsistenzDie Abfrage muss den gleichen Wert liefern wie in Logdateien
LesbarkeitDie Ausgabe muss natürlich lesbar sein (z. B. UTF‑8‑Text)

Integration ins Monitoring und Logging

Wir integrieren die Versionsinformationen immer in der Log-Ausgabe des Deployment-Artefaktes. Nur so können die Logeinträge eindeutig der jeweiligen Softwareversion zugeordnet werden.

Anwendungsgebiete

BereichNutzen
Rollback-StrategienEindeutige Identifikation des letzten lauffähigen Softwarestandes für schnelle Rollbacks
KonsistenzStandardisierter Ansatz erleichtert Verwaltung und Ăśberwachung komplexer Architekturen
AutomatisierungKlare Struktur ermöglicht einfache Automatisierung der Versionsprüfung in CI/CD-Pipelines
DebuggingSchnelle Zuordnung von Logeinträgen zur exakten Softwareversion
ComplianceNachweisbarkeit welche Version zu welchem Zeitpunkt im Einsatz war

Fazit

Die automatisierte semantische Versionierung in GitLab CI/CD ist mehr als nur eine technische Spielerei â€“ sie ist ein fundamentaler Baustein fĂĽr professionelle Softwareentwicklung. Durch die konsequente Anwendung vermeiden wir das gefĂĽrchtete „Dependency Hell“, ermöglichen präzise Rollbacks und schaffen Transparenz ĂĽber den gesamten Software-Lebenszyklus.

Die Investition in ein durchdachtes Versionierungskonzept zahlt sich mehrfach aus:

  • Weniger Fehler durch inkonsistente Versionen
  • Bessere Nachverfolgbarkeit bei Problemen
  • Klare Kommunikation ĂĽber Ă„nderungen zwischen Teams und Stakeholdern
  • Reproduzierbare Builds und Deployments
  • Einfache Integration in Package Registries (Maven, npm, Docker)

Ob Docker-Images fĂĽr Microservices oder JAR-Bibliotheken fĂĽr Maven-Projekte â€“ das Prinzip bleibt gleich: Jedes Artefakt erhält eine eindeutige, semantisch aussagekräftige Versionsnummer, die automatisch aus dem Commit-Kontext abgeleitet wird.

Quellen und Referenzen

  1. Die offizielle Spezifikation von Tom Preston-Werner – Semantic Versioning Specification (SemVer)
  2. Blog des SemVer-Erfinders und GitHub-MitgrĂĽnders – Tom Preston-Werner’s Blog
  3. Praxisbeispiel zu Dependency Hell – AndrĂ© Monteiro – Semantic Versioning and why it’s important
  4. Allgemeine Informationen zur Versionierung – Wikipedia – Versionsnummer
  5. Tutorial zur GitLab-Integration – Medium – Automatic releases in GitLab
  6. Offizielle GitLab-Dokumentation – GitLab Documentation – Package Registry

Wenn Sie Unterstützung in der Softwareentwicklung benötigen oder sich für moderne Webtechnologien interessieren, beraten wir Sie bei PASCADA Consulting GmbH gerne persönlich.