前后分离架构的探索之路

大约五年前,那时候我还是一个小小讲师(苹果 AATC 培训认证),完全不懂编程为何物的菜鸟,一个偶然的机会让我进入了公司的开发部门,任职什么呢?用户体验设计师,原因很操蛋——我以前干过广告设计,做过餐饮服务行业,因而我有两个优势:能聆听和揣摩客户的需求,然后能做一些图。

那时候很多像我们公司一样的中小 IT 企业(200人左右,组成成分主要是大大小小的项目团队)都有要做自主产品的诉求,这是市场决定的:出门找生意越来越难了。于是很多野路子出家的产品研发团队就这样诞生了……

说是产品研发团队,其实都只是一群习惯了听命于人去按照 RFP 实现功能的码农罢了,和其他项目组相比唯一的差别大概就是“尚有梦想的咸鱼”而已。所以研发过程中的种种幼稚和操蛋,你用脚趾头都能猜想得到。

一开始我就是把各位老大的设想整理成人人看得懂的需求,然后把它们串起来画成草图(mockup)再交给各种工程师去实现好了,这个角色类似于如今很时髦的“产品经理”。然而很快我发现大家老是加班,为什么呢?调 CSS 样式!

做惯了平面设计的我并不懂得把画出来的东西变成浏览器里的东西会有多麻烦。(今天,我在面试一些切图页面仔时,听他们大谈特谈像素级还原尚觉得好笑,但想想五年前的自己还是很有些羞愧的……然而更令我无语的是:到了如今初出茅庐的小前端们还把像素级还原的切页面当成是至高无上的本事,这件事情本身是不是很令人“沮丧”呢?)当写页面的同事一再告诉我我画的东西不实际之后,我憋不住了——我就不信我画的东西实现不出来!

抱着一口“怨气”,我义无反顾的踏上了 HTML+CSS 这条路,其中过程不用多讲,唯一的金玉良言只有一条:别看国内的教程,别信 w3school 之类的拼凑资源站。总之这事儿的结果是半年以后整个项目组几乎所有的页面都是我来写了。(今天,已然成为前端架构师的我,所有页面的自定义样式还是得我亲自写,我不怪任何人因为我知道在很多工程师内心里还是瞧不上写 HTML+CSS 的技术的,你们不愿意学我不勉强,我来。我还要感谢你们,为了能把饭喂到诸位的嘴里,我花费大量的时间学习 CSS 框架的开发,从而精通了整个生态链,从 pre-processing 一直到 post-processing)。

这个世界就是这样:一旦你专精了一项技能,你会很容易看出相关的技能在目前的水准如何。古人说:水涨船高。诚不欺我也。

所以成天耳濡目染 HTML+CSS 的我,经受着各种国外大神的视频+教程耳提面命的我,很快就明白了一件事:我做的这个叫前端开发,HTML+CSS 只是起了一个头,后面还有一座叫 JavaScript 的大山等着我。而我们做的前端开发还很嫩,活该你成天加班改样式,修 bug,因为你一开始就没走对路数。

幸好还不晚,不过这篇的主题是前后分离,所以我得按下快进按钮直奔主题而去。

JavaScript 对我来讲太难太难了,但是 jQuery 尚可,因为它有非常棒的 API 设计和兼容性处理,很适合我这样的菜鸟入门。那时候有一个叫 Jeffery Way 的家伙录制了一套 30 天学会 jQuery 的教程让我受益匪浅,我认为他讲得好,主要是两个原因:

1.他不主要讲各种 API 如何用,他从一开始就给我贯彻了一个重要的思想:学会看文档;

2.他主要讲如何分析一个功能的实现,如何组织以 jQuery 为核心的代码逻辑;

在这里先插一段旁述。各位能在工作中使用 Rails 的同行们,你们是无比幸运的!因为 Rails 已经把 View-Template 这一环节梳理的足够简单,哪怕完全不懂 Rails 的页面仔,你稍微提点提点,他也能很快学会如何把静态页面套进 Rails 的模版里去。

而我则是很不幸的,在那时我碰上了非常讨厌的 JSP!你们千万别笑,随便去问那些写页面出身的前端们对 JSP 是什么感受,绝对不会有好脸色的。对,我承认自己很菜。写静态页面我行,但转成 JSP 模版这件事在那时真的能把我难死!更要命的是,如果你把写好的页面交给后端工程师去套模版,最终的结果就是一塌糊涂!没错,他们根本不会细心周到的照顾你精心设计的每一个标签,他们会做出各种各样奇葩的事情来破坏原本完美的页面结构,逼迫你不停的修改样式和脚本来适应这些“补丁”。

更要命的是调试!原本写 HTML+CSS 一个轻量级编辑器就搞定了,但等他们转成 JSP 之后你再想去调试就没那么简单了。你需要:

1.运行环境,比如 Java+Tomcat,不懂吧?没事,学!

2.生态链,比如 Maven 或 Gradel,不懂吧?没事,学!

3.IDE,比如 eclipse 或 IDEA Intellij,不懂吧?没事,学!顺便一提,知道没接触过 Java 的人想跑起一个应用来有多难吗?我就是为此才爱上 Rails 的!

4.……

就这样,为了调试 HTML+CSS,你最终变成除了不会写 Java 代码外其他全都会的 Java 开发工程师。

你们这些从后端出身的家伙们能体会到前端页面仔们迈出这一步需要多大的勇气和毅力吗?

你们能想象他们之所以不得不学做这些,就是因为你们无法认真对待 HTML+CSS+JavaScript 吗?

为什么要在后端的环境下做前端的事情?其实就为了三个字:擦屁股!

你可以说我们这一群人都很菜,我也承认,可是你要知道:环境不是时时处处都可以给你各种选择的,有时候你唯一能做的选择就是改变自己。那么作为一个只懂 HTML+CSS+皮毛 JavaScript 的我,能做出什么?不知从何时开始,“如果可以不再依赖任何环境就可以做好我们的份内之事就好了”这样幼稚的念头开始萦绕在我的脑袋里……

回到 Jeffery 的视频教程,在其中的一节他演示了 Ajax 获取远程数据然后动态修改 DOM 的例子,当时的例子里用的是 Twitter 的 API,然后每隔一段时间拉取几条新数据让页面即时刷新这样子……

不要笑,知道我当时有多震惊吗?我觉得我们就他妈的是一群傻逼好吗?

第二天我慌不择路的把这段视频拿给后端架构师看,问他实现这样的东西,可行?他憋了半天:我们都是直接去数据渲染到 JSP 的,API 没做过……

操!没做过难道不能做?我赶紧抛出了诱饵:如果搞的出来,以后你们再也不用套模版了!

这货立马答应了……

此后就是翻天覆地的折腾,我搜遍了所有能找到的资料,把它们翻成中文或者直接当面讲给后端听,有些东西我们都无法理解就先记下来,晚上回去我上 SO 问,上 Youtube 搜会议等资料看。

然后他们告诉我,如果换 Spring 的话可能会比较简单,因为他们能百度到用 Spring 开发 API 的例子。于是我们就开始改造了。

改造的第一步是不用写 JSP(或者少量的写),但是静态资源其实还是放在 Tomcat 容器里的,因为我们经过尝试发现跨域问题解决不了(是的,当时就是菜,连反向代理都不懂),不过没关系,反正我已经学会了本地跑 Tomcat 了,至少我们可以不用写 JSP 了嘛。现在回想一下当初搞前后分离的原始动机竟然就是为了不再去写 JSP,多么滑稽啊!然而反过来想想,这也映衬了一个事实:前端工程师们的生态环境是有多糟糕!

再然后就是把 jQuery 修炼到满级开始无脑刷副本的无聊过程,当然在这个过程中也体验了一些新东西,比如前端的模版引擎(Jade/Handlebars/Art等),模块系统(SeaJS/RequireJS)等等,JavaScript 的水平和理解有了长足的进步,终于开始有一个工程师的样子了。

再之后就是大家都知道的剧本,node.js 横空出世,一下子 HTTP Server,API Service,Shell Scripting……等等这些统统都可以用 JavaScript 来搞了,npm bower grunt gulp……等等这些应运而生,忽然间前端开始有了自己的生态系统!尽管它还很弱小还很混乱,但是它给了我们这些野路子出身摸爬滚打浑身泥水的家伙们一道希望之光,它让我们看到:

1.我们可以不依赖后端的运行环境:node.js

2.我们可以有自己的生态圈:npm

3.我们可以随心所欲使用各种方便的开发工具:所以我后来成了 vim 党

4.……

我们可以有很多可能,我们可以把我们擅长的事情做得更棒而不需要后端哥哥们操心,我们可以省去很多后端要 cover 的工作让他们专心写好自己的代码,我们设想中的分离是有搞头的,不仅仅是为了分离而分离,而是为了更好的专精、多能、协作、管理而分离!

何以如此狭隘的看待前后分离?时至今日我也不懂为什么有那么多人抱持着种种怀疑与偏见。

你们不用担心数据层逻辑会有冗余,因为把 Model 的逻辑分摊到前端身上可以省去后端的部分代码和处理工作,而前端也可以更容易地按照业务来组合自己需要的 Model;

你们不用担心视图层的缓存,因为分离后前端只存在静态资源,我们可以利用 CDN,利用 负载均衡,利用很多很多技术分摊过去必须让后端来承担的工作;

你们不用担心页面渲染速度,首页怕慢我们可以交给服务端来渲染,或者在中间加一个很简单的 node server 来做首页静态化渲染,后面的事情交给前端就是,只快不慢;

你们不用担心要为多个客户端做不同的资源调度,只要 API 规划得到,一套 Service 可以支持多个客户端的业务体系,而前端行有余力甚至可以写出多个版本来做 A/B 测试;

你们不用担心 ……

优点多了去了。

不是说这些优点目前都很成熟,也不是说实现它们没有代价,但是你不去做就不可能成熟,代价也不是不可以有但关键是要看长远的收益。

很现实的例子就是我们有一套系统本来是为自己做的,后来让客户知道了觉得很感兴趣希望为自己定制一份。我们分析了一下,发现现有的 API 已经可以满足用户的需求,只需要针对几个具体的业务逻辑再扩充几个接口让数据负载更合理便可,于是我们只用了三天就给客户出了一个完全可用且相当稳定的 demo。客户觉得满意,开始按照他们的 VI 重新设计一套 UI,然后剩下的事就是找几个页面仔把页面写出来,现成的 Angular 逻辑往上一套小改几处即可。

你会觉得不值得?

当然了,我在这里不是鼓吹前后分离信仰,不是强求所有的事情都需要分离来做。一个产品的轨迹是需要产品研发团队自己把控的,如果人云亦云流行什么用什么那也就和无脑儿没啥区别了。有些场景也的确不需要分离,比如说门户网站,CMS,Mini Site 这类的需求就可以沿用成熟的开发体系。不过我之前谈到过,探索和实践分离体系还有一个重要的好处,就是能够让你现有的前端开发团队摸索和整理出一套单兵作战的环境体系,即便是不用分离架构,我单纯用 node.js 写一套门户网站,CMS,Mini Site 这样的东西也不会比 Rails 慢啊!这样一来,我还是可以把后端的资源用在更重要的底层服务或业务逻辑去,把那些和页面 UI 交互相关,但又和数据层有着小小关联的业务交给前端独立完成,又有什么不好呢?

前后分离=SPA?SPA=臃肿框架?

这一点我觉得有必要分析清楚,标题里的两个问号是我见到过最多的误解。

首先,前后分离是架构上的事情,第一次做肯定很痛苦,但做一遍之后好处还是很多的。举实例说明:

我们做过一个会议的应用,这个应用一开始设计是没有 web 端的前台的,只有一个管理后台,前台都是移动端。基于这个原因,我们还是分离的(因为你得提供 API 给移动端,不分离还能怎么搞呢?),后台用成熟的 Angular 很快就做好了。

没想到后来有一个额外的要求,用户要在创建会议的时候生成一套在线的会议手册,这个会议手册就是一个简单的多页面 CMS 系统,当用户创建新会议的时候在后台填写手册相关的内容,我们就要为它生成一系列的页面来显示(类似于 Mini Site),它有两个特定要求:

1.不需要登录,公开访问。然而最初的设计是没有账号就不能参加会议,需要报名,所以我们后台和移动 App 都是直接先要求登录或注册的,相应的 API 请求也是如此,有鉴权控制的。

2.要能多端访问,还要能嵌套在原生应用的 webview 里,因为加功能来不及了,只有一天时间。

传统的架构你的写页面然后套模版去调试,虽然只有不到10页,但也是很费时间的。但我们已经分离了,现在为了这么一个额外的需求也不值得再倒退回去,那么怎么做的呢?

1.单独建一个会议手册的项目;

2.模版用 Sass+Handlebars 很快搞定;

3.里面的接口请求为每一个会议服务商绑定一个 token(后来还在后台允许管理员重新生成和绑定 token),渲染页面时写死在 <meta> 标签里(就好像 CSRF 的处理),以此绕过鉴权

4.写一个简单的 node service,就干一件事:渲染handlebars模版

5.创建会议的时候,Java API 传会议 ID 给 node service,把渲染好后的页面单独保存在静态资源服务器下(用 ID 创建独立目录),然后返回调用地址

6.后台收到会议地址,嵌入一个 iframe 做手册预览

一天搞定,完事。值得一提的是,整个手册用了许多 HTML5 的新特性,比如 History API,SessionStorage,OfflineCache,GeoLocation,DesktopNotification,没有用 Polyfill,因为这些都是可选特性,不支持就不作用,关键是:从头到尾就是没用 jQuery——不是我跟 jQuery 过不去,的确用不着,还嫌大。更不要提 SPA 框架了,完全没有。

所以你看,分离架构可以让我们很快完成这样的小任务,并且可以单独维护管理,也可以直接共享现有的 API 资源,它不一定只是为了 SPA 才分离,而且也没有什么技术难度。能用很短的时间完成还能保证质量,是因为我们有成熟的构建和CI,如果换成是当初 JSP 那一套,光配置个本地环境就够够的了,其他我都不敢想象。

分离是架构选择,决定了你如何管理、分配与协调现有的资源,至于你分离后要做 SPA 还是其他模式的应用那完全是你的自由,并不是捆绑一加一的强制性决策。去构建一个分离体系当然会有挫折有代价,没有人否认这个,然而一)这是可选的;二)你能否看到和利用它的好处。

至于 SPA 一定是臃肿的吗?保持这种思想的我只能说你目光所见过浅。相比十年前的 web 开发,我能说现在 Rails 很臃肿吗?别说十年前了,就是今天一样也有人说 Rails too heavy!你觉得呢?那又怎样呢?还不是该用就用?水平高的自然知道拆分和减肥,连 Rails 自己都知道瘦身一个 Rails API 出来,你以为所谓“臃肿”是 SPA 框架的专利吗?SPA 之所以臃肿是有两个主要的现阶段环境因素决定的:

1.非常多的新特性层出不穷,为我们开发更丰富强大的应用程序提供了武器和弹药。但是浏览器(及其他运行环境)和设备碎片化的问题导致这些新特性无法提供始终一致的表现或性能,于是各种框架就要在底层做兼容性的补充与改良,顺便还要为尚未形成标准的新特性重新封装 API 接口。比如说 Ember 干嘛要造一个 Object 接口出来?不就是因为 Observable 接口没有吗?有什么大不了的?ES2016就有了(非常可能),或者你可以不用 Ember 自己的,用第三方的 Observable 组件来代替也行。

jQuery 做的事情和这有多大区别?没错,jQuery 是相对轻了,可是它负责的面儿也少啊,哪位用 jQuery 的不都得附带十个八个插件的?合在一起就轻了?

2.相对的,前端工程这块业界整体的水平差距很大,牛的特牛,菜的特菜;但是菜的也希望用牛的工具,可又没那个底蕴解决牛的能解决的问题,于是牛的就把一个一个特性统统封装好联系在一起,让你尽可能快速简单的就能用到。

如果大部分的工程师都成长起来了,也就没有必要非得搞大而全的方案了,React 及其生态体系不就是一个很好的例子吗?不给你搞大而全,只给你搞小而专,你以为你把那一堆连起来用就不叫 SPA 了?幼稚!

再说一遍,SPA 是一种产品的技术形态,而不是特定某(几)种框架下的产物,满足这种技术形态的工具链可以臃肿也可以简洁,这是因为环境和人决定的。

Single Page Application,not Some Particular Application

前后分离还有一方面的作用。前端工程师都有一个普遍的特点:你让他们写个页面信手拈来,但是你让他们负责一个完整的业务多半就得抓瞎。为什么?因为他们太偏门。最近一两年我开始大量的面试和储备新人,十有八九都是这样的:HTTP?不懂!Ajax?懂!(你觉得合理吗?)jQuery 请求 API?会!Promise 用过?……没。换个说法,deferred 对象?哦哦,见到过!(你觉得合理吗?)

诸如此类的问题屡见不鲜,让我对前端这个行业的未来充满忧虑。当初我也是从一窍不通的菜鸟开始,若那时没有“一定要摆脱 JSP”的幼稚理想,我怎么可能通过摸索前后分离让自己拥有今天这样相对全面的见识和理解?我走过的路让我明白,探索前后分离并不是像很多旁观者说的“为了分离而分离”,反而是“为了更好的理解 web 开发这回事而分离”。

因为当你开始摸索这条路,你就不得不面对许多根本性的问题,拿跨域资源共享来说吧,以前的架构前端工程师是极少需要面对这种问题的,但你只要一分离就必然会碰到,然后你就要去学诸如 JSONP,CORS,HTTP 协议,浏览器安全机制,PreFlight Request,反向代理等等技术细节。看似加重了学习成本(要我说,这些原本应该是学校的责任!),但作为同事,你希望你身边做的是个只会“追求像素级还原”的页面仔呢?还是对上述知识点有着扎实的理解和实践经验的工程师呢?

说到这,就昨天有人在 SF 上问了个问题,大致是问:JavaScript 怎样才算学好了?总觉得需要自己能写一个库或框架出来才算学好了,大家怎么看?

我刚好和人吵完了架,静下心想了想之后作出了如下回答:

少年苦练 10 年拳术欲下山扬名立万,路遇一使刀汉子,数招后不敌惨败而归……回山后找师傅问话“师傅,为何我苦练十年还会输?”“因为你不知道打架不止可以用拳头。”“可你也没告诉我啊!”“你只说要学拳法,又没说学打架!”“那我不学拳法了,我要学打架!”“那就不只是要学拳法了,打架想要赢就得十八般武艺都学,你未必要门门精通,但你最起码得有这些见识。除此之外,还得学挨打,学疗伤,学逃跑,学追踪,学暗器,学使毒……想赢?哪有那么简单的!”“那我还能成拳法宗师吗?”“呵呵,如果你打架再也不会输,谁敢说你不是宗师?”

这个寓言想表达的意思是不言而寓的,我很赞同这里一位朋友说的:我们不应该有前端后端之分,我们可以有专精之处,但是对于 web 开发这回事该懂的都应该要懂,否则你怎么可能打得赢?同理,如果说后端工程师需要靠写页面来了解前端的话,那么前端也应该有类似的方式来了解后端做的一些事情。在这里探索前后分离就是一个很好的教学与实践相结合的手段。没有哪个页面仔会甘于永远切图写页面,他们也很羡慕后端哥哥们大神般的风骚,只是他们所处的环境造成了他们只知道数十年如一日的就懂切页面了,如果能多给他们一些提携与帮助,谁敢说他们以后不会成为江湖高手?

很多人拿工作忙,缺人手,创业公司求效率等借口来回避在技术道路上的探索和进取,说真的我个人非常非常可以理解,我当初所做的事情其实和创业什么的也没多大区别,我们人手也很紧缺——今天我们只有三个人维护着四款前后分离架构的中大规模产品,这些产品有 Saas 版本的,还有大大小小十几个在客户那里独立部署的,你没看错就三个人!一个 Java 工程师,一个懂 Java 的前端工程师,再加上我这个什么都懂一点但什么都不专精的万金油。

我们做的还不够好,但我们已尽力做到自己能做的最好,与我们这五年来碰到的风风雨雨相比较,探索前后分离这真的不算个大事儿好吗?

作为前端工程师(并且是懂得和尊重后端开发的),我很欣慰能活跃在这个时代,就像有人说的:这是前端最好的时代,也是前端最坏的时代。然而历史无数次证明:真金不怕火炼,英雄应运而生。那些后端语言环境和框架体系难道没有经历过同样的革新与变迁?就因为我们过去是只会写 jQuery 的页面仔,所以我们就应该永远这样停滞不前?

这就是我探索前后分离的过程和心得感想,主要是在离职前为过去五年做一个总结。写得比较凌乱也没什么技术含量,根本的意思还是要鼓励众多的前端同行们:学校没有我们的专业课,社会对我们的工作没有准确的认知和评价,这都不要紧!重要的是我们自己不能看轻自己的能力,不能放弃自己的价值。在学习和工作尚有余力的时候勇于探索吧,别管别人说什么,本事学到手才是最重要的,要记住:你是一个工程师,你不是一个页面仔!

[ 全文完 ]

专业的开发者技术社区

提供多样化线上知识交流

丰富线下活动和给力工作机会