依赖注入,在 English 里缩写是
DI
,感兴趣但还不太了解这个概念的大哥可以去 Dependency Injection
开头先来一句:M$ 大法好,TypeScript真香!
前言
前些日子一直在抽空搞一个自己的TypeScript相关项目,大体是一个base在依赖注入(DI,后面就缩写了)平面之下的node服务端框架。写着写着,觉得很有必要把 DI 平面从框架里面剥离出来,改善一下扩展性,沉淀成一个通用的解决方案。在这里呢,我打算整理一下之前所写的东西,也大致讲讲怎样一步一步地搭建出一个简单的 DI 系统。
先列个提纲比较有利于最后烂尾:)
- DI 相关的技术概念
- DI 可以用来解决什么场景的问题
- 一个最小化功能的 DI 库应该拥有哪些功能
- 有哪些值得注意的点
- 那我们写一个?
- 后面的还没有想好。。。
DI 相关的技术概念
概念就不啰嗦了,直接来一发「 Dependency Injection 」维基百科解决。
我曾经接触过几款好用的 DI 框架,比如伟大的信仰「ASP.NET Core」,有近几年在前端引领ts潮流的「Angular」,也包括现在在node领域里也非常火的「nest」。我甚至在php中(我真的非常讨厌写php)也见过 DI 框架,只是对于php个人了解的太少,也就不拿出来细说了。
说来惭愧,我还没有看过其中任何一个的源代码,任何一个!我太懒了?? !!!
我曾经用过 C# 的「Simple Injector」,大致和「ASP.NET Core」官方的理念相似,我也在JS中见过 DI,比如最早的「angularjs」,相比静态语言强大的 DI 框架,JS 里面的 DI 虽然相对蹩脚,但那也的确算得上是实用至上了。
按照我自己的理解,只要是类C/S模型,在设计上秉承着静态语义、接口分离、开放封闭、供应消费、定义先行这些理念,DI基本可以认为总是一个首选的解决方案。DI 能够带来API层面更大的稳定性,不仅可以让我们更专注于业务的编排,解开领域之间的强耦合,还能优化逻辑的拆分隔离,为系统提供更强的健壮性。
相比较前端而言,后端老板们已经玩了 DI 很多年了,虽然有些复杂的DI特性在大多数情况下显得是屠龙术了,但 DI 本身上面提到最精干的那些能力,足以经受住时间和实践的考验。
而从另一个方面来讲,近年来TypeScript火起来其实也并不是一个偶然。JS 这门动态语言在经历岁月的磨洗之后,终于让广大的前端老板认识到 “不自由毋宁死,一自由就堕落” 这句真谛。稳定还是需要静态语法的强暴才可以保障,而 DI 则是可以突破静态语言缺陷的超级武器。
DI 到底要用来解决什么场景的问题
这个问题详细展开之前,不如来用一个例子来描述一下现实的痛点。
就拿用的很多的koa来讲吧(express的回调语法太蛋疼了,不用- -),koa2 是一个很优秀也很轻巧的node服务端框架,包了一些简单的能力出来,以适应最小化的服务器模型。
但 koa 终究还是过于轻量了,或者说并不存在那种绝对完备的node框架,可以适应一切业务的需求,大多数时候我们都需要对他进行定制。所以我们可能会对 koa2 进行基于 es6 class 语法的重新组装,构造出诸如controller、service的典型MVC概念。koa 提供了一个 context 对象来承载单词请求的所有信息,为了方便我们就在ctx(context,上下文)上扩展我们所要的能力,并把他送往请求的任意一个角落。
到目前为止这并不会存在问题。
接下来我们会面临一些能力共用的场景:假如有个基础能力,简单到诸如存储和读取当前session的用户信息,这个能力真的非常基础和简单,但可能用到的地方却又️相当广泛。在service里我们可能需要直接读取登录态来发动请求或者数据落库,在controller我们需要用户信息进行一些简单的逻辑组合,在中间件里我们可能需要针对用户信息进行权限处理。有一些基础而通用的能力,我们总是在很多地方重复的使用,这并不适合挂载在单继承模式的controller或者service的继承链路里面,因为存在一个很简单的矛盾:不同继承分支中的能力并不能够共用。
这些暂且都可以放在一遍,业务不大,这些问题并不是什么痛点。
然后业务扩大了,能力共用的需求迫在眉睫,框架继承是一种相比分包引用更佳稳妥的思路(虽然扩展和定制能力相对较弱)来适应业务的普适性。于是经验丰富的老板开始围绕koa设计出了一套框架继承的做法,每一层框架会继承前一层框架所有的配置、插件、ctx扩展和基础服务组件等等,相当牛b。这是合理的,一个公司可能会抽象一些通用逻辑作为基础框架,就省去了各个业务的大量重复开发成本。
但这样的处理,会加剧之前面临的问题:每一个仓库提供出来一个继承了原始框架基础service和controller的新base,事实上已经将后续的能力彻底的隔离开来了,未来基于这个框架的业务都会自己继承base扩展进去新的能力,并按照自己的继承链路继续下去,有些能力在业务中本来可以共享,但他们却在之前一步分道扬镳,进入了两条独立的继承链路中去了。形象的描述就是:一条单分叉的河(一单分叉就不会再汇合了)在一个地方分叉(分成支流controller和支流service)了,他的两条支流未来就不会再有交集了,包括一些通用的逻辑(比如获取session,两边必须有一份完全相同的逻辑),都不能在想互共享了,你只能写两份。
那不还有context吗?
对,context可以很好的解决这种困境。只要service和controller都拥有一个context,那挂载context伤的属性和行数就都可以在service和controller中共用了。这也正是koa的做法。koa 的 context 是一个巨大的对象,大到几乎整个业务的核心要素都在里面了。各级框架通过不断定制context,service和controller或者中间件通过修改context 的数据,就可以简单地做到数据共享。prefect!
事情似乎完美的解决了?
然后有一天,我们的老板一声令下,整个架构都应该准备切到TypeScript来了。
糟糕,这下问题可就大了。。。。
。。。 。。 。
太晚了,再不睡觉明天早上又要迟到了 ?? (待续