【问题标题】:How to use Dependency Injection in AWS Lambda C# implementation如何在 AWS Lambda C# 实施中使用依赖注入
【发布时间】:2018-06-01 23:11:47
【问题描述】:

我使用 AWS.Net SDK、.net 核心版本 1.0 创建了 Lambda 函数。我想实现依赖注入。由于 lambda 函数在 AWS 环境中触发并独立运行,因此不存在像 Startup 这样的类。我如何以及在哪里配置我的容器来实现这个实现?

【问题讨论】:

  • 老实说,我认为这是不可能的。但我可能是错的。

标签: c# .net-core aws-lambda


【解决方案1】:

我知道我迟到了,但我添加了这个,因为我相信互联网上有一些不好/缺乏的例子。 @Erndob 对已接受的答案是正确的。您只会创建更多实例。

根据您在 DI 容器中进行的注册,您需要牢记:

  1. 您正在执行哪些实现 IDisposable 的注册
  2. AWS 将您的对象实例保留多长时间。我找不到任何关于此的文档。

最后是这样的:

public class Function
{
    private ServiceCollection _serviceCollection;

    public Function()
    {
        ConfigureServices();
    }

    public string FunctionHandler(string input, ILambdaContext context)
    {
        using (ServiceProvider serviceProvider = _serviceCollection.BuildServiceProvider())
        {
            // entry to run app.
            return serviceProvider.GetService<App>().Run(input);
        }
    }

    private void ConfigureServices()
    {
        // add dependencies here
        _serviceCollection = new ServiceCollection();
        _serviceCollection.AddTransient<App>();
    }
}

使用这种模式,每个 lambda 调用都会得到一个新的ServiceProvider,并在完成时将其丢弃。

【讨论】:

  • 非常感谢克里斯,永远不会太晚。接受的答案解决了 DI 问题,但我们绝对应该考虑单例问题。因此,我将其作为该答案的扩展。投票赞成。
  • 你将如何在单元测试中测试这个?我们如何模拟ServiceCollection
  • 好的,我回答了我自己的问题!我只是添加了另一个构造函数,它接受一个模拟的ServiceCollection
  • 非常感谢,这对我改进项目有很大帮助!问候
【解决方案2】:

虽然 FunctionHandler 确实是您应用程序的入口点,但我实际上会将您的 DI 连接到无参数构造函数中。构造函数只被调用一次,所以这个纯粹的“设置”代码应该只需要调用一次。我们只想在路由到同一容器的每个后续调用中利用它。

public class Function
{
    private static ServiceProvider ServiceProvider { get; set; }

    /// <summary>
    /// The parameterless constructor is what Lambda uses to construct your instance the first time.
    /// It will only ever be called once for the lifetime of the container that it's running on.
    /// We want to build our ServiceProvider once, and then use the same provider in all subsequent 
    /// Lambda invocations. This makes things like using local MemoryCache techniques viable (Just 
    /// remember that you can never count on a locally cached item to be there!)
    /// </summary>
    public Function()
    {
        var services = new ServiceCollection();
        ConfigureServices(services);
        ServiceProvider = services.BuildServiceProvider();
    }

    public async Task FunctionHandler(SQSEvent evnt, ILambdaContext context)
    {
        await ServiceProvider.GetService<App>().Run(evnt);
    }

    /// <summary>
    /// Configure whatever dependency injection you like here
    /// </summary>
    /// <param name="services"></param>
    private static void ConfigureServices(IServiceCollection services)
    {
        // add dependencies here ex: Logging, IMemoryCache, Interface mapping to concrete class, etc...

        // add a hook to your class that will actually do the application logic
        services.AddTransient<App>();
    }

    /// <summary>
    /// Since we don't want to dispose of the ServiceProvider in the FunctionHandler, we will
    /// at least try to clean up after ourselves in the destructor for the class.
    /// </summary>
    ~Function()
    {
        ServiceProvider.Dispose();
    }
}

public class App
{
    public async Task Run(SQSEvent evnt)
    {
        // actual business logic goes here
        await Task.CompletedTask;
    }
}

【讨论】:

  • 将构造函数设为静态,这样就可以了。现在你有一个在实例之间共享的静态服务提供者,但是如果 AWS 在同一个 micro-vm 上创建一个新实例,它将再次覆盖服务集合。
  • 在当前实现中,您的 Scoped 服务将像 Singleton 服务一样创建(在每次调用 lambda 函数时),因为您没有控制 ServiceProvider 的范围。为了解决这个问题,FunctionHandler 应该以:` using var scope = ServiceProvider.CreateScope(); 开头scope.ServiceProvider.GetRequiredService().Run(); //下面的其余代码 `
  • 所以你的 Function 的构造函数只在冷启动期间被调用一次,对吗?
  • @Valera 是对的。还想补充一点,如果没有范围,所有瞬态一次性对象都不会被释放
【解决方案3】:

你可以这样做。您的 FunctionHandler 是您的应用程序的入口点。所以您必须从那里连接服务集合。

public class Function
{
    public string FunctionHandler(string input, ILambdaContext context)
    {
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);

        // create service provider
        var serviceProvider = serviceCollection.BuildServiceProvider();

        // entry to run app.
        return serviceProvider.GetService<App>().Run(input);
    }

    private static void ConfigureServices(IServiceCollection serviceCollection)
    {
        // add dependencies here

        // here is where you're adding the actual application logic to the collection
        serviceCollection.AddTransient<App>();
    }
}

public class App
{
    // if you put a constructor here with arguments that are wired up in your services collection, they will be injected.

    public string Run(string input)
    {
        return "This is a test";
    }
}

如果您想连接日志记录,请查看此处:https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.Logging.AspNetCore

【讨论】:

  • 有用的帖子@Donuts - 谢谢。在您的示例中,它可以帮助其他人展示您如何从 ConfigureServices 执行“App::Run”。例如serviceCollection.BuildServiceProvider().GetService&lt;App&gt;().Run()
  • 这是不正确且危险的。您将对每个请求创建一个新的服务集合。因此,您的“单例”不会是请求之间共享的实际单例。它们仅在请求本身的范围内是单例的。在重负载下,如果您使用某种类型的资源,可能会产生非常大的后果。
  • @Erndob 感谢您指出这一点。 Chris Dargis 已经对其进行了重构,我认为我们应该在实施时遵循。
  • 非常感谢,这对我改进项目有很大帮助!问候
猜你喜欢
  • 2019-01-18
  • 2021-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
相关资源
最近更新 更多