有关如何设计API的故事

一次,我帮助一位需要将物业管理系统中的免费住房和占用住房的数据与其客户的站点进行集成的朋友。 令我高兴的是,该系统具有一个API。 但是,不幸的是,安排得很糟糕。

图片

我决定写这篇文章并不是为了批评将要讨论的系统,而是为了讨论在开发API时遇到什么错误,并提出纠正这些错误的方法。

情况概述


有问题的组织使用Beds24系统管理生活空间。 关于空闲和忙碌信息的信息已与各种预订系统(如Booking,AirBnB等)同步。 该组织参与了该站点的开发,希望搜索仅显示有关在指定时间段内和容量合适的可用房间的信息。 这个任务似乎非常简单,因为Beds24提供了与其他系统集成的API。 实际上,事实证明,此API的开发人员在其设计中犯了很多错误。 我建议解析这些错误,识别特定问题,并讨论在所考虑的情况下如何进行API的开发。

问题#1:请求正文格式


由于客户仅对有关某个酒店房间空闲还是忙碌的信息感兴趣,因此我们仅对引用API /getAvailabilities感兴趣。 而且,尽管对这样的API的调用应该导致房间可用性的数据,但实际上该调用看起来像POST请求,因为API的作者决定为其配备接受过滤器作为请求的JSON主体的能力。 这是可能的查询参数的列表以及它们接受的值的示例:

 {   "checkIn": "20151001",   "lastNight": "20151002",   "checkOut": "20151003",   "roomId": "12345",   "propId": "1234",   "ownerId": "123",   "numAdult": "2",   "numChild": "0",   "offerId": "1",   "voucherCode": "",   "referer": "",   "agent": "",   "ignoreAvail": false,   "propIds": [       1235,       1236   ],   "roomIds": [       12347,       12348,       12349   ] } 

让我们遍历此JSON对象,并在这里讨论出了什么问题。

  1. 日期( checkInlastNightcheckOut )采用YYYYMMDD格式。 将日期转换为字符串时,绝对没有理由不使用标准的ISO 8601格式YYYY-MM-DD ),因为这是用于显示日期的广泛使用的标准。 许多开发人员都很熟悉,这是许多JSON解析器希望在输入中收到的内容。 另外,由于存在checkOut字段,因此lastNight字段是多余的,它总是由lastNight指定的日期之前一天的日期表示。 鉴于上述缺点,我建议在设计此类API时,应始终使用标准方法来显示日期,并尽量避免使API用户负担使用冗余数据的负担。
  2. 所有标识符字段以及numAdultnumChild都是数字,但表示为字符串。 在这种情况下,没有明显的理由将它们表示为字符串。
  3. 在这里,您会注意到以下字段对: roomIdroomIds以及propIdpropIdsroomIdpropId的存在是多余的,因为两者都可以用于传输标识符。 另外,类型有问题。 请注意, roomId字段是一个字符串字段,并且标识符的数值必须在roomIds数组中使用。 这可能会导致混乱,解析问题,此外,这表明在服务器上,某些操作是使用字符串执行的,而某些操作则使用数字,尽管事实上这些字符串和数字用于表示相同的内容数据。

我想邀请API开发人员在设计API时犯一些像API这样的错误,同时不要使那些将使用这些API的人的生活变得复杂。 也就是说,值得努力进行标准数据格式化,以确保它们不是冗余的,确保不使用不同类型的数据来表示同类实体。 而且,以线条的形式呈现并非一无所获。

问题2:回应正文格式


如前所述,我们只对API /getAvailabilities感兴趣。 让我们看一下该端点的答案,并讨论其形成过程中的缺点。 请记住,在访问API时,我们对对象标识符列表感兴趣,这些对象标识符在给定的时间内是免费的,并且可以容纳一定数量的人。 以下是对API的请求正文的示例,以及响应于此请求返回的示例。

这是请求:

 {   "checkIn": "20190501",   "checkOut": "20190503",   "ownerId": "25748",   "numAdult": "2",   "numChild": "0" } 

答案是:

 {   "10328": {       "roomId": "10328",       "propId": "4478",       "roomsavail": "0"   },   "13219": {       "roomId": "13219",       "propId": "5729",       "roomsavail": "0"   },   "14900": {       "roomId": "14900",       "propId": "6779",       "roomsavail": 1   },   "checkIn": "20190501",   "lastNight": "20190502",   "checkOut": "20190503",   "ownerId": 25748,   "numAdult": 2 } 

讨论响应问题。

  1. 在响应正文中, ownerIdnumAdult突然变为数字。 并且在请求中有必要以字符串形式表示它们。
  2. 房地产对象列表以对象属性的形式显示,其键为房间标识符( roomId )。 逻辑上期望此类数据将作为数组输出。 对我们来说,这意味着要获取可用房间的列表,我们需要roomsavail整个对象,同时检查其中包含的对象的某些属性(例如roomsavail ,而不关注诸如checkInlastNight类的东西。 然后,有必要检查roomsavail属性的值,如果该值大于0,我们可以得出结论,相应的属性可供预订。 现在,让我们看看roomsavail属性。 以下是在响应正文中呈现它的选项: "roomsavail": "0""roomsavail": 1 。 看到图案了吗? 如果房间被占用,则属性值由字符串表示。 如果免费-它变成一个数字。 这可能会导致严格与数据类型相关的语言出现许多问题,因为在它们中相同的属性不应采用不同类型的值。 结合上述内容,我想建议开发人员使用JSON对象数组来表示某些数据集,而不要使用类似于键值对形式的不便构造,类似于我们在此考虑的构造。 另外,必须确保同类对象的字段不包含不同类型的数据。 格式正确的服务器响应可能类似于以下响应。 请注意,以这种形式显示数据时,房间信息不包含重复数据。

 {   "properties": [       {           "id": 4478,           "rooms": [               {                   "id": 12328,                   "available": false               }           ]       },       {           "id": 5729,           "rooms": [               {                   "id": 13219,                   "available": false               }           ]       },       {           "id": 6779,           "rooms": [               {                   "id": 14900,                   "available": true               }           ]       }   ],   "checkIn": "2019-05-01",   "lastNight": "2019-05-02",   "checkOut": "2019-05-03",   "ownerId": 25748,   "numAdult": 2 } 

问题3:错误处理


这就是在此处考虑的API中如何组织错误处理的方式:即使发生错误,系统也会向所有请求发送代码为200响应。 这意味着,将正常响应与带有错误消息的响应区分开的唯一方法是解析响应的正文并检查其中是否存在errorerrorCode字段。 API中仅提供以下6个错误代码。


Beds24 API错误代码

我建议阅读此内容的每个人都尽量不要在处理请求时出问题的情况下返回代码为200(成功处理请求)的响应。 仅当框架基于您开发API时才提供此步骤。 返回适当的响应代码可以使API客户端提前知道它们是否需要解析响应主体,以及如何进行响应(即,解析常规服务器响应还是错误对象)。

在我们的案例中,有两种方法可以朝这个方向改进API:您可以针对6种可能的错误中的每一种都提供一个范围在400-499之间的特殊HTTP代码(最好这样做),或者在发生错误时返回代码500,该代码将允许至少,客户端在分析响应主体之前应该知道它包含有关错误的信息。

问题编号4:“指令”


以下是项目文档中使用API​​的“说明”:

使用API​​时,请阅读以下说明。

  1. API调用的设计应使其在执行过程中必须发送和接收最少数量的数据。
  2. API调用一次执行一次。 您必须等到下一次调用API才能进行下一次调用。
  3. 如果您需要多次调用该API,则应在它们之间提供几秒钟的暂停。
  4. API调用不必过于频繁,以将调用级别保持在解决客户端任务所需的最低级别。
  5. 在5分钟内过度使用API​​会导致您的帐户被暂停,而没有其他通知。
  6. 对于我们认为过度使用API​​的客户,我们保留阻止对其进行系统访问的权利。 此操作由我们自行决定,恕不另行通知。

虽然第1点和第4点看起来很合理,但我不同意该说明的其他观点。 考虑他们。

  1. 物品编号2。 如果您正在开发REST API,则假定它将是一个独立于状态的API。 API调用与之前的调用之间的独立性是R​​EST技术在云应用程序中得到广泛应用的原因之一。 如果系统的某个模块不支持该状态,则可以在发生错误的情况下轻松地重新部署它。 当此类模块上的负载发生变化时,基于它们的系统很容易扩展。 在设计RESTful API时,应确保它是一个独立于状态的API,并且使用它的人不必担心每次只执行一个请求之类的事情。
  2. 商品编号3。 这个项目看起来很奇怪和模棱两可。 我不明白编写此说明的原因,但是我感觉到,它告诉我们,在处理请求的过程中,系统会执行某些操作,如果它被另一个请求“分心”,没有按时发送,这可能会干扰她的工作。 另外,手册作者说“几秒钟”的事实不能使我们找出连续两次请求之间必须保持的确切的暂停时间。
  3. 项目5和项目6。 它指的是“ API的过度使用”,但没有给出“过度使用”的标准。 也许每秒10个请求? 也许1? 此外,某些Web项目可能会有大量流量。 如果他们在没有任何充分原因且没有通知的情况下关闭了对所需API的访问权限,则其管理员很可能会拒绝使用此类API。 如果您碰巧写了这样的说明,请在其中使用清晰的语言,并根据您的说明将自己置于必须使用系统的用户的位置。

问题5:文档


这就是API文档的样子。


Beds24 API文档

该文档的唯一问题是其外观。 如果格式正确,它看起来会好得多。 尤其是为了显示此类文档的外观,我使用Dillinger并花了不到两分钟的时间制作了以下版本的文档。 在我看来,它看起来比上面的要好得多。


改进的文档

要创建此类材料,建议使用特殊工具。 如果我们谈论的是类似于上述文档的简单文档,那么常规的markdown文件之类的内容就足以满足其设计需求。 如果文档比较复杂,那么在设计时最好使用SwaggerApiary之类的工具。

顺便说一句,如果您想看Beds24 API的文档,请看这里

问题6:安全性


所有API端点的文档都说明以下内容:

要使用这些功能,必须允许访问API。 这是在菜单设置→帐户→帐户访问中完成的。

但是,实际上,任何人都可以访问此API,并使用一些调用从中获取信息而无需提供任何凭据。 例如,这也适用于有关某些住宅可用性的查询。 文档的另一部分对此进行了讨论。

大多数JSON方法都需要API密钥才能访问帐户。 可以使用菜单设置→帐户→帐户访问来设置API访问密钥。

除了无法理解的身份验证问题解释之外,事实证明用户必须自己创建用于访问API的密钥(顺便说一句,通过手动填写相应的字段来完成此操作,但没有提供一些用于自动创建密钥的方法)。 密钥长度必须在16到64个字符之间。 如果允许用户创建自己的密钥来访问API,则可能会导致出现非常不安全的密钥,很容易被盗用。 在类似的情况下,由于您可以在密钥字段中输入任何内容,因此可能会出现与密钥内容相关的问题。 在最坏的情况下,这可能导致使用SQL注入或类似方法对服务进行攻击。 设计API时,不允许用户创建用于自己访问API的密钥。 而是自动为其生成密钥。 用户应该不能更改此类密钥的内容,但是,如果有必要,他应该能够生成新密钥,并将旧密钥识别为无效密钥。

对于需要身份验证的请求,我们会看到另一个问题。 它包含以下事实:身份验证令牌必须作为请求主体的一部分发送。 这是文档中描述的方式。


Beds24 API身份验证示例

如果身份验证令牌在请求主体中传输,则意味着服务器将需要在到达密钥之前解析请求主体。 之后,他提取密钥,执行身份验证,然后决定-他应对该请求做什么-是否满足它。 如果身份验证成功,则服务器将不会承受额外的负载,因为在这种情况下,仍必须解析请求正文。 但是,如果请求无法通过身份验证,那么宝贵的处理器时间将花在解析请求主体上,而不花任何代价。 最好使用Bearer身份验证方案在请求标头中发送身份验证令牌。 使用这种方法,仅当身份验证成功时,服务器才需要解析请求正文。 我们建议使用Bearer之类的标准方案进行身份验证的另一个原因是大多数开发人员都熟悉这种方案。

问题7:性能


这是我名单上的最后一个问题,但并不影响其重要性。 事实是,完成对相关API的请求只需要花费一秒钟多的时间。 在现代应用中,这种延迟可能是不可接受的。 实际上,您可以在这里建议与API开发相关的所有人不要忘记性能。

总结


尽管我们在这里讨论了所有问题,但有问题的API仍使我们能够解决项目所面临的问题。 但是开发人员花了很多时间来理解API并实现他们所需的一切。 另外,为了解决简单的问题,他们不得不编写相当复杂的代码。 如果按原样设计此API,那么工作会更快,并且交钥匙解决方案也会更简单。

因此,我想请所有设计API的人考虑一下他们的服务用户将如何使用它。 确保API文档完整描述了它们的功能,以便易于理解和精心设计。 控制实体的命名,确保您的API发出或接收的数据结构清晰,以便与它们一起使用时轻松便捷。 此外,不要忘记安全性和错误的正确处理。 如果考虑到在设计API时我们所讨论的所有内容,那么您就不需要编写类似于上面讨论的奇怪的“指令”之类的东西来使用它。

如前所述,该材料无意阻止读者使用Beds24或API设计不良的任何其他系统。 我的目标是展示一些错误和解决方案的例子,提出建议,然后每个人都可以提高开发质量。 我希望这些材料能够吸引阅读它的程序员对其开发的解决方案质量的关注。 这意味着世界上将有更多好的API。

亲爱的读者们! 您是否遇到设计不当的API?

Source: https://habr.com/ru/post/zh-CN436888/


All Articles