DI in Service Fabric Service Remoting

691 views Asked by At

I have a Service Fabric application with one service which is exposed to Internet (GatewayService) through an ASP.NET Web API and a couple of internal services not exposed to the Internet (let's call one of them InternalService). So far, InternalService is also an ASP.NET Web APIs, so InternalService.cs has a CreateServiceInstanceListeners() method which looks like this:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new[] {
        new ServiceInstanceListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                WebHost.CreateDefaultBuilder()
                    .UseStartup<Startup>()
                    .ConfigureServices((context, services) => { services.AddSingleton(serviceContext); })
                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                    .UseUrls(url)
                    .Build()))
    };
}

The Startup class (in Startup.cs) for InternalService configures some services, such as adding a SQL DbContext to the Dependency Injection system, and of course setting up ASP.NET with AddMvc() etc. I have a couple of ApiControllers which expose the API.

This works, BUT I don't get any real type safety with this, and it generally makes development a bit cumbersome, needing to deserialize the result manually in my GatewayService before manipulating it. So I decided to go with SF's Service Remoting instead, resulting in a CreateServiceInstanceListeners() method which looks like this:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return this.CreateServiceRemotingInstanceListeners();
}

Then I copied all the logic from the controllers into InternalService.cs too, but this lead to an issue: I don't have access to my DbContext anymore, because it was injected into the constructor of the ApiController, instantiated by ASP.NET according to the rules set in the Startup class, which isn't used anymore.

  1. Is there a way for me to use Startup in the same way when using Service Remoting?
  2. Can I separate the API into multiple classes, in the same way as ApiControllers are separated into multiple classes? I feel like having all exposed methods in the same class will be quite a hazzle.
2

There are 2 answers

1
LoekD On BEST ANSWER

You can use Autofac, there's an entire page that explains how to set it up:

  • Add the Autofac.ServiceFabric nuget package
  • Configure DI:

      // Start with the trusty old container builder.
      var builder = new ContainerBuilder();
    
      // Register any regular dependencies.
      builder.RegisterModule(new LoggerModule(ServiceEventSource.Current.Message));
    
      // Register the Autofac magic for Service Fabric support.
      builder.RegisterServiceFabricSupport();
    
      // Register a stateless service...
      builder.RegisterStatelessService<DemoStatelessService>("DemoStatelessServiceType");
    
      // ...and/or register a stateful service.
      // builder.RegisterStatefulService<DemoStatefulService>("DemoStatefulServiceType");
    
      using (builder.Build())
      {
        ServiceEventSource.Current.ServiceTypeRegistered(
          Process.GetCurrentProcess().Id,
          typeof(DemoStatelessService).Name);
    
        // Prevents this host process from terminating so services keep running.
        Thread.Sleep(Timeout.Infinite);
      }
    
  • check out the demo project.

2
Francesc Castells On

I know this has already an accepted answer, but I want to add my two cents.

As you have realized, remoting has two major differences compared to WebApi:

  1. Given a remoting interface, you have a single implementation class

  2. The remoting implementation class is a singleton, so, even if you use DI as explained in the accepted answer, you still can't inject a DbContext per request.

I can give you some solutions to these problems:

  1. This one is simple: create more interfaces. You can add as many remoting interfaces as you want in a single service fabric service. So, you should split your remoting API into smaller interfaces with groups that make sense (interface segregation). But, I don't think you should have many, because that would probably mean that your microservice has too many responsibilities.

  2. A naive approach to having dependencies per request is to inject factories into the remoting class, so you can resolve and dispose dependencies in every method instead of by constructor injection. But I found a much better approach using Mediatr, which might not seem trivial, but once set up it's very easy to use. The way it works is you create a little helper class that gets an ILifetimeScope (as you use Autofac) in the constructor and it exposes an Execute method. This method will create a child LifetimeScope, resolve Mediatr and send a WrapperRequest<TRequest> (the wrapper is a trick so that the remoting input and output objects don't have to depend on Mediatr). This will allow you to implement a Handler class for each remoting operation, which will be resolved per request so that you can inject the dependencies in the constructor as you do with a WebApi controller.

It might sound confusing if you are not familiar with Mediatr and Autofac. If I have time I'll write a blog post about it.