Dieser Post setzt den dreiundzwanzigsten Teil fort. Ein Architekturmodell das nur lokal validiert wird, ist erst halb abgesichert. Sobald mehrere Personen am Modell arbeiten, braucht es CI: automatische Prüfung bei jedem Push, sichtbarer Diff bei jedem Pull Request.

Bausteinsicht in der Pipeline installieren

GitHub Actions stellt keine Bausteinsicht-Action bereit — die Installation erfolgt manuell per curl:

- name: Install Bausteinsicht
  run: |
    curl -Lo bausteinsicht.tar.gz \
      https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
    tar xzf bausteinsicht.tar.gz
    sudo mv bausteinsicht /usr/local/bin/
    bausteinsicht --version

Für reproduzierbare Builds empfiehlt sich eine fixe Version statt latest:

https://github.com/docToolchain/Bausteinsicht/releases/download/v0.5.0/bausteinsicht_linux_amd64.tar.gz

validate als Build-Gate

bausteinsicht validate gibt Exit-Code 1 zurück wenn das Modell Fehler enthält — GitHub Actions bricht den Job ab:

name: Architecture Validation
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Bausteinsicht
        run: |
          curl -Lo bausteinsicht.tar.gz \
            https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
          tar xzf bausteinsicht.tar.gz && sudo mv bausteinsicht /usr/local/bin/

      - name: Validate architecture model
        run: bausteinsicht validate --format json
        working-directory: architecture/

Mit --format json landet die Ausgabe als strukturiertes JSON auf stdout — leichter zu parsen wenn man die Fehlerliste weiterverarbeiten will.

Was validate prüft (→ Teil 8):

  • Alle Element-IDs in Beziehungen und Views existieren im Modell

  • Alle verwendeten Tag-IDs sind in specification.tags definiert

  • Kein Element referenziert einen unbekannten Typen

  • Views haben keinen leeren include-Block

diff im Pull Request

bausteinsicht diff vergleicht zwei Modellzustände und zeigt welche Elemente, Beziehungen und Views sich geändert haben. Im PR-Kontext ist HEAD~1 vs HEAD der sinnvolle Vergleich:

      - name: Compute model diff
        id: diff
        working-directory: architecture/
        run: |
          DIFF=$(bausteinsicht diff HEAD~1 HEAD --format json 2>/dev/null || echo '{}')
          echo "result=$DIFF" >> $GITHUB_OUTPUT

      - name: Comment diff on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const diff = JSON.parse('${{ steps.diff.outputs.result }}');
            if (!diff.changes || diff.changes.length === 0) return;
            const body = [
              '## 🏗 Architektur-Änderungen',
              diff.changes.map(c => `- **${c.type}** \`${c.id}\`: ${c.description}`).join('\n')
            ].join('\n');
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body
            });
Für das allererste Commit im Branch (HEAD~1 existiert nicht) gibt diff einen Fehler zurück — der || echo '{}' fängt das ab.

SVG-Export nach jedem Push

Architekturdiagramme als SVG im Repository zu haben bedeutet: Previews in GitHub, direkte Einbindung in Dokumentation, keine lokale Installation nötig um die Diagramme zu sehen.

      - name: Export architecture diagrams
        run: bausteinsicht export --format svg --output out/
        working-directory: architecture/

      - name: Upload SVG artifacts
        uses: actions/upload-artifact@v4
        with:
          name: architecture-diagrams
          path: architecture/out/*.svg
          retention-days: 30

Optional: die exportierten SVGs direkt zurück in den Branch committen:

      - name: Commit exported SVGs
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add architecture/out/*.svg
          git diff --staged --quiet || git commit -m "chore: update architecture diagrams [skip ci]"
          git push

Das [skip ci] verhindert eine Endlosschleife: der Commit durch Actions triggert keinen neuen CI-Lauf.

Vollständiger Workflow

name: Architecture CI
on:
  push:
    paths:
      - 'architecture/**'
  pull_request:
    paths:
      - 'architecture/**'

jobs:
  architecture:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # für diff HEAD~1

      - name: Install Bausteinsicht
        run: |
          curl -Lo bausteinsicht.tar.gz \
            https://github.com/docToolchain/Bausteinsicht/releases/latest/download/bausteinsicht_linux_amd64.tar.gz
          tar xzf bausteinsicht.tar.gz && sudo mv bausteinsicht /usr/local/bin/

      - name: Validate model
        run: bausteinsicht validate --format json
        working-directory: architecture/

      - name: Compute diff (PR only)
        if: github.event_name == 'pull_request'
        id: diff
        working-directory: architecture/
        run: |
          DIFF=$(bausteinsicht diff HEAD~1 HEAD --format json 2>/dev/null || echo '{}')
          echo "result=$DIFF" >> $GITHUB_OUTPUT

      - name: Comment diff on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const diff = JSON.parse('${{ steps.diff.outputs.result }}');
            if (!diff.changes || diff.changes.length === 0) return;
            const body = [
              '## 🏗 Architektur-Änderungen',
              diff.changes.map(c => `- **${c.type}** \`${c.id}\`: ${c.description}`).join('\n')
            ].join('\n');
            github.rest.issues.createComment({
              owner: context.repo.owner, repo: context.repo.repo,
              issue_number: context.issue.number, body
            });

      - name: Export SVGs
        run: bausteinsicht export --format svg --output out/
        working-directory: architecture/

      - name: Commit SVGs to main
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add architecture/out/*.svg
          git diff --staged --quiet || git commit -m "chore: update architecture diagrams [skip ci]"
          git push
paths: ['architecture/**'] sorgt dafür dass der Workflow nur läuft wenn tatsächlich etwas am Modell geändert wurde — nicht bei jedem Commit.

Beispiel-Modell

Das Beispiel für diesen Teil (Modell mit Developer- und CI/CD-Aktoren) liegt unter teil_24.jsonc.

So sieht das Ergebnis in draw.io aus (bausteinsicht sync):

Das draw.io-File dafür findest du hier: teil_24.drawio

Generierte PNG-Dateien via bausteinsicht export --image-format png:

containers
context

Generierte PlantUML-Diagramme via bausteinsicht export-diagram:

Diagram
Diagram

Weiter geht es mit Migration

CI sichert das bestehende Modell ab. Aber was ist wenn man von einem anderen Tool kommt? Im nächsten Teil geht es darum wie man von draw.io, PlantUML und Structurizr zu Bausteinsicht migriert — was automatisch geht und was manuell überführt werden muss.

Offizielle Dokumentation: User Manual · Tutorial auf doctoolchain.org