AOP和IoC在点我达前端的实践

本文同步发表在豆米的博客:豆米的博客

1、前言

如今的编程模型有很多种,常用的是面向过程编程(POP)、面向对象编程(OOP)。其实还有好几种编程模型:面向切面编程(AOP,也就是我们今天要讨论的主题)、响应式编程、函数式编程。每种编程模型都有其对应的应用场景,今天我们只讨论AOP(顺带捎上IoC),其他几种后面有时间可以拿出来学习。

2、AOP和IoC的介绍

2.1、AOP简述

AOP(Aspect Oriented Programming)主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。它是对传统OOP编程的一种补充。

OOP是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系;AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。

2.2、IoC简述

IoC(Inversion of control)控制反转,它不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

谈到IoC就不得不说DI(dependency injection)依赖注入,这个概念是大师级人物Martin Fowler在2004年提出的,只是为了更明确地描述IoC(IoC的另外一种实现方式叫做)。所以二者本质是一样的。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

2.3、 AOP想要解决的痛点

假设有这么一个计算器类(Typescript):

interface Calculator {
  add: (number num1, number num2) => number
  sub: (number num1, number num2) => number
  div: (number num1, number num2) => number
  mul: (number num1, number num2) => number
}

class Cal implements Calculator {
  constructor() {}
  add(number num1, number num2) {
    return num1 + num2
  }
  sub(number num1, number num2) {
    return num1 - num2
  }
  div(number num1, number num2) {
    return num1 / num2
  }
  mul(number num1, number num2) {
    return num1 * num2
  }
}

然后你想要在每一个计算方法中添加追踪日志,于是改造成:

interface Calculator {
  add: (number num1, number num2) => number
  sub: (number num1, number num2) => number
  div: (number num1, number num2) => number
  mul: (number num1, number num2) => number
}

class Cal implements Calculator {
  constructor() {}
  add(number num1, number num2) {
    console.log(`add method calling, params: num1[${num1}],num2[${num2}]`)
    const result = num1 + num2
    console.log(`add method ending, result: [${result}]`)
    return result
  }
  sub(number num1, number num2) {
    console.log(`sub method calling, params: num1[${num1}],num2[${num2}]`)
    const result = num1 - num2
    console.log(`sub method ending, result: [${result}]`)
    return result
  }
  div(number num1, number num2) {
    console.log(`div method calling, params: num1[${num1}],num2[${num2}]`)
    const result = num1 / num2
    console.log(`div method ending, result: [${result}]`)
    return result
  }
  mul(number num1, number num2) {
    console.log(`mul method calling, params: num1[${num1}],num2[${num2}]`)
    const result = num1 * num2
    console.log(`mul method ending, result: [${result}]`)
    return result
  }
}

然后你可能还需要添加参数校验,于是又有各种校验逻辑加入,并且这种非业务的需求还会不断增加,于是就会不断添加重复代码,对于一个开发人员来说,新做一个需求就会不断地copy-paste。然后哪一天你需要改动日志显示方式,很可能就需要改所有的地方,一旦某处漏改,就造成显示不一致。由此可见这种代码是很难维护的。

那么针对这些痛点,AOP提出了横切关注点(crosscutting concern)的概念,这些与业务无关但是属于系统范围的需求并且会横跨多个模块的功能称为横切关注点。使用AOP编程模型,我们可以简化出这样的一张图:

image

看这个图是不是很像expressjs的中间件的角色?

2.4、IoC想要解决的痛点

在实际网关开发中,我们会创建很多service,诸如redis、disconf、logger、cache之类的,然后我们这样使用:

比如:

在controller1中用到redis: import redis from '...'

在controller2用到cache: import cache from '...'

大家都觉得这种写法没有什么不好,很符合我们的编程思维。但是这种写法有一个很大的问题,那就是耦合性太高。如果某一天你需要扩展redis service的实现方式,,比如需要增加一个参数,告知redis service去连接一个性能更好的redis数据库,这个时候所有引用到redis服务的都需要更改代码。

举个更具体的例子(typescript):

class Finder {
  find: (...) => {...}
}
class Fridge {
  finder:  Finder
  constructor() {
    this.finder = new Finder()
  }
  getApple() {
    return this.finder.find('apple')
  }
}

在上面的例子中我们看到创建一个冰箱类,提供一个查找苹果的方法,但具体怎么查找是使用另外一个实例Finder的。这样看起来一切都是ok的。但是如我们刚才说的,如果现在我们想要增加一个参数,保证Finder类查找的东西肯定位于冷藏室呢?于是我们就需要改造类Finder,改造完类Finder还需要改造类Fridge,如果在系统中我们有很多类同时用到了这个Finder呢?那么就得改动很多地方,万一有漏改的呢?

结合上面的例子和实际应用,我们的IoC便是要处理这样的耦合。我们借助容器的概念,将类的创建和查找都放在容器中实现,Fridge类不用关心Finder类的创建,只需要向容器要求使用Finder类,其他的一概不care,这样就将实体类(concrete class)与抽象类解耦掉,改造之前如图所示:

image

改造后:

image

3、 AOP在点我达网关的应用

在点我达网关项目开发中,一条request完整的链路大致如下:

image

相信这个处理过程,在别的公司也都是成立的。所有的controller代码除了业务逻辑不一样外,剩余的全都是一个套路,都是一套重复代码。起初网关的第一个框架版本就是这样设计的,而引入AOP之后,我们得到的效果图是这样的:

image

相对比于上面的那张图,这个改造可以看出我们将所有无关业务的需求作为切面抽出去,不再跟业务逻辑耦合,统一一个地方去维护代码,让整个网关的可维护性大大提高。代码量的大量减少也让开发人员可以更加专注于需求的开发。另外给整套网关带来了很大的灵活性和扩展性。

4、IoC在点我达网关的应用

在点我达网关项目开发,我们借助InversifyJS来实现IoC容器,实现的框架如下:

image

其他层次我们不必关注,有兴趣可以私聊。在容器层中我们会在服务器启动的时候主动创建多个实例,这些实例由容器维护并查找,当我们在中间件中或者业务逻辑层中需要用到这些实例的时候,只需要这么使用即可:

@lazyInject(TYPE.Logger) private logger: winston.LoggerInstance

我们得controller类不会再去关注依赖类的初始化创建,而只管使用,就好比是你找女朋友,如果去婚介所的话,你是不用关心你想找的女孩子在哪里,而只需要到婚介所拿到女孩子的信息即可,而这个容器就类似于婚介所。

在这里就不过多地展开,有兴趣的可以参考inversifyJs/Wiki

最后

点我达前端在Nodejs网关的实践中积累了不少的经验,包括微服务化和异地多活改造,当然也有今天说的这些概念。我们在整套前端工程化体系中同样沉淀了一些符合大家需求的经验,包括组件库、前端框架封装、Nodejs网关框架设计、脚手架、网关运维监控、Mock服务器等等。后面有时间可以先分享一下微服务在点我达网关的实践。同时也欢迎大家探讨交流。

参考

  1. 依赖注入和控制反转
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容