使用peasy-js编写可重用的JavaScript业务逻辑

Stephan Max同行评审了使用peasy-js编写可重用JavaScript业务逻辑。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

平板电脑,笔记本电脑和手机通过电线连接到点亮的灯泡

在编写应用程序时,我们经常将有价值的业务逻辑与特定于框架的代码结合在一起。 例如,使用Angular时 ,通常在服务,控制器甚至指令之间分散业务逻辑。

这也适用于为后端编写的JavaScript,在该处常见的是用业务逻辑乱扔我们的Sails (例如)控制器动作,这些业务逻辑直接通过ORM / ODM(例如Mongoose)消耗我们的数据访问逻辑,以及其他横切关注点。

这种耦合通常导致难以重用,扩展,测试,采用或迁移到新技术的代码。

在本文中,我将向您展示如何使用peasy-js库帮助您构建业务逻辑,其方式应使其在应用程序的前端和后端之间具有很高的可重用性,并且可以在不同的应用程序之间轻松移植构架。

披露 :我是peasy-js的作者

我们应该停止使用框架吗?

相反,我相信这些框架在客户端和服务器上都能带来巨大的好处。 但是,我建议的是,通过创建完全不了解其使用者的代码,将业务逻辑抽象为可组合的单元。

通过对业务逻辑进行组件化,我们可以使用可想象的任何JavaScript客户端,服务器,数据访问技术和框架,轻松地在任何应用程序体系结构中测试,交换,重新排列,重用和使用这些组件。

分离您的业务逻辑

peasy-js是一个中间层框架,通过以可组合,可重用,可伸缩和可测试的方式创建业务逻辑,使其在应用程序中异想天开地交换出UI,后端和数据访问框架变得微不足道。 换句话说,peasy-js通过编写遵循关注点分离(SoC)的代码,为业务逻辑抽象为可组合单元提供了指导。

框架疲劳

等一下,别走!

我知道您在想什么,“呃,另一个框架?”。 是的,peasy-js确实是一个微框架。 但是,如果我们冒险采用将业务逻辑组件化的途径,那么最终将无论如何编写我们自己的微框架。

peasy-js的设计,开发和测试已经投入了无数的时间,几乎支持所有可以想象的工作流程。 进入门槛低,我希望您会发现学习方面的少量投资非常值得您度过。

但是,如果您发现peasy-js不适合您,那么希望您能对如何使用框架中的某些模式实现自己的业务层有所了解。

主要概念

让我们看看peasy-js为我们提供了什么:

  • 易于使用,灵活的业务和验证规则引擎
  • 可伸缩性和可重用性(将业务和验证逻辑与使用代码和框架分离)
  • 易于测试

peasy-js包含四个主要概念。 下面概述了每个内容,并进行了简要说明,并将在本文中更深入地介绍。

商业服务

BusinessService实现表示一个实体(例如,用户或项目),并负责通过命令公开业务功能。 这些命令封装了CRUD和其他与业务相关的功能。

命令

命令分别负责通过命令执行管道协调初始化逻辑,验证和业务规则执行以及其他逻辑(数据代理调用,工作流逻辑等)的执行

规则

可以创建一个规则来表示验证规则(字段长度或必填)或业务规则(授权,价格有效性等)。 规则由命令使用,并且可以链接,配置为基于先前规则的执行等。规则也可以配置为基于其执行的结果来运行代码。

数据代理

DataProxy负责数据的存储和检索,并充当包含(但不限于)以下各项的数据存储的抽象层:

  • 关系数据库– SQLite,MySQL,Oracle,SQL Server等
  • 文档(NoSQL)数据库– MongoDB,VelocityDB等
  • 服务– HTTP,SOAP等
  • 缓存存储– Redis,Azure等
  • 队列– RabbitMQ,MSMQ等
  • 文件系统
  • 内存中数据存储以进行测试

示例:Peasy-js在实践中

注意:可以在plnkr查看一个简单的浏览器示例,其中涵盖了本节中讨论的所有内容。

以下是在客户端的Angular服务中使用peasy-js编写的业务逻辑的示例:

图A

var dataProxy = new CustomerHttpDataProxy();
var service = new CustomerService(dataProxy);
var customer = { name:  "Frank Zappa", birthDate: new Date('12/21/1940') };
var command = service.insertCommand(customer);

command.execute(function(err, result) {
  if (result.success) {
    customer = result.value;
  } else {
    console.log(result.errors);
  }
});

现在,让我们看一个示例,该示例显示在服务器上的Express.js控制器中使用相同的业务逻辑:

图B

var dataProxy = new CustomerMongoDataProxy();
var service = new CustomerService(dataProxy);
var customer = { name:  "Frank Zappa", birthDate: new Date('12/21/1940') };
var command = service.insertCommand(customer);

command.execute(function(err, result) {
  if (result.success) {
    customer = result.value;
  } else {
    console.log(result.errors);
  }
});

有区别吗? 美好的地方是没有什么区别,除了每个样本中注入到业务服务中的数据代理不同。

请记住,数据代理是我们的数据访问抽象,可以代表文件系统访问,数据库,队列,缓存,内存中和HTTP通信的具体实现。

这种抽象使我们可以根据所需的系统体系结构和配置交换数据代理,同时执行SoC并借以使其自身跨代码库重用,从而简化测试。 可能不会立即显而易见的是,这种方法总是使我们的有效负载服从相同的业务逻辑,而不管数据的源或目的地如何。 这一切很快就会揭晓。

从消费的角度来看,这实际上就是全部。 消费我们用peasy-js开发的业务逻辑将引入一个可识别的主题,而不管我们的体系结构和使用它的技术如何。

说到架构,让我们将注意力转移到以这种方式开发业务逻辑时易于实现的潜在架构,同时更深入地研究peasy-js参与者:

示例体系结构展示了peasy-js如何允许您在客户端和服务器之间共享业务逻辑

从左到右,我们看到客户端应用程序使用了Angular,React,Backbone等框架。要实现最大的可伸缩性,请注意,我们可以将业务逻辑实现从UI框架参与者实现(服务,控制器等)中移出。 。)放入自己的组件化代码库或中间层。

接下来,请注意中间层与Web服务器通信。 数据代理的存在使这成为可能。 参考图A,使用我们的业务逻辑的Angular服务实例化了CustomerHttpDataProxy 。 结果,当执行插入命令时,它将使提供的有效负载服从已配置的任何业务规则。 如果验证成功,则将调用我们的数据代理的相应insert功能,并相应地对我们配置的客户端点发出POST请求。

相反,请注意,node.js应用程序也消耗了前端使用的相同业务逻辑。 参考图B,使用我们的业务逻辑的快递控制器实例化CustomerMongoDataProxy 。 但是,这一次执行插入命令时,我们的数据代理的相应insert功能将使用MongoDB API或ORD(例如Mongoose)对我们的数据库执行INSERT。

最后,由于我们的数据代理实现遵循相同的接口,因此我们可以根据我们希望如何部署应用程序的方式将它们注入业务服务。 在该图中,业务服务使用了与客户端上的HTTP服务交互的数据代理。 但是,一旦Web API处理了请求,Node.js托管的相同业务服务就会注入与数据库,队列,缓存,文件系统等交互的数据代理。

现在,我们从较高的角度了解了peasy-js参与者,以及他们提供的一些好处,让我们逐步介绍它们的示例实现。

CustomerHttpDataProxy

var CustomerHttpDataProxy = function() {

  var request = require('request');

  return {
    insert: insert
  };

  function insert(data, done) {
    request({
      method: 'POST',
      url: 'http://localhost:3000/customers',
      body: data,
      json = true
    }, function (error, response, body) {
        done(error, body);
      }
    );
  };
};

CustomerMongoDataProxy

var CustomerMongoDataProxy = function() {

  var connectionString = 'mongodb://localhost:12345/orderEntry';
  var mongodb = require('mongodb').MongoClient;

  return {
    insert: insert
  };

  function insert(data, done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { return done(err); }
      var collection = db.collection('customers');
      collection.insert(data, function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

};

在这些数据代理代码示例中,请注意它们遵循相同的接口,但是抽象了实现逻辑。 这就是使我们能够扩展应用程序的原因。 通过交换数据代理可以看到,现在我们有了一个真正可重用的中间层,该中间层完全不了解任何使用代码(客户端或服务器)。 这种数据代理设计概念确实是实现可伸缩性和易于测试性的关键。

最后,请注意,为简便起见,我们仅在数据代理中定义了一个插入函数。 但是,在实际的生产环境中,我们很可能会公开所有CRUD操作,甚至可能更多。 您可以在此处看到CustomerMongoDataProxy的完整实现。

客户服务

var CustomerService = BusinessService.extend({
  functions: {
    _onInsertCommandInitialization: function(context, done) {
      var customer = this.data;
      utils.stripAllFieldsFrom(customer).except(['name', 'address']);
      utils.stripAllFieldsFrom(customer.address).except(['street', 'zip']);
      done();
    }
  }
}).service;

在此示例中,我们为CustomerService的公开的insertCommand提供了初始化逻辑,该逻辑在调用数据代理的insert函数之前将字段列入白名单。 通过我们的业务服务实现公开的每个默认CRUD操作都公开与每个命令关联的事件挂钩。 这些方法可以在这里查看。

请注意,我们使用静态BusinessService.extend函数,该函数创建一个通过返回对象的service成员公开的构造函数。 如果您更熟悉这些方法,则还可以自由使用ES6继承或原型继承。 两者的样本都可以在这里找到。

现在,我们已经为业务服务的insertCommand定义了初始化逻辑,让我们创建一些规则并将其相应地连接起来:

名称规则

var NameRule = Rule.extend({
  association: "name",
  params: ['name'],
  functions: {
    _onValidate: function(done) {
      if (this.name === "Jimi") {
        this._invalidate("Name cannot be Jimi");
      }
      done();
    }
  }
});

年龄规则

var AgeRule = Rule.extend({
  association: "age",
  params: ['birthdate'],
  functions: {
    _onValidate: function(done) {
      if (new Date().getFullYear() - this.birthdate.getFullYear() < 50) {
        this._invalidate("You are too young");
      }
      done();
    }
  }
});

请注意,我们在两个代码示例中都使用了静态Rule.extend方法,该方法为我们创建了一个构造函数。 和以前一样,您也可以使用ES6或原型继承( 此处为示例)。

现在,将它们连接到我们的CustomerService中:

连接我们的规则

var CustomerService = BusinessService.extend({
  functions: {
    _onInsertCommandInitialization: function(context, done) {
      var customer = this.data;
      utils.stripAllFieldsFrom(customer).except(['name', 'address']);
      utils.stripAllFieldsFrom(customer.address).except(['street', 'zip']);
      done();
    },
    _getRulesForInsertCommand: function(context, done) {
      var customer = this.data;
      done(null, [
        new NameRule("name", customer.name),
        new AgeRule("age", customer.birthDate)
      ]);
    }
  }
}).service;

在我们的最后一段代码中,我们在业务服务中连接了规则,并将其注入到我们的插入命令执行管道中。 为此,我们提供了_getRulesForInsertCommand()函数的实现。

在此示例中,我们将两个规则都配置为执行而不管彼此的结果如何。 例如,如果NameRule验证失败,AgeRule仍将被评估,反之亦然。

peasy-js规则的优点在于它们非常灵活,可以编写和配置为支持几乎所有可以想象的场景。 例如,我们可以以仅在NameRule验证成功的情况下执行AgeRule的方式链接规则的执行,反之亦然。 当我们的规则需要从数据存储中获取数据时(这可能是昂贵的命中),这非常有用。

有关规则的更多信息,请参见文档

测试我们的业务逻辑

由于peasy-js遵循SOLID编程原则,因此测试我们的业务服务,命令和规则变得非常容易。

让我们看一下如何轻松测试NameRule

it("fails when the supplied name is Jimi", () => {
  var rule = new NameRule("Jimi");
  rule.validate(() => {
    expect(rule.valid).toBe(false);
    expect(rule.association).toEqual("name");
  });
});

it("succeeds when the supplied name is not Jimi", () => {
  var rule = new NameRule("James");
  rule.validate(() => {
    expect(rule.valid).toBe(true);
  });
});

通过保持规则的简单性和针对性,它们不仅易于重用,而且非常易于测试。 这也适用于测试我们的业务服务和自定义命令。

测试本身就是一个很大的话题,因此这是本文的一个很好的终点。 请注意,使用peasy-js测试我们的业务逻辑非常容易,并且可以在此处找到许多测试示例。

想了解更多?

整个订单输入/库存管理示例应用程序可用,展示了用peasy-js编写的中间层。 业务逻辑由Node.js中托管的Express.js应用程序使用,该应用程序公开了Web API。 该示例易于运行,并附带文档,可帮助您在几分钟内启动并运行。

peasy-js鼓励我们编写与使用的框架完全分开的业务逻辑。 这样做的一个有益的副作用是,它可以轻松地以多种方式部署我们的代码。 最后,随着我们当前的框架时代的到来,迁移或采用新框架几乎变得不那么容易了。

您是否在应用中遵循任何类似的模式? 您认为使用peasy-js之类的东西会帮助您编写更好的代码吗? 让我知道您在下面的评论中的想法!

From: https://www.sitepoint.com/reusable-javascript-business-logic-peasy-js/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值