使用现代Rails走出Rabbit Hole SPA

TL; DR:SPA步道很暗,充满了恐怖。 您可以无所畏惧地与他们抗争...或选择另一条将您引向正确位置的道路:现代Rails。



我记得当2012年DHH宣布Turbolinks时,Rails专注于错误的目标。 然后我坚信,用户交互过程中的即时响应时间是高级UX的关键。 由于网络延迟,只有在最大程度地降低网络依赖性并保持客户端的大部分状态(而不是网络调用)的情况下,这种交互才是可能的。

我认为这对于我正在处理的应用程序是必要的。 基于这种观点,我尝试了多种方法和框架来实现同一模板:单页应用程序(SPA)。 我相信SPA是未来。 几年后,我不确定未来会怎样,但是我绝对想找到一个替代方案。

兔子洞SPA


一页应用程序是一个JavaScript应用程序,一旦下载,就可以完全控制,而无需重新加载页面:呈现,从服务器接收数据,处理用户交互,更新屏幕...

这样的应用程序看起来比传统的网页更原生,在传统的网页中,应用程序取决于服务器。 例如,如果您使用Trello,您可能会注意到创建卡片的速度。

当然,巨大的责任伴随着巨大的力量。 在传统的Web应用程序中,您的服务器应用程序包括域模型和业务规则,用于与数据库一起工作的数据访问技术以及用于控制如何响应HTTP请求组装HTML页面的控制器层。

C SPA有点复杂。 您仍然需要一个服务器应用程序,其中包括您的域模型和规则,Web服务器,数据库和某种数据访问技术……以及最上面的一些附加功能:

对于服务器:

  • 满足客户数据需求的API
  • 用于与客户端交换数据的JSON序列化系统和支持此功能的缓存系统

对于新的JavaScript客户端:

  • 用于将数据转换为HTML的模板系统
  • 介绍您的域模型和规则
  • 客户端应用程序中的数据访问层,用于将数据传输到服务器
  • 数据更改时更新视图的系统
  • 链接网址和屏幕的系统(希望您不会对所有内容都使用一个地址,它看起来像Web应用程序一样)
  • 一个用于胶合显示屏幕并为此获取数据的所有组件的系统
  • 新的模板和体系结构可以在需要时组织所有内容
  • 用于错误处理,日志记录,异常跟踪等的系统
  • 用于为服务器请求生成JSON的系统
  • 支持您的SPA的自动化测试框架
  • 一套额外的写作和支持测试
  • 一组用于组装,打包和部署新应用程序的工具

总之,有了SPA,您将有另一个支持应用程序。 并解决了一系列新问题。 请注意,您不能将一个应用程序替换为另一个。 您仍然需要服务器应用程序(现在它将呈现JSON而不是HTML)。

如果您从未使用过SPA,则可以低估将遇到的困难。 我知道这一点是因为我过去犯过同样的错误。 呈现JSON吗? 我可以处理。 JavaScript中的丰富域对象模型? 听起来很有趣。 而且,您知道,该框架将解决所有这些问题。 很棒的蛋糕!

错了

API和数据交换。


新应用程序与服务器之间的通信是一个复杂的问题。

有两种相反的力量:

  • 您将希望发出尽可能少的服务器请求,以提高性能。
  • 将单个记录转换为JSON很简单。 但是根本没有混合各种记录的大型模型以最小化查询数量。 您将需要仔细开发序列化逻辑,以优化数据库查询的数量并保持该级别的性能。

此外,您需要考虑下载什么内容以及何时在每个屏幕上进行下载。 我的意思是,您需要在引导程序,立即需要的内容和延迟加载的内容之间取得平衡,并开发一个API来执行此操作。

一些标准可能会有所帮助。 JSON API以标准化JSON格式; 或GraphQL,以便在一个查询中仅选择必要的数据(根据要求复杂)。 但其中没有一个能使您免于:
  • 每个数据交换的详细说明
  • 实施查询以在服务器上有效地选择数据

这两个方面都代表了足够的额外工作。

开机时间


人们习惯于将SPA与速度相关联,但事实是让它们快速加载并非易事。 造成这种情况的原因很多:

  • 应用程序在呈现某些内容之前需要数据,并且解析大量的JavaScript需要花费时间。
  • 除了下载应用程序的初始HTTP请求之外,您通常还需要发出一个或多个请求来获取呈现屏幕所需的JSON数据。
  • 客户端必须将JSON转换为HTML,以便至少显示某些内容。 根据设备和要转换的JSON数量,这可能会引起明显的延迟。

这并不意味着不可能强制SPA快速加载。 我只是说这很困难,您应该照顾好它,因为它不会随SPA架构一起提供。

例如,基于Ember的SPA Discourse的加载时间非常长,但是除其他外,它们预加载了大量JSON数据作为初始HTML的一部分,以免发出其他请求。 而且我注意到,话语团队在良好的速度意识上是疯狂的,他们的技能远高于平均水平。 在轻松地将其复制到SPA中之前,请仔细考虑一下。

同构JavaScript是解决此问题的雄心勃勃的解决方案:在服务器上呈现您的起始页并将其快速提供给客户端,而在SPA的后台,它会加载并在就绪时获得完全控制权。

这种方法需要在服务器上运行JavaScript运行时,并且也不是没有技术问题。 例如,开发人员应在加载过程更改时考虑SPA加载事件。

我喜欢这个想法中重用代码的机会,但是我还没有看到可以让我朝相反方向发展的实现。 另外,这个页面渲染过程对我来说似乎很有趣:

-服务器:请求服务器API
-服务器:数据库查询
-服务器:生成JSON
-服务器:将JSON转换为HTML
-客户端:显示初始HTML
-客户端:下载SPA
-客户端:解析初始HTML并订阅DOM事件

您能否仅从数据库中请求数据,生成HTML并开始工作?

这是不完全公平的,因为您不会成为SPA,并且因为大多数这种魔术都隐藏在框架的背后,但是在我看来仍然是错误的。

建筑学


用丰富的用户界面开发应用程序很困难。 这是因为这是促使出现面向对象方法和许多设计模式的问题之一。

管理客户状态很困难。 传统网站通常专注于单一职责屏幕,这些屏幕在重新启动后会丢失状态。 在SPA中,应用程序负责确保在使用过程中所有状态和屏幕更新均保持一致并顺利通过。

实际上,如果您开始少量编写JavaScript以改善交互性,那么在SPA中,您将不得不编写大量其他JavaScript代码。 在这里,您应该确保自己做的一切正确。

与SPA框架一样,有许多不同的体系结构:

  • 大多数框架不同于传统的MVC模板。 Ember最初受到Cocoa MVC的启发,但在最新版本中对其编程模型进行了很多更改。
  • 有一种趋势是,开发人员更喜欢组件,而不是传统的控制器和表示形式分离(某些框架,例如Ember和Angular,在最近的版本中已转移到这种方法)。 所有框架都实现某种单向数据绑定。 不鼓励双面粘合,因为它可能会产生副作用。
  • 大多数框架都包含一个路由系统,该系统允许您映射URL和屏幕,并确定如何实例化组件以进行呈现。 这是传统桌面界面上不存在的独特的Web方法。
  • 大多数框架将HTML模板与JavaScript代码分开,但是考虑到它的广泛使用,React将HTML生成和JavaScript混合在一起,并且相当成功。 关于在CSS中嵌入CSS也有一种炒作。 具有Flux架构的Facebook在很大程度上影响了该行业,Redux,vuex等容器也受到了很大的影响。

在我见过的所有框架中,Ember是我的最爱。 我喜欢他的坚定不移以及他固执的事实。 我也喜欢他的最新版本的编程模型,该模型结合了传统的MVC,组件和路由。

另一方面,我强烈反对Flux / Redux阵营。 我看到这么多聪明的人在使用它们,因此我竭尽全力去研究和理解它,而不是一次。 看到代码后,我忍不住摇了摇头。 编写此类代码时,我并不高兴。

最后,我无法忍受在充满JavaScript逻辑的组件中混合使用HTML和CSS。 我知道这解决了什么问题,但是这种方法带来的问题并不值得。

让我们保留个人喜好,最重要的是,如果您选择SPA路径,您将遇到一个非常困难的问题:正确创建新应用程序的体系结构。 而且该行业距离如何做到这一点还很遥远。 每年都会出现新的框架,模板和框架版本,这会稍微改变编程模型。 您将需要根据架构选择编写和维护大量代码,因此请仔细考虑。

代码重复


使用SPA时,您可能会遇到代码重复。

对于SPA逻辑,您将需要创建一个丰富的对象模型,以表示您的域区域和业务逻辑。 而且您仍然需要服务器逻辑相同的东西。 开始复制代码只是时间问题。

例如,假设您正在处理发票。 您可能在JavaScript中有一个Invoice类,其中包含一个total方法,该方法汇总所有元素的价格,以便呈现值。 在服务器上,您还需要带有total方法的Invoice类来计算此费用,以便通过电子邮件发送。 看到了吗 发票客户端和服务器类实现相同的逻辑。 代码重复。

如上所述,同构JavaScript可以缓解此问题,从而更易于重用代码。 我要说的是,因为客户端和服务器之间的对应关系并不总是一对一的。 您将要确保某些代码永远不会离开服务器。 大量的代码仅对客户端有意义。 而且,某些方面完全不同(例如,服务器元素可以将数据保存在数据库中,而客户端可以使用远程API)。 即使可能,重用代码也是一个复杂的问题。

您可以打赌,SPA中不需要丰富的模型,而是直接使用JSON / JavaScript对象,将逻辑分布在UI组件之间。 现在,您已经将相同的代码重复与UI代码混合在一起,祝您好运。

如果您想要在服务器和客户端之间呈现HTML的模板,也会发生同样的事情。 例如,对于SEO,如何在服务器上为搜索引擎生成页面? 您将需要在服务器上重新编写模板,并确保它们与客户端同步。 再次重复代码。

根据我的经验,需要在服务器和客户端上重现模式逻辑是导致程序员日益不幸的原因。 一次执行此操作很正常。 当您第20次这样做时,您会抓紧头。 第50次这样做之后,您将考虑是否需要所有这些SPA部件。

易碎性


以我的经验,开发良好的SPA比用服务器生成编写Web应用程序要复杂得多。

首先,无论您多么谨慎,无论编写多少测试。 您编写的代码越多,您将遇到的错误也越多。 SPA表示(抱歉,如果我努力的话)一大堆用于编写和支持的代码。

其次,如上所述,开发丰富的GUI很复杂,并且转换成由许多相互交互的元素组成的复杂系统。 您创建的系统越复杂,您所拥有的错误就越多。 与使用MVC的传统Web应用程序相比,SPA的复杂性简直是疯狂。

例如,要保持服务器上的一致性,可以在数据库,模型验证和事务中使用限制。 如果出现问题,则您将显示一条错误消息。 在客户端,一切都稍微复杂一些。 太多事情可能出错,因为发生了太多事情。 可能某些记录已成功保存,而另一些记录未成功保存。 也许您在某种操作过程中下线了。 您必须确保UI保持一致,并且应用程序正在从错误中恢复。 当然,所有这些都是可能的,只是复杂得多。

组织挑战


听起来很傻,但是要开发SPA,您需要知道如何处理SPA的开发人员。 同时,您不应低估SPA的复杂性,也不应认为任何具有正确动机和理解的经验丰富的Web开发人员都可以从头开始编写出色的SPA。 您需要正确的技能和经验,或者期望犯下重要的错误。 我知道这是因为这正是我的情况。

对于您的公司而言,这可能是比您想象的更为重要的挑战。 SPA方法鼓励高度专业化的团队,而不是通才团队:

  • SPA框架是复杂的软件,需要无数小时的经验才能提高生产力。 只有公司中花了这些时间在代码上的人员才能支持这些应用程序。
  • SPA框架需要经过深思熟虑的高效API。 满足这些要求需要的技能与需要使用SPA的技能完全不同。

很有可能您会遇到无法使用SPA且无法在服务器端工作的人员,这仅仅是因为他们不知道如何操作。

对于Facebook或Google及其由几层工程兵组成的团队来说,这种专业化可能是理想的选择。 但是,这对您的6人团队有好处吗?

现代护栏


现代Rails中有3件事可以改变您对开发现代Web应用程序的看法:
  • 其中之一是Turbolinks,这是大脑爆炸
  • 另一个是今天被忽视的老朋友:SJR响应和简单的AJAX渲染请求
  • 最近添加了最后一个:刺激

很难理解在不使用任何方法的情况下应用什么方法。 因此,在以下各节中,我将对Basecamp进行一些引用。 除了作为快乐的用户外,我与Basecamp无关。 对于本文而言,这只是现代Rails的一个很好的实时示例,您可以免费尝试。

涡轮链接


Turbolinks背后的想法很简单:通过用替换``元素的AJAX请求完全替换页面重新加载来加速您的应用程序。 从事这项工作的内在巫术是隐藏的。 作为开发人员,您可以专注于传统的服务器端编程。

Turbolinks受到pjax的启发,并进行了多次修订。

我曾经担心它的性能。 我错了 加速度是巨大的。 使我相信的是我在项目中的使用方式,但是您可以在Basecamp中试用试用版并使用它。 尝试创建包含一些元素的项目,然后通过单击部分在它们之间导航。 这将使您对Turbolink的外观有所了解。

我认为Turbolinks的新颖性(pjax-8年)简直令人称奇。 或其技术复杂程度。 与SPA相比,一个简单的想法如何使您的生产率提高一个数量级,这让我感到惊讶。

让我重点介绍一下它可以解决的一些问题:

  • 数据交换。 您没有。 无需序列化JSON,创建API或考虑性能方面满足客户需求的数据请求。
  • 初始负载。 与SPA不同,此方法可缩短加载时间(根据设计)。 要渲染屏幕,您可以直接从数据库中获取所需的数据。 HTML — .
  • : JavaScript-. , SPA.

在Rails和许多其他框架使用的版本中,服务器上的MVC比用于富GUI体系结构的任何模板都要简单得多:获取请求,使用数据库来满足它并显示HTML页面作为答案。

最后,始终被替换的限制具有奇妙的效果:您可以专注于页面的初始呈现,而不必更新某些部分(或更新SPA世界中的某些条件)。在一般情况下,他什么都做。

  • 代码重复。服务器上只有一个应用程序视图。您的域模型,其规则,应用程序屏幕等。无需在客户端中重复概念。
  • . SPA, JavaScript , . , , , .

请注意,我并不是在谈论命名问题,而是在解决它们。例如,GraphQL或SPA补液是针对非常复杂问题的超级智能解决方案。但是,如果您没有尝试找到解决方案,而是让自己陷入不存在这些问题的境地,该怎么办?这是解决问题方法的一种变化。我花了多年的时间来充分认识到这种方法解决问题的能力。

当然,Turbolinks并非一帆风顺。最大的问题是,它可能破坏现有的JavaScript代码:

  • Turbolinks附带其自定义的“页面加载”事件,并且依赖常规页面加载的现有插件将无法使用。如今,有更好的方法可以将行为添加到DOM,但是如果旧式窗口小部件不适应,将无法正常工作。
  • 修改DOM的JavaScript代码必须是幂等的,因为它可以多次运行。同样,这会使许多现有的JavaScript失效。
  • 速度非常好,但是与SPA不太一样,SPA可以在不加载服务器的情况下处理某些交互。稍后我将详细讨论折衷方案。

AJAX渲染和SJR答案


还记得15年前通过Ajax渲染HTML的趋势吗?你猜怎么着?这仍然是您军火库中的好工具:
  • HTML DOM, ( 100 ).
  • HTML , .

通过单击右上方的按钮



打开您的配置文件菜单,您可以了解Basecamp中这种方法的感觉:立即打开。从开发方面,您无需担心JSON序列化和客户端。您可以使用Rails的所有功能,在服务器上简单地显示此片段。

Rails多年来提供的类似工具是服务器端JavaScript(SJR)响应。它们允许您使用客户端执行的JavaScript响应Ajax请求(通常是表单提交)。它具有与HTML代码段的AJAX呈现相同的优点:它运行速度非常快,可以在服务器端重用代码,还可以直接访问数据库以创建响应。

如果您进入Basecamp并尝试创建新的待办事项,则可以看到这种情况。单击“添加待办事项”后,服务器将保存待办事项并以JavaScript片段响应,该片段将新的待办事项添加到DOM。

我认为当今许多开发人员都鄙视AJAX渲染和SJR响应。我也记得 它们是一种工具,因此可能会被滥用。但是,如果正确使用,这是一个了不起的解决方案。让您以非常低的价格提供出色的UX和交互性。不幸的是,像Turbolinks一样,如果您还没有打过SPA,就很难评估它们。

刺激物


Stimulus是几个月前发布的JavaScript框架。它不关心渲染或基于JavaScript的状态管理。相反,这只是组织JavaScript的一种不错的现代方式,您可以使用它来添加HTML:

  • 它使用MutationObserver将行为绑定到DOM,这意味着它并不关心HTML在页面上的显示方式。当然,这对于Turbolinks来说很好用。
  • 它将为您节省大量用于将行为附加到DOM,将事件处理程序附加到事件以及将元素放置在指定容器中的样板代码。
  • 它旨在使您的HTML代码具有可读性和可理解性,如果您遇到了找出JavaScript的哪个部分正在作用于该死元素的问题,那将是一个很好的选择。
  • 它鼓励在DOM中的持久性。同样,这意味着他并不关心HTML的生成方式,这适用于许多情况,包括Turbolinks。

如果您接受Rails路径,那么您的JavaScript将专注于修改服务器端HTML代码和改善交互(使用少量JavaScript)。刺激旨在组织此类代码。这不是SPA系统,也不假装是一个系统。

我在多个项目中使用了Stimulus,我真的很喜欢。它消除了许多样板代码,它基于最新的Web标准构建,并且读取效果非常好。还有我特别喜欢的东西:现在,这是做在每个应用程序中仍然必须解决的事情的标准方法。

折衷游戏


Turbolinks通常以“在不带来任何麻烦的情况下获得SPA的所有好处”的形式出售。我不认为这是完全正确的:

  • 使用现代Rails构建的应用程序看起来很快,但是SPA仍将对与服务器无关的交互做出更快的响应。
  • 在某些情况下,SPA更有意义。如果需要提供高级别的交互性,则需要管理大量状态,在客户端执行复杂的逻辑等。SPA框架将使您的生活更轻松。

现在,发展是折衷的游戏。在这个游戏中:
  • Modern Rails允许您创建足够快且外观精美的应用程序。
  • 对于各种各样的应用程序,Rails允许您以更少的代码和更少的复杂性来实现相同的功能。

我相信,使用R​​ails,您只需付出10%的努力就可以获得SPA提供的90%的东西。在性能方面,Rails杀死了SPA。对于UX,我认为许多开发人员都会犯与我相同的错误,前提是无法超越SPA UX。事实并非如此。 实际上,如上所述,您最好了解创建SPA时的操作,否则UX实际上会更糟。

结论


我看到公司大量接受SPA框架,并且看到无数有关如何做奇特的SPA风格的文章。我认为有很多“使用错误的工具来完成工作的方法”,因为我坚信证明使用SPA的应用程序类型是有限的。

我说这是合理的,因为SPA很复杂。无论如何,我希望我对此深信不疑。我并不是说不可能创建出色的SPA应用程序,或者现代Rails应用程序从定义上来说也很棒,只是一种方法非常复杂,而另一种则简单得多。

在准备本文时,我遇到了以下推文:



这让我发笑,因为如果没有合理的选择,我会选择第一个选项。他还是热爱复杂性并在其上蓬勃发展的开发人员思想的代表,甚至甚至认为其他条件不同的人都疯了。

多年后,我意识到复杂性通常是一种选择。但是在编程世界中,很难选择简单性。我们非常重视复杂性,以至于接受简单性常常会使我们有不同的想法,这从定义上讲是困难的。

请记住,您可以摆脱麻烦。如果选择SPA路径,请确保它是合理的,并且您了解问题所在。如果您不确定,请尝试不同的方法并自己看看。也许Facebook或Google在规模上没有做出这样的决定的奢望,但是您可能可以。

而且,如果您是许多年前退出Rails的Rails开发人员,那么我建议您重温它。我想你会很高兴的。

Source: https://habr.com/ru/post/zh-CN417875/


All Articles