依靠注入(DI)是一种解耦组件之间依靠关系的设计形式。在需求的时分,分歧组件之间可以经过一个一致的界面获得其它组件中的工具和形态。Go言语的接口设计,防止了非常多需求运用第三方依靠注入框架的状况(比方Java,等等)。我们的注入计划只供给十分少的相似Dager或Guice中的注入计划,而专注于尽可能防止手动去设置装备摆设工具和组件之间的依靠关系。由于,我们以为假如在Go代码库中,注入可以愈加轻易了解,就基本没有需要那样。
在Go中完成注入只需求这几个容易的步调:
先从一个一致的、高尚的目的开端,我们需求一些如Mongo、Memcache等办事的全局衔接工具。大致是如许的:
var MongoService mongo.Service func InitMongoService(url string) { MongoService = ... } func GetApp(id uint64) *App { a := new(App) MongoService.Session().Find(..).One(a) return a }
凡是 main() 函数会挪用设置装备摆设在flags或configuration文件中如 InitMongoService 如许的各类初始化函数。这时候,像 GetApp 如许的函数就能够运用这些办事和衔接了。固然,有时分我们会遗忘初始化全局变量,被 nil 激发panic。
固然在创立全局变量的时分同享资本让它们(最少)有两个缺陷:
起首,由于组件的依靠关系不明白,所以代码是很难写的;
其次,你很难去测试你写的代码,在并行前提下更是简直不成能。
虽然测试长短常快的(我们盼望确保不断很快),可是可以在并行情况下测试步崆最主要的。运用全局衔接工具时,后台办事没法在并发前提下测试出类似的数据。
为了肃清全局变量,我们先从一个通用形式开端。我们的组件如今显示依靠,我们将,一个Mongo办事,或许一个缓存办事。大致来说,我们上面阿谁老练的例子如今看起来该当是如许的:
type AppLoader struct { MongoService mongo.Service } func (l *AppLoader) Get(id uint64) *App { a := new(App) l.MongoService.Session().Find(..).One(a) return a }
很多援用全局变量的函数如今酿成了却构体中存储了它们的依靠。
真棒!在main()办法中,我们用一系列的结构替代了全局变量和函数,处理了我们之前碰到的问题。可是... 一看main()函数就晓得了,太芜杂无章了。
一开端就这么乱了:
func main() { mongoURL := flag.String(...) mongoService := mongo.NewService(mongoURL) cacheService := cache.NewService(...) appLoader := &AppLoader{ MongoService: mongoService, } handlerOne := &HandlerOne{ AppLoader: appLoader, } handlerTwo := &HandlerTwo{ AppLoader: appLoader, CacheService: cacheService, } rootHandler := &RootHandler{ HandlerOne: handlerOne, HandlerTwo: handlerTwo, } ... }
假如不断如许写下去,main()函数的办法体将会被被大量的代码盘踞。而这些代码仅仅只是做了两件很通俗的工作:分派内存空间、拆卸工具和组件关系。假如我们有十分多的二进制代码和库需求援用,我们就需求一遍又一遍的写这些无聊的代码。这里特殊需求留意的是,不要被nil激发panic。比方我们遗忘把CacheService通报给HandlerTwo,然后就激发了一个运转时panic。我们试图结构一个办法,可是却变得有点失控。还需求写一大堆的代码手动反省nil。由于必需手动拆卸工具并确保运转正常,我们的开辟对此十分恼火。测试职员乃至还需求本人拆卸工具、构建关系,明显他们不会在main()函数中共用这些代码。所以测试代码也变得愈来愈冗杂、冗余,却仍是常常找不出实践问题。简而言之,我们处理了一个问题,却发生了另外一种问题。
我们中的一些人对DI细叱比较有经历,而且我们都不以为这仅仅是纯文娱性的经历。因而,当我们第一次会商用 DI系统处理这个新问题时,就曾经有大量的push back(我了解为经历储藏...妙手求解)。
依据这些规矩,当我们需求一些工具的时分,我们决议需求确保防止已知的庞杂性并制订了一些根本原则:
1. 没有代码生成。我们的开辟编译步调仅仅用 go install,我们不想引入额定的步调。与这条规矩相干的是无文件扫描,我们不想把项目酿成一个O(大量文件)系统,同时也要避免增加编译工夫。
2. 没有子图。子图的观点是以每一个恳求为基准(a per-request basis)答应注入发作,容易来讲,一个子图必需可以完全地域分"global"性命周期和"per-request"(每一个恳求)性命周期的工具,而且确保在一切恳求中不混杂这些"per-request"工具。我们决议仅仅答应"global"性命周期工具的注入,由于这恰是我们如今面对的问题。
3. 防止代码履行。DI实质上使代码很难了解,我们想防止定制化的代码履行/钩子,使它更轻易了解。
依据这些原则,我们的目的变得比拟明晰了:
1. 注入应当分派工具。
2. 注入应当将工具图衔接起来。
3. 注入应当在顺序启动时仅仅运转一次。
我们也会商了supporting constructor(支撑结构函数)功用,但如今防止对他们增加支撑。
注入库是这项任务的效果和我们的处理计划。它运用构造标签(struct tags)来完成注入功用,可为详细的类型注入,也支撑对接口类型注入,只需明白接口类型的详细类型,它另有些不太经常使用的功用,比方按称号注入(named injection)。后面的容易示例如今看起来是如许:type AppLoader struct { MongoService mongo.Service `inject:""` } func (l *AppLoader) Get(id uint64) *App { a := new(App) l.MongoService.Session().Find(..).One(a) return a }
没有任何改动,除在 MongoService 字段上增加了注入标签。有几种分歧的方法运用注入标签,但这是最多见用法,它简练地标明了希冀注入一个 mongo.Service 实例。异样地,可以想象 HandlerOne,HandlerTwo 和 RootHandler 字段上也有注入标签。
我们的main()如今看起来如许:
func main() { mongoURL := flag.String(...) mongoService := mongo.NewService(mongoURL) cacheService := cache.NewService(...) var app RootHandler err := inject.Populate(mongoService, cacheService, &app) if err != nil { panic(err) } ... }
更短!注入的全部流程大约是如许:
1. 检查每一个曾经供给的实例,终极碰到RootHandler类型的app实例.
2. 检查RootHandler字段,寻觅带 inject 标签的*HandlerOne,发明没有*HandlerOne实例存在,因而就创立一个并将它赋值给这个字段.
3. 对方才创立的HandlerOne实例持续实行与步调2相似的查找,找到AppLoader字段,容易地创立它.
4. 关于AppLoader实例,它需求一个mongo.Service实例,它发明当我们挪用Populate时曾经创立过一个实例,因而它将阿谁实例赋值到这里.
5. 当它对HandlerTwo实行异样的查找时,它运用曾经创立的AppLoader实例,因而这两个Handlers同享这个AppLoader实例.
注入分派工具并为我们将graph衔接起来。挪用Populate后,注入不再做任何工作,剩下的跟之前没有注入时的行动都一样了.
我们的main()函数更容易管控了。如今,手动新建一个唯一两个case的实例:假如实例需求在main中失掉设置装备摆设信息,或许假如其需求恳求一个接口类型。即便如斯,我们常常新建一些不完好的实例,让依靠注入为我们弥补完好。测试代码大幅度的精简,而且如今可以在不需求晓得工具图表的状况下为测试供给履行。这使得测试更具弹性,可以改动相当大。重构异样变得容易起来,就像抽出逻辑而不需求手动调剂我们在各种main()中新建的工具图表。
整体来讲,我们对后果和自从引见了依靠注入,我们的代码库的演变觉得十分快乐。
你可以在Github上找到该库的资本:
https://github.com/facebookgo/inject
我们同时供给文档,虽然最好的进修方法是实践“玩”一下:
https://godoc.org/github.com/facebookgo/inject
我们十分喜欢能失掉奉献,所以在奉献时请确保下面的测试可以经过:
https://travis-ci.org/facebookgo/inject
本文中的一切译文仅用于进修和交换目标,转载请务必注明文章译者、出处、和本文链接。 2KB翻译任务按照 CC 协定,假如我们的任务有进犯到您的权益,请实时联络我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务