前端 保存后端传来数据的id_一篇来自前端同学对后端接口的吐槽

e88b401d010756417a730735558271d5.png

前言

去年的某个时候就想写一篇关于接口的吐槽,当时后端提出了接口方案对于我来说调用起来非常难受,但又说不上为什么,没有论点论据所以也就作罢。最近因为写全栈的缘故,团队内部也遇到了一些关于接口设计的问题,于是开始思考实现接口的最佳实践是什么。在参考了许多资料之后,逐渐对这个问题有了自己的理解。同时回想起过去的经验,终于恍然大悟自己当时的痛点在哪里。

既然是吐槽,那么请原谅我接下来态度的不友善。本文中列举的所有例子都是我个人的亲身经历。

谁应该主导接口的设计

或者更直白一些,应该是接口的消费方还是提供方来决定接口的设计?

当然是接口的消费方

「接口」最吊诡的地方在于提供方大费周章把它实现了,但它自己却(几乎)重来都不使用。于是这极易陷入一种自嗨的境地,因为他更本不知道接口的好坏。就好比一个从来不尝自己做的菜的厨子,你指望他的菜能好到哪里去,他的厨艺能好到哪里去。上面隐含的前提是(我认为)接口是有绝对好坏之分的,坏的接口消费者调用难受,提供者维护难受,还导致产品行为别扭体验变差。

然而接口的好坏与谁来主导设计有什么关系?因为坏接口产生的原因之一是提供方只站在开发者的角度解决问题:

例子一 (Chatty API)

某次需要实现允许用户创建仪表盘页面的功能(如果你对仪表盘页面感到陌生的话,可以想象它是一张集中了不同图表的页面,比如柱状图、折线图、饼图等等。用户可以添加自己想要的图表到页面中,并且手动调整它们的尺寸和位置。仪表盘通常用于总览某个产品或者服务的运行状态)。后端同学的接口初步设计是,当用户填写完基本信息、添加完图表、点击创建按钮之后,我需要连续调用两次接口才能完成一次仪表盘的创建:

  1. 利用用户填写的基本信息以及图表的尺寸和位置创建一个空的仪表盘
  2. 再向仪表盘中填充图表的具体信息,比如图表类型,使用的维度和指标等

很明显看出他完全是按照自己后端的存储结构在设计接口,不仅是存储结构,甚至存储过程都一览无余。想象一种极端的情况,那不只提供一些更新数据库表的接口得了,前端自己把通过接口把数据插入库中面对这类底层性质的接口,消费者在集成时需要考虑接口的调用步骤以及理解背后的原理。如果后端的底层结构一旦发生更改,接口很有可能也需要发生更改,前端的调用代码也需要随之更改。后端研发可能会辩解说:后端用了微服务啊,不同类型的数据存储在不同的服务上,所以你需要和不同的服务通信才能实现完整的存储。他们始终没有明白的事情是,后端的实现导致了接口的碎片化,那是你的问题,而不应该把这部分负担转移到前端的开发者上,其实也是间接转移到了用户身上。不要欺负我不懂后端,至少我了解加一层类似于 BFF 的 Orchestration Layer 就能解决这个问题Netflix 的工程师 Daniel Jacobson 在他的文章 The future of API design: The orchestration layer 中指出, API 无非是要面对两类受众:

  1. LSUD: Large set of unknown developers
  2. SSKD: Small set of known developers

随着产品服务化的趋势,很有可能需要像 AWS 或者 Github 那样对公共开发者即 LSUD 暴露接口。且不说上面例子中的接口方案会不会被唾沫星子淹死,如此明显的暴露内部服务的细节是非常危险的事情。所以在设计接口时,应该让消费者来主导。如果消费者没能给出很好的建议,那么至少提供者在设计时也应该站在消费者的立场上来思考问题。又或者,至少想一想如果你自己会乐意使用用你自己设计出来的接口吗?使用后端思维设计接口不仅体现在 URI 的设计上,还有可能体现在请求参数和返回体结构上:

例子二

假设现在需要一个请求批量文章的接口,接口同时返回多篇文章的内容,包括这些文章的内容,作者信息,评论信息等等。理想情况下,我们期望返回的数据是以文章为单位的,比如

articles: [{id: ,author: {},comments: []},{id:author: {},comments: []}
]

However, 后端的返回结果可能是以实体为单位:

{articles: [],authors: [],comments: []
}

comments 里包含不同文章的 comment,我必须通过类似于 articleId 的字段对它们执行 group by 操作才能分离出属于不同文章的评论。对其他实体做同样的操作,最终手动的拼接成前端代码需要的 articles 数据结构很明显这又是按照后端库表关系返回的结果,严格来说这并不算是 anti-pattern,在 redux 中也鼓励将数据 normalize。但如果前端用不到原始数据,请不要返回原始数据。例如我需要在页面上展示一个百分比格式的数据,除非用户有动态调整数据格式的需求,例如千分位、小数或者是切换精度等等,否则就直接返回给我百分比的字符串就好了,不要返回给我原始的浮点数据。前端对数据的二次加工还会给问题排查带来干扰,如果任何数据都需要前端进行二次加工,那么所以问题的排查都必须从前端发起,前端确认无误后再进入后端排查流程,这始终会占用两个端的人力,并且 delay 了排查的进度

关于 meta 信息

例子三:

后端接口在返回时通常会带上 meta 信息,meta 信息包含接口的状态以及如果失败时的失败原因,便于调试使用。后端提供的接口的 meta 信息的数据结构如下:

{meta: {code: 0,error: null,host: "127.0.0.1"},result: []
}

在我看来,以上数据结构有两个问题

meta 信息包含独立的状态信息

在包含状态码的 meta 信息接口设计中,一条默认的隐藏逻辑是:接口返回的 HTTP status code 一定是 200,数据是否真的获取成功需要通过 meta 里的自定状态码 code 进行判断(换句话说,上面你看到的接口实际上是 “接口的接口”)。最终在前端的代码中也不需要通过 HTTP code 判断返回是否正常,只需要判断接口里返回的meta.code即可** 但是谁给你们的自信保证后端接口一定是不会挂的?!** 无论后端如何保证接口的坚固,前端仍然需要首先判断 HTTP code 是否为 200,再判断meta.code是否与预期的符合一致。这和信任无关,和我程序的健壮有关。既然无论如何都要对接口判断两次,那为什么不将meta.code与 HTTP code 合二为一?更何况我还需要再本地维护一份自定义 code 的枚举值,还需要和后端保证同步。这就涉及到下一个问题了:

meta 信息的存放位置

我们需要 meta 信息没有错,但是我们没有那么需要 meta 信息。这体现在几点:

  1. 我们真的需要一个平行于返回结果的字段展示 meta 信息吗?
  2. 每一次请求我们都需要 meta 信息吗?
  3. meta 信息一定要在 meta 字段里吗?

以请求失败的错误信息为例,错误信息只会出现在接口非正常返回的情况下,但我们应该始终在返回体中用一个字段为它预留位置吗?在关于 meta 信息存在位置的这个问题上,我倾向于将它们整合进入 HTTP Header 中。例如meta.code完全可以使用 HTTP code 代替,我看不出始终要保证 200 返回以及自定义 code 的意义在哪里而至于其它的 meta 信息,可以通过以X-开头的自定义 HTTP Header 进行传递。例如Github API 中关于使用频率限制的信息就放在 HTTP Header 中:

Status: 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
X-RateLimit-Reset: 1372700873

Design for today

例子四

我们需要为某个指标的折线图设计查询接口,查询以天为单位,也就是说该接口只会根据查询的日期返回指定日期的查询结果,后端提供的返回数据结构如下:

{data: [{date: "2019-06-08",result: []}]
}

虽然需求很明确的指示只会返回某天的查询结果,但是后端还是决定给我返回一个数组。他这么设计的理由是为了防止日后需求发生改变需要返回多日的查询结果。这看上去是很聪明决策:“看,我预见性的 cover 了一个未来的需求!”,但实际上愚蠢至极:你的确 cover 了一个需求,不过是一个当前并不存在,未来也不见得会发生的需求;而且如果你真的想写 future-proof 的代码,那么还有未来千千万万的需求等待着你实现。问题在于没有人知道将来是否真的会允许同时查询多日数据,即使某天需要支持同时查询多日数据了,数据结构也不一定非要如此。在数据分析领域我们面临的查询需求并不是线性从单个到多个,在其他业务领域也是这样。这样导致的后果是你花费多余的时间实现了不需要的代码,并且前端也需要配合这样的数据结构进行实现。并且在将来的维护中,每个看到返回体是数组的人都会纳闷为什么返回的结果明明只有一条,还需要用数组封装,是不是我遗漏了什么?于是不得不投入精力来验证是否真的有可能返回更多的数据。API 和代码应该是精准的,准确表达你想实现的一切而不存在有歧义有人可能会说不就是多了一层封装吗?实现上也花不了多少的功夫何至于大惊小怪。抱歉我不是针对这一个 case,而是在强调任何场景下无论实现的难易都不应该添加无意义的代码,“勿以恶小而为之” 就是这个道理“关注当下” 还有另一个维度含义:

例子五

目前我们已经有创建单个文章的接口,现在需要支持批量创建文章。后端给出的建议是:不如调单个接口多次?

例子六

目前已经有一个接口能够取得文章相关数据,比如内容、评论、作者等等。现在我们需要增加一个新的页面用于展示用户信息。后端给出的建议是:不如使用文章数据接口,里面已经包含了作者信息,这样就不用开发新的接口了以上的例子看似都是想实现对接口的复用,但实际上起到的是事倍功半的效果在例五中,虽然语义上 “创建五篇文章” 和“连续五次创建一篇文章”是等效的,但是在实现和操作层面并不是如此。且不说调用五次和调用一次的性能大不相同,批量创建的五篇文章可能存在顺序关系,可能需要事务操作。在例六中虽然能够达到我们实现的效果,但这不能算是接口的复用,只能算是接口的 hack(hack 和复用的区别在于是否用物品的初衷功能做事情)。并且 hack 接口是有风险的,对于接口的提供者而言他们更关心接口服务 “正统” 的消费者,在这个 case 中接口的存在是为了展示完整的文章信息,如果有一天 “文章信息” 这个需求发生了变化很有可能会导致作者信息同时发生变化,缩减字段甚至取消字段。那么它们没有义务这些 hack 用户负责。一个接口本应该就专注一件事情所以最理想的事情是,为当前专注的业务开发独立的接口。在例六的例子中,可能我们在开发一个独立请求作者的信息的接口时实现代码完全复制自另一个接口的实现,但是接口的隔离在长远看来能给功能的维护带来更大的便利

不仅限于 REST API

“接口” 是一个概念。在概念之下如何实现它我们拥有很多种选择。目前看来绝大部分的方式是通过 REST API 来达成的,也并没有什么事情是 REST API 无法做到的,但事实上这几年技术的进步给了我们更多的选择,如果选择更有针对性的实现方案,效果会更好例如在实时数据的场景下,理论上是由后端(有数据更新时)驱动前端视图的更新,这理应是 push 操作。但是在传统实现中,我们不得不仍然通过被动的等待和轮询实现功能。对于事件驱动类型的需求使用 WebSocket 或者是 Streaming 似乎是更好的选择。如果是后端之间的交互还可以利用 WebHook。我通常对新技术持保留态度,但是不得不承认 GraphQL 在处理某些需求上也能够比 REST API 做的更好。并且大部分厂商对于 GraphQL 接口的支持表明它是可行的。我了解实现 API 来只是后端实现功能的一个很小的环节,在接口背后是更多业务逻辑的修改和库表结构的更迭。甚至说接口部分有一半都是交给框架来实现的。但是,哪怕只有很小的机会,也应该把这个环节做到尽善尽美。

结束语

对于糟糕的接口设计我还能继续没完没了的抱怨下去,但突然然觉得洋洋洒洒的继续写下去似乎没有太大意义。讲真我不是来真的大吐苦水的,只是想表达接口设计也至关重要。在工作中痛心的看到很多问题明明用一些很基础的技巧就能够解决,而大家却对它熟视无睹以造成两败俱伤的境地。以上就是我认为的在接口设计中需要遵循的一些原则和考虑要素,相信能够解决大多数的痛点和避免部分的问题后端同学们,如果你们有心让接口变得更好,多听听 “消费者” 的反馈。如果你们尝试使用过第三方接口开发过应用的话,例如 Slack、Github,你会发现它们的接口是在不断迭代的。不断有旧的接口被淘汰,新的接口投入使用。这种迭代背后不是闲着没事干,而是出于实际的用户的声音和需求最后推荐我最近阅读的关于 API 设计的图书,收益匪浅:

  • Web API 的设计与开发
  • Designing Web APIs
  • APIs A Strategy Guide

作者:李熠

链接:http://juejin.im/post/5cfbe8c7e51d4556da53d07f

文章著作权归作者所有,如有侵权,请联系小编删除。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2018-2019-1 《信息安全系统设计基础》教学进程

《信息安全系统设计基础》教学进程 目录 考核方式暑假准备教学进程 第01周学习任务和要求第02周学习任务和要求第03周学习任务和要求第04周学习任务和要求第05周学习任务和要求第06周学习任务和要求第07周学习任务和要求第08周学习任务和要求第09周学习任务和要求第10周学习任务…

Android中的数据库

2019独角兽企业重金招聘Python工程师标准>>> 1.1. 什么时候使用数据库 有大量相似结构的数据需要存储的时候就可以使用数据库。 1.2. SQLite的简介 SQLite是一款轻量级的数据库。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它。Androi…

python计算绩效工资_python实现 --工资管理系统

原博文 2017-07-25 22:41 − # -*- coding: utf-8 -*- __author__ hjianli # import re import os info_message """Alex 100000 Rain 80000 Egon 50000 Yuan 30000 """ #序列字典 xulie_...01669 相关推荐 2019-09-28 21:13 − Python python…

为Windows Server 2012 R2指定授权服务器

为Windows Server 2012 R2指定授权服务器在Windows Server 2008 R2的终端服务中,可以手动指定授权服务器,而在Windows Server 2012 R2中,默认只能通过"远程桌面连接服务"管理器,指定授权服务器,而要使用远程…

spring5高级编程_Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新

简介是什么让java世界变得更好,程序员变得更友爱,秃头率变得不是那么的高,让程序员不必再每天996,有时间找个女朋友?是Spring。是什么让企业级java应用变得简单易懂,降低了java程序员的进入门槛&#xff0c…

关于resolve非泛型方法不能与类型实参一起使用

今天mvc新建三层时,写到bll层中一直报下面的错误,检查了几遍赶脚并没有什么错。最后发现缺少一些引用。 如下面的图,少添加了下面的两个引用.Unity是微软模式与实践团队开发的一个轻量级、可扩展的依赖注入容器, Microsoft.Practices.Unity.C…

设计模式-Singleton

2019独角兽企业重金招聘Python工程师标准>>> Singleton算是知道的设计模式中最简单的最方便实现的了,模式实现了对于类提供唯一实例的方法,在很多系统中都会用到此模式。在实际项目中使用全局变量,或者静态函数等方式也可以达到这…

dump分析工具_Java应用CPU过高,如何排查?参考解决思路和常用工具总结

本文总结了一些常见的线上应急现象和对应排查步骤和工具。分享的主要目的是想让对线上问题接触少的同学有个预先认知,免得在遇到实际问题时手忙脚乱。毕竟作者自己也是从手忙脚乱时走过来的。只不过这里先提示一下。在线上应急过程中要记住,只有一个总体…

dos攻击命令_Kali Linux系列之拒绝服务攻击(DOS)实战(上)

(你的世界是个什么样的世界?你说,我们倾听!)-----------------小百科拒绝服务攻击即是攻击者想办法让目标机器停止提供服务,是黑客常用的攻击手段之一。其实对网络带宽进行的消耗性攻击只是拒绝服务攻击的一小部分,只要能够对目标…

stm32定时器配置

stm32通用定时器 STM32的定时器是个强大的模块,定时器使用的频率也是很高的,定时器可以做一些基本的定时,还可以做PWM输出或者输入捕获功能。 时钟源问题: 名为TIMx的有八个,其中TIM1和TIM8挂在APB2总线上,…

python数组定义_python定义数组

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 一、一维数组 1. 直接定义matrix2. 间接定义matrixprint(matrix)输出:3. 数组乘法matrix*5print…

Android-语言设置流程分析

Android手机语言切换行为,是通过设置-语言和输入法-语言来改变手机的语言,其实这个功能很少被用户使用。 以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:一、语言设置界面:首先在设置app中找到语言设置这个Preference…

charles 安装 ssl_最全面的解决Charles手机抓包的证书问题(步骤非常详细)

源自公众号文章: 彻底解决Charles手机抓包的证书问题简介: Charles 抓包是日常开发当中经常会用到的技术, 在 Android 6 之前, 手机系统既信任系统内置的证书, 也信任用户自己安装的证书, 但是在 Android 7 之后, 却发生了变化, 手机系统只信任系统内置的根证书. 当然了, 这是为…

excel 2007 vba与宏完全剖析_Excel宏VBA小技巧系列 | 分段加合

写在前面的话 知识产权算是一个盛产数据的行业。专利啊商标啊著作啊,都有著录项目。我们常说的专利分析、产业导航、企业导航、产业预警、竞争情报、技术综述、知识产权评议等等,常规操作之一就要先处理著录项目数据,然后再进行不同角度的分…

redhat虚拟机安装

做过好多使用VMware workstation虚拟机搭建的系统,这是我第一次使用Virtual Box,感觉跟Vmware差不多,我的本子的系统是win7 64位。 下面演示安装的是在VirtualBox里安装rhel 6.4 linux 64位系统。 一、VirtualBOX 版本。 二、虚拟机的配置。…

mysql 查看表v空间自增涨_MySQL InnoDB表空间加密

从 MySQL5.7.11开始,MySQL对InnoDB支持存储在单独表空间中的表的数据加密 。此功能为物理表空间数据文件提供静态加密。该加密是在引擎内部数据页级别的加密手段,在数据页写入文件系统时加密,加密用的是AES算法,而其解密是在从文件…

ideaspringboot项目上传服务器_PHP中使用 TUS 协议来实现可恢复文件上传

曾经尝试过用PHP上传大文件吗?想知道您是否可以从上次中断的地方继续上传,而不会在遇到任何中断的情况下再次重新上传整个数据?如果您觉得这个场景很熟悉,请接着往下阅读。文件上传是我们几乎所有现代Web项目中的一项很常见的任务…

java使用xml存储数据_用存储过程和 JAVA 写报表数据源有什么弊端?

用存储过程和 JAVA 写报表数据源有什么弊端?跟着小编一起来一看一下吧!我们在报表开发中经常会使用存储过程准备数据,存储过程支持分步计算,可以实现非常复杂的计算逻辑,为报表开发带来便利。所以,报表开发…

MAC OS上JAVA1.6 升级1.7,以及 maven3.2.1配置

一、我的MAC系统 预装的Jdk是1.6,由于需要使用eclipse MARs 2版本,故需要升级到1.7 二、下载JAVA jdk http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 不知道为什么直接下非常慢,后来用的迅雷就超级快…

sql server 创建唯一性非聚集索引语句_数据库专题—索引原理

深入浅出数据库索引原理参见:https://www.cnblogs.com/aspwebchh/p/6652855.html1.为什么给表加上主键?1.平时创建表的时候,都会给表加上主键。如果没有主键的表,数据会一行行的排列在磁盘上,查找一个数据需要一条条的进行对比。而…