CommunityCodierung & Entwicklunggithub.com

Akka.NET Best Practices

Kritische Akka.NET-Best-Practices inklusive EventStream vs. DistributedPubSub, Supervisionsstrategien, Fehlerbehandlung, Props vs. DependencyResolver, Arbeitsverteilungsmuster und Abstraktionen für Cluster/Lokal-Modus zur Testbarkeit.

Was ist Akka.NET Best Practices?

Dieses Skill bietet eine umfassende Sammlung von Best Practices für Akka.NET und adressiert häufige Fallstricke und Entscheidungspunkte bei der Entwicklung von Actor-Systemen. Es klärt den entscheidenden Unterschied zwischen dem lokalen EventStream und dem clusterweiten DistributedPubSub, erklärt, dass Supervisionsstrategien nur für Kinder-Actors gelten und dass die Standardstrategie in den meisten Fällen ausreicht. Für die Fehlerbehandlung wird ein hybrides Modell empfohlen: bekannte, erwartete Ausnahmen mit try-catch abfangen, während unbekannte oder auf korrupten Zustand hindeutende Ausnahmen an die Supervision weitergegeben werden sollten. Das Skill behandelt außerdem die Wahl zwischen einfachen Props und dem DependencyResolver, stellt drei Muster zur Arbeitsverteilung vor und beschreibt, wie man mit Abstraktionen wie `GenericChildPerEntityParent` und `IPubSubMediator` testbare Actor-Systeme baut, die sowohl lokal als auch im Cluster laufen. Es unterstützt primär die Entwicklung mit Akka.NET in C#, enthält Codebeispiele und Entscheidungsbäume und verweist auf separate Referenzdateien für die vollständige Implementierung.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/akka-best-practices

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht Akka.NET Best Practices?

Wann dieses Skill verwendet wird

Verwende dieses Skill, wenn:

  • Entwurf von Actor-Kommunikationsmustern
  • Entscheidung zwischen EventStream und DistributedPubSub
  • Implementierung von Fehlerbehandlung in Actors
  • Verständnis von Supervisionsstrategien
  • Wahl zwischen Props-Mustern und DependencyResolver
  • Entwurf von Arbeitsverteilung über Knoten hinweg
  • Erstellung testbarer Actor-Systeme, die mit oder ohne Cluster-Infrastruktur ausgeführt werden können
  • Abstraktion von Cluster Sharding für lokale Testszenarien

Referenzdateien


1. EventStream vs. DistributedPubSub

Wichtig: EventStream ist NUR LOKAL

Context.System.EventStream ist lokal auf einen einzelnen ActorSystem-Prozess beschränkt. Es funktioniert NICHT über Cluster-Knoten hinweg.

// SCHLECHT: Funktioniert nur auf einem einzelnen Server
// Wenn ein zweiter Server hinzugefügt wird, erhalten Abonnenten auf Server 2 keine Ereignisse von Server 1
Context.System.EventStream.Subscribe(Self, typeof(PostCreated));
Context.System.EventStream.Publish(new PostCreated(postId, authorId));

Wann EventStream angemessen ist:

  • Protokollierung und Diagnose innerhalb eines einzelnen Prozesses
  • Lokaler Ereignisbus für echt Einprozessanwendungen
  • Entwicklungs-/Testszenarien

DistributedPubSub für Multi-Node verwenden

Für Ereignisse, die Actors über mehrere Cluster-Knoten erreichen müssen, verwende Akka.Cluster.Tools.PublishSubscribe:

using Akka.Cluster.Tools.PublishSubscribe;

public class TimelineUpdatePublisher : ReceiveActor
{
    private readonly IActorRef _mediator;

    public TimelineUpdatePublisher()
    {
        // Den DistributedPubSub-Mediator holen
        _mediator = DistributedPubSub.Get(Context.System).Mediator;

        Receive<PublishTimelineUpdate>(msg =>
        {
            // In ein Thema veröffentlichen – erreicht alle Abonnenten auf allen Knoten
            _mediator.Tell(new Publish($"timeline:{msg.UserId}", msg.Update));
        });
    }
}

Akka.Hosting-Konfiguration für DistributedPubSub

builder.WithDistributedPubSub(role: null); // Verfügbar auf allen Rollen, oder eine Rolle angeben

Themen-Entwurfsmuster

MusterThemenformatAnwendungsfall
Pro Benutzertimeline:{userId}Timeline-Updates, Benachrichtigungen
Pro Entitätpost:{postId}Aktualisierungen zu Beitragsinteraktionen
Broadcastsystem:announcementsSystemweite Benachrichtigungen
Rollenbasiertworkers:rss-pollerArbeitsverteilung

2. Supervisionsstrategien

Wichtige Klarstellung: Supervision gilt für KINDER

Eine für einen Actor definierte Supervisionsstrategie legt fest, wie dieser Actor seine Kinder beaufsichtigt, NICHT wie der Actor selbst beaufsichtigt wird.

public class ParentActor : ReceiveActor
{
    // Diese Strategie gilt für Kinder von ParentActor, NICHT für ParentActor selbst
    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 10,
            withinTimeRange: TimeSpan.FromSeconds(30),
            decider: ex => ex switch
            {
                ArithmeticException => Directive.Resume,
                NullReferenceException => Directive.Restart,
                ArgumentException => Directive.Stop,
                _ => Directive.Escalate
            });
    }
}

Standard-Supervisionsstrategie

Die standardmäßige OneForOneStrategy beinhaltet bereits eine Ratenbegrenzung:

  • 10 Neustarts innerhalb von 1 Sekunde = Actor wird dauerhaft gestoppt
  • Dies verhindert Endlosschleifen von Neustarts

Du brauchst selten eine benutzerdefinierte Strategie, es sei denn, du hast spezifische Anforderungen.

Wann eine benutzerdefinierte Supervision definiert werden sollte

Gute Gründe:

  • Ein Actor wirft Ausnahmen, die auf eine nicht behebbare Zustandskorruption hindeuten -> Neustart
  • Ein Actor wirft Ausnahmen, die KEINEN Neustart verursachen sollten (erwartete Fehler) -> Fortsetzen
  • Fehler bei einem Kind sollten Geschwister betreffen -> AllForOneStrategy verwenden
  • Andere Wiederholungsgrenzen als die Standardeinstellung erforderlich

Schlechte Gründe:

  • „Nur um sicherzugehen“ – die Standardeinstellung ist bereits sicher
  • Du verstehst nicht, was der Actor tut – verstehe es zuerst

3. Fehlerbehandlung: Supervision vs. Try-Catch

Wann Try-Catch verwendet werden sollte (die meisten Fälle)

Try-Catch verwenden, wenn:

  • Der Fehler ist erwartet (Netzwerk-Timeout, ungültige Eingabe, externer Dienst nicht verfügbar)
  • Du weißt genau, warum die Ausnahme aufgetreten ist
  • Du kannst sie ordentlich behandeln (Wiederholung, Fehlerantwort senden, protokollieren und fortfahren)
  • Ein Neustart würde nicht helfen (derselbe Fehler würde erneut auftreten)
public class RssFeedPollerActor : ReceiveActor
{
    public RssFeedPollerActor()
    {
        ReceiveAsync<PollFeed>(async msg =>
        {
            try
            {
                var feed = await _httpClient.GetStringAsync(msg.FeedUrl);
                var items = ParseFeed(feed);
                // Items verarbeiten...
            }
            catch (HttpRequestException ex)
            {
                // Erwarteter Fehler – protokollieren und Wiederholung planen
                _log.Warning("Feed {Url} nicht verfügbar: {Error}", msg.FeedUrl, ex.Message);
                Context.System.Scheduler.ScheduleTellOnce(
                    TimeSpan.FromMinutes(5), Self, msg, Self);
            }
            catch (XmlException ex)
            {
                // Ungültiges Feed-Format – protokollieren und als fehlerhaft markieren
                _log.Error("Feed {Url} hat ein ungültiges Format: {Error}", msg.FeedUrl, ex.Message);
                Sender.Tell(new FeedPollResult.InvalidFormat(msg.FeedUrl));
            }
        });
    }
}

Wann die Supervision die Behandlung übernehmen sollte

Ausnahmen propagieren lassen (Supervision auslösen), wenn:

  • Du keine Ahnung hast, warum die Ausnahme aufgetreten ist
  • Der Zustand des Actors könnte korrupt sein
  • Ein Neustart würde helfen (frischer Zustand, Ressourcen neu verbinden)
  • Es handelt sich um einen Programmierfehler (NullReferenceException, InvalidOperationException durch fehlerhafte Logik)

Anti-Pattern: Unbekannte Ausnahmen verschlucken

// SCHLECHT: Ausnahmen verschlucken versteckt Probleme
catch (Exception ex)
{
    _log.Error(ex, "Fehler bei der Verarbeitung");
    // Actor fährt mit potenziell korruptem Zustand fort
}

// GUT: Bekannte Ausnahmen behandeln, unbekannte propagieren lassen
catch (HttpRequestException ex)
{
    // Bekannter, erwarteter Fehler – ordentlich behandeln
    _log.Warning("HTTP-Anfrage fehlgeschlagen: {Error}", ex.Message);
    Sender.Tell(new WorkResult.TransientFailure());
}
// Unbekannte Ausnahmen propagieren zur Supervision

4. Props vs. DependencyResolver

Wann einfache Props verwendet werden sollten

Props.Create() verwenden, wenn:

  • Der Actor benötigt weder IServiceProvider noch IRequiredActor<T>
  • Alle Abhängigkeiten können über den Konstruktor übergeben werden
  • Der Actor ist einfach und in sich geschlossen
// Einfacher Actor ohne DI-Bedarf
public static Props Props(PostId postId, IPostWriteStore store)
    => Akka.Actor.Props.Create(() => new PostEngagementActor(postId, store));

Wann DependencyResolver verwendet werden sollte

resolver.Props<T>() verwenden, wenn:

  • Der Actor benötigt IServiceProvider, um abgegrenzte Dienste zu erstellen
  • Der Actor verwendet IRequiredActor<T>, um Referenzen zu anderen Actors zu erhalten
  • Der Actor hat viele Abhängigkeiten, die bereits im DI-Container sind
// Registrierung mit DI
builder.WithActors((system, registry, resolver) =>
{
    var actor = system.ActorOf(resolver.Props<OrderProcessorActor>(), "order-processor");
    registry.Register<OrderProcessorActor>(actor);
});

Überlegungen zur Fernbereitstellung

Du brauchst fast nie eine Fernbereitstellung. Wenn du keine Fernbereitstellung durchführst (und das tust du wahrscheinlich nicht):

  • Props.Create(() => new Actor(...)) mit Closures ist in Ordnung
  • Die „Warnung zum Serialisierungsproblem“ trifft nicht zu

Verwende für die meisten Anwendungen Cluster Sharding anstelle von Fernbereitstellung – es übernimmt die Verteilung automatisch.


5. Arbeitsverteilungsmuster

Wenn du viele Hintergrundjobs hast (RSS-Feeds, E-Mail-Versand usw.), verarbeite sie nicht alle auf einmal – dies verursacht Thundering-Herd-Probleme.

Drei Muster zur Lösung:

  1. Datenbankgesteuerte Arbeitswarteschlange – Verwende FOR UPDATE SKIP LOCKED für eine natürliche knotenübergreifende Verteilung
  2. Akka.Streams-Ratenbegrenzung – Drossle die Verarbeitung innerhalb eines einzelnen Knotens
  3. Durable Queue (Outbox-Muster) – Datenbankgestützte Outbox für zuverlässige Verarbeitung

Siehe work-distribution-patterns.md für vollständige Codebeispiele.


6. Zusammenfassung häufiger Fehler

FehlerWarum es falsch istBehebung
EventStream für knotenübergreifendes Pub/Sub verwendenEventStream ist nur lokalDistributedPubSub verwenden
Supervision definieren, um einen Actor zu „schützen“Supervision schützt KinderDie Hierarchie verstehen
Alle Ausnahmen abfangenVersteckt Fehler, korrumpiert ZustandNur erwartete Fehler abfangen
Immer DependencyResolver verwendenFügt unnötige Komplexität hinzuEinfache Props verwenden, wenn möglich
Alle Hintergrundjobs auf einmal verarbeitenThundering Herd, RessourcenerschöpfungDatenbankwarteschlange + Ratenbegrenzung verwenden
Ausnahmen für erwartete Fehler werfenLöst unnötige Neustarts ausErgebnistypen zurückgeben, Messaging verwenden

7. Kurzreferenz

Entscheidungsbaum für Kommunikationsmuster

Muss zwischen Actors kommuniziert werden?
├── Nur derselbe Prozess? -> EventStream ist in Ordnung
├── Über Cluster-Knoten hinweg?
│   ├── Punkt-zu-Punkt? -> ActorSelection oder bekannte IActorRef verwenden
│   └── Pub/Sub? -> DistributedPubSub verwenden
└── Fire-and-Forget an ein externes System? -> Outbox-Muster in Betracht ziehen

Entscheidungsbaum für Fehlerbehandlung

Ausnahme in einem Actor aufgetreten?
├── Erwarteter Fehler (HTTP-Timeout, ungültige Eingabe)?
│   └── Try-catch, ordentlich behandeln, fortfahren
├── Zustand könnte korrupt sein?
│   └── Supervision neu starten lassen
├── Unbekannte Ursache?
│   └── Supervision neu starten lassen
└── Programmierfehler (Null-Ref, fehlerhafte Logik)?
    └── Supervision neu starten lassen, den Fehler beheben

Entscheidungsbaum für Props

Props für Actor erstellen?
├── Actor benötigt IServiceProvider?
│   └── resolver.Props<T>() verwenden
├── Actor benötigt IRequiredActor<T>?
│   └── resolver.Props<T>() verwenden
├── Einfacher Actor mit Konstruktorparametern?
│   └── Props.Create(() => new Actor(...)) verwenden
└── Fernbereitstellung erforderlich?
    └── Wahrscheinlich nicht – stattdessen Cluster Sharding verwenden

8. Abstraktionen für Cluster/Lokal-Modus

Für Anwendungen, die sowohl in geclusterten Produktions- als auch in lokalen Testumgebungen laufen müssen, verwende Abstraktionsmuster, um zwischen Implementierungen umzuschalten:

  • AkkaExecutionMode-Enum – Steuert, welche Implementierungen verwendet werden (LocalTest vs. Clustered)
  • GenericChildPerEntityParent – Ahmt Sharding-Verhalten lokal mit demselben IMessageExtractor nach
  • IPubSubMediator – Abstrahiert DistributedPubSub für austauschbare lokale/Cluster-Implementierungen

Siehe cluster-local-abstractions.md für den vollständigen Implementierungscode.


9. Actor-Protokollierung

ILoggingAdapter verwenden, nicht ILogger

In Actors verwende ILoggingAdapter von Context.GetLogger() anstelle von DI-injiziertem ILogger<T>:

public class MyActor : ReceiveActor
{
    private readonly ILoggingAdapter _log = Context.GetLogger();

    public MyActor()
    {
        Receive<MyMessage>(msg =>
        {
            _log.Info("Verarbeite Nachricht für Benutzer {UserId}", msg.UserId);
            _log.Error(ex, "Fehler bei der Verarbeitung von {MessageType}", msg.GetType().Name);
        });
    }
}

Warum ILoggingAdapter:

  • Integriert sich in Akkas Protokollierungspipeline und Supervision
  • Unterstützt semantische/strukturierte Protokollierung ab Version 1.5.57
  • Methodennamen: Info(), Debug(), Warning(), Error() (nicht die Log*-Varianten)
  • Kein DI erforderlich – wird direkt aus dem Actor-Kontext bezogen

Injiziere ILogger<T> nicht in Actors – es umgeht Akkas Protokollierungsinfrastruktur.

Semantische Protokollierung (v1.5.57+)

// Benannte Platzhalter für bessere Protokollaggregation und Abfragen
_log.Info("Bestellung {OrderId} für Kunden {CustomerId} verarbeitet", order.Id, order.CustomerId);

// Benannte Platzhalter gegenüber positionalen bevorzugen
// Gut: {OrderId}, {CustomerId}
// Vermeiden: {0}, {1}

10. Verwalten asynchroner Vorgänge mit CancellationToken

Wenn Actors asynchrone Vorgänge über PipeTo starten, können diese Vorgänge den Actor überleben, wenn sie nicht ordnungsgemäß verwaltet werden. Wichtige Praktiken:

  • Actor-CTS in PostStop – Immer in PostStop() abbrechen und entsorgen
  • Neuer CTS pro Vorgang – Vorherigen abbrechen, bevor neue Arbeit gestartet wird
  • Token überall übergeben – EF Core-Abfragen, HTTP-Aufrufe usw.
  • Verknüpfter CTS für Timeouts – Externe Aufrufe erhalten kurze Timeouts zum Verhindern von Hängenbleiben
  • Ordentliche Behandlung – Timeout vs. Herunterfahren in catch-Blöcken unterscheiden

Siehe async-cancellation-patterns.md für den vollständigen Implementierungscode.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

Verwandte Skills