【问题标题】:How to share Javascript Business Rules between Client and Server?如何在客户端和服务器之间共享 Javascript 业务规则?
【发布时间】:2016-03-18 09:58:34
【问题描述】:

我正在创建一个 MEAN 堆栈,我想澄清以下内容。

从编码标准的角度来看,我知道验证应该在客户端和服务器端都执行。我想要实现的是执行完全相同的验证代码,这样我就不会再次重复代码。这更像是客户端和服务器端的共享代码。

所以我怎样才能让 angular js 和 Express js 调用相同的 .js 文件来执行验证?有可能吗?

谢谢!

【问题讨论】:

    标签: angularjs node.js express mean-stack


    【解决方案1】:

    你肯定能做到。 RemObjects DataAbstract (http://old.wiki.remobjects.com/wiki/Business_Rules_Scripting_API) 使用了这种方法。这里的原则是定义适用于客户端和服务器或仅适用于服务器的业务规则。您几乎不必只在客户端上检查业务规则,因为您永远无法“信任”客户端检查您的业务规则。

    CQRS 和 DDD 是可以在这方面为您提供帮助的两个架构原则。领域驱动设计将“清理”或“优化”您的代码,将基础设施推离核心“领域”逻辑。并且业务规则仅适用于域,因此最好将域与其他域隔离。

    命令-查询-责任-分离。我非常喜欢这个。基本上,您定义了一组命令,这些命令将在应用之前进行验证。不再有类似于 Model.Set('a', 2) 的类似机器的代码。使用此原则,您的代码将类似于 MyUnderstandableBusinessObject.UnderstandableCommand(aFriendlyArgument)。在应用业务规则时,您的实际命令反映了您的域的用例,这非常方便。

    我在处理 node.js / javascript 项目时也经常遇到这个问题。问题是您没有客户端和服务器都可以理解的标准化 ORM。这是自相矛盾的,因为 node.js 和浏览器运行在同一种语言上。当我被 Node.js 吸引时,我告诉自己,客户端和服务器都运行相同的语言,这将节省大量时间。但这有点错误,因为即使 npm 非常活跃,也没有那么多成熟和专业的工具。

    我也想构建一个客户端/服务器都可以理解的 ORM,并为其添加一个关系方面(以便它与 SQL 兼容),但我有点放弃了这个项目。 https://github.com/ludydoo/affinity

    但是,还有其他几种解决方案。 Backbone 是其中之一,而且它很轻量级。

    您必须在此处实际实施业务规则检查。您需要将模型中的“验证”部分提取到另一个可以共享的对象中。让你开始的东西:

    https://jsfiddle.net/ludydoo/y0otcvrf/

    BusinessRuleRepository = function() {
      this.rules = [];
    }
    
    BusinessRuleRepository.prototype.addRule = function(aModelClass, operation, callback) {
      this.rules.push({
        class: aModelClass,
        operation: operation,
        callback: callback
      })
    }
    
    BusinessRuleRepository.prototype.validate = function(object, operation, args) {
      _.forIn(this.rules, function(rule) {
        if (object.constructor == rule.class && operation == rule.operation) {
          rule.callback(object, args)
        }
      })
    
    }
    
    MyObject = function() {
      this.a = 2;
    }
    MyObject.prototype.setA = function(value) {
      aBusinessRuleRepo.validate(this, 'setA', arguments);
      this.a = value;
    }
    
    
    // Creating the repository
    var aBusinessRuleRepo = new BusinessRuleRepository();
    
    //-------------------------------
    // shared.js
    var shared = function(aRepository) {
      aRepository.addRule(MyObject, 'setA', function(object, args) {
        if (args[0] < 0) {
          throw 'Invalid value A';
        }
      })
    }
    if (aBusinessRuleRepo != undefined) {
      shared(aBusinessRuleRepo);
    }
    //-------------------------------
    
    
    // Creating the object
    
    var aObject = new MyObject();
    
    try {
      aObject.setA(-1); // throws
    } catch (err) {
      alert('Shared Error : ' + err);
    }
    
    aObject.setA(2);
    
    
    
    //-------------------------------
    // server.js
    var server = function(aRepository) {
      aRepository.addRule(MyObject, 'setA', function(object, args) {
        if (args[0] > 100) {
          throw 'Invalid value A';
        }
      })
    }
    if (aBusinessRuleRepo != undefined) {
      server(aBusinessRuleRepo);
    }
    //-------------------------------
    // on server
    
    
    try {
      aObject.setA(200); // throws
    } catch (err) {
      alert('Server Error :' + err);
    }
    
    • 首先要建模和定义您的域。您将拥有一组代表您的业务对象的类,以及对应于业务操作的方法。 (对于您的情况,我真的会选择 CQRS)

    • 模型定义将在客户端和服务器之间共享。

    • 您必须定义两个文件或两个对象。分开。服务器规则和共享规则。这些将是一组 Repository.addRule() 调用,它们将在存储库中注册您的业务规则。您的客户端将获得 Shared.js 业务规则,而服务器将获得 Shared.js + Server.js 业务规则。这些业务规则将始终以这种方式应用于您的对象。

    我向您展示的代码小示例非常简单,仅在应用命令之前检查业务规则。也许您可以添加一个参数'beforeCommand'和'afterCommand'来检查更改前后的业务规则。然后,如果您在应用命令后添加检查业务规则的可能性,您必须能够回滚更改(我认为backbone有这个功能)。

    祝你好运


    您可以通过自动获取您所在方法的名称来稍微自动化一下 (Can I get the name of the currently running function in JavaScript?)

    function checkBusinessRules(model, arguments){
       businessRuleRepo.validate(model, getCalleeName, arguments);
    }
    
    Model.prototype.command = function(arg){
       checkBusinessRules(this, arguments);
       // perform logic
    }
    

    编辑 2

    我想在我的第一个答案中更正一个小细节。不要在属性设置器上实施您的业务规则!改用业务运营名称:

    您必须确保始终通过方法设置模型属性。如果您通过分配值直接设置模型属性,那么您将绕过整个业务规则处理器。

    便宜的方法是通过标准制定者来做到这一点,例如

    SetMyProperty(value);
    SetAnotherProperty(value);
    

    这是一种低级业务规则逻辑(基于 getter 和 setter)。然后,您的业务规则也将是低级的。这有点糟糕。

    更好的是,您应该通过业务可理解的高级方法名称来做到这一点,例如

    RegisterClient(client);
    InvalidateMandate(mandate);
    

    然后,您的业务规则变得更容易理解,您几乎可以很愉快地实施它们。

    BusinessRuleRepository.add(ModelClass, "RegisterClient", function(){
        if (!Session.can('RegisterClient')) { fail('Unauthorized'); }
    })
    

    【讨论】:

    • 非常感谢您花时间回答问题。
    猜你喜欢
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    相关资源
    最近更新 更多