源宝导读:Web应用程序一般都是前后端分离的基本架构,而前后端很可能分别是两拨人分别开发,前后端的接口连调成为高频沟通的对象,开发内耗最大的也在这个环节。本文将分享如何基于OpenAPI将前后端接口协议标准化和自动化,从而大幅降低开发内耗。
一、背景
通常的前端开发中, 接口service层依靠人工阅读接口文档来手动书写, 然而大部分工作是重复的, 骨架是相同的,只是接口数据和接口路径等改变, 对于一个庞大的项目(比如接口数量几百个), 并且客户端存在多个情况下(比如h5和小程序还有android, IOS), 重复的劳动成倍增加, 而且维护起来也会非常麻烦和不可靠。
重复的工作是,后端开发人员开发了一套接口, 前端开发又书写了同样一遍接口, 而且还要生成文档, 而这些接口可以统一成一份与语言无关的结构化的描述, 前端可以利用code-gen自动生成可调用的service层。
而且对于现在基于Typescript前端的开发, 接口协议生成的数据interface应该直接拿来使用, 用来约束各个业务模块的数据结构, 如果要组合生成新的数据结构, 应该使用Typescript的高级类型来组合生成(比如Pick<>,Required<>, Partial<>, Omit<>), 这样前端的业务组件数据结构将和接口协议描述保持一致。
1.1 传统前端后端联调模式问题: 不可靠, 重复工作量, 低效
整个过程写了两份协议, 服务端一份, 客户端一份, 工作重复。
联调周期很长,并且需要不断的重复沟通, B发布了一项接口参数更新, 需要通知C去查看文档, 那么C有这么一个待办事项, 什么时候执行大多数时候不得而知, 过了一段时间C查看了文档之后发现有几个地方不合理, 那么又通知B, 这样联调的周期又延时又低效。
令人忽视的一点是注释, 同一个接口描述, 同一个参数的描述可能存在3种描述, 这可能让团队每个人对业务的理解都不一样。
1.2 Typescript前端项目的特点
1.2.1 可使用丰富的高级派生类型
业务模块中的子组件的数据结构一般是派生的子类型
当SampleInterface结构发生变化,派生的类型会自动继承其中的属性, 如果有冲突, ts会给出错误提示
1.2.2 开发过程数据结构优先
前端的业务模块中:显示, 交互等与用户相关的功能操作都是围绕数据来进行。
前端开发, 扩展, 维护和重构等工作也是围绕数据来进行。
并且业务模块的数据结构与接口的数据结构有很强的关联性。
基于以上特点将数据结构定义来源单一化, 使业务数据定义更合理:
单一源化数据结构定义, 有利于统一维护, 利于重构。
当service接口数据结构发生变化, 那么派生的数据结构会自动适应, 如果有错误, 能够根据Typescript语法提示完成重构, 最后再根据语法提示修改相关的业务相关的组件。
前端业务组件大部分数据结构与API接口的数据结构是关联的。
能不能通过某种接口协议自动生成Typescript的接口定义呢?
Swagger Open API协议, protobuf协议等都是可以描述RESTFul API接口。
二、前后端联调的优化解决方案: 以API协议为中心
按照之前的思路的理解: 特定的业务逻辑的数据结构在不同的业务组件中是高度一致的, 一个数据结构的定义应该只存在一份, 不应该手写多份。
前后端维护唯一一个的规范来描述接口定义。
不同的客户端语言可以根据这个协议生成可执行的接口。
2.1 步骤
2.1.1 基于Open API Specification (Swagger)协议的转换研究
OAS用来定义一个标准的, 与编程语言无关的RESTFul API的接口, 可提供给计算机解析, 也可进行人工阅读和分析, 是一种能结构化描述API接口的约定, 而目前主流的文档生成工具都支持这种标准, 可实现个性化需求。
OAP协议可以用JSON Schema来定义, 详细可看: https://swagger.io /specification /v2/
基本结构:
对应的Typescript示例:
2.1.2 生成Typescript目标代码实际项目中的分析
命名空间根据项目的业务模块来区分, 在Swagger协议中一般在tags里做为标记:
2.1.3 利用模板引擎mustache生成代码
使用编程语言将原始的Swagger JSON转换成更加直接并且有丰富变量的模板数据。
mustache模板是根据项目实际来设定, 灵活使用不同模板生成不同的目标代码。
遇到少量 的特殊的接口, 可以过滤不生成, 然后手动实现接口即可。
具体项目在 https://github.com/mmmy/swagger-typescript-gen。
2.2 实践结果
参考了一个nodejs的开源转换工具, 修改增加了模板数据变量和模板:
Pulsar前端接口220+, code-gen生成了7000+行代码。
dmp前端接口250+, code-gen生成了9000+行代码。
后端生成的Swagger response中字段缺少必填属性和枚举属性, 生成的接口相应Typescript数据结构都是可选参数。
遇到的问题:类似递归的结构暂时无法生成:
遇到少量接口不能实现, 可以手动实现该接口。
不需要看api文档, 却能发现文档的很多问题:接口定义了一个query参数 还有一个类型为json的body参数。
还有少量的接口未定义返回参数, 实际上有返回参数。
2.2.1 带来的优势
✅一致性
前端的接口调用参数, 描述等任何信息将与协议一键同步。
✅重构优势
如果业务逻辑上接口发生变化, 将会自动影响相关的业务组件, 按照Typescirpt的提示能完成绝大部分工作。
✅减少工作量
前端不需要再去查看API文档, 手动同步书写接口代码, 后期维护也是维护源头的协议。
三、结合Typescript开发模式进一步优化
假设所有的模块都是具有完备的typescript设计和定义,为什么运行时还是会报语法错误, 类型错误?
Javasctript本身是动态弱类型语言, 运行时并不能保证红色箭头输入是合法的, 比如预期某个接口返回一个值为对象Object, 但是实际返回了一个null, 为了兼容, 不得不在每个组件中对很多值进行空值判断, 来防止运行错误, 实际上不应该在B中做无意义的判断, 因为在组件A中已经给了B正确的类型输入, 正确的做法是处理和兼容红色的输入, 来确保输入到A的值是合法的, 所以你对输入数据不确定的代码处才是问题的根本, 这和Tyepscript本身没有关系。
3.1 在数据进入组件之前进行纠错, 前端0错误成为可能
假设服务端的数据完整性是不可靠的, 比如对象和数组可能是null, 但是协议定义的却是必要的, 不能是null, 这样给前端运行时带来风险, 所有对这些空字段可以做空默认值处理, 这个操作可以在前端获取到数据的时进行校验并赋予默认值, 这样业务组件拿到的数据不会存在空类型操作错误。
四、总结与展望
基于协议为中心的前后端联调模式可以为软件开发带来益处, 特别是对于Typescript项目, 摒弃掉了低效的重复的工作, 让工作更集中在具体的业务实现方案上。
本次研究只是实现了基本的RESTFul的常规接口,模板也相对复杂度不高, 并不能完全处理通用Swagger协议,比如参数在路径中, 不同http状态码的处理。
对于其他平台还需研究, 比如小程序, Android(Java), IOS(Object-C)。
------ END ------
作者简介
杨同学: 前端工程师,目前在云技术创新中心负责DMP产品的研发工作。
也许您还想看
云客后台优化的“前世今生”(一)
云客后台优化的“前世今生”(二)
在明源云客,一个全新的服务会经历什么?
“弱中心”化的分布式配置管理技术可行性探索
基于 Go 的微服务运行情况监控实践