您是否曾试图协调一组不断增加的各种工具的行为,以便创建一些较大的系统?就我而言,我们的开发团队需要组装一个持续交付管道,并对其操作进行排序。实现此操作的一个选择是使用一个支持创建、标记和订阅事件的通知服务。
可以将这种服务应用于构建系统,宣布新版本的可用性。然后,管道的下游组件才可以接收通知,并根据新版本的出现来采取行动。行动可能包括主动配置新的测试系统和运行回归测试套件。
“通知服务并不是必须包含令人兴奋的消息才是有用的通知服务。”
我开发了一个简单的通知服务,使用 Node.js 运行时作为对使用 RESTful
API 快速开发 HTTP 服务器的实现。我还选择了使用 MongoDB 作为后端。其面向文档的特性对于快速开发原型似乎是完美的,我不必提供对
ACID(原子性、一致性、隔离性和持久性)属性的严格支持。
要了解如何实际创建通知服务,请单击 Run the app 来检索由通知服务处理的最近五个事件信号的日志。您看到的大多数信号都是由我们的产品构建系统生成的,而且都是
JSON 格式的。
构建类似应用程序的要求
对 Node.js 和 Node.js 开发环境有基本的了解
拥有以下这些 Node.js 模块:Express、Underscore、Nodemailer
用于自动化测试的其他模块:Mocha、Should、Supertest
一个 MongoDB NoSQL 数据库
阅读:Node.js 超越基础
安装了 Node 和 MongoDB 之后,您就可以使用 Node 的包管理器(npm)来加载所需的依赖关系。请参见从
DevOps Services 下载的代码中的 package.json 文件,了解支持这个应用程序的模块和框架的具体版本。
让我们开始吧!
步骤 1. 创建 API
该 API 与 REST 类似,资源的访问和修改都是通过使用惟一 URL
来完成的,这些 URL 应用了以下 HTTP 动词:GET、PUT、POST 和 DELETE。使用 HTTP
消息功能的任何应用程序都可以访问该服务。这种方法为将来的基于浏览器的工具提供了一个干净的界面。
由该服务管理的主要资源是事件和订阅。一个 API 可用于每个创建、读取、更新和删除操作。
我基于 Express 框架来构建此 API,因为该框架包含一个强大的
Web 应用程序开发特性集,而且只需要遵循惯例,就能非常轻松地使用它。我们来看一看应用程序的主 server.js
模块中的以下代码,该应用程序用于设置定向到这些事件的传入请求的路由:
console.log ('registering event routes with express'); app.get('/events', event.findAll); app.get('/events/:id', event.findById); app.post('/events', event.addEvent); app.put('/events/:id', event.updateEvent); app.delete('/events/:id', event.deleteEvent); |
然后,可以为订阅重复相同的基本模式:
console.log ('registering subscription routes with express'); app.get('/subscriptions', sub.findAll); app.get('/subscriptions/:id', sub.findById); app.post('/subscriptions', sub.addSubscription); app.put('/subscriptions/:id', sub.updateSubscription); app.delete('/subscriptions/:id', sub.deleteSubscription); |
除了针对事件和订阅的这些基本操作之外,还有另外两个重要的 API,具体为:
一个用于标记事件的 API:
app.post('/signals', signal.processSignal); |
一个用于检索最近的信号日志的 API:
app.get('/signallog', signallog.findRecent); |
步骤 2. 使用后端
MongoDB 提供了服务的持久存储。我直接使用后端文档(集合),而不是使用一个对象映射器。Node.js
原生驱动程序提供了所需的全部功能。
两个主要的资源(事件和订阅)被直接映射到数据库集合。用户可以直接访问它们。例如,像前面的代码段定义的那样,对事件资源的
GET 请求会产生对 event.findAll 的调用。findAll 函数是在 events.js
模块中定义的:
exports.findAll = function(req, res) { mongo.Db.connect(mongoUri, function (err, db) { db.collection('events', function(er, collection) { collection.find().toArray(function(err, items) { res.send(items); db.close(); }); }); }); } |
findAll 函数简单地连接到数据库,获得事件集合的一个句柄,并返回所有元素,然后关闭连接。所有 API
请求都以这种类似的非常直接的方式进行处理。在下面的示例中,删除事件请求由 events.js 模块中的
deleteEvent 函数处理:
exports.deleteEvent = function(req, res) { var id = req.params.id; mongo.Db.connect(mongoUri, function (err, db) { db.collection('events', function(err, collection) { collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) { res.send(req.body); db.close(); }); }); }); } |
和前面一样,建立一个连接,并获得某个集合的句柄。然后,根据所提供的 ID 来删除集合元素,并关闭连接。
步骤 3. 发送通知
除了对托管资源执行创建、替换、更新和删除操作之外,在标记事件时,该服务还会将消息发送给适当的订阅端点。该服务的第一个版本提供了电子邮件通知。接下来的几个代码段将跟踪端到端的整个过程。
可以通过信号路由来接收信号。您可以从 server.js 模块查看该信号,我们使用 signals 模块中的
processSignal 函数来处理此路由:
app.post('/signals', signal.processSignal); |
exports.processSignal = function(req, res) { var signal = req.body; console.log('Processing Signal: ' + JSON.stringify(signal));
mongo.Db.connect(mongoUri, function (err, db) { db.collection('subscriptions', function(err, collection) { collection.find().toArray(function(err, items) { matches = _.filter(items, function(sub){return sub.eventTitle == signal.eventTitle}); _.each(matches, function (sub) {processMatch(sub, signal)}); res.send(matches); }); }); });
|
和以前一样,建立一个数据库连接,并获得相关集合的一个句柄。然后,我们通过筛选来获得与 Event 标题匹配的所有订阅。随后,通过
processMatch 函数来处理此匹配订阅子集的每个元素。
您是否注意到我使用了 _.filter 和 _.each?这些都来自一个名为 Underscore 的非常酷的库,该库提供了一些巧妙的辅助函数,使用过面向对象的语言(比如
Ruby)的??用户应该对此非常熟悉。
processMatch 函数简单地为电子邮件消息分配了适当的值,并调用了 mailer.sendMail
函数:
function processMatch(subscription, signal) { opts = { from: 'Simple Notification Service', to: subscription.alertEndpoint, subject: subscription.eventTitle + ' happened at: ' + new Date(), body: signal.instancedata } // Send alert mailer.sendMail(opts); } |
sendMail 函数也非常直观,因为我使用了名为 NodeMailer 的另一个库。您可以看到,我的
sendMail 函数只使用了一些 NodeMailer 功能来发送电子邮件通知。从根本上讲,它使用了一些身份验证值来初始化传输对象,然后指定消息内容(主题、正文、地址等),并开始发送:
exports.sendMail = function (opts) { var mailOpts, smtpTransport;
console.log ('Creating Transport');
smtpTransport = nodemailer.createTransport('SMTP',
{
service: 'Gmail',
auth: {
user: config.email,
pass: config.password
}
});
// mailing options
mailOpts = {
from: opts.from,
replyTo: opts.from,
to: opts.to,
subject: opts.subject,
html: opts.body
};
console.log('mailOpts: ', mailOpts);
console.log('Sending Mail');
// Send mail
smtpTransport.sendMail(mailOpts, function (error,
response) {
if (error) {
console.log(error);
}else {
console.log('Message sent: ' + response.message);
}
console.log('Closing Transport');
smtpTransport.close();
});
} |
步骤 4. 测试应用程序
我已经介绍了通知服务的主要功能。接下来,我将快速介绍自动化测试。
Node.js 生态系统中的另一个巧妙的框架是用于测试的 Mocha。结合使用 Mocha 与附加项(比如
supertest 用于 HTTP,should 用于断言),为 API 提供可读的功能测试。以下是来自
eventtests.js 模块的片段,用于验证 readbyid Event API:
it('should verify revised News flash 6 event', function(done) { request(url) .get('/events/'+ newsFlash6id) .end(function(err, res) { if (err) { throw err; } res.should.have.status(200); res.body.should.have.property('title'); res.body.title.should.equal('News flash 6 - revised'); done(); }); }); |
这种语法和格式具有非常好的可读性,我希望它会比较容易维护。
我的自动化功能回归测试套件包括每个 API 的测试,我已经建立了一个测试文件夹,其中包括所有测试。Mocha
承认这一惯例。要运行测试套件,可从项目的根发出命令 mocha。
如下图所示,如果没有失败,测试套件的输出应该非常简洁:
我目前要做的是在本地运行此回归测试套件,(通常)在我做了修改时会执行此操作。我总是在推送变更之前运行它,以实现源代码控制。该服务的其中一个实例在
Bluemix 平台上运行, 如果您单击了 Run the app,就已经与此实例进行了互动,而且您还会看到一个最近的事件信号的日志。
目前,我实际上有两个环境:一个是我的本地开发系统,我在上面进行修改并执行测试,另一个环境是 Bluemix
上的生产系统。这样的安排目前运作良好,但我的下一个步骤是在 BlueMix 上创建另一个托管的临时环境。新的临时环境让我可以在更接近生产的环境中运行回归套件,并在修改源代码后,在生产环境中开启自动测试和部署等选项。当我迈出这一步时,我一定会在博客上记录这一切。
考虑构建一个类似的、简单的通知服务,用大量的工具来管理事件。尝试一下,并让我们知道您的尝试结果!
|