mcpField
A
akka-net-aspire-configuration

akka-net-aspire-configuration

github.com/aarononthewebクラウド・DevOps1.2k406 回インストール今日更新
リポジトリ

akka-net-aspire-configuration について

.NET Aspire を使った Akka.NET アプリケーションの設定を支援するスキルです。オーケストレーション、クラスター、永続化、ヘルスチェック、型付き設定を扱い、ローカル開発とデプロイのパターンを示します。

主な機能

  1. 01Aspire のオーケストレーションパターン
  2. 02Akka.Hosting の依存性注入
  3. 03クラスターのブートストラップと検出
  4. 04SQL Server 永続化の設定
  5. 05ヘルスチェックと起動時検証

活用シーン

  1. 01ローカルで複数レプリカのクラスターを構成する
  2. 02Akka.Persistence と SQL Server を設定する
  3. 03Akka.NET アプリの Kubernetes デプロイを準備する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill akka-net-aspire-configuration

インストール後、Claude Code で/akka-net-aspire-configurationを入力して読み込まれたか確認します。

詳細

Akka.NET を .NET Aspire で構成する

このスキルを使用するタイミング

次のような場合にこのスキルを使用します。

  • .NET Aspire のオーケストレーションを使用して、新しい Akka.NET プロジェクトを設定する
  • クラスターのブートストラップと検出を使用して Akka.Cluster を構成する
  • Akka.Persistence を SQL Server と統合する
  • クラスター管理用に Akka.Management を設定する
  • ローカル開発で複数レプリカのアクターシステムを構成する
  • Aspire を使用して Akka.NET アプリケーションを Kubernetes にデプロイする

関連スキル

  • akka-net-management - Akka.Management、Cluster Bootstrap、検出プロバイダー(Kubernetes、Azure、Config)の詳細
  • microsoft-extensions-configuration - 構成検証のための IValidateOptions パターン
  • akka-net-best-practices - テスト可能なアクターシステムのためのクラスター/ローカルモードの抽象化
  • aspire-integration-testing - 実際のインフラストラクチャを使用した Aspire アプリケーションのテスト

基本原則

  1. Microsoft.Extensions.Configuration による構成 - appsettings.json からバインドする、型安全な設定クラスを使用します(microsoft-extensions-configuration スキルを参照)
  2. DI 統合には Akka.Hosting を使用 - Akka.Hosting ライブラリを使用して、ASP.NET Core とシームレスに統合します
  3. オーケストレーションには Aspire を使用 - サービスの依存関係、ネットワーク、環境構成の管理を Aspire に任せます
  4. ヘルスチェック - クラスタリング、永続化、レディネスのヘルスチェックを必ず構成します
  5. 関心事を分離する - アクター定義、構成、Aspire のオーケストレーションを別々のレイヤーに保ちます
  6. 起動時に構成を検証する - IValidateOptions<T> と .ValidateOnStart() を使用して、構成ミスを迅速に検出します

プロジェクト構成

YourSolution/
├── src/
│   ├── YourApp.Actors/              # Actor definitions and business logic
│   │   ├── YourActor.cs
│   │   └── YourApp.Actors.csproj
│   ├── YourApp/                     # ASP.NET Core web application
│   │   ├── Config/
│   │   │   ├── AkkaConfiguration.cs  # Akka setup extension methods
│   │   │   └── AkkaSettings.cs       # Configuration model
│   │   ├── Program.cs
│   │   ├── appsettings.json
│   │   └── YourApp.csproj
│   └── YourApp.AppHost/             # Aspire orchestration
│       ├── Program.cs
│       ├── AkkaManagementExtensions.cs
│       └── YourApp.AppHost.csproj

必要な NuGet パッケージ

アクタープロジェクト用(YourApp.Actors.csproj)
<ItemGroup>
  <PackageReference Include="Akka.Cluster.Hosting" />
  <PackageReference Include="Akka.Streams" />
</ItemGroup>
Web アプリケーション用(YourApp.csproj)
<ItemGroup>
  <PackageReference Include="Akka.Hosting" />
  <PackageReference Include="Akka.Cluster.Hosting" />
  <PackageReference Include="Akka.Persistence.Sql.Hosting" />
  <PackageReference Include="Akka.Management" />
  <PackageReference Include="Akka.Management.Cluster.Bootstrap" />
  <PackageReference Include="Akka.Discovery.KubernetesApi" />
  <PackageReference Include="Akka.Discovery.Azure" />
  <PackageReference Include="Akka.Discovery.Config.Hosting" />
  <PackageReference Include="Petabridge.Cmd.Host" />
  <PackageReference Include="Petabridge.Cmd.Cluster" />
</ItemGroup>
AppHost 用(YourApp.AppHost.csproj)
<Sdk Name="Aspire.AppHost.Sdk" Version="$(AspireVersion)" />

<ItemGroup>
  <PackageReference Include="Aspire.Hosting.AppHost" />
  <PackageReference Include="Aspire.Hosting.Azure.Storage" />
  <PackageReference Include="Aspire.Hosting.SqlServer" />
</ItemGroup>

構成モデル(AkkaSettings.cs)

型安全な構成クラスを作成します。

using System.Net;
using System.Security.Cryptography.X509Certificates;
using Akka.Cluster.Hosting;
using Akka.Remote.Hosting;
using Petabridge.Cmd.Host;

namespace YourApp.Config;

public class AkkaSettings
{
    public string ActorSystemName { get; set; } = "YourSystem";

    public bool LogConfigOnStart { get; set; } = false;

    public RemoteOptions RemoteOptions { get; set; } = new()
    {
        PublicHostName = Dns.GetHostName(),
        HostName = "0.0.0.0",
        Port = 8081
    };

    public ClusterOptions ClusterOptions { get; set; } = new()
    {
        SeedNodes = [$"akka.tcp://YourSystem@{Dns.GetHostName()}:8081"],
        Roles = ["your-role"]
    };

    public ShardOptions ShardOptions { get; set; } = new();

    public AkkaManagementOptions? AkkaManagementOptions { get; set; }

    public PetabridgeCmdOptions PbmOptions { get; set; } = new()
    {
        Host = "0.0.0.0",
        Port = 9110
    };

    public TlsSettings? TlsSettings { get; set; }
}

public class TlsSettings
{
    public bool Enabled { get; set; } = false;
    public string? CertificatePath { get; set; }
    public string? CertificatePassword { get; set; }
    public bool ValidateCertificates { get; set; } = true;

    public X509Certificate2? LoadCertificate()
    {
        if (string.IsNullOrWhiteSpace(CertificatePath))
            return null;

        if (!File.Exists(CertificatePath))
            throw new FileNotFoundException($"Certificate file not found at: {CertificatePath}");

        return !string.IsNullOrWhiteSpace(CertificatePassword)
            ? X509CertificateLoader.LoadPkcs12FromFile(CertificatePath, CertificatePassword)
            : X509CertificateLoader.LoadCertificateFromFile(CertificatePath);
    }
}

public class AkkaManagementOptions
{
    public bool Enabled { get; set; }
    public string? Hostname { get; set; }
    public int Port { get; set; } = 8558;
    public string ServiceName { get; set; } = "your-service";
    public string PortName { get; set; } = "management";
    public int RequiredContactPointsNr { get; set; } = 1;
    public bool FilterOnFallbackPort { get; set; } = true;
    public DiscoveryMethod DiscoveryMethod { get; set; } = DiscoveryMethod.Config;
}

public enum DiscoveryMethod
{
    Config,
    Kubernetes,
    AzureTableStorage,
    AwsEcsTagBased,
    AwsEc2TagBased
}

Akka 構成の拡張メソッド(AkkaConfiguration.cs)

using Akka.Cluster.Hosting;
using Akka.Discovery.Azure;
using Akka.Discovery.Config.Hosting;
using Akka.Discovery.KubernetesApi;
using Akka.Hosting;
using Akka.Management;
using Akka.Management.Cluster.Bootstrap;
using Akka.Persistence.Sql.Config;
using Akka.Persistence.Sql.Hosting;
using Akka.Remote.Hosting;
using LinqToDB;

namespace YourApp.Config;

public static class AkkaConfiguration
{
    public static IServiceCollection ConfigureAkka(
        this IServiceCollection services,
        IConfiguration configuration,
        Action<AkkaConfigurationBuilder, IServiceProvider> additionalConfig)
    {
        var akkaSettings = BindAkkaSettings(services, configuration);

        var connectionString = configuration.GetConnectionString("DefaultConnection");
        if (connectionString is null)
            throw new Exception("DefaultConnection ConnectionString is missing");

        const string roleName = "your-role";

        services.AddAkka(akkaSettings.ActorSystemName, (builder, provider) =>
        {
            builder.ConfigureNetwork(provider)
                .WithAkkaClusterReadinessCheck()
                .WithActorSystemLivenessCheck()
                .WithSqlPersistence(
                    connectionString: connectionString,
                    providerName: ProviderName.SqlServer2022,
                    databaseMapping: DatabaseMapping.SqlServer,
                    tagStorageMode: TagMode.TagTable,
                    deleteCompatibilityMode: true,
                    useWriterUuidColumn: true,
                    autoInitialize: true,
                    journalBuilder: journalBuilder =>
                    {
                        journalBuilder.WithHealthCheck(name: "Akka.Persistence.Sql.Journal[default]");
                    },
                    snapshotBuilder: snapshotBuilder =>
                    {
                        snapshotBuilder.WithHealthCheck(name: "Akka.Persistence.Sql.SnapshotStore[default]");
                    });

            // Add your actors here
            // Example: builder.WithActors((system, registry) => { ... });

            additionalConfig(builder, provider);
        });

        return services;
    }

    public static AkkaSettings BindAkkaSettings(IServiceCollection services, IConfiguration configuration)
    {
        var akkaSettings = new AkkaSettings();
        configuration.GetSection(nameof(AkkaSettings)).Bind(akkaSettings);
        services.AddSingleton(akkaSettings);
        return akkaSettings;
    }

    public static AkkaConfigurationBuilder ConfigureNetwork(
        this AkkaConfigurationBuilder builder,
        IServiceProvider serviceProvider)
    {
        var settings = serviceProvider.GetRequiredService<AkkaSettings>();
        var configuration = serviceProvider.GetRequiredService<IConfiguration>();

        // Apply TLS configuration if enabled
        if (settings.TlsSettings is { Enabled: true })
        {
            ConfigureRemoteOptionsWithTls(settings);
        }

        builder.WithRemoting(settings.RemoteOptions);

        if (settings.AkkaManagementOptions is { Enabled: true })
        {
            // Clear seed nodes when using Akka.Management
            var clusterOptions = settings.ClusterOptions;
            clusterOptions.SeedNodes = [];

            builder
                .WithClustering(clusterOptions)
                .WithAkkaManagement(setup =>
                {
                    setup.Http.HostName = settings.AkkaManagementOptions.Hostname?.ToLower();
                    setup.Http.Port = settings.AkkaManagementOptions.Port;
                    setup.Http.BindHostName = "0.0.0.0";
                    setup.Http.BindPort = settings.AkkaManagementOptions.Port;
                })
                .WithClusterBootstrap(options =>
                {
                    options.ContactPointDiscovery.ServiceName = settings.AkkaManagementOptions.ServiceName;
                    options.ContactPointDiscovery.PortName = settings.AkkaManagementOptions.PortName;
                    options.ContactPointDiscovery.RequiredContactPointsNr =
                        settings.AkkaManagementOptions.RequiredContactPointsNr;
                    options.ContactPointDiscovery.ContactWithAllContactPoints = true;
                    options.ContactPointDiscovery.StableMargin = TimeSpan.FromSeconds(5);
                    options.ContactPoint.FilterOnFallbackPort =
                        settings.AkkaManagementOptions.FilterOnFallbackPort;
                }, autoStart: true);

            ConfigureDiscovery(builder, settings, configuration);
        }
        else
        {
            builder.WithClustering(settings.ClusterOptions);
        }

        return builder;
    }

    private static void ConfigureDiscovery(
        AkkaConfigurationBuilder builder,
        AkkaSettings settings,
        IConfiguration configuration)
    {
        switch (settings.AkkaManagementOptions!.DiscoveryMethod)
        {
            case DiscoveryMethod.Kubernetes:
                builder.WithKubernetesDiscovery();
                break;

            case DiscoveryMethod.AzureTableStorage:
                var connectionString = configuration.GetConnectionString("AkkaManagementAzure");
                if (connectionString is null)
                    throw new Exception("AkkaManagement table storage connection string [AkkaManagementAzure] is missing");

                builder
                    .WithAzureDiscovery(options =>
                    {
                        options.ServiceName = settings.AkkaManagementOptions.ServiceName;
                        options.ConnectionString = connectionString;
                        options.HostName = settings.RemoteOptions.PublicHostName?.ToLower() ?? "localhost";
                        options.Port = settings.AkkaManagementOptions.Port;
                    })
                    .AddHocon(AzureDiscovery.DefaultConfiguration(), HoconAddMode.Append);
                break;

            case DiscoveryMethod.Config:
                builder.WithConfigDiscovery(options =>
                {
                    options.Services.Add(new Service
                    {
                        Name = settings.AkkaManagementOptions.ServiceName,
                        Endpoints =
                        [
                            $"{settings.AkkaManagementOptions.Hostname}:{settings.AkkaManagementOptions.Port}"
                        ]
                    });
                });
                break;

            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    private static void ConfigureRemoteOptionsWithTls(AkkaSettings settings)
    {
        var tlsSettings = settings.TlsSettings!;
        var remoteOptions = settings.RemoteOptions;

        var certificate = tlsSettings.LoadCertificate();
        if (certificate is null)
            throw new InvalidOperationException("TLS is enabled but no certificate could be loaded");

        remoteOptions.EnableSsl = true;
        remoteOptions.Ssl = new SslOptions
        {
            X509Certificate = certificate,
            SuppressValidation = !tlsSettings.ValidateCertificates
        };

        // Update seed nodes to use akka.ssl.tcp:// protocol
        if (settings.ClusterOptions.SeedNodes?.Length > 0)
        {
            settings.ClusterOptions.SeedNodes = settings.ClusterOptions.SeedNodes
                .Select(node => node.Replace("akka.tcp://", "akka.ssl.tcp://"))
                .ToArray();
        }
    }
}

Program.cs の統合

using YourApp.Config;
using Petabridge.Cmd.Host;
using Petabridge.Cmd.Cluster;

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddRazorPages(); // or whatever your app needs

// Configure Akka.NET
builder.Services.ConfigureAkka(builder.Configuration,
    (configurationBuilder, provider) =>
    {
        var options = provider.GetRequiredService<AkkaSettings>();

        // Add Petabridge.Cmd for cluster management
        configurationBuilder.AddPetabridgeCmd(
            options: options.PbmOptions,
            hostConfiguration: cmd =>
            {
                cmd.RegisterCommandPalette(ClusterCommands.Instance);
            });
    });

var app = builder.Build();

// Configure middleware
app.MapRazorPages();
app.Run();

Aspire AppHost の構成(Program.cs)

using System.Net.Sockets;

var builder = DistributedApplication.CreateBuilder(args);

var config = builder.Configuration.GetSection("YourApp")
    .Get<YourAppConfiguration>() ?? new YourAppConfiguration();

var saPassword = builder.AddParameter(
    "sql-sa-password",
    () => "YourStrong!Passw0rd",
    secret: true);

var sqlServer = builder.AddSqlServer("sql", saPassword);

if (config.UseVolumes)
{
    sqlServer.WithDataVolume();
}

var db = sqlServer.AddDatabase("YourDb");

var app = builder.AddProject<Projects.YourApp>("yourapp")
    .WithReplicas(config.Replicas)
    .WithReference(db, "DefaultConnection")
    .ConfigureAkkaManagementForApp(config);

builder.Build().Run();

public class YourAppConfiguration
{
    public int Replicas { get; set; } = 1;
    public bool UseVolumes { get; set; } = false;
    public bool UseAkkaManagement { get; set; } = false;
}

Aspire Akka.Management の拡張機能(AkkaManagementExtensions.cs)

using System.Net.Sockets;
using Aspire.Hosting.Azure;

namespace YourApp.AppHost;

public static class AkkaManagementExtensions
{
    public static IResourceBuilder<ProjectResource> ConfigureAkkaManagementForApp(
        this IResourceBuilder<ProjectResource> appBuilder,
        YourAppConfiguration config)
    {
        if (!config.UseAkkaManagement) return appBuilder;

        var builder = appBuilder.ApplicationBuilder;

        // Setup Azure Table Storage for discovery
        var azureStorage = builder.AddAzureStorage("storage")
            .RunAsEmulator();

        var tableStorage = azureStorage.AddTables("akka-discovery");

        appBuilder.WaitFor(tableStorage)
            .WithReference(tableStorage, "AkkaManagementAzure");

        // Setup network endpoint ports
        appBuilder
            .WithEndpoint(name: "remote", protocol: ProtocolType.Tcp,
                env: "AkkaSettings__RemoteOptions__Port")
            .WithEndpoint(name: "management", protocol: ProtocolType.Tcp,
                env: "AkkaSettings__AkkaManagementOptions__Port")
            .WithEndpoint(name: "pbm", protocol: ProtocolType.Tcp,
                env: "AkkaSettings__PbmOptions__Port");

        // Configure Akka.Management settings via environment variables
        appBuilder
            .WithEnvironment("AkkaSettings__RemoteOptions__PublicHostName", "localhost")
            .WithEnvironment("AkkaSettings__AkkaManagementOptions__Enabled", "true")
            .WithEnvironment("AkkaSettings__AkkaManagementOptions__Hostname", "localhost")
            .WithEnvironment("AkkaSettings__AkkaManagementOptions__DiscoveryMethod", "AzureTableStorage")
            .WithEnvironment("AkkaSettings__AkkaManagementOptions__RequiredContactPointsNr",
                config.Replicas.ToString())
            .WithEnvironment("AkkaSettings__AkkaManagementOptions__FilterOnFallbackPort", "false");

        return appBuilder;
    }
}

appsettings.json の構成

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=YourDb;User Id=sa;Password=YourStrong!Passw0rd;"
  },
  "AkkaSettings": {
    "ActorSystemName": "YourSystem",
    "LogConfigOnStart": false,
    "RemoteOptions": {
      "PublicHostName": null,
      "HostName": "0.0.0.0",
      "Port": 8081
    },
    "ClusterOptions": {
      "Roles": ["your-role"],
      "SeedNodes": []
    },
    "PbmOptions": {
      "Host": "0.0.0.0",
      "Port": 9110
    }
  }
}

よく使われるパターン

パターン 1:依存性注入を使用したアクターの登録
// In your actor project
public static class ActorRegistration
{
    public static AkkaConfigurationBuilder AddYourActor(
        this AkkaConfigurationBuilder builder,
        string roleName)
    {
        builder.WithActors((system, registry, resolver) =>
        {
            var props = resolver.Props<YourActor>();
            var actor = system.ActorOf(props, "your-actor");
            registry.Register<YourActor>(actor);
        });

        return builder;
    }
}

// In AkkaConfiguration.cs
builder
    .ConfigureNetwork(provider)
    .WithSqlPersistence(...)
    .AddYourActor(roleName);  // Register your actor
パターン 2:クラスターシャーディングの設定
builder.WithShardRegion<YourEntityActor>(
    typeName: "your-entity",
    entityPropsFactory: (_, _, resolver) => resolver.Props<YourEntityActor>(),
    extractEntityId: ExtractEntityId,
    extractShardId: ExtractShardId,
    shardOptions: new ShardOptions
    {
        Role = "your-role",
        StateStoreMode = StateStoreMode.Persistence
    });

private static string ExtractEntityId(object message)
{
    return message switch
    {
        IEntityMessage msg => msg.EntityId,
        _ => null
    };
}

private static string ExtractShardId(object message)
{
    return message switch
    {
        IEntityMessage msg => (msg.EntityId.GetHashCode() % 10).ToString(),
        _ => null
    };
}
パターン 3:ヘルスチェック

Program.cs で必ずヘルスチェックを構成します。

builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy("Application is running"),
        tags: new[] { "liveness" });

// Akka health checks are added automatically by:
// - .WithAkkaClusterReadinessCheck()
// - .WithActorSystemLivenessCheck()
// - journalBuilder.WithHealthCheck()
// - snapshotBuilder.WithHealthCheck()

よくある問題と解決方法

問題 1:クラスターのノード同士が互いを検出できない

症状: クラスターの状態でノードが「Unreachable」のままになります

解決方法:

  1. RequiredContactPointsNr がレプリカ数と一致していることを確認します
  2. すべてのノードが AkkaManagementOptions で同じ ServiceName を使用していることを確認します
  3. Azure Table Storage の接続文字列が正しいことを確認します
  4. ファイアウォール/ネットワークでリモートポートと管理ポートへの TCP 通信が許可されていることを確認します
問題 2:永続化の初期化に失敗する

症状: SQL 接続エラーが発生してアプリケーションの起動に失敗します

解決方法:

  1. SQL Server が実行中であることを確認します(Aspire ダッシュボードを確認してください)
  2. 接続文字列が正しく構成されていることを確認します
  3. WithSqlPersistence で autoInitialize: true を設定します
  4. データベースが存在し、アクセス可能であることを確認します
問題 3:開発環境でスプリットブレインが発生する

症状: 1 つに統合されたクラスターではなく、複数の独立したクラスターが形成されます

解決方法:

  1. ローカル開発では FilterOnFallbackPort = false を使用します
  2. すべてのレプリカが同じ検出構成を使用していることを確認します
  3. ContactWithAllContactPoints = true を設定します
  4. 開発マシンの動作が遅い場合は StableMargin を増やします

Akka.NET アクターのテスト

Akka.Hosting.TestKit を使用した Akka.NET の包括的なテストパターンについては、akka-net-testing-patterns スキルを参照してください。

このスキルでは、以下について説明します。

  • Akka.Hosting.TestKit と依存性注入を使用した最新のテスト
  • アクター間のやり取りを検証する TestProbe パターン
  • 永続化アクターとイベントソーシングのテスト
  • AkkaExecutionMode.LocalTest を使用したローカルクラスターシャーディングのテスト
  • シナリオベースの統合テスト
  • ベストプラクティスとアンチパターン
簡単な例:Akka と Aspire の統合テスト

Aspire アプリケーションの Akka.NET アクターをテストする場合は、aspire-integration-testing のパターンと akka-net-testing-patterns を組み合わせます。

// Use Aspire's DistributedApplicationTestingBuilder for infrastructure
// Use Akka.Hosting.TestKit for actor testing
public class AkkaAspireIntegrationTests : IAsyncLifetime
{
    private DistributedApplication? _app;

    public async Task InitializeAsync()
    {
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.YourApp_AppHost>();

        _app = await appHost.BuildAsync();
        await _app.StartAsync();
    }

    [Fact]
    public async Task ActorSystem_WithRealDatabase_ShouldPersistEvents()
    {
        // Get SQL connection string from Aspire
        var dbResource = _app!.GetResource("yourdb");
        var connectionString = await dbResource.GetConnectionStringAsync();

        // Create HttpClient to test actor endpoints
        var httpClient = _app.CreateHttpClient("yourapp");

        // Test actor behavior through HTTP API
        var response = await httpClient.PostAsJsonAsync("/orders", new
        {
            OrderId = "ORDER-001",
            Amount = 100.00m
        });

        response.Should().BeSuccessStatusCode();

        // Verify data was persisted to real database
        await using var connection = new SqlConnection(connectionString);
        await connection.OpenAsync();

        var events = await connection.QueryAsync<string>(
            "SELECT EventType FROM EventJournal WHERE PersistenceId = 'order-ORDER-001'");

        events.Should().Contain("OrderCreated");
    }

    public async Task DisposeAsync()
    {
        if (_app is not null)
            await _app.DisposeAsync();
    }
}

個々のアクターを単体テストする場合は、インメモリ永続化を使用して akka-net-testing-patterns を利用してください(Aspire は不要です)。

ベストプラクティスのまとめ

  1. 必ずヘルスチェックを使用する - すべてのコンポーネントに対して、レディネスチェックとライブネスチェックを設定します
  2. 設定から設定値をバインドする - ホスト名、ポート、接続文字列をハードコードしないでください
  3. 複数ノードでは Akka.Management を使用する - レプリカが 1 つを超えるクラスターで静的シードノードを使用しないでください
  4. 本番環境では TLS を設定する - 本番環境では必ず TLS を使用します
  5. アクターロジックと設定を分離する - アクターを純粋に保ち、設定は拡張メソッドに記述します
  6. Petabridge.Cmd を使用する - クラスターのデバッグと管理に不可欠です
  7. 複数のレプリカでテストする - クラスター化の問題を検出できるよう、必ず Replicas > 1 でテストします
  8. 永続化の健全性を監視する - ジャーナルストアとスナップショットストアのヘルスチェックを設定します

コメント