Iwould like to implement a rest service using Akka and Asp.net. Following the example here
I create my AkkaService containing the FooActor ref and a controller who transform the http request to a RunProcess message which is sent to the FooActor.
[Route("api/[controller]")]
[ApiController]
public class MyController : Controller
{
private readonly ILogger<MyController> _logger;
private readonly IAkkaService Service;
public RebalancingController(ILogger<MyController> logger, IAkkaService bridge)
{
_logger = logger;
Service = bridge;
}
[HttpGet]
public async Task<ProcessTerminated> Get()
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
return await Service.RunProcess(cts.Token);
}
}
public class AkkaService : IAkkaService, IHostedService
{
private ActorSystem ActorSystem { get; set; }
public IActorRef FooActor { get; private set; }
private readonly IServiceProvider ServiceProvider;
public AkkaService(IServiceProvider sp)
{
ServiceProvider = sp;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var hocon = ConfigurationFactory.ParseString(await File.ReadAllTextAsync("app.conf", cancellationToken));
var bootstrap = BootstrapSetup.Create().WithConfig(hocon);
var di = DependencyResolverSetup.Create(ServiceProvider);
var actorSystemSetup = bootstrap.And(di);
ActorSystem = ActorSystem.Create("AkkaSandbox", actorSystemSetup);
// </AkkaServiceSetup>
// <ServiceProviderFor>
// props created via IServiceProvider dependency injection
var fooProps = DependencyResolver.For(ActorSystem).Props<FooActor>();
FooActor = ActorSystem.ActorOf(rebalProps.WithRouter(FromConfig.Instance), "foo");
// </ServiceProviderFor>
await Task.CompletedTask;
}
public async Task<ProcessTerminated> RunProcess(CancellationToken token)
{
return await FooActor.Ask<ProcessTerminated>(new RunProcess(), token);
}
public FooActor(IServiceProvider sp)
{
_scope = sp.CreateScope();
Receive<RunProcess>(x =>
{
var basketActor = Context.ActorOf(Props.Create<BarActor>(sp), "BarActor");
basketActor.Tell(new BarRequest());
_log.Info($"Sending a request to Bar Actor ");
});
Receive<BarResponse>(x =>
{
...... Here I need to send back a ProcessTerminated message to the controller
});
}
Now, let's imagine the FooActor send a message to the BarActor telling him to perform a given task and wait the BarResponse. How could I send back the ProcessTerminated message to the controller?
Few points to take into considerations:
- I want to ensure no coupling between BarActor and FooActor. By example, I could add the original sender ActorRef to the BarRequest and BarResponse. But the BarActor musn't know about the fooActor and MyController. The structure of the messages an how the barActor respond should not be dependent of what the FooActor do with the BarResponse.
- In the example I only use BarActor, but we can imagine to have many different actors exchanging messages before returning the final result to the controller.
Nitpick: you should use Akka.Hosting and avoid creating this mock wrapper service around the
ActorSystem. That will allow you to pass in theActorRegistrydirectly into your controller, which you can use to then accessFooActorwithout the need for additional boilerplate. See "Introduction to Akka.Hosting - HOCONless, "Pit of Success" Akka.NET Runtime and Configuration" video for a fuller explanation.Next: to send the
ProcessTerminatedmessage back to your controller you need to save theSender(theIActorRefthat points to the temporary actor created byAsk<T>, in this instance) during yourReceive<RunProcess>and make sure that this value is available inside yourReceive<BarResponse>.The simple ways to accomplish that:
Senderin a field on theFooActor, use behavior-switching while you wait for theBarActorto respond, and then revert back to your original behavior.Dictionary<RunProcess, IActorRef>(the key should probably actually be some unique ID shared byRunProcessandBarResponse- a "correlation id") and reply to the correspondingIActorRefstored in the dictionary whenBarResponseis received. Remove the entry after processing.Senderin theBarRequestandBarResponsemessage payloads themselves.All three of those would work. If I thought there were going to be a large number of
RunProcessrequests running in parallel I'd opt for option 2.