Mittelstand Radar: Kaufsignale aus dem deutschen Mittelstand — jetzt die erste Report-Ausgabe sichern.Zur Warteliste
Abstrakte Architekturzeichnungen und Baupläne auf einem Lichttisch
Zurück zum Blog
dotnetaspirearchitektur

Aspire 13: Neue Architektur, neue Developer Experience

Sven HennessenDevOps

Aspire 13 hat nicht nur neue Features bekommen – die gesamte interne Architektur wurde neu gebaut. Pipeline-Modell, Single-File AppHost und TypeScript-Unterstützung ändern, wie man mit Aspire arbeitet.

Die meisten sichtbaren Änderungen in Aspire 13 sind Features: neue Deployment-Targets, neue Sprachen, neue Integrationen. Weniger offensichtlich, aber fundamentaler, sind die Änderungen darunter: Wie Aspire intern Deployment-Schritte koordiniert, wie der AppHost selbst aussieht, und wie die CLI-Oberfläche aufgebaut ist. Dieser Artikel beschreibt die architektonischen Entscheidungen, die alles andere ermöglichen.

Das Ende des Publisher-Modells

In Aspire 9 gab es ein Erweiterungsmodell für Deployment: das IDistributedApplicationPublisher-Interface. Wer einen eigenen Deployment-Pfad bauen wollte, implementierte dieses Interface, registrierte es per WithPublishingCallback und bekam einen PublishingContext übergeben. Das Modell war sequenziell, schwer testbar und eng an das Manifest-Format gebunden.

Aspire 13 entfernte diese APIs vollständig. Ersatz ist das Pipeline-Modell.

Das Pipeline-Modell: PipelineStep und Dependency-Graph

Statt eines zentralen Publishers tragen Ressourcen im AppHost jetzt eigene PipelineStep-Objekte bei. Jeder Schritt deklariert explizit, von welchen anderen Schritten er abhängt. Aspire baut daraus einen gerichteten Graphen und führt unabhängige Schritte parallel aus.

resource.WithPipelineStepFactory((context, steps) =>
{
    var build = steps.AddStep("build-image", async ctx => {
        // Container-Image bauen
    });
    var push = steps.AddStep("push-image", async ctx => {
        // Image in Registry pushen
    }).DependsOn(build);
    var deploy = steps.AddStep("deploy", async ctx => {
        // Auf Cluster deployen
    }).DependsOn(push);
});

Das Ergebnis ist ein Deployment-Prozess, der von Aspire selbst parallelisiert wird, wo immer es die Abhängigkeiten erlauben. Images für unabhängige Services werden gleichzeitig gebaut und gepusht; Provisionierung und Build laufen parallel, solange kein Schritt das Ergebnis des anderen braucht.

Die Pipeline ist inspektierbar, bevor sie ausgeführt wird:

aspire deploy --list-steps          # Alle Schritte und Abhängigkeiten anzeigen
aspire deploy --pipeline-log-level debug  # Verbose-Output pro Schritt
aspire deploy --environment staging       # Gezielt auf eine Umgebung deployen

Deployment State Persistence

Ein einfacher aber wirksamer Wechsel: Aspire 13 merkt sich zwischen Deployments, was bereits eingegeben wurde.

In Aspire 9 fragte azd up bei jedem Durchlauf nach Subscription, Region und Ressourcengruppe. In Aspire 13 werden diese Angaben pro Projekt und Umgebung im Benutzerprofil gespeichert:

~/.aspire/deployments/<project-hash>/<environment>.json

Der nächste aspire deploy-Aufruf belegt die Werte vor. --clear-cache setzt den gespeicherten Zustand zurück. Wer zwischen mehreren Umgebungen wechselt, gibt den Namen per --environment an:

aspire deploy --environment production
aspire deploy --environment staging --clear-cache

Single-File AppHost

Bisher erforderte jedes Aspire-Projekt ein eigenes C#-Projektfile mit Paketverweisen. Ab Aspire 13 lässt sich ein AppHost als einzelne Datei schreiben, ohne separates .csproj:

#:sdk Aspire.AppHost.Sdk@13.0.0
#:package Aspire.Hosting.PostgreSQL@13.0.0
#:package Aspire.Hosting.Redis@13.0.0

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");
var db = builder.AddPostgres("db");
var api = builder.AddProject<Projects.Api>("api")
    .WithReference(cache)
    .WithReference(db);

builder.Build().Run();

Die #:sdk- und #:package-Direktiven ersetzen das Projektfile. Das SDK lädt die angegebenen Pakete beim ersten Start automatisch herunter. Für kleinere Setups, Prototypen oder Demos entfällt damit die Projektstruktur als Einstiegshürde.

TypeScript AppHost

Ab Aspire 13.4 (GA) lässt sich der AppHost auch in TypeScript schreiben. Statt eines C#-Projekts ist der Einstiegspunkt eine .mts-Datei:

import { createBuilder } from "@aspire/hosting";
import { addPostgres } from "@aspire/hosting-postgresql";
import { addRedis } from "@aspire/hosting-redis";

const builder = await createBuilder(process.argv);

const cache = addRedis(builder, "cache");
const db = addPostgres(builder, "db");
builder.addProject("api", "../Api/Api.csproj")
    .withReference(cache)
    .withReference(db);

await builder.build().run();

Die TypeScript-API spiegelt die C#-Oberfläche: dieselben Konzepte, dieselben Methoden, generiert aus denselben XML-Dokumentationskommentaren. Aspire validiert den TypeScript-Code vor dem Start.

Wer ein TypeScript-Frontend oder einen Python-Backend-Service orchestriert, braucht damit kein .NET im Toolchain des AppHost selbst – nur die Aspire CLI.

Die neue Aspire CLI

Parallel zur Pipeline-Architektur wurde die CLI-Oberfläche neu strukturiert. In 9.x war der primäre Weg azd up; ab Aspire 13.4 sind folgende Befehle stabil:

BefehlFunktion
aspire deployDeployment ausführen
aspire publishDeployment-Artefakte generieren (Helm-Charts, Compose-Files)
aspire destroyDeployment rückgängig machen
aspire runAppHost lokal starten
aspire doctorUmgebungsdiagnose (Versionen, Konflikte)
aspire integration listVerfügbare Hosting-Integrationen auflisten
aspire logs --searchLogs mit serverseitiger Filterung abrufen
aspire lsAlle AppHosts im aktuellen Verzeichnis auflisten

aspire doctor ist besonders nützlich in Umgebungen, in denen mehrere Aspire-Versionen koexistieren: Es prüft CLI-Version, SDK-Version, installierte Tools (Helm, Docker) und meldet Konflikte.

Bekannte Lücke: aspire doctor prüft aktuell, ob Docker läuft – aber nicht, ob das docker buildx-Plugin installiert ist. Auf frischen Ubuntu-Installationen mit docker.io (ohne docker-buildx) schlägt der Image-Build-Schritt mit einer irreführenden Fehlermeldung fehl:

Container runtime 'Docker' is not running or is unhealthy.

Die eigentliche Ursache ist das fehlende Buildx-Plugin. apt-get install -y docker-buildx löst das. Das Issue (microsoft/aspire#16118) ist bekannt und noch offen – aspire doctor wird diesen Check künftig abdecken.

Was das Pipeline-Modell noch nicht löst

Das Pipeline-Modell macht Deployment-Schritte sichtbar und parallelisierbar – eine echte Verbesserung gegenüber dem sequenziellen Publisher-Modell. Eine strukturelle Lücke bleibt aber: Werte, die ein Service erst nach dem Start produziert (Keycloak-Client-Secrets, Vault-Tokens, MinIO-Access-Keys), können nicht in derselben Pipeline-Ausführung an abhängige Services weitergegeben werden.

Lokal funktioniert das über ResourceReadyEvent nahtlos. Im Deploy-Modus gibt es dafür noch kein Äquivalent. Der aktuelle Workaround ist ein Zwei-Deploys-Pattern: Erster Deploy startet den Service, ein eigener Pipeline-Step provisioniert ihn und speichert die Werte im Deployment-State. Zweiter Deploy liest den State und injiziert die Werte. Wer Services dieser Art deployt, sollte das beim ersten aspire deploy einplanen (microsoft/aspire#18097).

Brauchst du Unterstützung?

Du möchtest das neue Pipeline-Modell in euren bestehenden Aspire-Projekten einführen oder eigene Deployment-Schritte via WithPipelineStepFactory bauen, aber bist unsicher, wie du die Migration angehst? Wir helfen dir gerne dabei! Melde dich einfach bei uns und wir schauen gemeinsam, wie wir eure Aspire-Architektur fit für 13 machen.

Kontakt aufnehmen