前后端分离不是什么新鲜事,前后端分离的做法比比皆是。然而,一些历史悠久的项目在从一体化网页设计转向前后端分离架构的过程中,仍然不可避免地会遇到各种问题。由于问题层出不穷,甚至有团队提出质疑,融合好了,为什么要把前后端分开?说到底,不是前后台分离不好,而是可能不适合,或者……设计思路还没有转变……一体化的Web架构说明前后台分离比为什么要前后分离更重要。真正的问题是什么时候需要前后端分离,也就是前后端分离的应用场景。说到这个问题,我想到了2011年左右,当时公司在.NET开发团队的基础上扩充了Java团队。虽然两个团队在做不同的产品,但是还是有很多重复开发,比如用ASP.NETWebPage写组织相关的页面,用JSP再写一遍。在这种情况下,团队开始思考这样的解决方案:如果前端实现与后端技术无关,那么页面呈现的部分可以共享,不同的后端技术只需要实现后端业务逻辑。解决方案要解决的根本问题是数据与页面的分离。满足这一要求的技术是现成的。前端采用静态网页相关技术,HTML+CSS+JavaScript,通过AJAX技术调用后端提供的业务接口。前后端协商的接口方法通过HTTP提供,统一使用POST动词。接口数据结构是使用XML实现的。前端jQuery解析XML很方便,后端处理XML的工具也多了。。。后来由于后端JSON库的兴起(如NewtonsoftJSON.NET,jackson,Gson,ETC。)。前端处理JSON也更容易(JSON.parse()和JSON.stringify()),数据结构换成了JSON实现。这种架构本质上就是SOA(Service-OrientedArchitecture)。当后端不提供页面,只是纯粹通过WebAPI提供数据和业务交互能力时,Web前端就变成了一个纯粹的客户端角色,与WinForm和移动端应用属于同一角色,它们可以结合,统称为前端。以前的集成架构需要自定义页面来实现Web应用,同时定义一套WebService/WSDL为WinForm和移动端提供服务。转换到新架构后,可以统一使用WebAPI的形式为各类前端提供服务。至于通过某种类型的前端对该WebAPI进行RPC包装,那就是另一回事了。通过这样的架构改造,实际上实现了前后端分离。抛开其他类型的前端不谈,这里只讨论Web前端和后端。由于分离,Web前端在开发的时候不需要知道后端使用什么技术。它只需要后端提供什么样的接口来做事情。什么C#/ASP.NET、Java/JEE、数据库……这些技术你都可以忽略。后端的.NET团队和Java团队也脱离了逻辑无关的审美思维。他们不需要面对细粒度的界面设计约束,也不需要在思考逻辑实现的同时考虑如何布局页面。处理自己擅长的逻辑和数据就好了。前后端分离后,两端的开发者就轻松多了。因为技术和业务更加专注,开发效率也提高了。分离带来的好处逐渐体现出来:前后端职责分离,前后端趋于呈现,关注与用户体验相关的问题;后端往往是业务逻辑、数据处理和持久化。在设计清晰的情况下,后端只需要负责以数据为中心的业务处理算法,并按照约定为前端提供API接口;而前端使用这些接口来负责用户体验。前后端技术分离前端不需要了解后端技术,也不关心后端用什么技术实现。您只需要了解HTML/CSS/JavaScript即可开始使用。后端只需要关心后端开发技术。除了省去学习前端技术的麻烦,就连Web框架的学习和研究也只需要关注WebAPI即可。而不是关注基于pageview的MVC技术(不是说不需要MVC,WebAPI的接口部分的数据结构呈现也是一个View),不需要考虑特别复杂数据组织和呈现。用户体验与业务处理解耦前端可以根据不同时期用户的体验需求快速修改,后端对此没有压力。同理,只要后端进行的业务逻辑升级和数据持久化方案变更不影响接口,前端不需要知道。当然,如果需求变了,接口变了,前后端就需要坐在一起同步信息。两端的设计后端可以单独缩减,只提供API服务,不考虑页面渲染的问题。实现SOA架构的API可以服务于各种前端,而不仅仅是Web前端,可以提供一套服务,供所有端使用。同时,对于前端,不依赖后端技术的前端部分可以独立部署,也可以应用Hybrid架构,嵌入各种“壳”(如Electron、Codorva等),快速实现多终端。前后分离架构的技术方案都不是灵丹妙药。前后分离,既带来好处,也带来矛盾。在我们实践的初期,由于前端团队的实力相对较弱,而且按照惯例,几乎所有的业务流程都是由后端(原技术骨干)来设计和定义的。在前端处理过程中,经常会发现接口定义不符合用户操作流程,AJAX异步请求过多。毕竟后端思维和前端思维还是有区别的——前端思维更倾向于用户体验,而后端思维更倾向于业务技术实现。此外,前后分离的安全要求也略有不同。由于前后台分离本质上是一种SOA架构,所以授权也需要按照SOA架构的方式来考虑。Cookie/Session方式虽然可以,但是不是特别适合。相对来说,基于Token的认证更合适。采用基于Token的认证意味着后端的认证部分需要重写...当然后端不想重写,所以会把球踢给前端让前端想办法实现Cookie/Session-basedauthentication...于是前端就开始抱怨(悲剧)...这些矛盾的出现到底是谁来主导的,归根结底还是设计不够清晰。毫无疑问,在开发过程中,领导者应该是架构师或设计师。但在实际场景中,架构师或设计师往往也是开发人员,因此他们的主要技术栈会极大地影响前后端在整个项目中的主次角色。骨干在哪里,发展的便利就向哪一端倾斜。这是一个不好的现象,但我们不得不面对这种情况。相信很多小团队也面临着类似的问题。如果没有很好的流程规范,通常前端暴露的角色会多于后端(大部分应用项目/产品,并非所有情况):前端开发人员会直接受到项目/产品经理或客户的影响:这个地方应该放一个按钮,那个操作应该是这样的。。。前端要跟美工对接——这种设计不好实现,能改成那样吗?客户要求做这个,但是这个设计做不到。前端也需要和后端对接,有些应用甚至有多个后端。也就是说,前端可以成为项目沟通的中心,比后端更适合担当主角。接口设计接口分为后端服务实现和前端调用两部分。技术都是成熟的技术,难度不大。界面设计是难点。前面说到,前后端之间会存在一些矛盾。从前端的角度来看,重点是用户体验,包括用户在业务运营过程中的流向和相关处理。从后端的角度来看,重点是数据的完整性、有效性和安全性。矛盾在于双方的关注点不同、信息不对称和自私自利。解决这些矛盾的重点是界面设计。在设计接口时,其粒度的大小往往代表着前后端工作负载的大小(不是绝对的,这与整体架构有关)。如果接口粒度太小,前端要处理的东西很多,尤其是各种异步处理,可能会觉得不堪重负。如果粒度太大,会出现高耦合,降低灵活性和可扩展性。当然,在这种情况下,后端工作不会轻松。业务层面的东西涉及到具体的产品,这里就不多说了。这里主要讨论一点技术性的东西。在形式上,WebAPI可以定义为REST或RPC,只要前后端协商确定即可。更重要的是,从一开始就有一个相对固定的输入参数和输出结果的定义,这往往取决于所采用的前端架构或UI框架。常见请求参数的数据形式如下:键值对,用于URL中的QueryString或POST等方法的Payload。XML/JSON/...,通常用于POST等方法的Payload,也可以使用multipart来传递。ROUTE,通过解析后端路由的URL得到,在RESTful中比较常用。服务器响应的数据形式多种多样。通常,一个完整的响应至少需要包含状态码、消息和数据三部分,其中:响应数据中的状态码、HTTP状态码或特定的状态属性。消息,通常放在响应主体中,作为数据的一部分。数据根据接口协议可以有多种格式,目前最流行的是JSON。我们在实践中使用的是JSON形式,最初定义了这样一种形式。代码主要用来引导前端进行一些特殊的操作。例如0表示API调用成功,非0表示调用失败。其中,1表示需要登录,2表示未获得授权……对于这个定义,前端收到响应后,可以在应用框架层做一些常规处理。例如code为1时,弹出登录窗口要求用户在当前页面登录,code为2时,弹出消息并附上链接,引导用户获取授权。前后端分离模型的封装Api调用参考:https://segmentfault.com/a/1190000012040777一开始这样做没有问题,直到前端框架切换到jQueryEasyUI。很多UI库,以EasyUI为例,都支持为组件配置数据URL。它会通过AJAX自动获??取数据,但是对数据结构有要求。如果仍然使用之前设计的响应结构,则需要为组件定义一个数据过滤器(filter)来处理响应结果。这样一来,为组件编写过滤器和声明过滤器的工作量就不小了。为了减少这部分工作量,我们决定改一下接口。新接口是一个可变结构。一般情况下,返回UI需要的数据结构。错误时,响应一个原结构的数据结构:对于新的响应数据结构,前端框架只需要判断是否有error属性,如果有,则检查其是否身份属性是指定的特殊值(例如特定的GUID)。然后使用它的代码和消息属性来处理错误。这个错误判断过程稍微复杂一些,但是可以由前端应用框架统一处理。如果使用RESTful接口,一些状态码可以用HTTP状态码代替。例如,401表示需要登录,403表示您未被授权,500表示程序处理过程中发生错误。当然,虽然HTTP状态码更适合RESTful风格,但非RESTful风格也可以使用HTTP状态码代替error.code。用户认证的认证方案有很多,比如Cookie/Session在某些环境下还是可行的,也可以使用token-based和OAuth或者JWT,甚至自己实现token-based的认证方式。基于Cookie/Session的认证方案采用传统的Cookie/Session认证方案并非不可行,但也有一定的局限性。如果前端和后端同源,比如页面发布在http://domain.name/,WebAPI发布在http://domain.name/api/.这样的话,原来Web一体化方案中使用的Cookie/Session方案可以直接迁移,没有任何压力。但是,如果上一个版本的源和API版本不同,这种方法处理起来会比较复杂。那么,在一般的前后端分离的开发方式中,无论是开发阶段还是发布阶段,不同来源的可能性占了很大的比重,所以认证方案通常采用无关的方案与饼干。基于OAuth的认证方案目前各大网站开放的接口都是SOA架构。如果把这些开放接口看成是服务提供者(服务器),把使用这些开放接口的应用看成是客户端,那么就可以有这样一种关系,对应于前后端分离:因此,开放接口中广泛使用的OAuth方案前后端分离是可行的,但是实现起来不是那么容易。尤其是在安全方面,由于前端是完全暴露的,相对于通常实现OAuth的环境(后端?服务器端),需要注意的是,认证不是使用注册的AppID和AppToken,而是使用用户名和密码。基于Token/JWT的认证方案虽然放在最前面,但也是目前最适合前后端分离的方案。基于token的认证方案已经讨论了很长时间,JWT相对成熟,得到了大多数人的认可。各种技术栈的JWT实现都可以从jwt.io上找到,应用起来更方便。话虽如此,JWT方案在处理上和之前使用的Cookie/Session还是有很大区别的,需要一定的学习成本。有人担心JWT的数据量太大。这确实是个问题,但是硬件不贵,而且4G已经开始进入不限流量阶段,一般应用不需要太在意这个问题。前后端测试分离后,前端测试主要以用户体验测试和集成测试为主,后端测试主要以单元测试和WebAPI接口测试为主。与集成的Web应用相比,多了一层接口测试,可以完全自动化。测试开发完成后,可以在很大程度上控制业务处理和数据错误。这样,集成测试的工作量会相对单一,也容易很多。前端测试的工作相对减少不了多少,前后端分离后的前端部分承担了原来的集成测试工作。但是,如果在WebAPI无误的前提下进行集成测试,工作量可以减少很多,用例也可以只关注前端体验问题。比如呈现是否正确,跳转是否正确,用户的操作步骤是否符合要求,提示信息是否准确等等。在项目时间紧的情况下,用户输入有效性的验证甚至可以完全交给WebAPI。不管前后端是否分离,Web开发有一个共识:永远不要相信前端!由于后端要保证数据的安全性和有效性,前端省略这一步不会对后端造成实质性的威胁,最多只是一种糟糕的用户体验。但是如果前后端要做数据校验,一定要严格按照文档来做,否则很容易出现前后端数据校验不一致的情况(这个不是一个前后台分离的问题,在集成架构中也存在同样的问题)。总结总的来说,前后台分离带来的好处还是很明显的,但是具体的实现需要一种新的思维方式,而不是基于原有的集成Web开发方式去思考。前后端分离的开放方式,将开发者从复杂的技术组合中解放出来,每个人都可以专注于自己擅长的领域进行开发。但同时也对前后端团队的沟通提出了更高的要求。前后端团队必须共同设计一个相对稳定的WebAPI接口(无论前后端是否分离,这部分工作其实是必不可少的,但是前后端separation架构对此有更高的要求,更明确的要求接口不仅存在于人们的记忆中,还需要文档化和持久化)。
