再谈 API 的撰写 - 总览

背景

去年我写过一篇文章:撰写合格的 REST API。当时 Juniper 裁掉了我们在德州的一支十多人的团队,那支团队有一半的人手在之前的半年里,主要的工作就是做一套 REST API。我接手这个工作时发现那些API写的比较业余,没有考虑几个基础的HTTP/1.1 RFC(2616,7232,5988等等)的实现,于是我花了些时间重写,然后写下了那篇文章。

站在今天的角度看,那时我做的系统也有不少问题,很多 API 之外的问题没有考虑:

  • API 的使用文档。当时我的做法是把文档写在公司使用的协作系统 confluence 里,但这样做的最大的问题是:代码和文档分离,不好维护。
  • API 的监控。整个 API 系统没有一个成体系的监控机制,各种 metrics 的收集严重依赖 API 的实现者处理,拿时髦的话说就是没法 orchestrate。
  • API 的测试。做过大量 API 工作的人都知道,为 API 写测试用例是非常痛苦的事情,你不但要对 API 使用的代码做 unit test,还需要对 API 本身做 smoke test(最基本的 functional test),保证所有 API 是可用的,符合预期的。由于需要撰写的测试用例的数量巨大,一般我们写写 unit test 就了事。

理想情况下,一个 API 撰写完成,应该能够自动生成文档和测试用例,而 API 系统也应该提供一整套统计的 API 用于生成 metrics。缺省情况下,API 系统本身就应该收集很多 metrics,比如每个 API 的 response time,status code 等等,使用 collectd / statd 收集信息,并可以进一步发送到 datadog / new relic 这样的 APM 系统。

在 adRise,我们有一套运行了数年的 API 系统,不符合 RFC,(几乎)没有文档,(几乎)没有测试,(几乎)没有监控,最要命的是,它的开发效率和运行效率都不高。因此,过去的一两个月,我主导开发了一个全新的 API 系统。

目标

在打造一个新的系统之前,我们需要确立一些目标。这是我在设计 API 时写下的一些目标:

  • A well defined pipeline to process requests
  • REST API done right (methods, status code and headers)
  • Validation made easy
  • Security beared in mind
  • Policy based request throttling
  • Easy to add new APIs
  • Easy to document and test
  • Introspection

其中,introspection 包含两层意思:

  • API 系统自动收集 metrics,自我监控
  • 无论是撰写者,还是调用者,都很很方便的获取想要获取的信息

选型

有了以上目标,接下来的就是进行技术选型。技术选型是无法脱离团队单独完成的,如果让我个人选择一个基础语言和框架,我大概会选择基于 Erlang/OTP,使用 Elixir 开发的 Phoenix,或者,干脆使用 Plug(Phoenix 的基石)。因为 Plug / Phoenix 通过组合来构建 pipeline 的方式很符合我的思维,Elixir 对 macro 的支持和 Erlang 语言核心的 pattern matching 让诸如路由这样的子系统高效简洁美观,而 Erlang / OTP 的高并发下的健壮性又是一个 API 系统苦苦追求的。

然而,我需要考虑团队的现实。在 adRise,我们使用 node.js 作为后端的主要技术栈(还有一些 PHP / Python / scala),因此 API 系统最好是基于 node.js 来完成。node.js 下有很多适合于写 API 的框架,比如说:express,restify,hapi,loopback,sails.js 等。在综合考察了这些框架之后,我选择了 restify,原因有三:

  • 接口和结构非常类似 express(团队对此非常有经验),但比 express 更专注于 REST API
  • 一系列 middleware 和 route actions 可以组成一个灵活高效的 pipeline
  • 简单,可扩展性强,容易和其他库结合,很适合作为一个新的框架的起点
  • 源代码很好理解,一天内就能读完(好吧这是个凑数的原因)

事实证明,这是个还算不错的选择。

定下了基础框架,接下来就是选择核心的组件。首先就是 validator。很多人做系统并不重视 validator,或者没有一个统一的视角去看待 validator,这样不好。任何一个系统的运行环境都是个肮脏的世界,到处是魑魅魍魉,污秽不堪;而我们希望系统本身是纯净的,是极乐净土,那怎么办?

简单,打造一堵叹息的墙壁,挡住五小强

简单,净化输入输出。对于一个 API,什么样的 header,body 和 querystring 是被允许的?什么样的 response body 是合格的?这个需要定义清楚。所以我们需要一个合适的 validator。如果说挑框架似四郎选秀女,环肥燕瘦让你眼花缭乱,选 validator 就像姜维点将,看来看去只有王平廖化堪堪可用。在 github 里逛了半天,最后能落入法眼的也只有 joi 和 json schema 可用。

json schema 其实很好用,很贴近各类 API 工具的 schema(swagger 直接就是用 json schema),可惜太 verbose,让程序员写这个有点太啰嗦:

而 joi 是 hapi 提供的 validator,接口很人性化,相同的 schema,描述起来代码量只有前者的 1/3:

而且它还可以比较容易地逆向输出(当然,需要各种适配)成 json schema。输出成 json schema 有什么好处?可以用来生成 swagger doc!swagger 是一种 API 描述语言,可以定义客户端和服务器之间的协议。swagger doc 可以生成 API 的文档和测试UI,比如说:

在接下来的文章中,我会详细介绍 swagger。

我们再看 ORM。经常使用 express 的同学应该了解,express 本身并不对你如何存取数据有过多干涉,任何人都可以按照自己的需求使用其所需要的数据访问方式:可以是 raw db access,也可以使用 ORM。这种灵活性在团队协作的时候是种伤害,它让大家很容易写出来风格很不统一的代码,而且,在写入数据库和从数据库中读取数据的 normalization,离了 ORM 也会带来很多 ad-hoc 的代码。因此,尽管 ORM 背负着很多骂名,我还是希望在涉及数据访问的层面,使用 ORM。

我们的系统的数据库是异构的,因此,纯种的,只对一类数据库有效的 ORM,如 Mongoose / Sequelize 就不太合适,上上之选是接口支持多种不同数据库,在需要特殊查询或者操作的时候还能转 native 的 ORM。这样,让工程师的效率和系统的效率达到一个平衡。在 node.js 下,这样的 ORM 不多,可用的似乎只有 waterline。waterline 是 sails.js 开源的一个 ORM,支持多种 db 的混合使用,在各个数据库无法统一的操作接口上(比如 mongodb 的 upsert),你可以方便地将其生成的 model 转 native,直接使用数据库的接口。

此外,waterline 的 model 的 schema 使用 json 来描述,这使得它可以很方便地转化成 joi schema,在系统的进出口进行 validation。

接下来是日志系统。一套 API 系统可能包含多台服务器,所以日志需要集中收集,处理和可视化。一般而言,我们可以用 ELK,或者第三方的服务。如果在设计系统之初就考虑日志的集中管理,那么日志的收集应该考虑用结构化的结构,而非字符串。字符串尽管可以使用 grok 来处理,但毕竟效率低,还得为每种日志写 grok 的表达式。由于 node restify 缺省使用 bunyan 作日志,而 bunyan 可以生成 json 格式的日志,因此直接满足我们的需求。

最后我们再看 test framework。一个合格的系统离不开一套合适的 test framework。我的选择是 ava / rewire / supertest / nyc。ava 是一个 unit test framwork,和 mocha / tape 等常见的 test framework 类似,解决相同的问题,不过 ava 能够并发执行,效率很高,而且对 es6 支持很棒,test case 可以返回 Promise,ava 处理剩下的事情。有时候我们需要测试一个模块里没有 export 出来的函数,或者 Mock 一些测试时我们并不关心的函数,rewire 可以很方便地处理这样的问题。supertest 可以做 API 级别的测试,也就是 functional testing,而 nyc 可以用来做 test coverage。

今天先讲这么多,下次谈谈如何架构一个 API 系统。

原文发布于微信公众号 - 程序人生(programmer_life)

原文发表时间:2016-03-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术分享

谈谈MVC模式

1. 如何设计一个程序的结构,这是一门专门的学问,叫做"架构模式"(architectural pattern),属于编程的方法论。 MVC模式就是架构模式的一...

2027
来自专栏张善友的专栏

让应用程序与您形影相随-PortableApps.com

作为一名 IT 专业人员,您可能会经常需要从一台计算机移到另一台计算机。当您这样做时,您可能会希望能拥有一组随时可用的标准应用程序、工具和文档。满足这些需求的一...

2095
来自专栏张善友的专栏

分布式存储系统Cassandra

从新闻 Twitter用户暴增20倍 计划弃用MySQL中看到了Cassandra数据库,网上查了一下这个Cassandra的资料,找到一篇较详细的中文资料: ...

2818
来自专栏IT派

谈谈 MVC 模式

如何设计一个程序的结构,这是一门专门的学问,叫做"架构模式"(architectural pattern),属于编程的方法论。

1120
来自专栏魏琼东

AgileEAS.NET平台开发实例-药店系统-快速的SAAS开发体验

一、AgileEAS.NET应用开发简介 在4月份,callhot写过一系列的有关于AgileEAS.NET平台的开发应用的系列AgileEAS.NET平台开发...

2516
来自专栏SDNLAB

数据中心网络虚拟化 主流平台产品介绍

为了对数据中心网络虚拟化有个初步的认识,本文将对当前比较主流的几款商业平台进行介绍,包括VMware公司的网络虚拟化技术,IBM公司的Dove及开源的OpenD...

3515
来自专栏依乐祝

.NET Core实战项目之CMS 第十章 设计篇-系统开发框架设计

这两天比较忙,周末也在加班,所以更新的就慢了一点,不过没关系,今天我们就进行千呼万唤的系统开发框架的设计。不知道上篇关于架构设计的文章大家有没有阅读,如果阅读后...

1332
来自专栏java一日一条

电商网站秒杀与抢购的系统架构

在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战。如果Web系统不做针对性的优化,会轻而易举地陷入到异常...

1882
来自专栏个人分享

一次极限项目管理,设计,开发,联调与测试

     什么是All In? 是你不知道全力做这件事情会得到什么。但你只想把它做好的感觉。

1211
来自专栏Flutter入门到实战

开发工具总结(9)之开源项目的README文档的最全最规范写法

版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/813b70d5b0de

1081

扫码关注云+社区

领取腾讯云代金券