博客> API杂谈
API杂谈
2017-12-11 19:23 评论:0 阅读:334 1498893132
ios API 规范

(转自http://mp.weixin.qq.com/s?__biz=MjAzNzMzNTkyMQ==&mid=2653750299&idx=1&sn=6a79422c5aac75bbc2837d9da51fed6d&scene=23&srcid=0706ooklKcg0Et5K3pQEbIt1#rd)

编者按:本文来自微信公众号“嘀嗒嘀嗒”(ID:AngelaTalk),授权36氪发布。所有代码都需要不断迭代的过程,API同样,作者朱赟(Angela)是美国硅谷Airbnb的资深工程师,她写下了本篇对API设计和实践的感悟,系统和语言虽不同,但思考和方法论希望能对所有工程师有所帮助。

经历过一个小公司成长为大公司,可能你也遇到过这样的情形:当你看到一行代码,觉得不是那么值得推敲。于是你用git blame寻找它的主人,赫然发现,居然原作者是那位如今早已不写code的CTO或者VP或者Director了。

然后一个偶然的机会,你跟他聊天提到这件事,他会很自豪地给你讲个故事:“哦,那时候,我们必须一天做出这个产品特性。当时也就我一个程序员吧,可能Tom也在。一天的时间,这是当时能做出最好的方案了。” 说完,他便陷入了对美好时光的遐思……

你可能也听说过类似这样的故事:有一天,你的CTO突发奇想,觉得自己其实还可以写一些代码。于是华丽丽提交了一段代码。大家一看,很激动啊,于是很多人咔咔咔开始在PR上给comments。你的CTO一看:靠,几十条comments……现在这个代码要这么写啊?这么麻烦啊?于是跟一个工程师说,“你把comments address下,然后merge吧。” 然后就开开心心地自己该干啥干啥去了。

哦,有点离题了。

其实这两个故事,想说的是:一个公司早期的代码因为各种历史原因,可能不是那么完美,但是在特定的时候,那就是最好的方案。随着时间的消逝,功能不断叠加,代码架构不断优化。系统可能会经历一些变复杂、再简化的迭代过程。然后某一天,代码会面目全非,最初的主人也已经不认识自己当初的作品了。

API的设计和实现尤为如此。一套成熟的API,很多时候都是不断演化迭代出来的,很少有API的设计和实现从最开始就是完美无瑕疵的,说说自己做API的一些体会吧。

先从API的signature说起吧。也就是API request和response支持哪些格式、哪些参数。

首先,做过 API 的人都知道,一个上线使用的 API 再想改它的signature,通常由于compatibility的原因,后期再想改,都是格外痛苦不堪的。因此,API signature设计初期,一定要反复推敲再推敲,尽量避免上线后的改动。

而除了一些基本的RESTful原则外,Signature的定义很多时候是对业务逻辑的抽象过程。一个系统的业务逻辑可能错综复杂,因此API设计的时候就应该做到用最简洁直观的格式去支持所有的需求。这其实往往是API设计中相对立的两面。有时候我们为了支持某一个功能,似乎不得不增加一个很违反设计的接口;而有时候我们为了保证API绝对规范,似乎又不得不放弃对某一些功能的直接支持,因此功能只能通过迭代调用或client端预处理来实现。

而这种设计上的取舍,通常只有列出所有可行的方案,从简单的设计到繁杂的设计,然后通过分析各种使用实例的频率和使用某种设计时的复杂度,从实际的系统需求入手,尽可能让常用的功能得到最简单直接的支持,而一定程度上 “牺牲” 一些极少用到的功能。反复推敲系统场景,尽可能取得一个合理的折衷。

在这个取折衷的过程中,始终保证下面的一些基本原则被满足,例如:

保证API 100%RESTful。RESTful的核心是everything is a “resource”,所有的action和接口,都应该是相应resource上的CRUD操作。如果脱离这种设计模式,一定要再三考虑是不是必要?有没有其他方案可以避免。

在request和response中,都应该尽可能的保持参数的结构化。如果是一个hash,就传一个hash(不要传hash.to_string)。API的serialization/deserialization会将其自动序列化成字符串。多语言之间的API,比如Ruby,Java,C#之间的调用,通常都是在serialization/deserialization中完成不同语言间类型的转换。

Authentication和Security的考虑,应该始终放在首位。保证对特定的用户永远只expose相关的接口和权限。Authentication可能是使用证书和白名单,也可能是通过用户登陆的credentials生成的验证token,或者session/cookie等方式来处理。此外,所有的API层的logging,应该保证不要log任何sensitive的信息。

API本身应该是client无关的。也就是说,一个API对request的处理尽可能避免对client是mobile还是web等的考虑。Client相关的response格式,不应该在API中实现。而所有的client无关的计算和处理,又应该尽可能的在server端统一处理。以提高性能和一致性。

尽可能让API是Idempotent(幂等)的。这有好几个不同层次的含义。举几个例子:同一个request发一遍和发两遍是不是能够保证相同结果?Request失败后重发和第一次发是不是能保证相同结果?当然具体的做法还是要看应用场景。

另外,每个语言都已经提供了很好的API框架。设计前先多了解这些框架。

为什么说多了解呢?如果你是一个小团队,可能多方比较后,选一个合适的框架入手,适当调整,比从零开始造轮子要好。但实际中,很多公司由于各自业务逻辑的特殊需求,最终都会有一套自己的定制方案。

而评估一个框架,可以从以下几个方面考虑:

对访问权限的统一控制

自动测试的支持

对request和response的formatting,以及serialization和deseialization的支持

对logging和logging filtering的支持

对自动文档生成的支持

实际实现的架构以及性能的考虑

最后,如何处理设计中的一些对立面。

除了上文中提到的,接口简洁和功能繁复之间偶尔存在的对立,API设计和实现中还有很多别的对立和取舍。

1、自由总是相对的。

就好像在一个群体里,如果没有规则,完全行为自由,就会出现各种问题。小群体还好,而对于一个大群体,就会有人被别人自由的误伤。

写软件也是一样。一个小startup里,API怎么设计,代码怎么写,几个人一协商,达成共识,并不需要那么多的条条框框,也照样行的通。公司一大,代码协作的人越多,每个人的自由就会导致最终的冲突甚至问题。所以,很多大公司会制定一些API的best practice,强制要求设计和实现中必须按照某种模式来做。有些规则虽有道理,但也不是说不这样不行。很多时候,就因为这样的原因,我们的API设计中会有很多限制,表面上似乎给设计带来无谓的难度,但是仔细考量,从规范代码一致性的角度而言,还是有很大好处的。

2、为当前设计?还是为未来设计?

API设计里很常见的一个情况是,有一个系统功能,目前并没有人使用,只是有人提出:“这种情况我们以后应该要支持。” 之前说过,由于API上线后再改很困难,所以在设计初期就要尽可能的考虑未来的发展。但是这些 “可能” 的应用场景因为需求的细节和使用频度都不明确,最容易造成系统的over-design。

我记得好像听过一个说法,重新概括一下,就是:think about future, design with flexibility, but only implement for production。中文大概就是说:要考虑未来的场景,设计时留有余地,但永远只实现production确实要用的功能。

3、Maintainability v.s. Efficiency

设计和实现里常常会有一些封装和抽象的概念。某些特殊情况下,封装再分拆的过程可能一定程度上影响API的速度,或者是代码质量的优化和性能的优化上有冲突。这个很难一概而论,还是要看具体代码是不是在critical path或者是不是一段需要很多人协作的代码。最终的选择还是要看情况。

4、AOP v.s. non-AOP

AOP编程本身就是一个极具争议的话题。概括说来,AOP的理念是从主关注点中分离出横切关注点,是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在在整个应用程序中的变动就可以很好的管理起来。

因为API的设计和实现中有很多通用的关注点,如Logging、Authentication、Parsing、Monitoring等等,所以API成了AOP 一个很自然的应用领域。使用AOP的API design继承了AOP的优势,如:代码的重用性,规整性,以及程序员可以集中关注于系统的核心业务逻辑等。但也自然而然继承了AOP固有的问题,如代码的profiling和debugging等;程序员experience的要求以及相互协作的要求(例如改变某一个功能可能会影响到其它的功能)等。

这篇考虑到不同系统和语言情况都不太一样,因此没有涉及到太多细节。只把一些做API中得到的感悟泛泛写了写,大家有什么体会,留言里说说吧。

收藏
0
sina weixin mail 回到顶部