Communityコーディング&開発github.com

akka-net-best-practices

Akka.NETのベストプラクティスをまとめたスキル。EventStreamとDistributedPubSubの使い分け、監視戦略、エラーハンドリング、PropsとDependencyResolverの選択、作業分散パターン、テスト容易性のためのクラスタ/ローカルモード抽象化などをカバーします。

akka-net-best-practices とは?

このスキルは、Akka.NETを使ったアクターベースシステムの設計と実装における重要なベストプラクティスを提供します。主なトピックとして、EventStream(ローカルのみ)とDistributedPubSub(クラスタ全体)の適切な使い分け、子アクターの監視戦略(デフォルト戦略の理解とカスタム戦略が必要なケース)、期待されるエラーにはtry-catch、予期しないエラーや状態破損には監視に任せるというエラーハンドリングの原則、PropsとDependencyResolverの選択基準、データベースキューやAkka.Streamsを使った作業分散パターン、そしてテスト容易性のためにクラスタ/ローカルモードを抽象化する方法が含まれます。また、アクターログにはILoggingAdapterを使用すべき理由、非同期操作におけるCancellationTokenの適切な管理方法、さらにリファレンスとして作業分散パターン、クラスタ/ローカル抽象化、非同期キャンセルパターンの詳細コードも提供します。このスキルは、Akka.NETの実践的な利用をガイドし、一般的な間違いを避けるための決定木やクイックリファレンスも含んでいます。

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

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Akka.NET ベストプラクティス

このスキルを使うべき時

以下のような状況でこのスキルを使用してください:

  • アクター間の通信パターンを設計するとき
  • EventStreamとDistributedPubSubのどちらを使うか決めかねているとき
  • アクター内でエラーハンドリングを実装するとき
  • 監視戦略を理解したいとき
  • PropsパターンとDependencyResolverのどちらを選ぶか検討しているとき
  • ノード間の作業分散を設計するとき
  • クラスタインフラの有無にかかわらず実行可能なテスト可能なアクターシステムを作成するとき
  • ローカルテストシナリオのためにクラスタシャーディングを抽象化するとき

リファレンスファイル


1. EventStream vs DistributedPubSub

重要: EventStreamはローカル専用

Context.System.EventStream は 1つのActorSystemプロセス内に限定 されます。クラスタノード間では動作しません。

// 悪い例: これは単一サーバーでのみ動作する
// 2台目のサーバーを追加すると、サーバー2の購読者はサーバー1からのイベントを受信できない
Context.System.EventStream.Subscribe(Self, typeof(PostCreated));
Context.System.EventStream.Publish(new PostCreated(postId, authorId));

EventStreamが適切な場合:

  • 単一プロセス内でのログ記録や診断
  • 本当に単一プロセスだけのアプリケーションのローカルイベントバス
  • 開発/テストシナリオ

マルチノードではDistributedPubSubを使用

複数のクラスタノード上のアクターに到達する必要があるイベントには、Akka.Cluster.Tools.PublishSubscribe を使用します:

using Akka.Cluster.Tools.PublishSubscribe;

public class TimelineUpdatePublisher : ReceiveActor
{
    private readonly IActorRef _mediator;

    public TimelineUpdatePublisher()
    {
        // DistributedPubSubメディエーターを取得
        _mediator = DistributedPubSub.Get(Context.System).Mediator;

        Receive<PublishTimelineUpdate>(msg =>
        {
            // トピックに公開 - 全ノードの全購読者に到達
            _mediator.Tell(new Publish($"timeline:{msg.UserId}", msg.Update));
        });
    }
}

Akka.HostingでのDistributedPubSub設定

builder.WithDistributedPubSub(role: null); // 全ロールで利用可能。または特定のロールを指定

トピック設計パターン

パターントピック形式ユースケース
ユーザー単位timeline:{userId}タイムライン更新、通知
エンティティ単位post:{postId}投稿エンゲージメント更新
ブロードキャストsystem:announcementsシステム全体の通知
ロールベースworkers:rss-poller作業分散

2. 監視戦略

重要な明確化: 監視は子アクターに対して

アクターに定義された監視戦略は、そのアクターが子アクターをどのように監視するか を指示するものであり、そのアクター自身がどのように監視されるかではありません。

public class ParentActor : ReceiveActor
{
    // この戦略はParentActorの子アクターに適用され、ParentActor自身には適用されない
    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
            });
    }
}

デフォルトの監視戦略

デフォルトの OneForOneStrategy には既にレート制限が含まれています:

  • 1秒間に10回の再起動 = アクターは永続的に停止
  • これにより無限再起動ループが防止されます

特別な要件がない限り、カスタム戦略はめったに必要ありません。

カスタム監視を定義すべき時

良い理由:

  • アクターが回復不可能な状態破損を示す例外をスローする -> Restart
  • 再起動を引き起こすべきではない例外(予期される障害)をスローする -> Resume
  • 子アクターの障害が兄弟アクターに影響を与えるべき場合 -> AllForOneStrategy を使用
  • デフォルトと異なる再試行制限が必要な場合

悪い理由:

  • 「念のために」 - デフォルトは既に安全です
  • アクターの動作を理解していない - まず理解しましょう

3. エラーハンドリング: 監視 vs Try-Catch

Try-Catchを使うべき時(ほとんどの場合)

try-catch を使用する状況:

  • 障害が 予期されている(ネットワークタイムアウト、無効な入力、外部サービスダウン)
  • 例外が発生した 正確な理由 を把握している
  • それを 適切に処理できる(再試行、エラーレスポンスの返却、ログ記録して続行)
  • 再起動しても 役に立たない(同じエラーが再発する)
public class RssFeedPollerActor : ReceiveActor
{
    public RssFeedPollerActor()
    {
        ReceiveAsync<PollFeed>(async msg =>
        {
            try
            {
                var feed = await _httpClient.GetStringAsync(msg.FeedUrl);
                var items = ParseFeed(feed);
                // アイテムを処理...
            }
            catch (HttpRequestException ex)
            {
                // 予期される障害 - ログ記録して再試行をスケジュール
                _log.Warning("フィード {Url} は利用不可: {Error}", msg.FeedUrl, ex.Message);
                Context.System.Scheduler.ScheduleTellOnce(
                    TimeSpan.FromMinutes(5), Self, msg, Self);
            }
            catch (XmlException ex)
            {
                // 無効なフィード形式 - ログ記録して不良としてマーク
                _log.Error("フィード {Url} の形式が無効: {Error}", msg.FeedUrl, ex.Message);
                Sender.Tell(new FeedPollResult.InvalidFormat(msg.FeedUrl));
            }
        });
    }
}

監視に任せるべき時

例外を伝播させる(監視をトリガーする)状況:

  • 例外の原因が 全くわからない
  • アクターの 状態が破損している可能性がある
  • 再起動が有効である(新しい状態、リソースの再接続)
  • プログラミングエラーである(NullReferenceException、論理エラーによるInvalidOperationException)

アンチパターン: 未知の例外を飲み込む

// 悪い例: 例外を飲み込むと問題が隠蔽される
catch (Exception ex)
{
    _log.Error(ex, "作業処理中にエラー発生");
    // アクターは潜在的に破損した状態で継続する
}

// 良い例: 既知の例外は処理し、未知の例外は伝播させる
catch (HttpRequestException ex)
{
    // 既知の予期される障害 - 適切に処理
    _log.Warning("HTTPリクエスト失敗: {Error}", ex.Message);
    Sender.Tell(new WorkResult.TransientFailure());
}
// 未知の例外は監視に伝播

4. Props vs DependencyResolver

プレーンなPropsを使うべき時

Props.Create() を使用する状況:

  • アクターが IServiceProvider や IRequiredActor<T> を必要としない
  • すべての依存関係をコンストラクタで渡せる
  • アクターがシンプルで自己完結している
// DI不要のシンプルなアクター
public static Props Props(PostId postId, IPostWriteStore store)
    => Akka.Actor.Props.Create(() => new PostEngagementActor(postId, store));

DependencyResolverを使うべき時

resolver.Props<T>() を使用する状況:

  • アクターがスコープ付きサービスを作成するために IServiceProvider を必要とする
  • アクターが他のアクターへの参照を取得するために IRequiredActor<T> を使用する
  • アクターに多くの依存関係があり、それらが既にDIコンテナに登録されている
// DIによる登録
builder.WithActors((system, registry, resolver) =>
{
    var actor = system.ActorOf(resolver.Props<OrderProcessorActor>(), "order-processor");
    registry.Register<OrderProcessorActor>(actor);
});

リモートデプロイに関する考慮点

リモートデプロイはほとんどの場合必要ありません。 リモートデプロイを行わない場合(おそらく行わないでしょう):

  • Props.Create(() => new Actor(...)) とクロージャは問題ありません
  • 「シリアライゼーション問題」の警告は適用されません

ほとんどのアプリケーションでは、リモートデプロイの代わりに クラスタシャーディング を使用してください。分散を自動的に処理します。


5. 作業分散パターン

多数のバックグラウンドジョブ(RSSフィード、メール送信など)がある場合、それらをすべて一度に処理しないでください。群集崩壊(thundering herd)問題を引き起こします。

これを解決する3つのパターン:

  1. データベース駆動の作業キュー - FOR UPDATE SKIP LOCKED を使用して自然なクロスノード分散を実現
  2. Akka.Streams レート制限 - 単一ノード内で処理をスロットル
  3. 永続キュー(Outboxパターン) - データベースバックアップによる信頼性の高い処理

完全なコードサンプルについては work-distribution-patterns.md を参照してください。


6. よくある間違いのまとめ

間違いなぜ間違いか修正方法
クロスノードpub/subにEventStreamを使用するEventStreamはローカル専用DistributedPubSubを使用する
アクターを「保護」するために監視を定義する監視は子アクターを保護する階層を理解する
すべての例外をキャッチするバグを隠蔽し、状態を破損する予期されるエラーのみキャッチする
常にDependencyResolverを使用する不必要な複雑さを追加する可能な場合はプレーンなPropsを使用する
すべてのバックグラウンドジョブを一度に処理する群集崩壊、リソース枯渇データベースキュー+レート制限を使用する
予期される障害に対して例外をスローする不必要な再起動をトリガーする結果型を返し、メッセージングを使用する

7. クイックリファレンス

通信パターン決定木

アクター間で通信する必要がありますか?
├── 同一プロセスのみ? -> EventStreamで十分
├── クラスタノード間?
│   ├── ポイントツーポイント? -> ActorSelectionまたは既知のIActorRefを使用
│   └── Pub/Sub? -> DistributedPubSubを使用
└── 外部システムへのファイアアンドフォーゲット? -> Outboxパターンを検討

エラーハンドリング決定木

アクター内で例外が発生しましたか?
├── 予期される障害(HTTPタイムアウト、無効な入力)?
│   └── Try-catchで適切に処理し続行
├── 状態が破損している可能性がある?
│   └── 監視に再起動させる
├── 原因不明?
│   └── 監視に再起動させる
└── プログラミングエラー(null参照、論理エラー)?
    └── 監視に再起動させ、バグを修正する

Props決定木

アクターのPropsを作成しますか?
├── アクターがIServiceProviderを必要とする?
│   └── resolver.Props<T>() を使用
├── アクターがIRequiredActor<T>を必要とする?
│   └── resolver.Props<T>() を使用
├── コンストラクタパラメータを持つシンプルなアクター?
│   └── Props.Create(() => new Actor(...)) を使用
└── リモートデプロイが必要?
    └── おそらく不要 - 代わりにクラスタシャーディングを使用

8. クラスタ/ローカルモードの抽象化

クラスタ化された本番環境とローカル/テスト環境の両方で実行する必要があるアプリケーションでは、抽象化パターンを使用して実装を切り替えます:

  • AkkaExecutionMode 列挙型 - 使用する実装を制御します(LocalTest vs Clustered)
  • GenericChildPerEntityParent - 同じ IMessageExtractor を使用してローカルでシャーディング動作を模倣します
  • IPubSubMediator - 交換可能なローカル/クラスタ実装のためにDistributedPubSubを抽象化します

完全な実装コードについては cluster-local-abstractions.md を参照してください。


9. アクターログ記録

ILoggingAdapterを使用し、ILoggerは使用しない

アクター内では、DIで注入された ILogger<T> ではなく、Context.GetLogger() からの ILoggingAdapter を使用します:

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

    public MyActor()
    {
        Receive<MyMessage>(msg =>
        {
            _log.Info("ユーザー {UserId} のメッセージを処理中", msg.UserId);
            _log.Error(ex, "{MessageType} の処理に失敗しました", msg.GetType().Name);
        });
    }
}

ILoggingAdapter を選ぶ理由:

  • Akkaのログパイプラインおよび監視と統合
  • v1.5.57以降、セマンティック/構造化ログをサポート
  • メソッド名: Info(), Debug(), Warning(), Error() (Log* バリアントではない)
  • DI不要 - アクターコンテキストから直接取得

ILogger をアクターに注入しないでください - Akkaのログインフラストラクチャをバイパスします。

セマンティックログ記録(v1.5.57以降)

// 名前付きプレースホルダーを使用すると、ログの集約と検索が向上
_log.Info("注文 {OrderId} が顧客 {CustomerId} に対して処理されました", order.Id, order.CustomerId);

// 位置指定よりも名前付きプレースホルダーを推奨
// 良い: {OrderId}, {CustomerId}
// 避ける: {0}, {1}

10. CancellationTokenを使用した非同期操作の管理

アクターが PipeTo 経由で非同期操作を開始する場合、適切に管理されないと、それらの操作がアクターより長く存続する可能性があります。重要なプラクティス:

  • PostStop内のアクターCTS - PostStop() で必ずキャンセルおよび破棄を行う
  • 操作ごとに新しいCTS - 新しい作業を開始する前に前のものをキャンセル
  • トークンをあらゆる場所に渡す - EF Coreクエリ、HTTP呼び出しなど
  • タイムアウト用のリンクCTS - 外部呼び出しは短いタイムアウトを設定し、ハングを防止
  • 適切な処理 - catchブロックでタイムアウトとシャットダウンを区別

完全な実装コードについては async-cancellation-patterns.md を参照してください。

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.

関連スキル