web前端新手入门教程:Web 框架的架构模式探讨

在写干货以前,我想先探(qiang)讨(diao)两个问题,模式的局限性?模式有什么用?javascript


最近看到一篇文章对我启发很大,许来西在知乎的回答《哲学和科学有什么关联?》,全篇较长,这里摘录我要引出的一点:前端

科学做为一种经验主义的认识论,有着经验主义的巨大缺陷:它永远不能产生绝对正确的真理。这是概括法的本质决定的。并且值得注意的是,概括不具备惟一性。java


举一个简单的例子,咱们假设一个世界,以下图:web

0e66c2f8750145728d1ea4da25d0c73e.png

科学家很快有了两种概括方式:算法


  • 世界上全部的青蛙都戴眼镜express

  • 世界上全部戴眼镜的都是青蛙编程


在没有更多的信息的时候,咱们应该如何选择正确的理论呢?答案是没法选择。c#

举个模式的例子,Scott Wlaschin 在《Functional Programming Design Patterns》(函数型编程模式)中对比了经常使用面向对象模式、原则,在函数型编程语言里面等价实现:后端

OOP 和 FP,到底哪一种编程范式更加先进呢?答案一样是没法选择。只能在不一样的时候选用不一样的假设和不一样的理论来解释问题,许来西的文章讲到科学必定程度上经过放弃一向性换取了实用性,放弃自洽性换取了它洽性。科学追求实用和工具(实用主义和工具主义)。当我看完许来西的文章,欣喜若狂,一直对编程技术理论的善变和不自洽感到恐惧和厌恶,其实只是经验主义科学发展的必然过程,善变表明更好的理论(更方便)在替换基础理论,表明蓬勃发展。设计模式

因此我想引入第一个观点:


  • 模式是一套立足于特定背景,基于共性总结出的方案,它毫不是真理。


了解这些有助于帮助从对模式的盲目崇拜到探究它的实用性和工具性,也就是我要引出的第二个问题:模式有什么用?

很差好写代码看哲学文章不是偶然,在文章落笔以前,我有思考过在 JavaScript 这门动态,多范式,单线程,基于事件I/O的语言环境下,甚至在当前时代,模式是否还有意义?显然我不是惟一这样想的,还有篇深度好文《20年前GoF提出的设计模式,对这个时代是否还有指导意义?》。这篇文章引经据典,摘录了GoF(又称Gang of Four,即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)在设计模式一书中观点:

这本书的实际价值也许还值得商榷。毕竟它并无提出任何史无前例的算法或者编程技术。它也没能给出任何严格的系统设计方法或者新的设计开发理论——它只是对现有设计成果的一种审视。你们固然能够将其视为一套不错的教程,但它显然没法为经验丰富的面向对象设计人员带来多少帮助。

换言之,模式显然毫无实际用处。

不只如此,文章还列举了一度模式滥用致使许多弊端,可谓警钟长鸣。

可是……模式这一称谓仍然不断出现,直到今天咱们亦在大量使用。为何?GoF实际早设计模式的书中作出了预言:

“设计模式为设计师们提供一种共通的词汇储备,帮助其沟通、编写文档并探索设计方案。设计模式容许咱们立足于高级抽象层面进行探讨,而非设计标注或者编程语言,这就大大下降了系统复杂性。设计模式提高了咱们设计及与同事进行设计探讨时的切入点层级。”(第389页)

简言之,模式方便了咱们的沟通,提高了思考问题的抽象层级。

这个意义很是巨大,想象一下没有 MVC 架构模式,可能全部的 Web 框架必然的会实现一套几乎解决一样问题的方案,可是命名和文档却各不同,当你去看一个新的框架文档的api 接口,从头至尾看完之后才恍然大悟,这不就是以前用的框架里面的 XXX 相似吗,这样的编程世界简直地狱。庆幸的是,得益于计算机科学家(码农)对问题和方案持续的抽象成模式,使得当前高度复杂的计算机科学也能获得合理分层和适配,大大简化了学习和沟通的成本。

为了感谢模式,是时候学习一波了,本文要介绍的主要有三种架构模式:Middleware,MVC,DI。

Middleware 中间件模式

相信作过 Node.js 服务端开发的同窗对这个模式必定不陌生,考虑以下 Web 应用的场景:

在一个简单的 HTTP 请求响应周期里,有以下条件处理,


  • 记录开始时间

  • 须要验证用户的身份 authentication。

  • 解析cookie 并加载body

  • 根据路由返回不一样的业务处理结果

  • 没有命中路由则返回404页面

  • 记录日志

  • 记录总共花费时间

  • 处理异常并显示页面(开发环境)


有些处理会根据是否成功决定是否继续后面的粗粒,有些处理会生成额外的数据,还有的要求拦截某些处理的开始和结束,最后异常处理和记录日志要求必定被执行。

通常的解决方法是用嵌套条件判断结合 try catch finally return 等控制语句,可是这样的方案会致使代码碎片化和复制粘贴的编码风格,由于控制流和逻辑耦合到了一块儿。理想的方案应当以下:


  • 中心化控制流

  • 解耦处理模块(重用性)

  • 声明式、可配置的服务(配置和代码无关)


这些场景由来已久,好久之前J2EE总结了 Intercepting Filter 模式,有兴趣你们能够看看这篇文(lun)章(wen),里面由浅入深提到三种方案,其中最初级的方案代码以下:

a61b5e517a1541238caa09b34e611120.png

这个和 express 和 Koa 的中间件模式极其类似,可是由于静态语言自己一些特征,致使最后造成的企业级代码极其繁琐,而且有许多局限性。最主要的问题是处理模块之间难以重用和共享数据,由于 ServletRequest ServletResponse 没法动态添加属性。以致于 JavaEE 把这个模式的适用性加了许多限制,包括和核心处理逻辑分开。

在动态语言的世界里面,咱们能够很方便的往 req 和 res 里面添加数据(基于约定),由于没有了不少 OOP 世界里面的”束缚“,Node.js 的实现一般更加优雅和通用。

Express 中间件模式

express 实现现在普遍接受的 Middleware 中间件模式。中间件的意思是在 请求 和 响应 中间执行的函数(为了区分另外一个中间件),签名以下:

41d7f4cbd384487aa1a21ad0fbd50684.png

这个模式包含了一套声明式的路由规则,和 middleware 函数上的 next 签名,它们共同构成了整个中间件模式的控制流,如图:

这个模式的核心构成不是权限,解析等中间件逻辑,而是路由判断,next和中断响应(验证失败、解析失败),其做为中间件执行控制,解耦了具体的处理逻辑,使得更容易写出通用的细粒度的中间件。express 内置的强大的声明式路由,而且路由和 middleware 分离能够说是它最成功的设计之一。

然而在一些稍微复杂点的业务中,好比一个网站有管理端和用户端,两个端至关于独立的app。express 4.0 提供了一个很是强大的功能 Router。Router 拓展了链式决策变成树形决策,可让 express 更好的支持大型项目。


ecc2de6586a64c869574127c73a77c31.png

Koa 异步中间件模型

Koa 的异步中间件模式-洋葱模型,相比 Express,其中间件函数返回 Promise,支持 async/await,而且能够轻松实现前置和后置的处理。毫无疑问这个模式更加先进,一些在 express 里面很差实现的拦截处理逻辑,好比异常处理和统计时间,在 Koa 里用一个中间件就能搞定。然而遗憾的是 Koa 自己只提供了 Http 模块和洋葱模型的最小封装。

将来我看好 Koa,其实 express 也意识到这点,他们计划在 5.0 版本里添加 Promise 的支持,然而做为一个老牌和完整生态的框架,要克服的困难远不是技术层面上看似的简单,直到目前仍然没有看到 5.x 宣布支持 Promise, 让咱们拭目以待。

MVC 模式

MVC 模式也须要介绍吗,咱们每天都在聊 MVC,无论前、后端框架,说一句 MVC,对一下眼神,基本肯定对方懂你了。

事实是,前端框架已经不适合用 MVC 讨论,这个模式从1979年提出以来,做为万精油模式,在各个框架和场景中被套用,背负了太多的历史包袱,你们能够看 winter 的文章 谈谈UI架构设计的演化。拨乱反正我以为有但愿,讨论前端框架你们之后统称 MV 模式就行了,就是模型和视图分离。

咱们今天要讲的 MVC 模式是指在服务器上(后端) MVC 模式,它的定义经受了时间和实践的检验,在许多企业级 Web 框架的实现中高度一致。先列举场景:

若是你的网站只有几个简单的页面,全部逻辑都写在 Controller 里面,是没有问题的。随后网站迅速的增加,你发现,

  • 许多页面里面的视图是一致的,可是背后的数据模型不一致。好比:网站上几乎没有一个视图或者组件是独一无二的,表格,下拉框等。

  • 许多页面里面的数据模型是同样的,可是展示的视图不一致。好比:同时支持PC和移动端,国际化本地化。

咱们作一个数学模型模拟极端状况,你们很容易能看到问题

假设左边是咱们的系统最终的样子,它恰好能够表示成 M(模型)和 V(视图)的内积,咱们更倾向于右边的表达,由于它更简洁并且没有重复。这里的内积操做你们就能够理解成控制器,实际上不会如此巧合,可是分离模型和视图帮助咱们提升代码复用,下降设计复杂度的好处是很显然的,一个更通用的表达

模型视图和控制器之间都是单向连接,因此整个系统的行为很是可控且容易测试,单独把路由分开是想强调 Router 和 Controller 是两个概念,Router 只是一个触发器(或者提供了一种映射关系),在写测试的时候,咱们也能够跳开 Router 单独调用 Controller。

看到上面的两种模式,是否是已经开始想,那有没有一个框架同时是 Koa + Router + MVC 呢,推荐你们一个很是好用的企业级 Web 框架 ThinkJS 3.0,最新版的 ThinkJS 集成了大量最佳实践和完善的文档,无论是学习或者企业级开发都很是推荐。并且 ThinkJS 一样实现了接下来要讲的模式。

DI 依赖注入模式

仍是先说场景,假如服务端须要实现session,前期考虑到成本和用户量,单台服务器存到文件就够用了。后期若是用户量大的时候,须要横向扩展(Scale-out),就把 session 实现基于中心化的 Redis 服务。

咱们系统设计目标是:

  • 不须要修改业务逻辑代码实现替换

  • 不须要关注服务的建立和生命周期

解决这类系统扩展性问题有一个很是著名的设计原则 控制反转(IoC Inversion of control),而 依赖注入(DI dependency injection) 就是其中的一个实现模式。

DI 的基本思路是这样,首先咱们的代码不能依赖具体的服务,须要总结概括出一套抽象接口,业务实现依赖接口,而服务实现接口,最后经过框架专门负责建立和提供接口的实例。

这里的 IoC 容器或者说 Ioc 框架,会在启动的时候读取配置文件,并在运行的时候根据须要建立实例提供给使用者,在静态语言如 java,c# 须要用到反射等高级语法,而 JavaScript 自己是动态的,接口基于约定,而且使用的方式也更加灵活。好比 ThinkJS 3.0 里面的 extend 和adapter 就能够理解成接口和实现,如图:

那之因此称为 extend,是由于框架会直接把接口注入(mixin)到 controller 或者 think 对象中。这样的好处是使用起来更方便,缺点是不一样 extend 须要约定好不能重名。


最后

本文介绍的三个架构模式,你会发现几乎在全部的Web框架实现都大同小异,这就是模式的好处。模式的意义相似于 IoC,我关注抽象和接口,抹平了具体语言特性下的细节问题,帮助咱们更好的学习,沟通和思考。

做者:蔡斯杰

https://75team.com/post/web-architecture-patterns-javascript

相关文章
相关标签/搜索