Skip to content

Akka.NET in ConnectSoft Microservice Template

Purpose & Overview

Akka.NET is a port of the popular Akka framework for building distributed, concurrent, and resilient applications using the Actor Model. While the ConnectSoft Microservice Template primarily uses Microsoft Orleans as its actor runtime, Akka.NET provides an alternative implementation option for teams requiring more fine-grained control over actor lifecycle, supervision hierarchies, and message routing.

Akka.NET enables:

  • Hierarchical Actors: Parent-child actor relationships with supervision strategies
  • Explicit Lifecycle Management: Fine-grained control over actor creation, restart, and termination
  • Advanced Routing: Complex message routing patterns (round-robin, consistent hashing, broadcast)
  • Persistence: Event sourcing and snapshots for stateful actors
  • Cluster Sharding: Automatic distribution of actors across cluster nodes
  • Location Transparency: Actors can be local or remote with transparent communication
  • Fault Tolerance: Supervision hierarchies for automatic error recovery
  • Stream Processing: Akka Streams for reactive data processing pipelines

Akka.NET vs Orleans

Akka.NET provides more control and flexibility but requires more configuration and understanding of actor lifecycle. Orleans abstracts away many complexities but provides less fine-grained control. Choose Akka.NET when you need hierarchical supervision, complex routing, or event sourcing patterns. Choose Orleans for simpler virtual actor semantics with automatic lifecycle management.

Architecture Overview

Akka.NET in ConnectSoft Architecture

Application Layer
    ├── Processors (Commands/Writes)
    └── Retrievers (Queries/Reads)
    ↓ (Actor Invocation)
Akka.NET ActorSystem
    ├── ActorSystem (Actor Runtime)
    ├── Actors (Actor Instances)
    │   ├── Domain Actors (BankAccountActor, etc.)
    │   ├── Supervisor Actors
    │   └── Router Actors
    ├── Actor Persistence (Event Sourcing)
    ├── Cluster Sharding (Distributed Actors)
    └── Akka Streams (Reactive Processing)
Storage Layer
    └── Event Store / Snapshot Store

Key Integration Points

Layer Component Responsibility
ActorModel IBankAccountActor Domain actor interface (framework-agnostic)
ActorModel.Akka BankAccountActor, BankAccountActorRef Akka.NET-specific actor implementation
ApplicationModel AkkaExtensions ActorSystem configuration, clustering, persistence
DomainModel Domain Logic Business rules executed within actors
PersistenceModel Persistence Store Event sourcing and snapshot storage

Comparison: Akka.NET vs Orleans

Key Differences

Feature Akka.NET Orleans
Actor Model Hierarchical actors with explicit lifecycle Virtual actors with automatic lifecycle
Lifecycle Management Manual creation, supervision, termination Automatic activation/deactivation
State Persistence Event sourcing with snapshots (manual) Built-in state storage (automatic)
Clustering Akka Cluster with manual sharding Automatic grain placement
Supervision Explicit supervision hierarchies Automatic reactivation
Message Routing Advanced routing (routers, sharding) Simple grain invocation
Fault Tolerance Supervision strategies Automatic failover
Complexity Higher (more control) Lower (more abstraction)
Learning Curve Steeper Gentler
Use Cases Complex routing, event sourcing, streaming Virtual actors, stateful services

When to Choose Akka.NET

Choose Akka.NET when: - You need hierarchical actor supervision - You require event sourcing patterns - You need complex message routing (routers, sharding) - You want fine-grained control over actor lifecycle - You're building streaming/reactive pipelines - You need Akka Streams for data processing

Choose Orleans when: - You want virtual actors with automatic lifecycle - You prefer simpler state management - You need built-in clustering and failover - You want less boilerplate code - You're building traditional stateful services

Core Concepts

Actor System

The ActorSystem is the root of the actor hierarchy and manages actor creation, supervision, and configuration:

// AkkaExtensions.cs
public static class AkkaExtensions
{
    public static IServiceCollection AddMicroserviceAkka(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var akkaConfig = configuration.GetSection("Akka");

        // Create ActorSystem
        var actorSystem = ActorSystem.Create(
            "MicroserviceActorSystem",
            LoadAkkaConfiguration(akkaConfig));

        services.AddSingleton(actorSystem);

        // Register actor props
        services.AddScoped<BankAccountActorProps>();

        return services;
    }

    private static Config LoadAkkaConfiguration(IConfigurationSection akkaConfig)
    {
        var configBuilder = new StringBuilder();
        configBuilder.AppendLine("akka {");
        configBuilder.AppendLine("  actor {");
        configBuilder.AppendLine("    provider = cluster");
        configBuilder.AppendLine("  }");
        configBuilder.AppendLine("  cluster {");
        configBuilder.AppendLine($"    seed-nodes = [{akkaConfig["SeedNodes"]}]");
        configBuilder.AppendLine("  }");
        configBuilder.AppendLine("}");

        return ConfigurationFactory.ParseString(configBuilder.ToString());
    }
}

Actor Interface (Framework-Agnostic)

Actors are defined with framework-agnostic interfaces in the ActorModel project:

// IBankAccountActor.cs (ActorModel project)
namespace ConnectSoft.MicroserviceTemplate.ActorModel
{
    using System.Threading.Tasks;

    /// <summary>
    /// Bank account actor interface.
    /// </summary>
    public interface IBankAccountActor
    {
        /// <summary>
        /// Withdraw the given amount from the bank account.
        /// </summary>
        /// <param name="input">Withdraw input.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        Task<WithdrawOutput> Withdraw(WithdrawInput input);

        /// <summary>
        /// Deposit the given amount to the bank account.
        /// </summary>
        /// <param name="input">Deposit input.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        Task<DepositOutput> Deposit(DepositInput input);

        /// <summary>
        /// Get the current balance.
        /// </summary>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        Task<GetBalanceOutput> GetBalance();
    }
}

Akka.NET Actor Implementation

Akka.NET actors inherit from ReceiveActor and implement message handlers:

// BankAccountActor.cs (ActorModel.Akka project)
namespace ConnectSoft.MicroserviceTemplate.ActorModel.Akka
{
    using System;
    using Akka.Actor;
    using ConnectSoft.MicroserviceTemplate.ActorModel;
    using ConnectSoft.MicroserviceTemplate.DomainModel;
    using Microsoft.Extensions.Logging;

    /// <summary>
    /// Bank account actor implementation using Akka.NET.
    /// </summary>
    public sealed class BankAccountActor : ReceiveActor, IBankAccountActor
    {
        private readonly IActorRef retrieverActor;
        private readonly IActorRef processorActor;
        private readonly ILogger<BankAccountActor> logger;

        private decimal balance;

        public BankAccountActor(
            IActorRef retrieverActor,
            IActorRef processorActor,
            ILogger<BankAccountActor> logger)
        {
            this.retrieverActor = retrieverActor;
            this.processorActor = processorActor;
            this.logger = logger;

            // Register message handlers
            Receive<WithdrawInput>(HandleWithdraw);
            Receive<DepositInput>(HandleDeposit);
            Receive<GetBalanceRequest>(HandleGetBalance);

            // Initialize state
            this.balance = 0;
        }

        private void HandleWithdraw(WithdrawInput input)
        {
            try
            {
                if (this.balance < input.Amount)
                {
                    Sender.Tell(new WithdrawOutput 
                    { 
                        Success = false, 
                        ErrorMessage = "Insufficient funds" 
                    });
                    return;
                }

                this.balance -= input.Amount;
                this.logger.LogInformation(
                    "Withdrawn {Amount} from account. New balance: {Balance}",
                    input.Amount,
                    this.balance);

                Sender.Tell(new WithdrawOutput { Success = true });
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error withdrawing from account");
                Sender.Tell(new WithdrawOutput 
                { 
                    Success = false, 
                    ErrorMessage = ex.Message 
                });
            }
        }

        private void HandleDeposit(DepositInput input)
        {
            try
            {
                this.balance += input.Amount;
                this.logger.LogInformation(
                    "Deposited {Amount} to account. New balance: {Balance}",
                    input.Amount,
                    this.balance);

                Sender.Tell(new DepositOutput { Success = true });
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error depositing to account");
                Sender.Tell(new DepositOutput 
                { 
                    Success = false, 
                    ErrorMessage = ex.Message 
                });
            }
        }

        private void HandleGetBalance(GetBalanceRequest request)
        {
            Sender.Tell(new GetBalanceOutput { Balance = this.balance });
        }

        protected override void PreStart()
        {
            this.logger.LogInformation("BankAccountActor started");
            base.PreStart();
        }

        protected override void PostStop()
        {
            this.logger.LogInformation("BankAccountActor stopped");
            base.PostStop();
        }
    }
}

Actor Factory/Props

Akka.NET uses Props to create actors:

// BankAccountActorProps.cs
namespace ConnectSoft.MicroserviceTemplate.ActorModel.Akka
{
    using Akka.Actor;
    using Microsoft.Extensions.Logging;

    /// <summary>
    /// Factory for creating BankAccountActor instances.
    /// </summary>
    public sealed class BankAccountActorProps
    {
        private readonly IActorRefFactory actorSystem;
        private readonly ILoggerFactory loggerFactory;

        public BankAccountActorProps(
            IActorRefFactory actorSystem,
            ILoggerFactory loggerFactory)
        {
            this.actorSystem = actorSystem;
            this.loggerFactory = loggerFactory;
        }

        public IActorRef Create(string accountId)
        {
            var props = Props.Create(() =>
            {
                var retrieverActor = this.actorSystem.ActorOf(
                    Props.Create<RetrieverActor>(),
                    $"retriever-{accountId}");

                var processorActor = this.actorSystem.ActorOf(
                    Props.Create<ProcessorActor>(),
                    $"processor-{accountId}");

                return new BankAccountActor(
                    retrieverActor,
                    processorActor,
                    this.loggerFactory.CreateLogger<BankAccountActor>());
            });

            return this.actorSystem.ActorOf(props, $"bank-account-{accountId}");
        }
    }
}

Actor Reference Wrapper

Wrap IActorRef to implement the framework-agnostic interface:

// BankAccountActorRef.cs
namespace ConnectSoft.MicroserviceTemplate.ActorModel.Akka
{
    using System;
    using System.Threading.Tasks;
    using Akka.Actor;
    using ConnectSoft.MicroserviceTemplate.ActorModel;

    /// <summary>
    /// Akka.NET implementation of IBankAccountActor.
    /// </summary>
    public sealed class BankAccountActorRef : IBankAccountActor
    {
        private readonly IActorRef actorRef;
        private readonly TimeSpan timeout;

        public BankAccountActorRef(IActorRef actorRef, TimeSpan timeout)
        {
            this.actorRef = actorRef;
            this.timeout = timeout;
        }

        public async Task<WithdrawOutput> Withdraw(WithdrawInput input)
        {
            return await this.actorRef.Ask<WithdrawOutput>(input, this.timeout);
        }

        public async Task<DepositOutput> Deposit(DepositInput input)
        {
            return await this.actorRef.Ask<DepositOutput>(input, this.timeout);
        }

        public async Task<GetBalanceOutput> GetBalance()
        {
            return await this.actorRef.Ask<GetBalanceOutput>(
                new GetBalanceRequest(), 
                this.timeout);
        }
    }
}

Actor Lifecycle

Lifecycle Hooks

Akka.NET actors have explicit lifecycle hooks:

public class MyActor : ReceiveActor
{
    protected override void PreStart()
    {
        // Called before actor starts processing messages
        // Initialize resources, connect to external services
    }

    protected override void PreRestart(Exception reason, object message)
    {
        // Called before actor restarts after failure
        // Clean up resources
    }

    protected override void PostRestart(Exception reason)
    {
        // Called after actor restarts
        // Reinitialize resources
    }

    protected override void PostStop()
    {
        // Called after actor stops
        // Clean up resources, disconnect from services
    }
}

Supervision

Akka.NET uses supervision hierarchies for fault tolerance:

// Supervisor actor
public class SupervisorActor : ReceiveActor
{
    private IActorRef childActor;

    public SupervisorActor()
    {
        // Create child actor with supervision strategy
        this.childActor = Context.ActorOf(
            Props.Create<BankAccountActor>(),
            "bank-account");

        // Define supervision strategy
        Context.SupervisorStrategy = new OneForOneStrategy(
            maxNrOfRetries: 10,
            withinTimeRange: TimeSpan.FromMinutes(1),
            localOnlyDecider: ex =>
            {
                switch (ex)
                {
                    case ArgumentException _:
                        return Directive.Stop; // Stop on invalid arguments
                    case InvalidOperationException _:
                        return Directive.Restart; // Restart on operation errors
                    default:
                        return Directive.Escalate; // Escalate to parent
                }
            });
    }
}

Actor Persistence (Event Sourcing)

Persistent Actor

Akka.NET supports event sourcing for stateful actors:

// PersistentBankAccountActor.cs
namespace ConnectSoft.MicroserviceTemplate.ActorModel.Akka
{
    using Akka.Actor;
    using Akka.Persistence;

    /// <summary>
    /// Persistent bank account actor using event sourcing.
    /// </summary>
    public sealed class PersistentBankAccountActor : ReceivePersistentActor
    {
        public override string PersistenceId { get; }

        private decimal balance;

        public PersistentBankAccountActor(string accountId)
        {
            this.PersistenceId = $"bank-account-{accountId}";

            // Command handlers
            Command<WithdrawInput>(cmd => HandleWithdraw(cmd));
            Command<DepositInput>(cmd => HandleDeposit(cmd));

            // Event handlers
            Recover<DepositedEvent>(evt => HandleDeposited(evt));
            Recover<WithdrawnEvent>(evt => HandleWithdrawn(evt));
            Recover<SnapshotOffer>(offer => HandleSnapshot(offer));
        }

        private void HandleWithdraw(WithdrawInput input)
        {
            if (this.balance < input.Amount)
            {
                Sender.Tell(new WithdrawOutput 
                { 
                    Success = false, 
                    ErrorMessage = "Insufficient funds" 
                });
                return;
            }

            var evt = new WithdrawnEvent(input.Amount, DateTime.UtcNow);
            Persist(evt, persisted =>
            {
                HandleWithdrawn(persisted);
                Sender.Tell(new WithdrawOutput { Success = true });
            });
        }

        private void HandleDeposit(DepositInput input)
        {
            var evt = new DepositedEvent(input.Amount, DateTime.UtcNow);
            Persist(evt, persisted =>
            {
                HandleDeposited(persisted);
                Sender.Tell(new DepositOutput { Success = true });
            });
        }

        private void HandleDeposited(DepositedEvent evt)
        {
            this.balance += evt.Amount;
        }

        private void HandleWithdrawn(WithdrawnEvent evt)
        {
            this.balance -= evt.Amount;
        }

        private void HandleSnapshot(SnapshotOffer offer)
        {
            if (offer.Snapshot is BankAccountSnapshot snapshot)
            {
                this.balance = snapshot.Balance;
            }
        }
    }
}

Persistence Configuration

Configure persistence plugins:

// AkkaExtensions.cs
private static Config LoadAkkaConfiguration(IConfigurationSection akkaConfig)
{
    var configBuilder = new StringBuilder();
    configBuilder.AppendLine("akka {");
    configBuilder.AppendLine("  persistence {");
    configBuilder.AppendLine("    journal {");
    configBuilder.AppendLine("      plugin = \"akka.persistence.journal.sql-server\"");
    configBuilder.AppendLine("      sql-server {");
    configBuilder.AppendLine($"        connection-string = \"{akkaConfig["ConnectionString"]}\"");
    configBuilder.AppendLine("      }");
    configBuilder.AppendLine("    }");
    configBuilder.AppendLine("    snapshot-store {");
    configBuilder.AppendLine("      plugin = \"akka.persistence.snapshot-store.sql-server\"");
    configBuilder.AppendLine("      sql-server {");
    configBuilder.AppendLine($"        connection-string = \"{akkaConfig["ConnectionString"]}\"");
    configBuilder.AppendLine("      }");
    configBuilder.AppendLine("    }");
    configBuilder.AppendLine("  }");
    configBuilder.AppendLine("}");

    return ConfigurationFactory.ParseString(configBuilder.ToString());
}

Clustering and Sharding

Cluster Configuration

Configure Akka Cluster for distributed actors:

// AkkaExtensions.cs
private static Config LoadAkkaConfiguration(IConfigurationSection akkaConfig)
{
    var seedNodes = akkaConfig.GetSection("SeedNodes")
        .GetChildren()
        .Select(node => $"\"akka.tcp://MicroserviceActorSystem@{node.Value}\"")
        .ToList();

    var configBuilder = new StringBuilder();
    configBuilder.AppendLine("akka {");
    configBuilder.AppendLine("  actor {");
    configBuilder.AppendLine("    provider = cluster");
    configBuilder.AppendLine("  }");
    configBuilder.AppendLine("  remote {");
    configBuilder.AppendLine("    dot-netty.tcp {");
    configBuilder.AppendLine($"      hostname = {akkaConfig["Hostname"]}");
    configBuilder.AppendLine($"      port = {akkaConfig["Port"]}");
    configBuilder.AppendLine("    }");
    configBuilder.AppendLine("  }");
    configBuilder.AppendLine("  cluster {");
    configBuilder.AppendLine($"    seed-nodes = [{string.Join(", ", seedNodes)}]");
    configBuilder.AppendLine("  }");
    configBuilder.AppendLine("}");

    return ConfigurationFactory.ParseString(configBuilder.ToString());
}

Cluster Sharding

Use cluster sharding for automatic actor distribution:

// ShardedBankAccountActor.cs
public class ShardedBankAccountActor : PersistentBankAccountActor
{
    public ShardedBankAccountActor() : base(EntityId)
    {
    }

    private static string EntityId => 
        Context.Self.Path.Name;

    protected override void PreStart()
    {
        base.PreStart();
        Context.Become(Active);
    }

    private void Active()
    {
        Command<ShardRegion.StartEntity>(start => 
            Sender.Tell(start.EntityId));
        Command<WithdrawInput>(cmd => HandleWithdraw(cmd));
        Command<DepositInput>(cmd => HandleDeposit(cmd));
    }
}

// Sharding configuration
var sharding = ClusterSharding.Get(actorSystem);
var shardRegion = await sharding.StartAsync(
    typeName: "BankAccount",
    entityProps: Props.Create<ShardedBankAccountActor>(),
    settings: ClusterShardingSettings.Create(actorSystem),
    messageExtractor: new BankAccountMessageExtractor());

Routing

Router Actors

Use routers for load balancing and message distribution:

// Round-robin router
var router = actorSystem.ActorOf(
    Props.Create<BankAccountActor>()
        .WithRouter(new RoundRobinPool(5)));

// Consistent hashing router
var hashRouter = actorSystem.ActorOf(
    Props.Create<BankAccountActor>()
        .WithRouter(new ConsistentHashingPool(5)));

// Broadcast router
var broadcastRouter = actorSystem.ActorOf(
    Props.Create<BankAccountActor>()
        .WithRouter(new BroadcastPool(5)));

Custom Router

Create custom routing logic:

public class AccountIdRouter : CustomRouterConfig
{
    public override Router CreateRouter(ActorSystem system)
    {
        return new Router(new AccountIdRoutingLogic());
    }

    private class AccountIdRoutingLogic : RoutingLogic
    {
        public override Routee Select(object message, Routee[] routees)
        {
            if (message is IHasAccountId hasAccountId)
            {
                var index = hasAccountId.AccountId.GetHashCode() % routees.Length;
                return routees[Math.Abs(index)];
            }

            return routees[0];
        }
    }
}

Integration with Clean Architecture

Domain Model Integration

Actors call domain services:

public class BankAccountActor : ReceiveActor
{
    private readonly IMicroserviceAggregateRootsRetriever retriever;
    private readonly IMicroserviceAggregateRootsProcessor processor;

    public BankAccountActor(
        IMicroserviceAggregateRootsRetriever retriever,
        IMicroserviceAggregateRootsProcessor processor)
    {
        this.retriever = retriever;
        this.processor = processor;

        Receive<WithdrawInput>(async input =>
        {
            // Use domain services
            var result = await this.processor.ProcessAsync(
                new ProcessInput { /* ... */ });

            Sender.Tell(new WithdrawOutput { Success = result.Success });
        });
    }
}

Service Registration

Register actors in dependency injection:

// AkkaExtensions.cs
public static IServiceCollection AddMicroserviceAkka(
    this IServiceCollection services,
    IConfiguration configuration)
{
    // Create ActorSystem
    var actorSystem = ActorSystem.Create(
        "MicroserviceActorSystem",
        LoadAkkaConfiguration(configuration));

    services.AddSingleton(actorSystem);

    // Register actor factories
    services.AddScoped<BankAccountActorProps>();

    // Register actor reference factory
    services.AddScoped<Func<string, IBankAccountActor>>(sp =>
    {
        var props = sp.GetRequiredService<BankAccountActorProps>();
        var actorSystem = sp.GetRequiredService<ActorSystem>();
        var timeout = TimeSpan.FromSeconds(30);

        return accountId =>
        {
            var actorRef = props.Create(accountId);
            return new BankAccountActorRef(actorRef, timeout);
        };
    });

    return services;
}

Configuration

appsettings.json

{
  "Akka": {
    "Hostname": "localhost",
    "Port": 8080,
    "SeedNodes": [
      "localhost:8080",
      "localhost:8081"
    ],
    "ConnectionString": "Server=localhost;Database=AkkaPersistence;...",
    "Persistence": {
      "Journal": {
        "Plugin": "akka.persistence.journal.sql-server"
      },
      "SnapshotStore": {
        "Plugin": "akka.persistence.snapshot-store.sql-server"
      }
    }
  }
}

Testing

TestKit

Use Akka.TestKit for testing:

// BankAccountActorTests.cs
[TestClass]
public class BankAccountActorTests : TestKit
{
    [TestMethod]
    public void Withdraw_WithSufficientBalance_ShouldSucceed()
    {
        // Arrange
        var actor = Sys.ActorOf(Props.Create<BankAccountActor>());

        // Act
        actor.Tell(new DepositInput { Amount = 100 });
        var depositResult = ExpectMsg<DepositOutput>();

        actor.Tell(new WithdrawInput { Amount = 50 });
        var withdrawResult = ExpectMsg<WithdrawOutput>();

        // Assert
        Assert.IsTrue(depositResult.Success);
        Assert.IsTrue(withdrawResult.Success);
    }
}

Best Practices

Do's

  1. Use Supervision Hierarchies

    // ✅ GOOD - Supervision strategy
    Context.SupervisorStrategy = new OneForOneStrategy(...);
    

  2. Handle Actor Lifecycle

    // ✅ GOOD - Clean up in PostStop
    protected override void PostStop()
    {
        // Clean up resources
    }
    

  3. Use Persistent Actors for State

    // ✅ GOOD - Event sourcing for state
    public class PersistentActor : ReceivePersistentActor
    {
        // Persist events
    }
    

  4. Use Routers for Load Distribution

    // ✅ GOOD - Router for load balancing
    var router = actorSystem.ActorOf(
        Props.Create<MyActor>().WithRouter(new RoundRobinPool(5)));
    

Don'ts

  1. Don't Block in Message Handlers

    // ❌ BAD - Blocking call
    Receive<Message>(msg => Thread.Sleep(1000));
    
    // ✅ GOOD - Async/await
    ReceiveAsync<Message>(async msg => await Task.Delay(1000));
    

  2. Don't Share Mutable State

    // ❌ BAD - Shared mutable state
    private static int counter;
    
    // ✅ GOOD - Actor-local state
    private int counter;
    

  3. Don't Create Too Many Top-Level Actors

    // ❌ BAD - Too many top-level actors
    for (int i = 0; i < 100000; i++)
    {
        actorSystem.ActorOf(Props.Create<MyActor>(), $"actor-{i}");
    }
    
    // ✅ GOOD - Use cluster sharding
    var shardRegion = await sharding.StartAsync(...);
    

Troubleshooting

Issue: Actor Not Responding

Symptoms: Actor doesn't respond to messages.

Solutions: - Check actor is started (PreStart called) - Verify actor reference is correct - Check supervision strategy isn't stopping actor - Review logs for exceptions

Issue: Messages Lost

Symptoms: Messages not received by actor.

Solutions: - Use persistent actors for guaranteed delivery - Check actor lifecycle (stopped/restarted) - Verify message type matches Receive handler - Check dead letter queue

Issue: Cluster Not Forming

Symptoms: Actors can't communicate across nodes.

Solutions: - Verify seed nodes are reachable - Check network configuration - Verify cluster configuration in appsettings.json - Check firewall rules

Summary

Akka.NET in the ConnectSoft Microservice Template provides:

  • Hierarchical Actors: Parent-child relationships with supervision
  • Explicit Lifecycle: Fine-grained control over actor creation and termination
  • Event Sourcing: Persistent actors with event journaling
  • Advanced Routing: Complex message routing patterns
  • Cluster Sharding: Automatic actor distribution across nodes
  • Fault Tolerance: Supervision strategies for error recovery
  • Stream Processing: Akka Streams for reactive pipelines
  • Clean Architecture: Framework-agnostic interfaces with Akka.NET implementation

Akka.NET is ideal for teams requiring: - More control over actor lifecycle - Event sourcing patterns - Complex routing and supervision hierarchies - Fine-grained fault tolerance strategies

While Akka.NET provides more flexibility than Orleans, it requires more configuration and understanding of actor lifecycle management. Choose Akka.NET when you need hierarchical supervision, event sourcing, or complex routing patterns that aren't available in Orleans.