背景

之前团队主要的工作就是做一套 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 的撰写 - 总览的更多相关文章

  1. 谈 API 的撰写 - 架构

    在 谈 API 的撰写 - 总览 里我们谈到了做一个 API 系统的基本思路和一些组件的选型,今天谈谈架构. 部署 首先要考虑的架构是部署的架构.部署的方案往往会深刻影响着系统的结构.我们需要问自己一 ...

  2. 谈 API 的撰写 - 子系统

    在做一个系统时,有一些子系统几乎是必备的:配置管理,CLI,以及测试框架. 配置管理 我们先说配置管理.一个系统的灵活度,和它的配置管理是离不开的.系统中存在的大量的预置的属性(下文简称 proper ...

  3. 再谈API GateWay服务网关

    前面在谈微服务架构的时候,我博客上转过Chris Richardson 微服务系列中对微服务网关的描述: 通常来说,使用 API 网关是更好的解决方式.API 网关是一个服务器,也可以说是进入系统的唯 ...

  4. 浅谈API网关(API Gateway)如何承载API经济生态链

    序言 API经济生态链已经在全球范围覆盖, 绝大多数企业都已经走在数字化转型的道路上,API成为企业连接业务的核心载体, 并产生巨大的盈利空间.快速增长的API规模以及调用量,使得企业IT在架构上.模 ...

  5. 浅谈API设计

    为什么需要了解一些API设计? 只要你编程,你就是API Designer 一个好的设计,模块之间的耦合应该也是API级别的 一个程序,如果你独立开发,那你既是API的Designer,也是API的U ...

  6. 浅谈API安全设计

    一.简述 安全是恒久的话题,如果不注意防范,会带来很严重的后果.比如: 1.接口被大规模调用消耗系统资源,影响系统的正常访问,甚至系统瘫痪 2.数据泄露 3.伪造(篡改)数据,制造垃圾数据 4.App ...

  7. 谈API网关的背景、架构以及落地方案

    Chris Richardson曾经在他的博客上详细介绍过API网关,包括API网关的背景.解决方案以及案例.对于大多数基于微服务的应用程序而言,API网关都应该是系统的入口,它会负责服务请求路由.组 ...

  8. 小程序入门心得(不谈api)

    小程序入门 一.准备 首先先去微信公众平台注册一个小程序账号,去拿到一个AppID(没AppID也可以开发,只是有些功能会受限),注册成功后到开发设置获取自己的AppID,即使有AppID有些功能还是 ...

  9. 浅谈API和SDK的区别

    首先了解一下他们的定义 API:application program interface 应用程序接口 通常表示一些事先定义好的函数,为了向外部提供一组功能的实现,实现和其他软件的交互 SDK:so ...

随机推荐

  1. python 使用入的坑

    如测试代码,并没有将li.li_ 的交集查询出来 li=[1,2,3,4,5] li_=[2,5,6,7,9] for i in li_: if i in li: li_.remove(i) prin ...

  2. 项目记事【StreamAPI】:使用 StreamAPI 简化对 Collection 的操作

    最近项目里有这么一段代码,我在做 code-review 的时候,觉得可以使用 Java8 StreamAPI 简化一下. 这里先看一下代码(不是源码,一些敏感信息被我用其他类替代了): privat ...

  3. AtCoder Regular Contest 092 B Two Sequences

    题目大意 给定两个长为 $n$ 个整数序列 $a_1, \dots, a_n$ 和 $b_1, \dots, b_n$ .求所有 $a_i + b_j$($1\le i, j\le n$)的 XOR ...

  4. POJ 1941 The Sierpinski Fractal ——模拟

    只需要开一个数组,记录一下这个图形. 通过一番计算,发现最大的面积大约是2k*2k的 然后递归下去染三角形. 需要计算出左上角的坐标. 然后输出的时候需要记录一下每一行最远延伸的地方,防止行末空格过多 ...

  5. BZOJ1415 [Noi2005]聪聪和可可 【SPFA + 期望dp记忆化搜索】

    题目 输入格式 数据的第1行为两个整数N和E,以空格分隔,分别表示森林中的景点数和连接相邻景点的路的条数. 第2行包含两个整数C和M,以空格分隔,分别表示初始时聪聪和可可所在的景点的编号. 接下来E行 ...

  6. Python 读取 pkl文件

    使用python 的cPickle 库中的load函数,可以读取pkl文件的内容 import cPickle as pickle fr = open('mnist.pkl') #open的参数是pk ...

  7. python 读取文件夹下的图片进行处理

    python的os模块中有一个listdir函数可以遍历读取文件夹下的文件. import os for filename in os.listdir(r"./file"): #l ...

  8. 幸运数字(bzoj 1853)

    Description 在中国,很多人都把6和8视为是幸运数字!lxhgww也这样认为,于是他定义自己的“幸运号码”是十进制表示中只包含数字6和8的那些号码,比如68,666,888都是“幸运号码”! ...

  9. 【CF1068B】LCM(数学)

    题意:给定b,求lcm(a,b)/a有几种不同的取值 b<=1e10 思路:只有a取b的因子时答案两两不同 #include<cstdio> #include<cstring& ...

  10. Firebug Console API

    原文发布时间为:2011-06-06 -- 来源于本人的百度文章 [由搬家工具导入] Console API 当打开 firebug (也包括 Chrome 等浏览器的自带调试工具),window 下 ...