更好的接口

更自动化更稳定的前后端交互模式

  • why?
  • how?

why?

JavaScript本身的类型系统完全是runtime的

js是解释性语言(“非编译语言”又是不恰当的)
虽然有JIT可以很大程度优化速度问题
但一切质量问题都要线上来检验,这谁顶得住

前端视角

async askForSomething function () {
  const result = await askServer()
  if (_.isString(result)) {
    // 可能是字符串
    return
  }
  if (_.isNumber(result)) {
    // 也可能是数字
    return
  }
  // 具体是类型依赖运行时的判断
  
  if (_.isObject(result) && _.isFunction(someTool.alert)) {
    // 所谓防御型编程就是要在运行时把所有的准备安排明白
    // 从而让调用和取值安全
    someTool.alert(result.msg || '未知消息')
    return
  }
}

因为JavaScript变量类型的不确定,所以开发者需要依赖繁琐的防御型编程无趣的类型转化,来保证变量可以正确的被使用,从而减少runtime error

  • 缺字端了
  • 类型错了
  • undefined is not a function
  • cannot read property 'xxx' of undefind
  • 😂...

否则

变量来源

  • 外部([网络, 存储] + unserializer)
  • 内部(var,let,const...)

由于JavaScript本身没有帮助开发者解决这个问题,特别是外部变量,规模大,语言相关(java vs golang?),不确定性更大,一般来说解决外部变量的类型安全性问题,内部变量的安全性问题也就迎刃而解了。更好的接口,解决的其实就是外部数据在JavaScript中的类型安全性问题

从根基上稳固类型安全。

how?

如果将前后端当做一个整体,那么接口调用的本质即为跨平台调用。在微服务体系中,IDL作为中立桥接,让平台间的调用更容易,更可靠。我也曾在前后端交互中使用IDL

thrift IDL

java client stub code

go client stub code

py thrift service

  • thrift
  • protobuf
  • ...

项目实践

  • 通过IDL + 内部工具
    • 后端:生成api架子,可直接开写逻辑
    • 前端:生成接口调用,可直接调用

后端step1

编写protobuf描述文件

后端step2.1

生成api架子

#!/bin/bash
export GOOS=darwin
export GOARCH=amd64

binpath="./bin"
echo "binpath="$binpath

if [ -n "$GOPATH" ]; then
    # ...
    # proto编译器protoc,有一个插件体系
    # 分别调用不同的插件,以生成不同的代码
    # protoc-gen-go用来生成golang的stub代码
    # protoc-gen-tttool用来生成自定义架子,这个是内部自研的工具
	${binpath}/protoc -I ./example/proto  --plugin=${binpath}/protoc-gen-go # 一些参数
	${binpath}/protoc -I ./example/proto  --plugin=${binpath}/protoc-gen-tttool # 一些参数
else
	echo "GOPATH is needed!"
fi



后端step2.2

看看架子

func Add(
	c *context.Context, 
	request pb_gen.ReqOfAdd
) (resp *pb_gen.RespOfAdd, err error) {
	// Developer fill here
	return &pb_gen.RespOfAdd{}, nil
}

前端step1.1

解析protoc-gen-go生成的文件,生成前端调用代码,先看看生成的go数据结构

前端step1.2

解析方式是golang ast加模板,核心是生成TypeScript的结构定义。ts帮助开发者在compile阶段提前发现错误,以减少runtime error,再加上现代ide,可提升防御型编程的体验

declare interface ITSUserInfo {
    avatar_uri: string;
    child: ITSChild | null;
    ...
    gender: ITSGender;
    klass_info: (ITSKlassInfo | null)[] | null;
    ...
}
  • compile phase(前端文明时代👏)
  • runtime phase(文明的背后依然荒蛮)

拓宽思路

compile phase

typescript

  • flow
  • eslint
  • ...
TypeScript运行在compile阶段,runtime阶段线上跑的仍然是JavaScript,ts对runtime错误束手无策,所以可以酌情加入runtime类型检测。

runtime phase

在绩效系统使用了mobx-state-tree,types.model限制了runtime阶段数据的类型,从根源掐住。Instance+typeof限制了compile阶段的类型,实现两个阶段校验的对称。

mobx-state-tree + typescript

方式很多,但

万变不离其宗

唯一的接口定义 + 稳定的工具链 =

  1. 更自动化
    1. 自动的服务端接口代码
    2. 自动的客户端调用代码
  2. 更稳定
    1. 稳定的compile phase
    2. 稳定的runtime phase

所谓的接口定义,唯一即可,甚至一段强类型语言的有规律的代码段,也可以用ast读取结构加模板生成目标代码。

问题本源

JavaScript本身的变量类型系统完全是runtime的

工程化中的意义

提升项目效率

提升项目质量

方式

IDL + 工具链

思考角度

compile phase

runtime phase

Q&A

Made with Slides.com