前后端分离

前后端分离的问题,不仅仅是技术上的选型问题,还涉及到整个团队在认知、职责、流程上面重新定义的问题,这也是为什么前后端分离概念看起来简单易懂,但真正团队在落地的时候却有诸多水土不服。笔者认为前后端分离最早可以追溯到 Roy Thomas Fielding 在博士论文 Architectural Styles and the Design of Network-based Software Architectures架构风格与基于网络的软件架构设计)中提出的所谓表述性状态转移(Representational State Transfer, REST )架构风格;该论文描述了指导 REST 的软件工程原则和选择用来支持这些原则的交互约束:

  • CS 架构:客户 - 服务器约束背后的原则即是关注点分离(Separation of concerns , SOC),通过分离用户接口和数据存储这两个关注点,我们改善了用户接口跨多个平台的可移植性;同时通过简化服务器组件,改善了系统 的可伸缩性。然而对于 Web 来说,最重要的是这种关注点的分离允许组件独立地进化,从而支持多个组织领域的 Internet 规模的需求。

  • 无状态:从客户到服务器的每个请求都必须包含理解该请求所必需的所有信息,不能利用任何存储在服务器上的上下文,会话状态因此要全部保存在客户端。其优势在于不必为了确定一个请求的全部性质而去查看该请求之外的多个请求,减轻了从局部故障中恢复的任务量,并且允许服务器组件迅速释放资源,在云端部署时能够完成弹性伸缩。其缺陷在于增加了在一系列请求中发送的重复数据(每次交互的开销),可能会降低网络性能;并且将应用状态放在客户端还降低了服务器对于一致的应用行为的控制。

  • 缓存:缓存约束要求一个请求的响应中的数据被隐式地或显式地标记为可缓存的或不可缓存的。如果响应是可缓存的,那么客户端缓存就可以为以后的相同请求重用这个响应的数据。

  • 统一接口:REST 架构区别于其他基于网络的架构风格的核心特征即是强调组件之间要有一个统一的接口,通过在组件接口上应用通用性的软件工程原则,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了独立的可进化性。

  • 分层系统:分层系统风格通过限制组件的行为(即,每个组件只能 “ 看到 ” 与其交互的紧邻层),将架构分解为若干等级的层。通过将组件对系统的知识限制在单一层内,为整个系统的复杂性设置了边界,并且提高了底层独立性。

  • 按需代码:通过下载并执行 applet 形式或脚本形式的代码,REST 允许对客户端的功能进行扩展。这样, 通过减少必须被预先实现的功能的数目,简化了客户端的开发。允许在部署之后下载功能代码也改善了系统的可扩展性。通过在组件接口上应用通用性的软件工程原则,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了独立的可进化性。

笔者认为对于前后端分离的理念有重要启发意义的即是 CS 架构与无状态这两个约束,传统的 BS 开发中因为使用了 Cookie-Session 这种存储模式,将数据的传输在服务端与客户端透明化;虽然方便了我们在服务端使用,却也导致了强耦合

服务端模板渲染(Aspx 、 JSP、PHP )、客户端渲染(ExtJS )与服务端渲染(ServerSide Rendering )这三类

笔者认为的前后端分离架构下的团队组织方式如下:

《状态管理》一节中我们讨论了分离前后端状态的意义,这也是笔者的一贯看法,在 JavaScript 中,纯粹的同构的前后端是个非常酷的概念,不过就如笔者在文首所讲,在 CS 架构中还是保持客户端与服务端的某个边界还是很有意义的。而在实践中,这个边界往往就是所谓的 Public API。从前端开发者的角度而言,对于服务端我只需要了解 API 相关的内容,自然也希望 API 能够稳定的保证一种向后兼容性,不向后兼容的 API 会强制前端开发者及时地做出响应。一般来说,客户端需要维护两种状态,用户的交互以及需要持久化存储到某个域中的结果。因此,我们希望能够以一种清晰可预测的方式来管理我们的状态。以 API 的方式分隔客户端状态与服务端状态即是为了保证所写代码的职责分离,你肯定不希望模块对于外部系统有太多的依赖。在具体的实现 API 的时候,我们的目标兼括了稳定性与性能本身,即使你自己作为全栈开发者同时负责了前后端的代码,我也是不建议在开发服务端业务逻辑的代码时过多的考虑到 UI State。我使用 Redux 来管理应用前端的 UI 状态,它提倡的模式与文档支持都非常好,社区本身也非常地活跃。GraphQL 也是值得推荐的用于分割状态的工具,在 GraphQL 中,客户端能够以查询地方式从服务端请求状态,GraphQL 可以将多个 Endpoint 的状态合并返回给前端,从而使前端不需要去关心后端到底是如何实现的。综上所述,随着应用的日渐增长,状态管理的复杂度也与日俱增。而能够掌控这种复杂的情形,也是软件工程师的必备能力之一。