许多Go开发者,尤其是新开发者,发现一个不明显的问题是,我到底该如何把所有我需要的东西都传到我的处理程序中?
我们没有像Java或C那样花哨的控制反转系统。 http.处理程序是静态签名,所以我不能只传递我真正想要的东西。看来我们只有3个选择:使用globals、将处理程序包裹在一个函数中,或者在context.Context中传递东西。
这里举例是电子商务:
任务是写一个端点,给定某个类别的ID,返回该类别的物品列表。
这个端点需要访问我们的
- items.Serviceto 来做实际的查询,
- logging.Service 是用来以防出错,
- 还有 metrics.Service 来获得甜蜜的营销指标。
让我们来看看这三种选择:
1、全局变量
我们在处理程序中的第一次尝试是使用全局变量globals。这是一种相当自然的方式,许多初学者都倾向于这样做。
func GetItemsInCategory(w http.ResponseWriter, r *http.Request) { |
我们都曾经写过这样的代码。然后我们很快就知道,全局变量并不好的,因为它们会使代码变得不灵活,无法测试。
这自然而然地导致了一个由书籍、文章、视频等组成的兔子洞,告诉你 "注入你的依赖关系!"。
这段代码是不可能的,让我们尝试一下注入。
2. 依赖性注入|获得你的助推器
好吧,我们不想使用globals全局变量,但http.Handler有一个固定的签名。那么我们该怎么做呢?当然是包裹它
func GetItemsInCategory(itemsService items.Service, metricsService metrics.Service, loggingService logging.Service) http.Handler { |
这样做比较好,因为我们不再依赖全局状态,但它仍然有很多地方需要改进。主要是,它使每一个处理程序都变成了一个大的、长的、令人讨厌的混乱的编写。很容易想象,如果我们再增加几个服务,这个签名就会延长2-3倍。
所以我们阅读了一下,发现我们的*http.Request里面有一个context.Context! 我们可以创建一个中间件,注入我们所有的依赖项,然后处理程序可以直接提取它需要的东西!这肯定会解决我们所有的问题。
2. 引入Context上下文
首先,让我们制作我们所谈论的中间件。我们只是要在设置好所有的依赖关系后再进行内联。
// initialize things |
现在,我们已经添加了所有这些内容,我们可以再次重做我们的处理程序。
func GetItemsInCategory(w http.ResponseWriter, r *http.Request) { |
不错,很容易! 这里不会出什么问题。好吧,除了在3个月后,有人在应用程序的其他地方移动了一行代码,这些服务中的一个不再存在于context中。
你可能会说:"好吧,聪明的先生,我只需要检查一下确保即可!"。
func GetItemsInCategory(w http.ResponseWriter, r *http.Request) { |
正如你所看到的,即使在小的、简单的、设计好的例子中,以安全的方式来做这件事也变得有点混乱。
我们使用Go的原因之一就是为了避免所有这些类型检查的混乱! 通过使用上下文作为我们的依赖关系的某种抓包,我们有效地放弃了类型安全。此外,我们在运行时才知道事情是否存在。为了解决这个问题,我们最终会发现到处都是长串的错误检查。
这可不好。我们的三个选项都以各自的方式糟糕。如果我们想注入我们的依赖关系,似乎我们无法逃离冗长的地狱。当然,你可以绕过这些问题,创建花哨的函数来包装很多东西,但它实际上并没有解决问题。
banq注:依赖注入后还需要检测?在java中从来不检测,运行时会报错,没有注入,认为需要手工检测是一种过度思考,或者不熟悉IOC/DI概念以后的焦虑表现吧?
4. Structs
你们中的一些人可能已经对我大喊大叫,让我说到这个问题,但是在教过这个课题好几次之后,先看看其他的解决方案确实有帮助。
我们没有考虑的 "第四种方法 "其实很简单。
我们创建一个结构来保存我们需要的依赖关系。
我们将我们的处理程序作为方法添加到该结构中。
让我们来看看一个例子。
type CategoryHandler struct { |
这个解决方案有几个很大的好处。
- 我们所依赖的一切在构建时都是已知的,并且是类型安全的(与上下文不同)。
- 我们只有最少的额外模板(不像封装函数)。
- 保持可测试性(与globals不同)
- 允许我们将相关的处理程序 "分组group "为单元
第4条是我们还没有触及的一个问题,但是现在我们有了这个结构,我们就可以对有共同依赖关系的处理程序进行分组,或者从逻辑上将它们放在一起。
例如,我们可以在这里添加一个处理程序来添加一个新的类别。我们可以创建一个MetricsHandler,将所有与度量相关的端点组合在一起。你可以根据自己的意愿,对其进行细化或扩展(在大多数情况下,细化可能更好)。
banq注:这个方法也是一种依赖注入,不过是构造函数形式的依赖注入,静态上下文是一种类似JavaBeans的setterX方法注入依赖。静态上下文的注入见下面链接: