如何在客户端和服务器之间共享 Javascript 业务规则?
How to share Javascript Business Rules between Client and Server?
我正在创建一个 MEAN 堆栈,我想澄清以下内容。
从编码标准的角度来看,我知道验证应该同时在客户端和服务器端执行。我想要实现的是执行完全相同的验证代码,这样我就不会再次重复代码。这更像是客户端和服务器端的共享代码。
so 如何让 angular js 和 Express js 调用相同的 .js 文件来执行验证?有可能吗?
谢谢!
你一定能做到。 RemObjects DataAbstract (http://old.wiki.remobjects.com/wiki/Business_Rules_Scripting_API) 使用了这种方法。这里的原则是定义将应用于客户端和服务器或仅应用于服务器的业务规则。您几乎永远不必只在客户端检查业务规则,因为您永远无法 "trust" 客户端检查您的业务规则。
CQRS 和 DDD 是两个可以在这方面为您提供帮助的架构原则。域驱动设计会 "clean" 或 "refine" 您的代码,将基础架构从核心 "domain" 逻辑中推开。业务规则仅适用于域,因此最好将域与其他域隔离开来。
命令-查询-职责-隔离。我很喜欢这个。基本上,您定义了一组命令,这些命令将在应用之前进行验证。没有更多类似机器的代码看起来像 Model.Set('a', 2)。使用此原则,您的代码将类似于 MyUnderstandableBusinessObject.UnderstandableCommand(aFriendlyArgument)。在应用业务规则时,这非常方便,因为您的实际命令反映了您域的用例。
我在做node.js / javascript 项目时也经常遇到这个问题。问题是您没有客户端和服务器都能理解的标准化 ORM。这是自相矛盾的,因为 node.js 和浏览器使用相同的语言 运行。当我被吸引到 Node.js 时,我告诉自己,伙计,客户端和服务器都是 运行 相同的语言,这将节省很多时间。但这有点错误,因为那里没有那么多成熟和专业的工具,即使 npm 真的很活跃。
我也想构建一个 ORM,既可以被 client/server 理解,又可以向它添加关系方面(以便它与 SQL 兼容),但我有点放弃了该项目。 https://github.com/ludydoo/affinity
但是,还有其他一些解决方案。 Backbone 是一个,而且是轻量级的。
此处业务规则检查的实际实施是您将要做的工作。您需要将模型中的 "validation" 部分提取到另一个可以共享的对象中。让您入门的东西:
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'); }
})
我正在创建一个 MEAN 堆栈,我想澄清以下内容。
从编码标准的角度来看,我知道验证应该同时在客户端和服务器端执行。我想要实现的是执行完全相同的验证代码,这样我就不会再次重复代码。这更像是客户端和服务器端的共享代码。
so 如何让 angular js 和 Express js 调用相同的 .js 文件来执行验证?有可能吗?
谢谢!
你一定能做到。 RemObjects DataAbstract (http://old.wiki.remobjects.com/wiki/Business_Rules_Scripting_API) 使用了这种方法。这里的原则是定义将应用于客户端和服务器或仅应用于服务器的业务规则。您几乎永远不必只在客户端检查业务规则,因为您永远无法 "trust" 客户端检查您的业务规则。
CQRS 和 DDD 是两个可以在这方面为您提供帮助的架构原则。域驱动设计会 "clean" 或 "refine" 您的代码,将基础架构从核心 "domain" 逻辑中推开。业务规则仅适用于域,因此最好将域与其他域隔离开来。
命令-查询-职责-隔离。我很喜欢这个。基本上,您定义了一组命令,这些命令将在应用之前进行验证。没有更多类似机器的代码看起来像 Model.Set('a', 2)。使用此原则,您的代码将类似于 MyUnderstandableBusinessObject.UnderstandableCommand(aFriendlyArgument)。在应用业务规则时,这非常方便,因为您的实际命令反映了您域的用例。
我在做node.js / javascript 项目时也经常遇到这个问题。问题是您没有客户端和服务器都能理解的标准化 ORM。这是自相矛盾的,因为 node.js 和浏览器使用相同的语言 运行。当我被吸引到 Node.js 时,我告诉自己,伙计,客户端和服务器都是 运行 相同的语言,这将节省很多时间。但这有点错误,因为那里没有那么多成熟和专业的工具,即使 npm 真的很活跃。
我也想构建一个 ORM,既可以被 client/server 理解,又可以向它添加关系方面(以便它与 SQL 兼容),但我有点放弃了该项目。 https://github.com/ludydoo/affinity
但是,还有其他一些解决方案。 Backbone 是一个,而且是轻量级的。
此处业务规则检查的实际实施是您将要做的工作。您需要将模型中的 "validation" 部分提取到另一个可以共享的对象中。让您入门的东西:
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'); }
})