如果你曾经遇到过不得不使用第三方API的情况,不管是来自你公司的另一个团队,还是来自某个在地球另一端维护的开源项目,你很可能在途中遇到了某种API文档。
现在,根据这个文档的质量,你的工作很可能要么是轻而易举,要么是绝对的地狱。每个经历过这种情况的人都会知道,文档是很重要的,不仅对你当前项目的成功,而且对未来使用你的代码或API的每一个项目的成功,无论大小。
然而,尽管根据我的经验,大多数专业的软件开发人员都知道这一点,但经常发生的情况是,在开发过程中根本就没有做文档,或者没有对其给予足够的重视。
特别是在最近,根据我的经验,这背后的原因与技术方面没有什么关系,而是与开发软件的开发过程和业务流程有关。人们争辩说,他们想快速迭代,或者他们只是在建立一个MVP来验证一个商业想法。
现在,这些论点并没有错--如果我们甚至不确定我们正在建造的东西是否是一个有价值的东西,为什么还要为其API的未来可能的消费者投资呢?我相信这种把文档和测试往后推一点的方法对于深入的文档和大规模的端到端测试来说其实是可以的。
然而,在这篇文章中,我想谈谈创建复杂的分布式系统所固有的一种特定的文档--API文档。我相信,尤其是在使用微服务架构的时候,在开发周期的早期为这些服务的接口提供文档,对协作和迭代速度有很大好处。
例如,拥有API文档对于从另一个团队获得早期反馈是相当有价值的,该团队将在未来的某个时间点使用该API。文档,特别是如果它可以被每个人访问的话,也可以成为围绕复杂的交互和工作流程进行讨论的有用工具,以便发现可能的问题。
这种能够及早发现误解和直接的思维错误的力量,就是为什么在我看来,API文档应该在你写第一个端点的时候就可以使用。
在接下来的几段话中,我将提供一些例子和对我在使用Node.js创建良好的文档化Web API时喜欢使用的过程的见解。
我们将涵盖。
- hapi.js
- 我常用的Node.js网络框架,用于健壮和可扩展的网络应用程序
- joi
- 用于JavaScript对象的模式验证库
- [hapi-swaggered]&[hapi-swaggered-ui]
- 用于从你的hapi.js路由中自动创建swagger-ui文档的插件
让我们开始吧!
hapi.js和joi
使用Node.js构建Web应用的方法数不胜数,但在过去的几年里,我最喜欢的方法无疑是使用伟大的hapi.js 。它是由沃尔玛创建的,目的是为了可持续地处理其服务的巨大负载,同时还能快速迭代。沃尔玛支持这个项目并将其用于他们自己的大规模服务,你知道它将会有规模(想想黑色星期五)。
关于hapi ,我最喜欢的可能是它无可挑剔的文档,它总是最新的(有非常频繁的新版本),以及它进一步发展的理智方法。
在网上和他们的[教程]中,有几个很好的使用hapi 的教程,但只是为了让你了解它的样子,请看这个例子。
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 3000, host: 'localhost' });
server.route({
method: 'GET',
path: '/{name}',
handler: function (request, reply) {
reply(`Hello, ${request.params.name}!`);
}
});
server.start((err) => {
if (err) {
throw err;
}
console.log(`Server running at: ${server.info.uri}`);
});
当然,在hapi 生态系统中,有无穷无尽的方法来扩展这个例子,如日志、认证、静态内容、缓存等等,横跨整个网络世界。
然而,我特别喜欢并在很多其他JavaScript项目中使用的一个东西是joi,它是一个JavaScript对象的模式验证器。现在,这本身并不那么令人激动,但是它对诸如表单验证之类的东西却相当有用。
当把hapi 和joi 一起使用时,真正酷的事情是你可以把它直接插入到hapi 路由中,进行自动请求和响应验证。考虑一下上面例子中的/{name} 路由,假设我们想确保名字不超过10个字符。
server.route({
method: 'GET',
path: '/{name}',
handler: function (request, reply) {
reply(`Hello, ${request.params.name}!`);
},
config: {
validate: {
params: {
name: joi.string().max(10)
}
}
}
});
就这样了。而且我们还可以对路径参数、查询参数、头信息和请求有效载荷进行验证。如果请求没有通过验证,hapi ,将以这样一个形式良好的错误来回应。
{
"error": "Bad Request",
"message": "the length of name must be less than or equal to 10 characters long",
"statusCode": 400,
"validation": {
"keys": [
"name"
],
"source": "params"
}
}
这些错误是根据joi 模式自动生成的,并且可以使用joi 的全部力量进行定制,这一点是相当重要的。
我们可以做的另一件事是响应验证。因此,我们可以为每个状态代码指定我们的响应应该是什么样子的。在下面的例子中,我们定义了一个基本的错误对象,并为我们的验证错误扩展了它。然后,我们定义不同状态代码的响应应该是什么样子。
const success = joi.object({
success: joi.boolean().truthy().required(),
});
const error = joi.object({
statusCode: joi.number().required(),
error: joi.string(),
message: joi.string(),
});
const validationError = error.keys({
validation: joi.object().optional(),
});
server.route({
method: 'GET',
path: '/{name}',
handler: function (request, reply) {
reply(`Hello, ${request.params.name}!`);
},
config: {
validate: {
params: {
name: joi.string().max(10)
}
}
}
response: {
status: {
200: success,
400: validationError,
500: error
},
},
});
现在,这本身已经很强大了,在测试和构建我们的应用程序时可以捕捉到很多错误。然而,我们可以进一步使用这些伪类型注释来记录我们正在构建的API。为了这个目的,我们可以使用hapi-swaggered和hapi-swaggered-ui。
hapi-swaggered & hapi-swaggered-ui
有几个软件包可以从hapi 路径中创建一个swagger 文档,但到目前为止我只使用了hapi-swaggered ,并且对它非常满意。如果你不知道swagger ,我就推荐你去他们的网站--它基本上是一个框架,可以做关于REST APIs的各种事情。
我们感兴趣的是[swagger-ui],它允许我们以互动的方式可视化我们的API。还提供了一个[演示]。
这相当酷,足以提供我在文章开始时概述的好处。这个UI很容易浏览,人们可以真正感受到手头的API。
好了,通过hapi的精心设计的插件系统,整合hapi-swaggered 和hapi-swaggered-ui 是非常简单的。
const hapi = require('hapi');
const swaggered = require('hapi-swaggered');
const swaggeredUI = require('hapi-swaggered-ui');
const vision = require('vision');
const inert = require('inert');
const server = new hapi.Server();
server.connection({ port: config.get('port'), labels: ['api'] });
server.register([
inert,
vision,
{
register: swaggered,
options: {
info: {
title: 'My Cool API',
description: 'API documentation for my cool API',
version: '1.0',
},
},
},
{
register: swaggeredUI,
options: {
title: 'My Cool API',
path: '/docs',
swaggerOptions: {
validatorUrl: null,
},
},
}
], (err) => {
if (err) {
throw err;
}
server.start((err) => {
if (err) {
throw err;
}
console.log(`Server running at: ${server.info.uri}`);
})
});
有几个有据可查的配置选项,但这个默认配置应该提供我们现在所需要的一切。我们基本上有一个/docs 路线,在这里我们可以看到我们的路线的可视化swagger-ui 。
当然,现在我们需要给我们希望在API文档中显示的路由贴上上面指定的api 标签。我们还可以给路由添加一些元数据。
server.route({
method: 'GET',
path: '/{name}',
handler: function (request, reply) {
reply(`Hello, ${request.params.name}!`);
},
config: {
tags: ['api'],
description: 'Says hello!',
notes: 'Some important notes when using this',
validate: {
params: {
name: joi.string().max(10)
}
}
}
});
一旦贴上标签,路由就会在swagger-ui ,所有可用的模式数据都会被解析。作为参考,你可以查看swagger petstore的演示,看看这可能是什么样子的。
根据你的joi 模式,该界面将显示端点期望的数据以及响应的样子。
你可以用joi 做的另一件很酷的事情是用元数据来注释你的模式,就像这样。
const validationError = error.keys({
validation: joi.object().optional(),
}).meta({ className: 'ValidationError' });
这些元数据会被解析并包含在swagger-ui ,为你提供漂亮的、可重复使用的类型定义。
关于joi 、hapi 和hapi-swaggered 的进一步配置,我参考了它们各自的文档,这应该能满足你的所有需求。
总结
这篇文章提供了一个例子,说明如何从Node.js网络应用的现有端点自动生成一个交互式API文档。如果你不喜欢Node.js,也有类似的库和工具适用于其他语言和网络框架,本帖没有涉及。
我相信,由于这种方法的简单设置和配置,以及这些工具的互动所提供的模式定义的巨大力量,这样做的好处远远超过了让它发挥作用所需的额外努力。
特别是Node.js,它在迭代速度方面很出色,但在健壮性和维护方面肯定会有一些折衷,在我看来,记录良好的验证模式的方法,在运行时检查,有相当大的好处。
另外,在为一个项目设置了这个过程后,你可以简单地在其他具有相同堆栈的项目中重新使用它,因为配置很可能是相当相似的。
就这样吧。祝你在记录你的API时愉快!:)