尽管你的API代码超级干净和亮眼,但你的客户可能永远看不到它。他们对你的API架构并不感冒。前端
您应该尝试使用清晰的能解释的名称和尽量接近文档要求的架构。此外,经过一些简单的规则,您应该可以对其进行之后的扩展,以即可以继续向现有响应中添加更多信息,而没必要早早的淘汰旧版本。数据库
几年来,我遇到的最多见的与模式相关的错误就是因为API开发环境缺少严格性。在设计API时,使用适合您的编程语言的框架对您将要作的事情大有裨益。编程
花些时间研究可用的选项,并记住,尽管大多数Web框架提供了可帮助您实现REST API的工具,但仍须要您本身完成大量的工做,永远不要重复造轮子。json
最流行的后端Web技术(也包括前端)是基于松散类型的脚本编程语言,这些语言具备不良的编程习惯,这些习惯很难摆脱。PHP和Javascript(以Node.js的形式)是数据类型松散型的典型,由于它们常常在没有严格的数据模型下使用。后端
确保为您的数据库使用ORM或其余数据模型基础结构严格建模的内部数据层。您的API响应应该被包含在严格定义的嵌套数据中。不要将数据库查询的结果生成简单的数据结构。不管怎样,保持严格的数据层是您在全部应用程序中应该作的。数组
在暴露给API响应以前,应始终对数据进行过滤,以避免泄露敏感信息。对暴露给API的字段所作的任何修改,都应该注意与当前API版本的兼容性,并将其添加到API的更新日志中安全
对代码的命名应该在整个API,URI以及请求和响应中保持一致,而且具备特殊的意义。命名不明确会给其余开发人员以及您本身形成很大的压力。数据结构
请记住,您的URI和架构应尽量清晰地向使用者传达目的,而无需他们不断的浏览文档。不要惧怕使用冗长的名称。架构
即便您用的编程语言不严格,您也应该使用严格的数据类型。数字字段永远应该只有数字,字符串字段永远应该只包含字符串,诸如子类。您永远都不该该在相同的字段中混合不一样的数据类型。您的字段对应的值有可能会不同,但该字段的额数据类型应该肯定。app
在松散类型的环境中看到同一个字段在一个响应中是数字42,但在另外一个响应中是字符串“42”是很常见的错误。这个作法先后不一,很难再全部的客户端安全解析。在松散类型的架构中,客户在解析每一个字段时都很危险。
显然,这不只使用与原始数据类型(数字,字符串,布尔值等),并且使用于JSON对象和数组。请勿在包含Table类型的对象字段中返回Chair类型的对象,或在包含Bicycles数组的字段中返回Cars数组。
虽然上面说的都是很是基础的知识,但不少优秀的开发人员都犯过相似的错误。
一个强壮的模型层可帮助您避免此类尴尬的错误。
当某个字段没有可用值时,请勿彻底忽略该字段。根据数据类型和缺失值的语义,使用null,空字符串,空数组或零。
当一个接口知足要求时,不要使用10个接口来获取全部的字段。也许他们能够在文档中查找它,但为何不能让文档更容易理解呢?请记住,文档变得陈旧比代码快得多,而代码才是生成架构的缘由。
再一次强调,若是在实现中使用强大的模型层,则会更容易避免这种类型的错误。
我见过的次数超过了我在API请求和响应中记得的次数,这也一般源于与松散类型语言相关的不良作法。
假设您有一个包含惟一id做为主键和子对象做为值的people对象
{ "people": { "1234": { "name": "John", "surname": "Smith" }, "5678": { "name ": "John ", "surname": "Smith" } } }
people对象随着它内部的内容数据类型的变化而变化,在这个例子中,它的key值是1234和5678,可是没有人知道下一次请求时它的key值是什么。
这是个很可怕的作法,而且在任何严格类型的语言中进行解析时,都会致使代码不一致而广泛糟糕。API中的每一个JSON对象在请求时都应始终具备一组不变的严格定义的字段。
下面的是一个很好的数组用例,只需返回一个数组,并将id包含在每一个数组元素中便可。
{ "people": [ { "id": 1234, "name": "John", "surname": "Smith" }, { "id": 5678, "name": "John", "surname": "Smith" } ] }
一般,当您尝试经过使用带有惟一ID键的字段来使后端代码的查找更容易时,就会出现JSON滥用。您必须谨记,在这种状况下,内部实现的详细信息会泄露给用户-在软件开发的全部方面都应避免这种现象。
若是您遵循了先前的建议,并将某些对象更改成数组,那你作的很好!
如今,您必须确保数组仅包含一种类型的对象。不要将apples和oranges混合一块儿! 请记住,并不是全部的客户端都是用松散类型的容器来存储数据,而且解析异构资源列表不只不一致且使人讨厌,并且也不安全。
当你确实没法避免在同一数组中返回不一样种类的实体时,请尝试返回一个超级对象列表,这些列表足够抽象以描述您须要返回的全部对象类型的属性。
在苹果和橙子的例子中,或许你应该返回一个Fruit对象,一个Fruit对象能够包含Apple和Organge对象的全部属性,以及一个字段,该字段能够准确描述每一个对象的水果类型。
若是您返回的项的属性对于每种返回的类型都是彻底不一样的,但仍必须将它们返回到同一列表中,则可能必须使用极端措施,好比容器对象。虽然不是很是优雅的解决方案,但这也是必须的。
下面是容器对象的例子:
你的接口将返回一我的拥有的飞行器的列表,以及每一个飞行器的一些基本特征。飞行器能够是飞机,也能够是热气球,二者之间有很大的不一样。从语义上讲,向热气球添加翼展,引擎数量或马力等属性几乎没有意义,向飞机添加蓝,气球材质和睦球形状等属性也没有意义。
将全部这些属性字段添加到单个对象类型是毫无心义的。相反,您能够将飞机对象和热气球对象存储在一个容器对象中。
在这种状况下,类型为Vehicle(本质上是超类型)的容器对象将包含两个字段airplane和ballon,分别对应不一样的子对象。 请记住,即便其中的一个字段没有数据,也要将该字段及其数据类型返回。
{ "vehicles": [ { "type": "airplane", "airplane": { "engines": 1, "wing\_span": 12, "horsepower": 240 }, "balloon": null }, { "type": "balloon", "airplane": null, "balloon": { "basket": "rattan", "balloon\_material": "dacron", "balloon\_shape": "natural" } } ] }
再次强调,若是可能的话,请避开这种设计,可是若是您必须在同一集合中返回彻底不一样的对象,则容器对象是一种维护严格类型的架构的好方法。
我不想让您失望,可是不管您的错误消息多么有趣和风趣,它们几乎都不会引发用户的兴趣。并非其余开发人员不欣赏您的写做技巧,而是你永远不知道客户将怎样呈现一个错误。
此外,您必须始终以简洁,机器可读的方式返回错误,以使其被客户端易于解析。您应该返回正确的HTTP状态代码,并在相应正文中的错误对象中包含特定的错误消息。
下面是一个例子:
您的用户经过下面的API请求一个订单
GET /customers/21/order/42
若是未找到客户或订单,则响应404 Not Found状态码,但这样就好了吗?用户将没法准确的区分致使错误的缘由,由于他不知道错误的缘由是客户仍是订单。
这就是您的响应正文中的错误对象派上用场的地方。
一个可读的机器码使事情对客户端而言更简单。此外,将说明(例如:“customer_not_found”)而不是数字,可使开发人员更轻松-无需查看API文档中的数字表和错误说明。
最后,message 字段能够更好地向开发人员解释错误的缘由,所以他们对如何处理错误以及在何处查找其余信息有了更好的了解。 理想状况下,应根据客户请求的“接受语言”标头对错误进行本地化。 谁知道呢,也许最终用户会在某种程度上阅读您的杰做。
正如一遍又一遍提到的,您应该有一个易于阅读和自我记录的架构。 枚举时请勿使用数字。 使用简单的字符串。
您的动物对象中有一个类型字段吗? 请勿使用一、二、三、4和5做为其值。“ dog”,“ cat”,“ parrot”,“ armadillo”和“ elephant”更容易被人阅读,而且对 知道如何比较字符串的机器。
人们一般在后端内部使用数字枚举时执行此操做,但这(一样)是一种实现细节,不该泄漏给API使用者。
我还听到了一些借口,例如增长字符串方法的带宽消耗,可是还有其余更好的方法来解决该问题。 您的架构应足够详细,以便一眼就能理解,而且应该使用Gzip减小带宽消耗,与使用数字枚举节省几个字节相比,Gzip具备很大的不一样。
在这种状况下,封装(或JSON)究竟是什么?简而言之,这意味着将响应数据包装(或封装)到JSON对象中,而后将其返回到响应主体根目录中的data(或其余相似名称)字段中。
某些人彷佛认为这是对全部响应的一种好习惯,由于它容许您未来添加元数据字段(例如错误或分页信息),而不会篡改主要响应对象。尽管在解析时可能须要更多的代码,但这确实使API模式更加简洁。
即便您不想对全部响应都这样作,但我相信当您返回对象集合时,它很是有用(甚至有必要)。在这种状况下,您永远不要将数组做为响应的根容器!
上面的主要理由是,若是您的根容器是JSON数组,则在响应须要返回错误(不可避免地将是JSON对象)时,您的架构会发生根本性的变化。这使得解析更加复杂,而没有提供任何实际好处。
此外,(即便您不理会上述状况),数组也使早期弃用API的可能性更大,由于在不弃用架构的状况下,毫不能以任何方式对其进行更改或修改。另外一方面,将对象用做根响应容器可以让您之后添加任意多个字段,而不会引发弃用。哎呀,您甚至能够在新数组中返回不一样的更新类型的对象,只要您确保保留旧数组便可。
我我的的喜爱始终是将Unix时间戳做为响应中的日期,由于它们相对较短而且很容易解析。 可是,除非您是机器,不然它们很难转换,而且实际上只能做为真实日期来读取。 另外一方面,从可读性的角度来看,ISO-8601日期更好,但解析起来却有点困难(尽管很少)。
应该不惜一切代价避免使用除这两种之外的任何其余字符串格式,由于它可能在解析时形成歧义。 我知道您能够在文档中指定本身的日期时间格式,客户端能够基于此格式解析日期,可是请记住:在没有太多外部帮助的状况下,API应该尽量易于理解。
若是对象A不是用户; 而且包含诸如user_id,user_name,user_favorite_color,user_pet等字段,也许是时候在对象A内使用封装的User对象了……
因为(数据库以及API)架构会随着时间的流逝而变得愈来愈复杂,所以最好一开始就对其进行规范化并使其保持尽量的干净。
始终尽力以可能的方式使API面向将来。尝试保留未来极可能须要其余信息的属性,以即可以延长主版本的使用寿命。
您须要先考虑一下:
假设您每一个Book对象都有一个is_available布尔值。尽管这足以让咱们知道一本书为假时该书不可用,但它并无告诉咱们为何该书不可用或什么时候能够再次使用。未来,若是要添加该信息,则必须经过添加两个额外的字段来加入到Book对象。
一种更干净的方法是使用一个可用性字段,该字段存储一个Availability对象,该对象最初仅包含is_available字段,但能够进行修改以包含有关该书的可用性的其余信息(例如,该书不可用的缘由以及什么时候将其再次可用的时间戳记),而不会在原来的架构中添加更多字段。
若是您使用的是版本控制方法,可在保证结构稳定的同时对API进行小的增量更改,可是每次更改时都应格外当心。
对结构的某些更改意味着当即弃用当前的主版本。 除非绝对必要,不然应避免使用它们。
请记住,下面列出的更改不是架构弃用的惟一缘由(一般能够由您特定设计中的细节引发),而只是最多见的缘由。
您永远没法肯定您的用户如何使用您的信息。无论字段看起来多么微不足道或多余,若是您将其交付生产时都犯了错误,那么您将一直坚持到下一个主要版本。更改字段名称显然与删除字段相同。
数据类型不只必须在响应中保持严格,并且在次要版本中也必须保持严格。这是与松散类型的开发环境有关的另外一种很差的作法。
您必须更改当前版本才能更改字段的数据类型。若是您在初次发布时的响应中将数字做为字符串传递,那么在下一个主要版本以前,应始终将其做为字符串返回。
返回数字(请记住,您不该该这样)或字符串是枚举形式,这很常见。
例如,您可能使用如下状况:“ car”,“ truck”和“ motrcycle”字段用于vehicle_type字段。若是您注意到“摩托车周期”中的错字,我知道您必定感到沮丧,但您没法解决!不过,您能够在下一个主要版本中进行操做(而且不要忘记将其添加到变动日志中)。