uber fx_使用uber fx简化依赖注入

东郭俊楠
2023-12-01

uber fx

TL; DR (TL;DR)

Here you can find the full example on Github.Here you can find a link to Uber’s FX repository.

在这里,您可以在Github上找到完整的示例。 在这里,您可以找到指向Uber FX存储库的链接。

Who am I, and why should you care?

我是谁,你为什么要关心?

I’m Erez, the Tech Lead of the Infrastructure Team at OpenWeb, and I have been writing Go for the past 3 years.

我是OpenWeb基础架构团队的技术负责人Erez,过去三年来我一直在写Go。

I came to Go from a C# background, where you get a lot of “magic” behind the scenes with libraries like Unity Dependency Injection. When I made the move to this new language, I missed the ability to inject what I need without worrying about all the constructors I’ll have to change.

我是从C#背景来到Go的,您在其中通过Unity Dependency Injection等库在幕后获得了很多“魔术”。 当我转向这种新语言时,我错过了注入我所需要的内容的能力,而不必担心我将要更改的所有构造函数。

As the services I wrote got more complex, I got to a point where it was crucial to simplify the dependency injection to ease testing, clear the dependency graph, and get better visibility on our code dependencies.

随着我编写的服务变得越来越复杂,我到达了一个关键点,即简化依赖注入以简化测试,清除依赖图并更好地了解我们的代码依赖至关重要。

为什么Go中的依赖注入如此困难? (Why is Dependency Injection in Go so Difficult?)

At face value, managing dependencies on your own isn’t such a difficult task.

从表面上看,依靠自己管理依赖性并不是一件困难的任务。

Your database needs a logger so that you can trace what’s happening, so you pass a logger to it. Your business service needs the database so that it can access data, so you pass the database to it. Then you realize that you also want to use your logger in the business logic service; tracing is very important after all. And to be more efficient, you want to add a cache to your service so that you aren’t always going to the database. That cache should probably have a logger as well.

您的数据库需要一个记录器,以便您可以跟踪正在发生的事情,因此您可以将记录器传递给它。 您的业​​务服务需要数据库,以便它可以访问数据,因此您将数据库传递给它。 然后,您意识到您还希望在业务逻辑服务中使用记录器; 跟踪毕竟很重要。 为了提高效率,您想向服务中添加一个缓存,这样就不必总是访问数据库了。 该缓存可能还应该有一个记录器。

All of these things add up, and you end up passing instances of everything here and there, getting lost in the mayhem of it all. After some time working on your project, you’re left with a complicated mess, and that’s just to start up the service.

所有这些事情加起来,最终导致传递到处都是的实例,迷失在这一切的混乱之中。 经过一段时间的项目处理后,您将陷入一个复杂的混乱局面,而这仅仅是启动服务的开始。

So here are the problems we face:

所以这是我们面临的问题:

  1. Long initialization functions.

    长初始化函数。
  2. Difficulties injecting the same dependencies that only need to be initialized only once (loggers, etc.)

    注入相同依赖项的困难仅需要初始化一次(记录器等)
  3. Complicated dependency graph.

    复杂的依赖图。
  4. Adding new dependencies forces you to change multiple function signatures.

    添加新的依赖关系会迫使您更改多个功能签名。
  5. Replacing real dependencies with mocks while testing forces you to duplicate all of the initialization functions for the mocks

    在测试时用模拟代替真实的依赖关系会迫使您复制模拟的所有初始化函数

那么,如何解决这些问题呢? 让我们潜入 (So, how can we solve these problems? let’s dive in)

First, let’s create a simple server using fasthttp and fasthttprouter:

首先,让我们使用fasthttp和fasthttprouter创建一个简单的服务器:

This doesn’t seem so tricky, so let’s spice things up by caching the result and logging any errors we might encounter. In order to do this, we’ll start by converting the functional handler into a struct that contains our dependencies, a logger and a cache:

这似乎并不那么棘手,所以让我们通过缓存结果并记录可能遇到的任何错误来为事情加分。 为了做到这一点,我们将从将函数处理程序转换为包含我们的依赖项,记录器和缓存的结构开​​始:

And once we have this, we implement a Handle method that matches the signature of a fasthttp Handler:

有了这些,我们将实现与fasthttp处理程序的签名匹配的Handle方法:

Now, we have our handler, but we need to make sure to make some simple initializers for the logger and the cache client (we’ll be using Redis for our cache in this case):

现在,我们有了处理程序,但是我们需要确保为记录器和缓存客户端创建一些简单的初始化程序(在这种情况下,我们将对缓存使用Redis):

Let’s put all of this together in a main function and see how it looks now:

让我们将所有这些放到一个主函数中,看看现在的样子:

We can see here that we have a handler and a cache. Both have dependencies, some shared (logger) and some exclusive (Redis client). Of course, in real-life scenarios, we can have a lot more. So what can we do to simplify this?

我们在这里可以看到我们有一个处理程序和一个缓存。 两者都有依赖关系,有一些是共享的(记录器),有一些是互斥的(Redis客户端)。 当然,在现实生活中,我们可以拥有更多。 那么,我们可以做些什么来简化呢?

外汇救助! (Fx to the rescue!)

Fx has two main functionalities, Providers and Invokers.

Fx具有两个主要功能, ProviderInvokers

Providers are our “Constructors”, they provide a dependency to others. On startup, Fx creates a container, and we register our constructors to it as Providers. That would look something like this:

提供者是我们的“构造者”,他们提供对他人的依赖。 在启动时,Fx创建一个容器,我们将其构造函数注册为提供者。 看起来像这样:

Once we have our Providers, we create our Invokers. These are methods we want to run using the provided values that we just gave Fx. The Invokers can receive what Fx calls a Lifecycle, a struct that allows us to subscribe to specific behaviors such as OnStart and OnStop.

一旦有了我们的提供者,我们就创建了Invokers 。 这些是我们要使用刚刚提供给Fx的值运行的方法。 调用者可以接收Fx所谓的生命周期,该结构允许我们订阅特定行为,例如OnStart和OnStop。

Let’s take the end of our main function, where we created the HTTP server, and wrap it in a small function that can be used as an Fx Invoker. We’ll pass in the Fx Lifecycle, and the main dependency that the server needs.

让我们结束我们创建HTTP服务器的主要功能的结尾,并将其包装在一个可用作Fx Invoker的小功能中。 我们将传递Fx生命周期以及服务器所需的主要依赖关系。

Now we add this function as an Invoker in our Fx container:

现在,我们在Fx容器中将此函数添加为Invoker:

And finally, just to make things interesting, let’s add a simple listener on signals so we can quickly stop our server, and we’ll be ready to go!

最后,为了使事情变得有趣,让我们在信号上添加一个简单的侦听器,以便我们可以快速停止服务器,我们就可以开始了!

结论: (Conclusions:)

So, let’s take a look at all of our problems from the beginning, and see how we tackled them.

因此,让我们从一开始就看看我们所有的问题,并看看我们如何解决它们。

1. Long initialization functions.Fx simplified our initializations since we could easily inject edge dependencies of the dependency graph without knowing the full lifecycle.

1.长初始化函数。Fx简化了初始化过程,因为我们可以轻松注入依赖关系图的边缘依赖关系,而无需了解整个生命周期。

2. Difficulties injecting the same dependencies that only need to be initialized only once (loggers, etc.).Fx was a game-changer when it came to initializing once and injecting a lot. We only had to create a Provider one time, and Fx took care of passing it to all of the necessary places.

2.注入相同的依赖项的困难仅需要初始化一次(记录器等)。当进行一次初始化并注入很多东西时,Fx改变了游戏规则。 我们只需创建一次Provider ,Fx便将其传递给所有必要的地方。

3. Complicated dependency graph.Fx didn’t actually solve the complexity of the dependency graph, but it gives us the ability to care less about it. We only had to tell Fx what each constructor needed, and it took care of the rest!

3.复杂的依赖关系图。Fx并没有真正解决依赖关系图的复杂性,但是它使我们能够更少地关心它。 我们只需要告诉Fx每个构造函数都需要什么,其余的就由它负责!

4. Adding new dependencies forces you to change multiple function signatures.Fx solved the ease of adding or using dependencies that are already registered to the container; all we needed to do was add the dependencies in the constructor of the type, and Fx took care of injecting all necessary dependencies to each constructor for us.

4.添加新的依赖关系会迫使您更改多个函数签名。Fx解决了添加或使用已经注册到容器的依赖关系的麻烦。 我们要做的就是在类型的构造函数中添加依赖项,Fx负责为我们将所有必需的依赖项注入到每个构造函数中。

5. Replacing real dependencies with mocks while testing forces you to duplicate all of the initialization functions for the mocks.Fx solved the ease of replacing real dependencies with mock ones; we could create a separate container for tests that uses the mock dependencies to test various scenarios.

5.在测试时用模拟替换真实的依赖关系会迫使您复制该模拟的所有初始化函数。Fx解决了用模拟的替换真实依赖关系的麻烦。 我们可以为测试创建一个单独的容器,该容器使用模拟依赖项来测试各种场景。

如果您想更深入一点: (If You Want To Dive In A Little Deeper:)

  1. Fx is a heavy user of reflection. We were ready to pay the cost of reflection during the application startup time, but this is important to remember. Reflection in Go has a heavy cost.

    Fx是反射的沉重使用者。 我们已经准备好在应用程序启动期间支付反射的成本,但是要记住这一点很重要。 在Go中进行反射会带来沉重的成本。
  2. Using reflection, Fx gets the type of the Provider that needs to be injected. When using interfaces, you can automatically inject only one implementation of that interface. This can easily be solved in a few different ways, but it is something to keep in mind as you consider the move to Fx.

    使用反射,Fx获取需要注入的提供程序的类型。 使用接口时,您只能自动注入该接口的一种实现。 这可以通过几种不同的方式轻松解决,但是在考虑迁移到Fx时要牢记这一点。
  3. The cost of reflection is always important to consider when it comes to low latency systems if used on runtime.

    如果在运行时使用低延迟系统,则反射成本始终是重要的考虑因素。

* If you want to dive into a more complete example, take a look Here.

*如果您想深入了解更完整的示例,请看这里

翻译自: https://medium.com/@erez.levi/using-uber-fx-to-simplify-dependency-injection-875363245c4c

uber fx

 类似资料: