Introducing Akka.Hosting - HOCONless Akka.NET Configuration and Runtime

Best Practices and Patterns for Asynchronous Programming with Akka.NET Actors

In our Akka.NET Community Standup on March 9th, 2022 we presented for the very first time Akka.Hosting - a new approach to configuring Akka.NET and managing ActorSystems that requires zero HOCON, automatically enforces Akka.NET best practices, is type-checked, and makes it easy to pass IActorRefs via Microsoft.Extensions.DependencyInjection using the brand new ActorRegistry construct. Although Akka.Hosting is part of the current Akka.NET v1.5 development effort underway, it is already available for use with Akka.NET v1.4 and is ready for production use.

In this post and accompanying video we demonstrate how to use Akka.Hosting to streamline Akka.NET configuration, ActorSystem life-cycle management, actor instantiation, dependency injection, and more.

If you want to see the code samples that goes along with this video and blog post, you can find them here:

Fall into the “Pit of Success” with Akka.Hosting

You can view the Akka.Hosting GitHub repository to see this sample, but this is a complete Akka.NET (using clustering) + ASP.NET Core sample running on .NET 6 running in 37 lines of code using Akka.Hosting:

using Akka.Hosting;
using Akka.Actor;
using Akka.Actor.Dsl;
using Akka.Cluster.Hosting;
using Akka.Remote.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithRemoting("localhost", 8110)
        .WithClustering(new ClusterOptions(){ Roles = new[]{ "myRole" }, 
            SeedNodes = new[]{ Address.Parse("akka.tcp://MyActorSystem@localhost:8110")}})
        .WithActors((system, registry) =>
    {
        var echo = system.ActorOf(act =>
        {
            act.ReceiveAny((o, context) =>
            {
                context.Sender.Tell($"{context.Self} rcv {o}");
            });
        }, "echo");
        registry.TryRegister<Echo>(echo); // register for DI
    });
});

var app = builder.Build();

app.MapGet("/", async (context) =>
{
    var echo = context.RequestServices.GetRequiredService<ActorRegistry>().Get<Echo>();
    var body = await echo.Ask<string>(context.TraceIdentifier, context.RequestAborted).ConfigureAwait(false);
    await context.Response.WriteAsync(body);
});

app.Run();

That is tremendously concise compared to the experience most Akka.NET developers do today:

  • Parse HOCON files;
  • Create their own version of the AkkaService to manage the ActorSystem;
  • Manually create their own Setup instances for things like Akka.DependencyInjection and more;
  • Create a static class to store all of the IActorRefs that they need to pass to their WebAPIs or to other actors inside their application; and
  • Much more ugly boilerplate.

Akka.Hosting eliminates the need for all of that through its highly extensible AkkaConfigurationBuilder - which can be and is extended through multiple NuGet packages:

All of these packages target .NET Standard 2.0 - so they’ll work on any platform that Akka.NET v1.4+ supports. We will also be adding additional packages, convenience methods, and possibly other Microsoft.Extensions.* integrations over the course of the Akka.NET v1.5 implementation. For instance, see this proposal “Add Support for Micrsoft.Extensions.Diagnostics.IHealthCheck?

WithActors

One of the things that makes Akka.Hosting so powerful is how composable it is across different libraries and services within your solution or across open source projects.

For instance, we have a base WithActors method that registers a delegate that accepts an instance of the ActorRegistry and the ActorSystem as arguments:

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithRemoting("localhost", 8110)
        .WithClustering(new ClusterOptions()
        {
            Roles = new[] { "myRole" },
            SeedNodes = new[] { Address.Parse("akka.tcp://MyActorSystem@localhost:8110") }
        })
        .WithSqlServerPersistence(builder.Configuration.GetConnectionString("sqlServerLocal"))
        .WithShardRegion<UserActionsEntity>("userActions", s => UserActionsEntity.Props(s),
            new UserMessageExtractor(),
            new ShardOptions(){ StateStoreMode = StateStoreMode.DData, Role = "myRole"})
        .WithActors((system, registry) =>
        {
            var userActionsShard = registry.Get<UserActionsEntity>();
            var indexer = system.ActorOf(Props.Create(() => new Indexer(userActionsShard)), "index");
            registry.TryRegister<Index>(indexer); // register for DI
        });
})

The WithShardRegion<TKey> method is actually an extension built on top of WithActors - and you’ll notice that the subsequent WithActors call is able to reference the ShardRegion IActorRef created in the previous method - this is because Akka.Hosting calls the WithActors methods in the order in which they were originally invoked, so previous calls’ actors will be available for use in subsequent calls if the IActorRefs were registered in the ActorRegistry.

AkkaService

Behind the scenes of the Akka.Hosting builder is the AkkaService, an IHostedService that follows all of our headless Akka.NET hosting best practices by default so you don’t have to:

  • Automatically binds the ActorSystem to the IServiceCollection, so any process that needs access to the ActorSystem can get it via dependency injection;
  • Automatically passes the current IServiceProvider into the ActorSystem via Akka.DependencyInjection (again, best practices);
  • Automatically instantiates all actors in the order in which their WithActors call was arranged;
  • Automatically terminates the process via IHostApplicationLifetime in the event that the ActorSystem errs, fails to start actors, or if the ActorSystem is terminated (i.e. if it gets kicked out of the cluster.)

ActorRegistry

As part of Akka.Hosting, we need to provide a means of making it easy to pass around top-level IActorRefs via dependency injection both within the ActorSystem and outside of it.

The ActorRegistry will fulfill this role through a set of generic, typed methods that make storage and retrieval of long-lived IActorRefs easy and coherent:

var registry = ActorRegistry.For(myActorSystem); // fetch from ActorSystem
registry.TryRegister<Index>(indexer); // register for DI
registry.Get<Index>(); // use in DI

The Index class in this case is just a marker type - for most actors you could probably just use the actor’s own implementation type as its key inside the ActorRegistry.

Future of Akka.Hosting

We are in the early days of development of Akka.Hosting but here at Petabridge we are already working on integrating it into Phobos, Petabridge.Cmd, and all of our Akka.NET code samples. Akka.Hosting is here to stay.

If you have some ideas or suggestions for how we can make it better, please file an issue here!

If you liked this post, you can share it with your followers or follow us on Twitter!
Written by Aaron Stannard on April 4, 2022

 

 

Observe and Monitor Your Akka.NET Applications with Phobos

Did you know that Phobos can automatically instrument your Akka.NET applications with OpenTelemetry?

Click here to learn more.