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 ActorSystem
s that requires zero HOCON, automatically enforces Akka.NET best practices, is type-checked, and makes it easy to pass IActorRef
s 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:
- https://github.com/akkadotnet/Akka.Hosting
- https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/clustering/sharding-sqlserver
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 theActorSystem
; - Manually create their own
Setup
instances for things like Akka.DependencyInjection and more; - Create a static class to store all of the
IActorRef
s 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:
- Akka.Hosting package - includes the base
AkkaConfigurationBuilder
definitions,AkkaService
, andActorRegistry
definitions used by every other library; - Akka.Remote.Hosting package - includes Akka.Remote instrumentation;
- Akka.Cluster.Hosting package - includes Akka.Cluster, Akka.Cluster.Tools.DistributedPubSub, Akka.Cluster.Sharding, and Akka.Cluster.Tools.Singleton instrumentation; and
- Akka.Persistence.SqlServer.Hosting package - enables Akka.Persistence.SqlServer to grab its configuration directly from an
IConfiguration
connection string. No more HOCON.
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 WithActor
s - and you’ll notice that the subsequent WithActor
s 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 IActorRef
s 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 theIServiceCollection
, so any process that needs access to theActorSystem
can get it via dependency injection; - Automatically passes the current
IServiceProvider
into theActorSystem
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 theActorSystem
errs, fails to start actors, or if theActorSystem
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 IActorRef
s 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 IActorRef
s 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!
- Read more about:
- Akka.NET
- Case Studies
- Videos
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.