简单地说,就是要把复杂的问题简单化,把一个从0到N的问题转化为N个0到1的问题。另一个相近的说法就是“解耦”。
举个例子,我们接到一个客户需求,要求写一个应用,这个应用中有页面的切换,有对应页面的数据交互,数据的获取,数据的计算,如果把这些功能放在一个单一的应用中的话,从大局观的角度来看,这个单一的应用是比较复杂的。
为了降低这个复杂度,我们首先要做的就是前后端分开,关于前后端的定义,大体来说是这样的:前端负责的内容主要有页面路径管理,页面对应数据的显示与管理等等;后端负责的内容主要有数据的提供,数据的计算,安全性管理等等;前后端的通信一般通过HTTP请求来实现;当然这里也有个例外情况,比如有一部分功能可能要求实时性,像类似聊天类的功能,共享文档的功能,画板分享的功能,多人协同操作的功能等等,需要通过socketio这样的通信机制进行。
再细分一下讲,单一的程序框架模型会是MVC(Model-View-Controller)结构的。前后端分离以后,后端会有Model/Entity-Repository/Service-Controller,前端会有View-Model-API调用。
看一下前端部分,View主要是指网页的页面,手机端的页面(Android就是Activity,Layout和View等, iOS就是View Controller和UIView等),如果使用跨平台技术(如React Native, Flutter,Xamarin等)也离不开上述的概念,大同小异。这部分的处理当然离不开数据Model和API调用。处理好这些以后前端的任务也就做好了。
再看一下后端部分,界面相关的处理已经不再是后端的任务了,后端只需要通过API提供前端需要的数据就可以了。Controller就是用来定义这些API的,这一部分会分析或者计算出前端的输入和输出模型,具体处理方式有以下几个因素考虑:请求的类型如GET,POST, PUT, DELTE,PATCH等;数据From Body,数据From Route,数据From Form等等;数据处理完成以后,一个是通过API返回,一个是更新数据源,这里的数据源可以是数据库如SQL和NoSQL,可以是消息中间件如Kafka,也可以是数据缓冲服务器如Redis等等。通过上述的描述,我们可以看到后端的任务变得更加轻量级了,逻辑上也更加简单了。
所以,通过前后端的分离,我们把前端和后端的复杂度始终维持在可控的范围内。如果在软件开发中始终使用这种理念的话,我们会大大扩展我们的软件开发效率和程序质量。因为解决一个N难度的问题显然要比解决N个1难度问题要困难得多。对于一个N难度的问题,我们将其分解为N个1难度的问题之后,我们可以各个击破,步步为营,一步一个脚印,在工作中从容而且自信,因为我们始终用最简单的方法去解决问题。如果碰上复杂的问题,就再将其分化为多个难度为1的问题,以此类推。简单一些讲,就是要保证我们在解决问题的时候,不打无准备之仗,始终脚踏实地,分而治之,避免把我们自己架到火炉上烤。
复杂度降低以后,整个项目的维护成本和扩展成本都会非常的低。我们对项目开发的驾驭能力也会提高很多。
再说一个数据流向的问题,这个主要发生在前端。 这部分的处理会直接关联到前端程序设计的复杂度。在三大前端框架中有双向数据流向和单向数据流向,其中React只支持单向数据流向。但是目前有了Hooks机制以后,你可以通过传Setter和Getter两个Reference来达到修改数据的目的。这种通过传递参数到其他Component的模式实际上增加了程序的复杂度,因为一层层的传递使得组件之间的耦合性增强了。
那么如何解决这个问题呢?
在Angular中可以通过Service中定义Getter和Setter来解决这个问题,每个需要操作这个数据的组件都可以依赖注入这个Service,有了这个Service,就可以很轻松的读取和更新其中的数据了,对于数据的监听,可以通过在Service定义一个Subject,监听到数据变化以后,你可以更新界面,向服务器发送请求等等,这样做,组件之间的耦合性就大大降低了。
再说一下单向数据流向机制如Ngrx, VueX, Mux等等。单向数据流向机制在目前的前端开发中使用很普遍,然而,实际上这种机制会增加程序的复杂度。这个主要是因为这种机制会在前端开发层级中引入另外一套体系来维护数据的流向,假如我们对已有的业务定义为从0到1的实现,加上这个机制以后,就变成了0到1再加上另外一个0到1,变成了0到2的问题。这就变复杂了。
前端只要做好如下的任务如页面的切换,数据与后端的交互,数据模型与API的对应, 组件尽量的写成Self-Contained就可以了。
所以我不建议在前端中再额外的添加类似的数据流向机制了。
再谈一下后端的分析。我们在谈后端的时候,我们不太关心使用什么技术(Node JS,.net Core,SpringBoot,PHP, Python, Ruby On Rails等等),这是因为后端的逻辑是清晰的,不管你用那种技术,程序的设计的层级是大同小异的。
后端的主要任务是API数据的提供。比如用户相关的API,创建一个用户,更新一个用户,删除一个用户等等的操作都是在一个用户Controller定义相关的API。对于Controller的添加,我建议要根据业务层来添加,不要在一个Controller中做太多的事情。比如我们可以添加Book Controller来处理Book相关的操作,订单Controller来处理订单相关的操作。
这样做是为了把API设计变成线性的,平行的,各自Controller之间的耦合性降到最低,从而也就降低了复杂度。
进一步说,在实现的层级上,我们可以有Data Set对应数据库表格,Entity对应数据表格记录,Model对应API数据模型,Service/Repository负责逻辑实现等等。
在后端技术选取中,数据库相关的操作是一个不可逾越的门槛,有的框架使用类似EntityFramework的机制如.NetCore和PHP Laravel, 有的使用类似JPA Hibernate,Mybatis如Spring Boot,有的则直接使用JDBC SQL语句执行的方式,有的结合Stored Procedure。在这个地方,按照程序复杂度排序的话,由低到高分别是EntityFrameWork<JPA Hibernate+Mybatis< JDBC SQL < Stored Procedure。
当然,数据库的设计,数据库的升级Migration也都是关系到程序项目复杂度的因素。这个要结合具体的后端技术来谈。在本文中我们就不展开了。
好了,本文我们主要探讨了前后端分离的作用,如何进行前后端分离,如何解耦,有不足之处,请不吝指正。