Queues ohne Überstunden: C#, Azure Service Bus und AWS SQS in einem belastbaren Ablauf

Interaktiver Workshop zu C# 14, .NET 10, Azure Service Bus und Amazon SQS: Outbox, Idempotenz, Blob/S3-Claim-Check, DLQ, Ausfallkosten, animierte Karte und Quiz mit E-Mail-Bericht.

Der vollständige Artikel steht unten. Aktivieren Sie JavaScript für Quiz und Simulationen.

LAB 01 · BELASTBARER ABLAUF C# 14 · .NET 10 · Azure Service Bus · Amazon SQS
Entscheidungen im Artikel 0/4 Löse die Entscheidungen in den Kapiteln.

Ergebnis löschen

Freitag, 16:47

Eine Queue soll Ausfälle nicht verstecken. Sie verschafft Zeit für eine sichere Reaktion

Ein Kunde bestätigt einen Auftrag. Die API antwortet korrekt, das externe Abrechnungssystem wird aber langsam. Der Worker verliert nach der Ausführung die Verbindung; dieselbe Nachricht kommt erneut. Setzt der Entwurf genau eine Zustellung voraus, beginnt Samstagmorgen mit doppelter Wirkung und manueller Datenbereinigung.

Dies ist ein Workshop, keine erfundene Kundenerfolgsgeschichte. Wir bauen einen Ablauf für Wiederholungen, Verzögerungen und Teilausfälle: gemeinsamer C#-Port, Transportstrategie, Transactional Outbox, idempotenter Konsument, Retry, Dead-Letter Queue und Claim-Check für Dateien in Azure Blob Storage oder Amazon S3. Abschließend folgen zehn Prüfungsfragen und ein persönlicher E-Mail-Bericht.

01

Denkmodell

Eine Queue ist ein kontrollierter Warteraum, kein magischer Beschleuniger

Im synchronen System wartet der Nutzer auf alle abhängigen Schritte. Asynchron nimmt die API eine Absicht an, speichert eigenen Zustand und reicht die Ausführung weiter. Ein vorübergehender Integrationsausfall muss dann nicht den ganzen Pfad stoppen. Dafür muss das System Duplikate erkennen, Arbeit wieder aufnehmen, Rückstände beobachten und Fälle bewusst entscheiden, die Automatisierung nicht mehr reparieren kann.

01Annehmen

Absicht speichern und eine stabile ID vergeben.

02Entkoppeln

Nutzer nicht auf fremde Systeme warten lassen.

03Wiederholen

Retry ist Normalfall, keine Entwurfsausnahme.

04Nachweisen

Metrik, Log und Receipt müssen eine Wirkung belegen.

Karte zum Behalten

Vom Request zur Wirkung: ein Vertrag, zwei Transporte

Ablauf aktiv
01APICommand + requestId
02DB + Outboxein COMMIT
03DispatcherVeröffentlichung nach Speicherung
AAzure Service BusPeekLock · Sessions · DLQ
BAmazon SQSVisibility · FIFO · DLQ
04Consumer + InboxIdempotenz
05WirkungReceipt + Telemetrie
RetryBackoff + JitterDLQ
QR-Code zur mobilen Karte des Queue-Ablaufs
AUF DEM TELEFON ÖFFNEN

Scanne die Karte und nutze sie als Review-Checkliste

Der Code führt direkt zu dieser animierten Karte. Öffne sie beim Worker-Entwurf oder bei der Incident-Analyse, ohne das Kapitel suchen zu müssen.

Karte ohne Scannen öffnen →
Entscheidung 1/4

Die API hat den Auftrag gespeichert, der Broker ist vorübergehend unerreichbar. Was tust du?

Ich warte und wiederhole die Veröffentlichung im HTTP-Request.

Ich speichere Zustand und Outbox in einer Transaktion; der Dispatcher setzt das Senden fort.

Wähle die Lösung, die keinen angenommenen Auftrag verliert.

Technologiestand bei Veröffentlichung

Neues C# repariert keine falschen Grenzen, kann sie aber einfacher ausdrücken

C# 14 kommt mit .NET 10, einem LTS-Release. Extension Members, Null-Conditional Assignment und verbesserte Span-Nutzung erleichtern den Code; Zuverlässigkeit entsteht weiterhin aus Vertrag und Daten. Wichtiger als syntaktische Kürze sind ID, Version, Korrelation, Ereigniszeit und minimale Konsumentendaten.

Wichtige Azure-Migration

Microsoft kündigte das Supportende der älteren Bibliotheken WindowsAzure.ServiceBus und Microsoft.Azure.ServiceBus zum 30. September 2026 an. Neuer Code sollte Azure.Messaging.ServiceBusverwenden; bestehende Systeme brauchen eine geplante Migration mit Abnahmetest.

AWS für .NET

AWS SDK for .NET V4 bietet direkte SQS-APIs; das AWS Message Processing Framework for .NET kann Veröffentlichung und Verarbeitung ordnen. Es ersetzt jedoch weder Idempotenz noch korrekte Visibility Timeouts oder DLQ-Kontrolle.

Transportwahl

Azure Service Bus und Amazon SQS lösen verwandte Aufgaben mit unterschiedlichen Werkzeugen

BedarfAzure Service BusAmazon SQSEntwurfsentscheidung
Grundlegende ZustellungPeekLock liefert At-least-once-SemantikStandard Queue liefert At-least-onceDer Konsument muss Wiederholungen vertragen
Reihenfolge innerhalb einer GruppeSessions mit SessionIdFIFO mit MessageGroupIdNach Aggregat gruppieren, nicht systemweit
VeröffentlichungsduplikateDuplicate Detection über MessageIdFIFO-Deduplizierung über Deduplication-IDErgänzende Schicht, kein Inbox-Ersatz
AusführungszeitLock-Dauer und ErneuerungVisibility Timeout und AnpassungOberhalb p95 setzen, Überschreitungen beobachten
Poison MessagesEingebaute Dead-Letter QueueRedrive Policy zur DLQAlarm, Verantwortliche, Retry-Policy und Replay-Verfahren
Große DatenBlob Storage + Schlüssel in der NachrichtS3 + Schlüssel in der NachrichtClaim-Check, Prüfsumme und begrenzte Zugriffszeit

„Exactly once“ darf kein Wunsch im Entwurf sein. Ein sicheres Ziel lautet: Die Nachricht kann erneut eintreffen, die beobachtbare Geschäftswirkung entsteht einmal.

02

C#-Vertrag

Erst der Nachrichtenumschlag, dann die Cloud-Wahl

Das Domänenmodell sollte keine Azure- oder AWS-SDK-Typen importieren. Zuerst definieren wir einen eigenen kleinen Vertrag. Der Transportadapter übersetzt ihn in Brokereigenschaften.

public sealed record WorkEnvelope<T>(
    Guid MessageId,
    Guid CorrelationId,
    string TenantId,
    int SchemaVersion,
    DateTimeOffset OccurredAt,
    T Payload);

public interface IWorkQueue
{
    Task PublishAsync<T>(
        WorkEnvelope<T> message,
        QueueRoute route,
        CancellationToken cancellationToken);
}

MessageId schützt vor wiederholter Wirkung, CorrelationId verbindet die Spur zwischen API und Worker, TenantId ermöglicht Isolation, und SchemaVersion schafft Raum für sichere Weiterentwicklung. Secrets, vollständige Dokumente und Daten „für alle Fälle“ gehören nicht in den Umschlag.

STRATEGY + ADAPTER

Transportwahl durch eine Policy

public interface IQueueStrategy
{
    bool Supports(CloudBroker broker);
    Task PublishAsync<T>(
        WorkEnvelope<T> message,
        QueueRoute route,
        CancellationToken ct);
}

public sealed class QueueRouter(
    IEnumerable<IQueueStrategy> strategies)
{
    public Task PublishAsync<T>(
        CloudBroker broker,
        WorkEnvelope<T> message,
        QueueRoute route,
        CancellationToken ct)
        => strategies.Single(x => x.Supports(broker))
            .PublishAsync(message, route, ct);
}
POLICY STATT IF-WIRRWARR

Wann lohnt sich ein Router?

  • Verschiedene Regionen oder Mandanten brauchen unterschiedliche Transporte,
  • eine Cloud-Migration läuft,
  • kontrolliertes Failover ist nötig,
  • Tests nutzen In-Memory-Transport.

Nutzt das System immer einen Broker, ist ein Adapter einfacher. Abstraktion soll echte Variabilität beherrschen, nicht vorsorglich erzeugen.

Entscheidung 2/4

Eine Nachricht soll ein 18-MB-PDF übertragen. Was kommt in die Queue?

Die gesamte Datei als Base64, damit der Worker sofort alles hat.

Objektschlüssel, Prüfsumme und Metadaten; die Datei liegt in Blob Storage oder S3.

Wähle einen kleinen, sicheren Vertrag.

Claim-Check

Blob Storage oder S3 trägt das Gewicht. Die Queue trägt die Absicht

Die API speichert unter einem unvorhersehbaren Schlüssel, berechnet SHA-256 und veröffentlicht nur die Referenz. Der Worker greift mit Managed Identity oder IAM-Rolle zu, prüft den Hash und verarbeitet als Stream. Öffentlicher Zugriff ist deaktiviert; Aufbewahrung und Verschlüsselung folgen der Datenklasse.

UploadBlob / S3objectKey + SHA-256QueueWorker
03

Atomarität

Die Outbox schließt die Lücke zwischen COMMIT und Veröffentlichung

PostgreSQL und Broker teilen keine verteilte Transaktion. Deshalb speichern wir Geschäftsänderung und Publikationsdatensatz in derselben lokalen Transaktion.

await using var transaction =
    await db.Database.BeginTransactionAsync(ct);

var order = WorkOrder.Accept(command.RequestId, command.CustomerId);
db.WorkOrders.Add(order);

db.OutboxMessages.Add(OutboxMessage.Create(
    messageId: command.RequestId,
    type: "work-order.accepted.v1",
    payload: serializer.Serialize(new WorkOrderAccepted(order.Id))));

await db.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);

// Dispatcher odczyta OutboxMessages dopiero po COMMIT.

Der Dispatcher liest unbearbeitete Datensätze portionsweise, veröffentlicht und markiert sie. Zwei Dispatcher dürfen denselben Datensatz nicht ohne Nebenläufigkeitskontrolle übernehmen. PostgreSQL kann Zeilensperren mit SKIP LOCKED oder einen ausdrücklichen Lease-Status nutzen. Wichtig bleibt das Alter des ältesten Outbox-Eintrags: Die Anzahl allein zeigt nicht, wie lange ein Kunde wartet.

04

Idempotenter Empfang

Die Inbox entscheidet über Duplikate vor irreversiblen Wirkungen

Service Bus mit PeekLock und SQS Standard können Nachrichten erneut zustellen. Der Konsument prüft sein Receipt, führt die Wirkung aus und bestätigt erst nach dauerhaftem Speichern.

public async Task HandleAsync(
    WorkEnvelope<WorkOrderAccepted> message,
    CancellationToken ct)
{
    if (await db.InboxReceipts.AnyAsync(
        x => x.MessageId == message.MessageId, ct))
        return;

    await using var tx = await db.Database.BeginTransactionAsync(ct);

    await pricing.CreateQuoteAsync(
        message.Payload.WorkOrderId,
        idempotencyKey: message.MessageId.ToString("N"),
        ct);

    db.InboxReceipts.Add(new InboxReceipt(message.MessageId));
    await db.SaveChangesAsync(ct);
    await tx.CommitAsync(ct);
}
Entscheidung 3/4

Der Worker führte die Aktion aus, verlor aber vor Complete/Delete die Verbindung. Was tut er bei erneuter Zustellung?

Receipt anhand der MessageId erkennen und ohne erneute Wirkung abschließen.

Alles erneut ausführen, weil der Broker bewusst wiederholt hat.

Was belegt den Abschluss, wenn die Brokerbestätigung verloren ging?

Die Kosten eines Ausfalls

Eine Queue spart erst Geld, wenn sie manuelle Reaktionskosten senkt

Die folgende Rechnung ist ein Beispiel für eigene Zahlen, kein ArcCloud-Kundenfall. Angenommen: vier Vorfälle pro Monat, drei Beteiligte für durchschnittlich 2,5 Stunden und 180 PLN Vollkosten je Arbeitsstunde.

4 Vorfälle×3 Personen×2,5 Std.×180 PLN= 5.400 PLN / Monat

Hinzu kommen Konzentrationsverlust, Roadmap-Verzögerungen und Kundenrisiken. Vergleiche diese Kosten mit Broker, Telemetrie und automatischen Tests. Die günstigste Architektur hat nicht die kleinste Cloud-Rechnung, sondern die niedrigsten Gesamtkosten eines sicheren Ergebnisses.

Architekturspiel

Ordne sieben Schritte sicherer Verarbeitung

Klicke die Felder in Reihenfolge. Eine falsche Wahl kostet keinen Punkt, sondern zeigt die noch fehlende Verantwortung.

Fortschritt0/7

BrokerASB oder SQS speichert die Nachricht

CommandAPI nimmt eine stabile requestId an

GeschäftswirkungAusführung mit Idempotenzschlüssel

Dispatcherveröffentlicht die gespeicherte Outbox

SettlementComplete oder Delete nach dauerhaftem Ergebnis

DB + OutboxZustand und Nachricht in einem COMMIT

Consumer + InboxDuplikat- und Korrelationsprüfung

Beginne mit einer Absicht mit stabiler ID.

Neu ordnen

Entscheidung 4/4

37 Nachrichten liegen in der DLQ. Was ist der erste richtige Schritt?

Sofort alle Nachrichten an die Hauptqueue zurücksenden.

Ursache klassifizieren und beheben, danach eine kleine Charge kontrolliert wiederholen.

Die DLQ ist eine Betriebsqueue, kein Papierkorb zum Leeren per Klick.

Incident-Simulator

Führe eine Nachricht durch Timeout, Retry und Duplikat

Der kontrollierte Ablauf prüft requestId, Outbox, Transport, Inbox, Geschäftswirkung, Receipt und Settlement.

Ablauf starten

APIOUTBOXBROKERRETRYINBOXEFFECT

Labor bereit. Starte den kontrollierten Ablauf.

Produktions-Runbook

Was du beobachten solltest, bevor nach Feierabend das Telefon klingelt

01

Nachrichtenalter

p50, p95 und älteste Nachricht. Queuelänge ohne Eingangsrate ist irreführend.

02

Retry-Rate

Versuche nach Fehlertyp, Abhängigkeit und Vertragsversion. Alarmiere Trendänderungen, nicht jeden einzelnen Retry.

03

DLQ-Eingang

Neue Einträge, Grund, Verantwortung und Zeit bis zur Entscheidung. Eine DLQ ohne Verantwortliche wächst unbemerkt.

04

Lock lost / Timeout

Zeigt unpassende Arbeits- und Sperrzeiten oder überlastete Konsumenten.

05

Verhinderte Duplikate

Der Zähler belegt, dass Idempotenz das Ergebnis tatsächlich schützt und nicht nur dokumentiert ist.

06

Outbox-Verzögerung

Alter des ältesten unveröffentlichten Datensatzes und Dispatcher-Durchsatz bilden grundlegende Integrations-SLOs.

START

Für Einsteiger

  • Command, Event und Document Message unterscheiden.
  • Verstehen, wann Complete/Delete sicher ist.
  • MessageId, correlationId und einen Duplikattest ergänzen.
PRAXIS

Für Fortgeschrittene

  • Outbox und Inbox mit eindeutigem Index implementieren.
  • Lock oder Visibility Timeout an p95 ausrichten.
  • Retry mit Backoff, Jitter und kontrollierter DLQ bauen.
SENIOR

Für Seniors

  • SLO, Fehlerbudget und Queue-Verantwortung festlegen.
  • Schemaentwicklung und Replay historischer Daten testen.
  • Betriebskosten berechnen und sicheres Rollback entwerfen.

Checkliste vor Produktion

Schon ein „weiß ich nicht“ bedeutet: Die Queue ist noch nicht bereit

  • Hat jede Nachricht stabile MessageId und correlationId?
  • Werden Zustand und Outbox atomar gespeichert?
  • Liefert ein Duplikat dasselbe beobachtbare Ergebnis?
  • Nutzen große Payloads Blob Storage/S3 und Claim-Check?
  • Unterscheidet Retry vorübergehende und dauerhafte Fehler?
  • Hat die DLQ Alarm, Verantwortliche und Replay-Verfahren?
  • Passt Lock/Visibility Timeout zum tatsächlichen p95?
  • Zeigt das Dashboard Nachrichtenalter statt nur Anzahl?
  • Können wir den Konsumenten ohne Verlust angenommener Aufträge stoppen?
  • Simuliert ein Test Timeout nach Wirkung, aber vor Settlement?
FINALE · 10 FRAGEN

Baust du eine Queue oder verschiebst du nur den Ausfall?

Das Quiz behandelt Azure Service Bus, Amazon SQS, Outbox, Inbox, Claim-Check, Idempotenz, Retry und Telemetrie. Nach allen Antworten bestätigst du deine E-Mail per Einmalcode. Ergebnis, Niveau und nächste Schritte erhältst du in einem persönlichen Bericht. Die Adresse wird nicht im Blog veröffentlicht.

Technische Quellen

Der Beitrag basiert auf der bei Veröffentlichung aktuellen Herstellerdokumentation: