Skip to content
Development
Skill

/akka-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

From plugin
dotnet-skills
1.1k36 skills6 agents
Install
$ npx -y skills add aaronontheweb/dotnet-skills --skill akka-best-practices --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/akka-best-practices

Context preview

The summary Claude sees to decide when to auto-load this skill.

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

SKILL.md

akka-best-practices.SKILL.md
name: akka-net-best-practices
description: Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.
invocable: false

Akka.NET Best Practices

When to Use This Skill

Use this skill when:

  • Designing actor communication patterns
  • Deciding between EventStream and DistributedPubSub
  • Implementing error handling in actors
  • Understanding supervision strategies
  • Choosing between Props patterns and DependencyResolver
  • Designing work distribution across nodes
  • Creating testable actor systems that can run with or without cluster infrastructure
  • Abstracting over Cluster Sharding for local testing scenarios

Reference Files

  • [work-distribution-patterns.md](work-distribution-patterns.md): Database queues, Akka.Streams throttling, outbox pattern
  • [cluster-local-abstractions.md](cluster-local-abstractions.md): GenericChildPerEntityParent, IPubSubMediator, execution mode wiring
  • [async-cancellation-patterns.md](async-cancellation-patterns.md): Actor-scoped CancellationToken, linked CTS, timeout handling

---

1. EventStream vs DistributedPubSub

Critical: EventStream is LOCAL ONLY

`Context.System.EventStream` is **local to a single ActorSystem process**. It does NOT work across cluster nodes.

// BAD: This only works on a single server
// When you add a second server, subscribers on server 2 won't receive events from server 1
Context.System.EventStream.Subscribe(Self, typeof(PostCreated));
Context.System.EventStream.Publish(new PostCreated(postId, authorId));

**When EventStream is appropriate:**

  • Logging and diagnostics within a single process
  • Local event bus for truly single-process applications
  • Development/testing scenarios

Use DistributedPubSub for Multi-Node

For events that must reach actors across multiple cluster nodes, use `Akka.Cluster.Tools.PublishSubscribe`:

using Akka.Cluster.Tools.PublishSubscribe;

public class TimelineUpdatePublisher : ReceiveActor
{
    private readonly IActorRef _mediator;

    public TimelineUpdatePublisher()
    {
        // Get the DistributedPubSub mediator
        _mediator = DistributedPubSub.Get(Context.System).Mediator;

        Receive<PublishTimelineUpdate>(msg =>
        {
            // Publish to a topic - reaches all subscribers across all nodes
            _mediator.Tell(new Publish($"timeline:{msg.UserId}", msg.Update));
        });
    }
}

Akka.Hosting Configuration for DistributedPubSub

builder.WithDistributedPubSub(role: null); // Available on all roles, or specify a role

Topic Design Patterns

| Pattern | Topic Format | Use Case | |---------|--------------|----------| | Per-user | `timeline:{userId}` | Timeline updates, notifications | | Per-entity | `post:{postId}` | Post engagement updates | | Broadcast | `system:announcements` | System-wide notifications | | Role-based | `workers:rss-poller` | Work distribution |

---

2. Supervision Strategies

Key Clarification: Supervision is for CHILDREN

A supervision strategy defined on an actor dictates **how that actor supervises its children**, NOT how the actor itself is supervised.

public class ParentActor : ReceiveActor
{
    // This strategy applies to children of ParentActor, NOT to ParentActor itself
    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
            });
    }
}

Default Supervision Strategy

The default `OneForOneStrategy` already includes rate limiting:

  • **10 restarts within 1 second** = actor is permanently stopped
  • This prevents infinite restart loops

**You rarely need a custom strategy** unless you have specific requirements.

When to Define Custom Supervision

**Good reasons:**

  • Actor throws exceptions indicating irrecoverable state corruption -> Restart
  • Actor throws exceptions that should NOT cause restart (expected failures) -> Resume
  • Child failures should affect siblings -> Use `AllForOneStrategy`
  • Need different retry limits than the default

**Bad reasons:**

  • "Just to be safe" - the default is already safe
  • Don't understand what the actor does - understand it first

---

3. Error Handling: Supervision vs Try-Catch

When to Use Try-Catch (Most Cases)

**Use try-catch when:**

  • The failure is **expected** (network timeout, invalid input, external service down)
  • You know **exactly why** the exception occurred
  • You can handle it **gracefully** (retry, return error response, log and continue)
  • Restarting would **not help** (same error would occur again)
public class RssFeedPollerActor : ReceiveActor
{
    public RssFeedPollerActor()
    {
        ReceiveAsync<PollFeed>(async msg =>
        {
            try
            {
                var feed = await _httpClient.GetStringAsync(msg.FeedUrl);
                var items = ParseFeed(feed);
                // Process items...
            }
            catch (HttpRequestException ex)
            {
                // Expected failure - log and schedule retry
                _log.Warning("Feed {Url} unavailable: {Error}", msg.FeedUrl, ex.Message);
                Context.System.Scheduler.ScheduleTellOnce(
                    TimeSpan.FromMinutes(5), Self, msg, Self);
            }
            catch (XmlException ex)
            {
                // Invalid feed format - log and mark as bad
                _log.Error("Feed {Url} has invalid format: {Error}", msg.FeedUrl, ex.Message);
                Sender.Tell(new
Read more
Ships withdotnet-skills

A comprehensive AI coding plugin with 30 skills and 5 specialized agents for professional .NET development. Battle-tested patterns from production systems covering C#, Akka.NET, Aspire, EF Core, testing, and performance optimization.

Get the whole plugin