写这个题目估计会招人骂。
这三个著名的 MVC(或者 MTV) framework,分别对应 Ruby,Python,Elixir 三种语言。说他们是这几门语言的顶梁柱毫不为过。很多人都是慕着 framework 的名而来,进而学了语言。典型的就是曾经大红大紫(现在也算是一线明星)的 rails:很多 rails 工程师最初只知 rails,写了 rails 后发现语言的短板才反过来学的 Ruby。Phoenix 和 Elixir 大抵也是如此。
在 django / phoenix 上能看得出 rails 的很多影子。rails 在 flickr / delicious 时代是工程师追捧的明星框架。其后有了很多其他语言的跟风者或者学习者,django 不算是第一个, phoenix 也不是最后一个。通过这些框架,工程师可以快速地创建一个 web 项目的脚手架,和数据库(一般是 RDBMS)绑定,生成 model,controller 和 view,不消数日,一个可以运行部署的「网站」就攒出来了。
开发者的效率高么?很高。代码的效率高么?rails / django 虽让人诟病,但 phoenix 很高,在 benchmark 中狂胜各大 framework。
架构优秀么?似乎也很优秀 —— 如果让你我从头写一套 web framework,决计赶不上它们的水平。
那它们错在哪里?
它们错在给 web app 开发者带来「人人都能写 web app」的希望的同时,又把诸多程序员的思维禁锢在那一方小小的 MVC 中。
假设我们要做一个 MOOC 软件。用户可以浏览课程,可以注册课程,收藏课程,在上课的过程中可以为课程评分,记笔记,并和别人互动,等等。
我们看通常情况下一个 rails 程序员如何开始构建其后端:
顺着 framework 的思路,我们不知不觉地做了一些假设:
有了这些假设,我们能够很快地搭建出应用程序,却付出了高耦合度的代价。
有同学疑惑了,MVC 设计模式的初衷不就是解耦么?为什么反倒耦合度变高了呢?经典的 MVC 分层设计是一种纵向的解耦,数据有序流动,各层只管自己的工作,「上帝的归上帝,凯撒的归凯撒」,不必关心其他层次如何实现。然而它并不能避免横向的耦合,比如 model 和 model 的耦合,controller 和多个 model 的耦合。而 web framework 却有意无意地在倡导这种耦合。更令人发指的是,它还将这种耦合做进了数据层面,使得日后无论是从代码层面解耦,还是数据层面解耦,都困难重重。
在 rails 出现以前,我们知道写代码还有一个 business logic layer —— 业务层。在 rails 出现之后,在大家的实践当中,业务层被莫名并入 model 层,有些功能还去了 controller,就此消失。然而,业务层被这样揉进了一个 web framework 中,是不是哪里不太对劲?
rails 们代表的 web 层并不是业务的全部。如果哪天我们要向第三方提供 API 呢?如果 web 的逻辑被大刀阔斧地改变怎么办?如果突然哪天公司被收购,用户账号整合到对方系统里,自己并不保留一个所谓的用户表怎么办?
回到我们的 MOOC 软件的例子里。课程的管理,排期,注册等等,都是业务层的事情。一个用户注册一门课程,在业务层,应该表述成为:{:enroll, uid, cid} -> true/false,而非 controller 和 model 里那些繁杂的逻辑。而展示一个用户订阅的所有课程,应该表述为:{:show, uid} -> [a list of courses]。
所有这些,和 model 无关。User model 甚至不该看见 Content model,也看不见作为连接表的 enroll 表。
这是横向的解耦。大家都是一个个黑盒的服务,user service 负责用户的个人信息的维护和展示,auth service 负责验证身份,content service 负责管理课程内容,content enroll service 处理 enroll 相关的事宜,等等。如下图:
我们甚至还可以将这些服务按照属性分成不同的部分,有些是核心服务,有些是社交服务,有些是交流服务。这些服务都有各自明确的接口,比如 auth 服务提供:
auth service 存储的数据只是用户/密码相关的信息,这信息只有 auth 服务自己知道,连 user service 都没有访问的权限。
起初,这种解耦会带来很多工作量,但随着系统的发展,你会发现,这样设计会为系统的扩展和可重用带来很多的好处。添加新的服务并不会影响已有的服务,我们甚至可以撰写一个已有服务的全新升级替代版,把部分流量导入新的服务,测试良好后把旧服务直接删掉。
这样做的另一个好处是重归以业务为中心的正道。说句不太好听的话,rails 等 framework 很容易引导人们走向一个 web 前端为中心的歧路。这里所说的「前端」,是指后端的前端。我们应该根据需求,先把业务模型构建出来,各个服务构建妥当后,再使用 rails 等打造前端。我们可能需要一个面向用户的前端,可能还要面向管理员的前端,每个独立的服务可能也需要它们各自的管理前端,我们还要有统计分析的前端,用户行为分析的前端等等。这些所有的前端基本都没有所谓的 model,因为数据的存储在各个服务中解决了。
如此这般,我们打破了上述的假设,数据变得弱耦合,每个服务有各自独立的数据,它们只是在需要的时候被组装起来。
至于这样一个个服务嘛,你管它叫 micro service 也好,叫 application 也好,只要它们足够独立,能够随需而动就好。