How to Build Headless Akka.NET Services with IHostedService
Creating Stand-alone Akka.NET Services with Microsoft.Extensions.Hosting and IHostedService
At Akka.NET’s inception, most of the server-side code samples we produced all demonstrated how to build so-called “headless” Akka.NET services as outright Windows Services primarily using libraries like Topshelf.
.NET has changed tremendously since then and become a truly cross-platform runtime that is capable of working with and within all of the amazing innovations that have sprung from the greater global open source community, such as Kubernetes and Docker. Thus, it’s time we modernize our guidance for how to build headless services in the age of .NET Core / .NET 5 and beyond.
We’ve produced a YouTube video demonstrating how to Host Akka.NET using IHostedService
which demonstrates the modern approach to configuring and hosting stand-alone Akka.NET services:
This blog post compliments the YouTube video!
“Headless” Service?
“Headless service” is a concept derived from “headless” computers - computer systems without a monitor, mouse, and keyboard. Headless computers could only be accessed over the network.
A “headless” service is one that doesn’t expose a public-facing API or UI of any kind - no HTTP APIs, no gRPC, etc. Headless services can still communicate with other services via private messaging systems such as Akka.Remote, Kafka, RabbitMQ, and so forth - but they run in the background of most networked applications.
Hosting Akka.NET with IHostedService
The IHostedService
interface was added as part of the .NET Core 2.0 update via the Microsoft.Extensions.Hosting NuGet package, but that package targets .NET Standard 2.0 and thus is available to .NET Framework 4.6.1 and upward.
The goals of IHostedService
are threefold:
- To make it easy to run background services inside ASP.NET Core applications, which is another method by which Akka.NET is commonly deployed;
- To make it possible to compose many different services together into the same host process; and
- To provide a standardized model for integrating configuration, logging, and dependency injection into .NET services.
We can take advantage of those in Akka.NET - especially dependency injection support.
AkkaService
So using the Cluster.WebCrawler code sample, let’s take a look at simple but complete and functional IHostedService
implementation for one of the four services that runs inside WebCrawler:
public sealed class AkkaService : IHostedService
{
private IActorRef _apiMaster;
private ActorSystem ClusterSystem;
private IActorRef _downloadMaster;
private readonly IServiceProvider _serviceProvider;
public TrackerService(IServiceProvider sp){
_serviceProvider = sp;
}
public Task StartAsync(CancellationToken cancellationToken)
{
var config = HoconLoader.ParseConfig("tracker.hocon");
var bootstrap = BootstrapSetup.Create()
.WithConfig(config.ApplyOpsConfig()) // injects environment variables into HOCON
.WithActorRefProvider(ProviderSelection.Cluster.Instance); // launch Akka.Cluster
// N.B. `WithActorRefProvider` isn't actually needed here
// the HOCON file already specifies Akka.Cluster
// enable DI support inside this ActorSystem, if needed
var diSetup = ServiceProviderSetup.Create(_serviceProvider);
// merge this setup (and any others) together into ActorSystemSetup
var actorSystemSetup = bootstrap.And(diSetup);
// start ActorSystem
ClusterSystem = ActorSystem.Create("webcrawler", actorSystemSetup);
ClusterSystem.StartPbm(); // start Petabridge.Cmd (https://cmd.petabridge.com/)
// instantiate actors
_apiMaster = ClusterSystem.ActorOf(Props.Create(() => new ApiMaster()), "api");
_downloadMaster = ClusterSystem.ActorOf(Props.Create(() => new DownloadsMaster()), "downloads");
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// strictly speaking this may not be necessary - terminating the ActorSystem would also work
// but this call guarantees that the shutdown of the cluster is graceful regardless
await CoordinatedShutdown.Get(ClusterSystem).Run(CoordinatedShutdown.ClrExitReason.Instance);
}
}
The IHostedService
interface includes two simple methods:
Task StartAsync(CancellationToken cancellationToken)
- we start ourActorSystem
and any of our top-level actors here andTask StopAsync(CancellationToken cancellationToken)
- we terminate theActorSystem
here, in this instance usingCoordinatedShutdown
and return theTask
from that.
Notice that in our implementation we also have a constructor that takes a dependency on an IServiceProvider
- that interface is our DI container and we can hang onto to a copy of it for use with Akka.DependencyInjection later.
HostBuilder
After we’ve created our Akka.NET IHostedService
we need to actually run it inside Program.cs
, and this is accomplished via the .NET Generic Host, which we can instantiate via the HostBuilder
class:
internal class Program
{
private static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
// register our host service
services.AddHostedService<AkkaService>();
})
.ConfigureLogging((hostContext, configLogging) =>
{
configLogging.AddConsole();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
}
The IHostedService
contract guarantees that when your generic host starts the IHostedService.StartAsync
method will be called.
When the process attempts to exit the .NET Generic Host guarantees that all of your IHostedService
instances will have their StopAsync
methods called and usually those will be allowed to run to completion. This helps us guarantee that our Akka.NET nodes will gracefully leave the cluster, for instance.
Akka.NET Project Templates
So all of this is pretty straightforward and gives us a repeatable way of hosting, configuring, and safely disposing our Akka.NET services. If you want to use this type of functionality inside your own applications we’ve made a set of dotnet new
template for this exact purpose.
Install
To install Petabridge’s Akka.NET templates, execute the following command using the .NET CLI:
PS> dotnet new -i "Petabridge.Templates::*"
Use
To create a brand new headless Akka.NET application that uses this IHostedService
setup, just run the following dotnet new
command:
PS> dotnet new pb-akka-cluster -n [app name]
This will give you a brand new project that is ready to be Dockerized and deployed as a .NET 5 headless service.
We also include a second template that will do the same, but for running Akka.NET as a background service inside an ASP.NET application:
PS> dotnet new pb-akka-web -n [app name]
Happy coding!
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.