如何避免客户端和服务端重复业务逻辑?

How to avoid repeating business logic between client and server?

随着 Web 应用程序需求的增长,我发现自己编写了越来越多的 API 驱动的 Web 应用程序。我使用像 AngularJS 这样的框架来构建与这些 API 通信的丰富的 Web 客户端。目前我在服务器端使用PHP(流明或Laravel)/API。

问题是,我发现自己经常在客户端和服务器端之间重复业务逻辑。

当我说业务逻辑时,我指的是订单表单的规则:

为了使该应用既响应又快速,计算逻辑(连同其他业务逻辑)在客户端完成。由于我们不应该信任客户端,因此我会在服务器端重新验证这些数字。这种逻辑可能会变得非常复杂,在这两个地方编写这种复杂的逻辑感觉很危险。

我想到了三个解决方案:

  1. 让所有需要业务逻辑的东西都对 API 进行 ajax 调用。所有的业务逻辑都将存在于一个地方并且可以被测试一次。这可能会很慢,因为客户必须等待他们对订单所做的每一次更改才能获得更新的值和结果。拥有非常快的 API 会对此有所帮助。主要缺点是当用户连接不良(移动设备)时,这可能无法正常工作。

  2. 在客户端和服务器端编写业务逻辑。客户在表单上进行更改时会得到即时反馈,一旦他们在服务器上提交,我们就会验证所有数据。这里的缺点是我们必须复制所有的业务逻辑,并对双方进行测试。这肯定需要更多的工作,并且会使未来的工作变得脆弱。

  3. 相信客户!?!在客户端编写所有业务逻辑并假设它们没有篡改数据。在我目前的情况下,我正在开发一个报价生成器,它总是会被人工审查,所以也许这实际上没问题。

老实说,我对任何解决方案都不满意,这就是我向社区寻求建议的原因。我很想听听您对这个问题的意见或方法!

我觉得选项 1 是未来最好的选择。 API 第一次开发让所有的业务逻辑都测试正常,允许接口访问。你永远不应该相信用户!

与为每个需要的接口一次又一次地编写相同的逻辑相比,首次开发的力量 API 是无限的。

这里有一个关于将逻辑放在客户端还是服务器端的类似话题。归根结底,每种情况都是独一无二的,需要采取不同的计划,但此线程中有一些很好的指导性提示。

Client-side vs. Server-side

当我决定为后端使用 Laravel 并为前端使用 Angular 2 创建应用程序时,我遇到了同样的问题。在我看来,到目前为止还没有避免业务逻辑重复的解决方案,因为:

目前 PHP 和 JavaScript 无法相互转换。如果我们可以使用相同的语言来编写业务逻辑,然后将它们嵌入后端和前端,那该多好。从这一点开始,我又想到了另一点:

要实现这个目标,我们应该只用一种语言编写业务逻辑,目前JavaScript是最好的解决方案。如您所知 TypeScript/EMCA 脚本帮助我们以 OOP 方式编写代码。 Meteor框架NodeJS基础设施帮助我们在JavaScript为运行编写代码在后端和前端。

所以从我的角度来看,我们可以使用TypeScript/EMCA来编写业务逻辑包,例如用JavaScript编写的用于验证的class可以在双方都实现,所以你只写一次,但它也会被前端和后端调用两次。

这就是我的观点。希望看到这个非常有趣的主题的其他解决方案。

我自己做一些项目的时候也是这个位置。人们总是很想利用客户端设备的强大功能来完成繁重的工作,然后只在服务器端验证结果。这将导致业务逻辑出现两次,前端和后端。

我认为选项 1 是最好的选项,它最有道理,看起来也最合乎逻辑。如果你想在未来将你的网络应用程序扩展到原生移动应用程序,你将能够通过调用那些 APIs 来重用所有的业务逻辑。对我来说,这是一个巨大的胜利。

如果担心发出太多 API 请求而这可能会影响移动性能,那么可以尝试将一些请求组合在一起并在最后执行一次检查吗?因此,与其检查表单中的每个字段,不如在用户提交整个表单时进行检查。此外,如果您将请求和响应数据保持在最低限度,大多数互联网连接就足够了,所以我不担心这个。

我通常遇到的一个更大的问题是,因为您的 Web 应用程序将被分解成多个部分,每个部分调用相关的 APIs。应用程序的状态理解起来要复杂得多,因为用户可以在这些状态之间跳转。您需要非常仔细地考虑用户旅程,并确保该过程没有错误。

以下是我必须处理的一些常见问题:

  • 如果APIreturns一个,前端显示错误吗?
  • 如果用户犯了错误并提交了表单,he/she 应该会看到一个错误。但是一旦用户修正了错误并再次提交,错误应该隐藏,现在应该显示成功消息。
  • 如果 API 有问题或互联网连接不稳定怎么办,所以什么也没有返回。前端会不会挂?
  • 如果有多个错误信息,can/does前端会全部显示出来怎么办?

我建议在前端进行大量单元测试以确保其稳定,即使业务逻辑仅在后端也是如此。

你还可以做一件事。

仅使用 JavaScript 创建您的验证和业务逻辑代码。但要尽可能松耦合。如果可能,只将 JSON 作为输入并给出 JSON 作为输出。

然后在现有 PHP 服务器旁边设置一个单独的 NodeJS 服务器来为客户端提供该逻辑,以便在客户端无需 AJAX 调用即可使用它。

然后从 PHP 服务器,当你需要验证和 运行 所有这些业务逻辑规则时,使用 cURL 调用 NodeJS 业务逻辑并验证数据。这意味着从 PHP 服务器到 NodeJS 服务器的 HTTP 调用。 NodeJS 服务器将有额外的代码来获取数据,使用相同的代码进行验证,然后 return 结果。

通过这种方式你可以

  1. 更快的开发 - 一个对您的逻辑进行单元测试的地方。
  2. 更快的客户端代码执行 - 不需要 AJAX,因为相同的验证 JavaScript 代码由 NodeJS 提供给您的客户端。
  3. 所有业务逻辑都在NodeJS服务器中 - 当业务逻辑发生变化时,你只需要接触这部分;这样在不久的将来,如果你需要创建一些其他额外的接口,那么你可以使用这个服务器来验证你的数据。它将像您的业务规则服务器一样工作。

您唯一需要做的就是 在 PHP 服务器旁边设置一个 NodeJS 服务器。但是您不需要在 NodeJS 服务器上将所有代码更改为 运行。

首先:永远不要相信客户。

话虽如此,我一直都在处理这个问题,遗憾的是我还没有找到一个简单的解决方案。您需要对两侧进行验证,但是,您不需要对它们都进行整个验证。

我所做的就是尝试平衡它。在客户端,你做大部分简单(但有价值)的验证,正常的东西,数字必须是数字,日期必须是日期,范围内的数据等等,所以当你提交它时,它会去服务器得到完整的经过验证,但您要确保在客户端,大部分信息至少采用正确的格式,并且其中一些(或大部分)信息已经过验证,但是,真正的业务逻辑是在服务器端完成的, 但由于大部分数据已经正确,服务器端验证很可能会批准请求,因此您将避免大量重新提交。

现在,如何做到当你需要改变某些东西时,你不需要在两边都改变?好吧,有时你无法避免这种情况,当需要进行重大更改时,但是,可以共享业务逻辑参数,正如你所建议的,这可以通过 ajax 来完成。您制作了一个 php 文件,其中包含所有业务逻辑参数,并通过 ajax 请求将其加载到客户端,仅一次(加载脚本时),您需要优化这样,所以您只获得参数值,其他所有内容都应该已经存在于客户端,因此如果业务逻辑中的某些参数值发生变化,您只需在参数文件中进行更改。 (如果脚本加载后更改参数,服务器端验证将失败,现在您需要决定是否强制他们重新加载脚本,所以参数是否重新加载,我让他们重新加载)

我想你明白了。这就是我所做的,对我来说效果很好,为我节省了很多重新编码的时间。

希望对您有所帮助。

一种可能的解决方案是以声明性抽象语言(如 XML 或 JSON 模式声明验证规则。

然后在客户端,说 AngularJS——您可以将这些规则转换为现成的表单渲染器。所以现在在客户端,您最终会得到验证声明规则的表单。

然后在您的服务器端 API 您需要创建一个可重复使用的验证引擎,它将根据定义的规则进行验证。

你最终得到的是一个单一的地方,你的 JSON 架构或者你声明定义规则的地方,你的表单和验证规则已经定义。

今天的解决方案显然来自@ParthaSarathiGhosh,但不久的将来肯定会给我们另一个解决方案...

WebAssembly 是一种低级汇编语言,可以与您的应用程序和 运行 一起部署在浏览器中。它将允许您通过调用程序集中的已编译代码从 JavaScript 请求一些逻辑。建议用于 运行 客户端的繁重脚本,但同时允许您在前端重用后端代码。这样,您就可以为后端编写逻辑,并在前端重用它。

今天大多数现代浏览器都支持这项技术,但只有 c/c++ 才能使用。所以如果你有这些技能,你已经可以使用它了。

当然也计划将其扩展到其他语言(因为已经有一些针对 c# - ex: blazor 和其他语言的研究)。但成熟度水平似乎不够稳定,无法用于生产(甚至 blazor 开发团队也不推荐将其用于生产)。

这只是我个人的意见,但是 => NodeJS 中的逻辑是重用 javascript 代码的解决方案,但是当涉及到大型可维护逻辑代码时,我仍然觉得需要强类型语言。 (是的,我知道 TypeScript,它真的很棒,但我错过了一些东西)。 WebAssembly 还很年轻,但肯定会在尊重 DRY 原则方面带来很大的改进。

非常有趣的问题 - 另一个警告可能是我们希望支持离线模式,即应用程序也必须 运行 离线。

另一个更复杂的情况是,如果让我们说您的服务器端全部采用一种技术,如 java 或 .Net 等,而在客户端,您在本机工具或 Xamarin 之类的东西之间进行选择,但不幸的是不是与服务器相同。

所以 Partha 的方法似乎最有前途 - 但正如它所说的那样,这在完全离线模式下是行不通的。因此,一种略微修改的方法是将验证规则视为数据。但不是简单的数据——而是说 "the whole damn code is data"。您可以选择任何您喜欢的解释型代码语言 - Groovy、JavaScript、CScript 等 - 但您将 100% 遵循的一条规则是所有业务逻辑都在该代码中!

如果你能做到这一点,那么在离线模式下——当你同步数据时——你也会同步这种非常特殊的数据类型,即代码! (所以没有 "trusting" 客户的风险)

然后离线 API 和在线 API 是 100% 相同的代码 - 但代码是我们的解释语言。我认为这种方法不仅可以解决这个问题,而且可以使业务逻辑维护变得更加简单。我们经常创建高度复杂的数据模型来支持规则;实际上在 2019 年 - 您可以简单地使用 ifs/elses 创建规则,这会简单得多。我们可以使用一个非常简单的脚本工具来培训最终用户,并使用更少的代码来做更好的事情。

我根据这些想法整理了一个博客 post:https://medium.com/@thesaadahmad/business-logic-conundrum-offline-mobile-apps-a06ecc134aee