谈 API 的撰写 - 总览
背景
之前团队主要的工作就是做一套 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 的撰写 - 总览的更多相关文章
- 谈 API 的撰写 - 架构
在 谈 API 的撰写 - 总览 里我们谈到了做一个 API 系统的基本思路和一些组件的选型,今天谈谈架构. 部署 首先要考虑的架构是部署的架构.部署的方案往往会深刻影响着系统的结构.我们需要问自己一 ...
- 谈 API 的撰写 - 子系统
在做一个系统时,有一些子系统几乎是必备的:配置管理,CLI,以及测试框架. 配置管理 我们先说配置管理.一个系统的灵活度,和它的配置管理是离不开的.系统中存在的大量的预置的属性(下文简称 proper ...
- 再谈API GateWay服务网关
前面在谈微服务架构的时候,我博客上转过Chris Richardson 微服务系列中对微服务网关的描述: 通常来说,使用 API 网关是更好的解决方式.API 网关是一个服务器,也可以说是进入系统的唯 ...
- 浅谈API网关(API Gateway)如何承载API经济生态链
序言 API经济生态链已经在全球范围覆盖, 绝大多数企业都已经走在数字化转型的道路上,API成为企业连接业务的核心载体, 并产生巨大的盈利空间.快速增长的API规模以及调用量,使得企业IT在架构上.模 ...
- 浅谈API设计
为什么需要了解一些API设计? 只要你编程,你就是API Designer 一个好的设计,模块之间的耦合应该也是API级别的 一个程序,如果你独立开发,那你既是API的Designer,也是API的U ...
- 浅谈API安全设计
一.简述 安全是恒久的话题,如果不注意防范,会带来很严重的后果.比如: 1.接口被大规模调用消耗系统资源,影响系统的正常访问,甚至系统瘫痪 2.数据泄露 3.伪造(篡改)数据,制造垃圾数据 4.App ...
- 谈API网关的背景、架构以及落地方案
Chris Richardson曾经在他的博客上详细介绍过API网关,包括API网关的背景.解决方案以及案例.对于大多数基于微服务的应用程序而言,API网关都应该是系统的入口,它会负责服务请求路由.组 ...
- 小程序入门心得(不谈api)
小程序入门 一.准备 首先先去微信公众平台注册一个小程序账号,去拿到一个AppID(没AppID也可以开发,只是有些功能会受限),注册成功后到开发设置获取自己的AppID,即使有AppID有些功能还是 ...
- 浅谈API和SDK的区别
首先了解一下他们的定义 API:application program interface 应用程序接口 通常表示一些事先定义好的函数,为了向外部提供一组功能的实现,实现和其他软件的交互 SDK:so ...
随机推荐
- 【bzoj1095】[ZJOI2007]Hide 捉迷藏 动态点分治+堆
题目描述 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这 ...
- HDU 4614 Vases and Flowers(线段树+二分)
Vases and Flowers Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others ...
- [luogu_P1251][LOJ#6008]「网络流 24 题」餐巾计划
[luogu_P1251][LOJ#6008]「网络流 24 题」餐巾计划 试题描述 一个餐厅在相继的 \(N\) 天里,第 \(i\) 天需要 \(R_i\) 块餐巾 \((i=l,2,-,N)\) ...
- 在react当中巧用扩展运算符
...props可以把没有写到的属性补充完整 ...style 可以把style 属性在styles当中展开
- [AGC004E] Salvage Robots (DP)
Description 蛤蟆国的领土我们可以抽象为H*W的笼子,在这片蛤土上,有若干个机器人和一个出口,其余都是空地,每次蛤蟆会要求让所有的机器人向某个方向移动一步,当机器人移动到出口时会被蛤蟆活摘出 ...
- aspx生成静态页面html 例子
原文发布时间为:2009-07-26 -- 来源于本人的百度文章 [由搬家工具导入] using System;using System.Data;using System.Configuration ...
- EOJ Monthly 2017.12 A B C D
// 因为是中文题面就偷一次懒不写题意啦QAQ // 各种大作业然后又要期末还不知道什么时候能补题QAQ A. 唐纳德先生和假骰子 直接模拟 #include <bits/stdc++.h> ...
- hdu 5950 Recursive sequence 递推式 矩阵快速幂
题目链接 题意 给定\(c_0,c_1,求c_n(c_0,c_1,n\lt 2^{31})\),递推公式为 \[c_i=c_{i-1}+2c_{i-2}+i^4\] 思路 参考 将递推式改写\[\be ...
- 【Linux】自主实现my_sleep【转】
转自:http://blog.csdn.net/scenlyf/article/details/52068522 版权声明:本文为博主原创文章,未经博主允许不得转载. 首先说一下信号相关的内容. ** ...
- 启动tomcat服务器自动执行一个方法
第一步:配置web.xml文件 添加如下代码 <servlet> <servlet-name>Timer</servlet-name> <servlet-cl ...