【问题标题】:How to avoid repeating business logic between client and server?如何避免在客户端和服务器之间重复业务逻辑?
【发布时间】:2016-09-26 22:20:53
【问题描述】:

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

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

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

  • 买Y就可以买X。
  • 有Z就不能买Y。
  • 如果您购买其中 10 件,您将获得 10% 的折扣。
  • 高度 x 宽度 x 深度 x 成本 = 最终成本。
  • 如果宽度大于 5,高度必须在 10 到 20 之间。
  • 等等等等

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

我想到了三个解决方案:

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

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

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

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

【问题讨论】:

  • 你的问题不就是简单的MVC设计模式作为解决方案吗?
  • 在服务器 ajax 上使用 PHP 是最酷的方法,并且不应超过几毫秒,如果确实需要更多时间,您也可以设置加载屏幕或警告。你可以转移到像流星/节点这样的框架,如果你对整个事情进行了一次编码,并且只为服务器或客户端明确了一些细节。您可以对 HTML 表单进行基本验证,然后在服务器上进行大型验证。您可以在 js 中创建可供客户端访问和从服务器访问的库。看看这个php.net/manual/en/v8js.executestring.php
  • 愿我发现的另一位导师对您感兴趣:phpied.com/server-side-react-with-php
  • 也许你可以使用 websocket?它非常快,您可以使用 javascript。老实说,我不知道它是否使用了很多带宽。您可以考虑在需要时连接到此 websocket,并在事务完成时断开连接。 Websockets 很有趣并且很容易实现。看this
  • 我会同时使用服务器端和客户端验证;但我不喜欢重复代码的想法,所以很可能我会让服务器将验证规则传递给客户端并让客户端使用这些规则,所以它是验证所有案例的 1 个函数(所有更改都将在服务器端完成因此客户端仍然能够正确验证的任何更改)然后我会在保存到服务器端之前进行验证(最终提交)

标签: php angularjs ajax laravel api-design


【解决方案1】:

你可以再做一件事。

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

然后在现有的 PHP 服务器旁边设置一个单独的 NodeJS 服务器,以将该逻辑提供给客户端,这样在客户端就可以在没有 AJAX 调用的情况下使用它。

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

这样你就可以制作

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

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

【讨论】:

  • 或者只是在节点中进行所有服务器端开发。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 需要注意的是,它需要更改语言(从 php 到 nodejs)。看起来这可能是唯一没有任何重复的方法:(
  • 您可以在纯javascript中使用业务逻辑并将其部署在nodejs上。您可以将其余代码放在 PHP 服务器上。通过这种方式,您不需要在 nodejs 上切换整个代码,并且可以保存重复的工作。看我的回答。
【解决方案3】:

一种可能的解决方案是使用声明性抽象语言(如 XML 或 JSON Schema)声明您的验证规则。

然后在客户端,比如AngularJS——你可以将这些规则转换成一个现成的表单渲染器。因此,现在在客户端,您最终会得到验证声明规则的表单。

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

你最终得到的是一个地方,你的 JSON 模式或者你声明定义规则的地方,你的表单和验证规则被定义。

【讨论】:

    【解决方案4】:

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

    我认为选项 1 是最好的选择,它最有意义,也最合乎逻辑。如果您希望将来将您的 Web 应用程序扩展到本地移动应用程序,您将能够通过调用这些 API 来重用所有业务逻辑。对我来说,这是一场巨大的胜利。

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

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

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

    • API返回错误,前端是否显示错误?
    • 如果用户犯了错误并提交了表单,他/她应该会看到一个错误。但是一旦用户修复了错误并再次提交,错误应该会隐藏并且现在应该会显示成功消息。
    • 如果 API 有问题或互联网连接不稳定,那么什么都不会返回怎么办。前端会挂吗?
    • 如果有多个错误消息,前端可以/是否都显示它们?

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

    【讨论】:

      【解决方案5】:

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

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

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

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

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

      希望对您有所帮助。

      【讨论】:

        【解决方案6】:

        我觉得选项 1 是未来最好的选择。 API优先开发允许测试所有业务逻辑并正常工作并允许接口访问。你永远不应该信任用户!

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

        【讨论】:

          【解决方案7】:

          这是关于将逻辑放在客户端还是服务器端的类似主题。归根结底,每种情况都是独一无二的,需要制定不同的计划,但本主题中有一些很好的指导技巧。

          Client-side vs. Server-side

          【讨论】:

            【解决方案8】:

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

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

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

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

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

            【讨论】:

              【解决方案9】:

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

              另一个更复杂的情况是,如果假设您的服务器端采用 java 或 .Net 等技术,而在客户端,您在本机工具或 Xamarin 等技术之间进行选择,但不幸的是与服务器端不同。

              因此,Partha 的方法似乎最有希望 - 但正如其所述,这在完全离线模式下是行不通的。因此,稍作修改的方法是将验证规则视为数据。但不是简单的数据——而是说“整个该死的代码都是数据”。您可以选择任何您喜欢的解释代码语言 - Groovy、JavaScript、CScript 等 - 但您将 100% 遵循的一条规则是所有业务逻辑都在该代码中!

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

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

              我整理了一篇包含这些想法的博文:https://medium.com/@thesaadahmad/business-logic-conundrum-offline-mobile-apps-a06ecc134aee

              【讨论】:

                猜你喜欢
                • 2013-03-14
                • 1970-01-01
                • 2012-07-22
                • 1970-01-01
                • 2018-08-20
                • 1970-01-01
                • 1970-01-01
                • 2012-02-05
                • 1970-01-01
                相关资源
                最近更新 更多