Service Fabric Service Remoting 中的 DI

DI in Service Fabric Service Remoting

我有一个 Service Fabric 应用程序,其中一项服务通过 ASP.NET Web API 和一些内部服务公开给 Internet (GatewayService)未暴露于 Internet 的服务(让我们称其中之一为 InternalService)。到目前为止,InternalService 也是一个 ASP.NET Web API,因此 InternalService.cs 有一个 CreateServiceInstanceListeners() 方法,如下所示:

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()))
    };
}

Startup class (in Startup.cs) for InternalService 配置了一些服务,比如添加一个 SQL DbContext 到 Dependency Injection系统,当然还有设置 ASP.NET 和 AddMvc() 等。我有几个 ApiControllers 暴露了 API.

这行得通,但是我没有得到任何真正的类型安全,它通常使开发有点麻烦,之前需要在我的 GatewayService 中手动反序列化结果操纵它。所以我决定改用 SF's Service Remoting,结果是 CreateServiceInstanceListeners() 方法,如下所示:

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

然后我也将控制器中的所有逻辑复制到 InternalService.cs 中,但这导致了一个问题:我无法再访问我的 DbContext,因为它被注入到 ApiController 的构造函数中,由 ASP.NET 根据 Startup class 中设置的规则实例化,不再使用。

  1. 有没有办法让我在使用 Service Remoting 时以同样的方式使用 Startup
  2. 我可以将 API 分成多个 class 吗,就像 ApiControllers 分成多个 class 一样?我觉得将所有暴露的方法都放在同一个 class 中会很麻烦。

您可以使用 Autofac,有一整篇 page 解释了如何设置它:

  • 添加Autofac.ServiceFabric nuget package
  • 配置 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);
      }
    
  • 查看 demo 项目。

我知道这已经是一个可接受的答案,但我想补充两分钱。

如您所知,远程处理与 WebApi 相比有两个主要区别:

  1. 给定一个远程接口,你只有一个实现 class

  2. 远程处理实现 class 是单例,因此,即使您按照接受的答案中的说明使用 DI,您仍然无法为每个请求注入 DbContext。

我可以给你一些解决这些问题的办法:

  1. 这个很简单:创建更多接口。您可以在单个服务结构服务中添加任意数量的远程接口。因此,您应该将您的远程处理 API 分成更小的接口,这些接口具有有意义的组(接口隔离)。但是,我认为你不应该有很多,因为这可能意味着你的微服务有太多责任。

  2. 为每个请求添加依赖项的一种天真的方法是将工厂注入远程处理 class,因此您可以在每个方法中解析和处理依赖项,而不是通过构造函数注入。但我发现使用 Mediatr 的方法要好得多,这可能看起来并不简单,但一旦设置好就非常容易使用。它的工作方式是您创建一个小助手 class,它在构造函数中获取一个 ILifetimeScope(当您使用 Autofac 时),它公开一个 Execute 方法。此方法将创建一个子 LifetimeScope,解析 Mediatr 并发送一个 WrapperRequest<TRequest>(包装器是一个技巧,因此远程处理输入和输出对象不必依赖于 Mediatr)。这将允许您为每个远程操作实现一个 Handler class,这将根据请求进行解析,以便您可以像使用 WebApi 控制器一样在构造函数中注入依赖项。

如果您不熟悉 Mediatr 和 Autofac,这听起来可能会令人困惑。如果我有时间,我会写一篇关于它的博客 post。