阅读更多

1顶
0踩

Web前端

转载新闻 如何处理好前后端分离的 API 问题

2017-04-17 16:31 by 副主编 jihong10102006 评论(1) 有10830人浏览
API
引用
API 都搞不好,还怎么当程序员?如果 API 设计只是后台的活,为什么还需要前端工程师。

作为一个程序员,我讨厌那些没有文档的库。我们就好像在操纵一个黑盒一样,预期不了它的正常行为是什么。输入了一个 A,预期返回的是一个 B,结果它什么也没有。有的时候,还抛出了一堆异常,导致你的应用崩溃。

因为交付周期的原因,接入了一个第三方的库,遇到了这么一些问题:文档老旧,并且不够全面。这个问题相比于没有文档来说,愈加的可怕。我们需要的接口不在文档上,文档上的接口不存在库里,又或者是少了一行关键的代码。

对于一个库来说,文档是多种多样的:一份 demo、一个入门指南、一个 API 列表,还有一个测试。如果一个 API 有测试,那么它也相当于有一份简单的文档了——如果我们可以看到测试代码的话。而当一个库没有文档的时候,它也不会有测试。

在前后端分离的项目里,API 也是这样一个烦人的存在。我们就经常遇到各种各样的问题:
  • API 的字段更新了
  • API 的路由更新了
  • API 返回了未预期的值
  • API 返回由于某种原因被删除了
  • 。。。
API 的维护是一件烦人的事,所以最好能一次设计好 API。可是这是不可能的,API 在其的生命周期里,应该是要不断地演进的。它与精益创业的思想是相似的,当一个 API 不合适现有场景时,应该对这个 API 进行更新,以满足需求。也因此,API 本身是面向变化的,问题是这种变化是双向的、单向的、联动的?还是静默的?

API 设计是一个非常大的话题,这里我们只讨论:演进、设计及维护。

前后端分离 API 的演进史
刚毕业的时候,工作的主要内容是用 Java 写网站后台,业余写写自己喜欢的前端代码。慢慢的,随着各个公司的 Mobile First 战略的实施,项目上的主要语言变成了 JavaScript。项目开始实施了前后端分离,团队也变成了全功能团队,前端、后台、DevOps 变成了每个人需要提高的技能。于是如我们所见,当我们完成一个任务卡的时候,我们需要自己完成后台 API,还要编写相应的前端代码。

尽管当时的手机浏览器性能,已经有相当大的改善,但是仍然会存在明显的卡顿。因此,我们在设计的时候,尽可能地便将逻辑移到了后台,以减少对于前端带来的压力。可性能问题在今天看来,差异已经没有那么明显了。

如同我在《RePractise:前端演进史》中所说,前端领域及 Mobile First 的变化,引起了后台及 API 架构的一系列演进。

最初的时候,我们只有一个网站,没有 REST API。后台直接提供 Model 数据给前端模板,模板处理完后就展示了相关的数据。

当我们开始需要 API 的时候,我们就会采用最简单、直接的方式,直接在原有的系统里开一个 API 接口出来。

为了不破坏现有系统的架构,同时为了更快的上线,直接开出一个接口来得最为直接。我们一直在这样的模式下工作,直到有一天我们就会发现,我们遇到了一些问题:
  • API 消费者:一个接口无法同时满足不同场景的业务。如移动应用,可能与桌面、手机 Web 的需求不一样,导致接口存在差异。
  • API 生产者:对接多个不同的 API 需求,产生了各种各样的问题。
于是,这时候就需要 BFF(backend for frontend)这种架构。后台可以提供所有的 MODEL 给这一层接口,而 API 消费者则可以按自己的需要去封装。

API 消费者可以继续使用 JavaScript 去编写 API 适配器。后台则慢慢的因为需要,拆解成一系列的微服务。

系统由内部的类调用,拆解为基于 RESTful API 的调用。后台 API 生产者与前端 API 消费者,已经区分不出谁才是真正的开发者。

瀑布式开发的 API 设计
说实话,API 开发这种活就和传统的瀑布开发差不多:未知的前期设计,痛苦的后期集成。好在,每次这种设计的周期都比较短。

新的业务需求来临时,前端、后台是一起开始工作的。而不是后台在前,又或者前端先完成。他们开始与业务人员沟通,需要在页面上显示哪些内容,需要做哪一些转换及特殊处理。

然后便配合着去设计相应的 API:请求的 API 路径是哪一个、请求里要有哪些参数、是否需要鉴权处理等等。对于返回结果来说,仍然也需要一系列的定义:返回哪些相应的字段、额外的显示参数、特殊的 header 返回等等。除此,还需要讨论一些异常情况,如用户授权失败,服务端没有返回结果。

整理出一个相应的文档约定,前端与后台便去编写相应的实现代码。

最后,再经历痛苦的集成,便算是能完成了工作。

可是,API 在这个过程中是不断变化的,因此在这个过程中需要的是协作能力。它也能从侧面地反映中,团队的协作水平。

API 的协作设计
API 设计应该由前端开发者来驱动的。后台只提供前端想要的数据,而不是反过来的。后台提供数据,前端从中选择需要的内容。

我们常报怨后台 API 设计得不合理,主要便是因为后台不知道前端需要什么内容。这就好像我们接到了一个需求,而 UX 或者美工给老板见过设计图,但是并没有给我们看。我们能设计出符合需求的界面吗?答案,不用想也知道。

因此,当我们把 API 的设计交给后台的时候,也就意味着这个 API 将更符合后台的需求。那么它的设计就趋向于对后台更简单的结果,比如后台返回给前端一个 Unix 时间,而前端需要的是一个标准时间。又或者是反过来的,前端需要的是一个 Unix 时间,而后台返回给你的是当地的时间。

与此同时,按前端人员的假设,我们也会做类似的、『不正确』的 API 设计。

因此,API 设计这种活动便像是一个博弈。

使用文档规范 API
不论是异地,或者是坐一起协作开发,使用 API 文档来确保对接成功,是一个“低成本”、较为通用的选择。在这一点上,使用接口及函数调用,与使用 REST API 来进行通讯,并没有太大的区别。

先写一个 API 文档,双方一起来维护,文档放在一个公共的地方,方便修改,方便沟通。慢慢的再随着这个过程中的一些变化,如无法提供事先定好的接口、不需要某个值等等,再去修改接口及文档。

可这个时候因为没有一个可用的 API,因此前端开发人员便需要自己去 Mock 数据,或者搭建一个 Mock Server 来完成后续的工作。

因此,这个时候就出现了两个问题:
  • 维护 API 文档很痛苦
  • 需要一个同步的 Mock Server
而在早期,开发人员有同样的问题,于是他们有了 JavaDoc、JSDoc 这样的工具。它可以一个根据代码文件中中注释信息,生成应用程序或库、模块的API文档的工具。

同样的对于 API 来说,也可以采取类似的步骤,如 Swagger。它是基于 YAML语法定义 RESTful API,如:
swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths: {}

它会自动生成一篇排版优美的API文档,与此同时还能生成一个供前端人员使用的 Mock Server。同时,它还能支持根据 Swagger API Spec 生成客户端和服务端的代码。

然而,它并不能解决没有人维护文档的问题,并且无法及时地通知另外一方。当前端开发人员修改契约时,后台开发人员无法及时地知道,反之亦然。但是持续集成与自动化测试则可以做到这一点。

契约测试:基于持续集成与自动化测试
当我们定好了这个 API 的规范时,这个 API 就可以称为是前后端之间的契约,这种设计方式也可以称为『契约式设计』。(定义来自维基百科
引用
这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。这种方法的名字里用到的“契约”或者说“契约”是一种比喻,因为它和商业契约的情况有点类似。

按传统的『瀑布开发模型』来看,这个契约应该由前端人员来创建。因为当后台没有提供 API 的时候,前端人员需要自己去搭建 Mock Server 的。可是,这个 Mock API 的准确性则是由后台来保证的,因此它需要共同去维护。

与其用文档来规范,不如尝试用持续集成与测试来维护 API,保证协作方都可以及时知道。

在 2011 年,Martin Folwer 就写了一篇相关的文章:集成契约测试,介绍了相应的测试方式:

其步骤如下:
  • 编写契约(即 API)。即规定好 API 请求的 URL、请求内容、返回结果、鉴权方式等等。
  • 根据契约编写 Mock Server。可以彩 Moco
  • 编写集成测试将请求发给这个 Mock Server,并验证
如下是我们项目使用的 Moco 生成的契约,再通过 Moscow 来进行 API 测试。
[
    {
        "description": "should_response_text_foo",
        "request": {
          "method": "GET",
          "uri": "/property"
        },
        "response": {
            "status": 401,
            "json": {
                "message": "Full authentication is required to access this resource"
            }
        }
    }
]


只需要在相应的测试代码里请求资源,并验证返回结果即可。

而对于前端来说,则是依赖于 UI 自动化测试。在测试的时候,启动这个 Mock Server,并借助于 Selenium 来访问浏览器相应的地址,模拟用户的行为进行操作,并验证相应的数据是否正确。

当契约发生发动的时候,持续集成便失败了。因此相应的后台测试数据也需要做相应的修改,相应的前端集成测试也需要做相应的修改。因此,这一改动就可以即时地通知各方了。

前端测试与 API 适配器
因为前端存在跨域请求的问题,我们就需要使用代理来解决这个问题,如 node-http-proxy,并写上不同环境的配置:

这个代理就像一个适配器一样,为我们匹配不同的环境。

在前后端分离的应用中,对于表单是要经过前端和后台的双重处理的。同样的,对于前端获取到的数据来说,也应该要经常这样的双重处理。因此,我们就可以简单地在数据处理端做一层适配。

写前端的代码,我们经常需要写下各种各样的:
if(response && response.data && response.data.length > 0){}

即使后台向前端保证,一定不会返回 null 的,但是我总想加一个判断。刚开始写 React 组件的时候,发现它自带了一个名为 PropTypes 的类型检测工具,它会对传入的数据进行验证。而诸如 TypeScript 这种强类型的语言也有其类似的机制。

我们需要处理同的异常数据,不同情况下的返回值等等。因此,我之前尝试开发 DDM 来解决这样的问题,只是轮子没有造完。诸如 Redux 可以管理状态,还应该有个相应的类型检测及 Adapter 工具。

除此,还有一种情况是使用第三方 API,也需要这样的适配层。很多时候,我们需要的第三方 API 以公告的形式来通知各方,可往往我们不会及时地根据这些变化。

一般来说这种工作是后台去做代码的,不得已由前端来实现时,也需要加一层相应的适配层。

小结
总之,API 使用的第一原则:不要『相信』前端提供的数据,不要『相信』后台返回的数据。
  • 大小: 193.1 KB
  • 大小: 188 KB
  • 大小: 175.5 KB
  • 大小: 14.2 KB
  • 大小: 80.9 KB
  • 大小: 94.4 KB
  • 大小: 22.6 KB
  • 大小: 37 KB
  • 大小: 12.4 KB
来自: zhihu
1
0
评论 共 1 条 请登录后发表评论
1 楼 alvin198761 2017-04-18 08:52
swagger 搞定

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 解决实体类属性与表字段名不一致的问题

    如果实体类属性与表字段名不一致时,会出现对象为null,如何处理? 有三种解决方法: 准备工作: 项目工程如下: 1.引用依赖 <!--配置junit依赖 --> <dependency> <...

  • sql返回大写java接收是小写,解决使用mybatis-plus时,生成的SQL大写变小写加下划线问题...

    在application.xml加上以下配置mybatis-plus.configuration.map-underscore-to-camel-case=false补充知识:mybatis中的大小写转义在使用mybatis 时我们sql是写在xml 映射文件中,如果写的sql中有一些特殊的字符的话...

  • mybatis -plus 将数据库中表名和字段名中的下划线去掉并且按照驼峰命名法映射

    mybatis-plus 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射

  • JPA映射数据库mysql表名,字段名大小写转化,下划线分割.

    遇到小写加下划线"_" 的命名 spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 添加配置类 表名大写,字段名小写,下划线分割. ...

  • mybatisplus映射解读

    Mybatis框架之所以能够简化数据库操作,是因为他内部的映射机制,就可以快速高效的完成SQL操作的实现。既然MybatisPlus是基于Mybatis的增强工具,所以也具有这样的映射规则。我们先来了解一下自动映射规则。

  • MyBatis框架(四)自定义映射

    我们先把如下所示写好,然后下来写resultMap里面常用的标签一共有四个,id来处理主键和属性的映射关系,result来处理普通字段和属性的映射关系,association来处理多对一,collection处理一对多。我们进行查看主键的...

  • mybatis-映射器-select

    select元素是我们最常用也是功能最强大的sql语言。select元素帮助我们从数据库中读取出数据,组装数据给业务人员。执行select语句之前,我们需要定义...执行sql后,mybatis也提供了强大的映射规则,甚至自动映射来帮助

  • Mybatis--自定义映射ResultMap

    ResultType是默认映射,要求字段名要和属性名一致

  • MyBatis配置文件开启驼峰命名映射

    MyBatis配置文件开启驼峰命名映射 今天看SpringBoot整合Mybatis时看到了,遇到了一个遗忘的点,mybatis属性使用驼峰命名,我居然给忘...--是否启用下划线与驼峰式命名规则的映射(如first_name => firstName)-->

  • mybatis 字段名自动转小写_mybatis会自动把字段名中的下划线转为驼峰命名法?

    先看一下转化的调用堆栈: 代码如下: 上面代码只是去掉了下划线,并没有首字母小写变大写的代码。再跟进findProperty方法可以找到获取驼峰结果的代码如下: 可以看出通过reflector.findPropertyName方法把去掉下划线...

  • Mybatis处理列名—字段名映射(一) — 驼峰式命名映射

    -- 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别 --> <setting name="mapUnderscoreToCamelCase" value="true" /> </...

  • 为什么使用mybatis

    话不多说进入主题 ...在每一段这样的代码里面,都需要自己去管理数据库的连接资源,如果忘记写close()了,就可能会造成数据库服务连接耗尽。 对于结果集的处理,要把 ResultSet转换成POJO的时候,必须根据字段

  • 【SpringBoot】26、SpringBoot中整合MyBatis-Plus实现数据库操作

    MyBatis 框架相信大家都用过,虽然 MyBatis 可以直接在 xml 中通过 SQL 语句操作数据库,很是灵活。但正其操作都要通过 SQL 语句进行,就必须写大量的 xml 文件,很是麻烦。于是 MyBatis-Plus 应运而生,作为 ...

  • 【SpringBoot】SpringBoot 中使用 Mybatis 来操作数据库

    【开发环境】: ...在 SpringBoot 中使用 Mybatis 需要引入一个 starter: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-st

  • grails之对象关系映射GORM

    GORM是Grails的对象关系映射(ORM)的实现,实际上它使用的是Hibernate3(非常流行和灵活的开源ORM解决方案),但因为有Groovy的动态特性支持,因此GORM既支持动态类型也支持静态类型,再加上Grails的规约,现

  • 多数据源 使用 mybatis-plus-generator 3.5.1版本进行代码生成

    多数据源 使用 mybatis-plus-generator 3.5.1版本进行代码生成

  • Mybatis-Plus使用案例(包括初始化以及常用插件)

    “” 数据库字段名 exist boolean 否 true 是否为数据库表字段 select boolean 否 true 是否进行 select 查询 使用: @Data @TableName("tb_user") public class User { @TableId(value = "id", type = IdType.) ...

  • Go数据库操作:1、xorm包-基础配置与映射

    参考:xorm操作指南,这边有一些配置或者其他的数据库配置(主要是mysql)会省去,具体查看该文档。...结构体 映射1、映射规则配置1-名称映射规则2-设置方法3-混合使用和自定义4-对应的类型映射2、自定义映射和映射...

  • mybatisPlus使用说明介绍

    MybatisPlus使用 简介 官网直通:https://mp.baomidou.com/ MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 本文主要对一些特殊的地方...

  • MyBatis第五天学习笔记——高级结果映射

    高级结果映射 一对一映射 1.实体类SysUser.java public class SysUser implements Serializable { private static final long serialVersionUID = -328602757171077630L; private Long id; private String ...

Global site tag (gtag.js) - Google Analytics