JSONSpec: Build a well-typed Web.

原文发布于 2015年5月2日 我的Lofter:http://kimleo.lofter.com/post/46977_6d03e67

开始写这篇文章的时候忽然想到了我之前设计的Lispairs。也就是那个时候,我开始思考,关于数据、表示、约束和语义:JSON格式的不足,XML属性的必要性,以及如何用List表示一切结构。

刚好是在跟一群朋友交流REST服务以及交互接口的时候,想起了Google JSON style guide里面表示JSON格式类型的一个方式。加上本来自觉对类型和形式约束有些简单的理解,于是就想到,定义一种提供JSON格式的数据约束的方式,来解决我们可能遇到的问题。

好吧,先说如果缺少这种约束我们会遇到哪些问题吧:

  1. 数据传输缺乏统一的模式(Schema)约束。JavaScript本身就是动态弱类型的编程语言,而且HTTP协议是只通过简单的文本来传输数据,于是当使用REST API + JSON进行交互的时候,没有一个类型安全的保障机制。
  2. 服务-客户端开发人员沟通困难。至少在缺乏统一的形式化的定义的情况下,难以达成一致的格式约定。同时,因为JSON格式过于简单,也增加了对业务理解的难度。
  3. 缺乏数据验证机制。数据在发送前或者接收并处理之前,没有一个统一的验证机制,这样会增加接收端的整体负担。

当然这不是全部。但仅仅如此就已经带来足够多的麻烦:我们会看到前后端人员在反复交流不同的数据目的与用途,或者是大量的数据验证代码充斥于业务逻辑中间。

所以我们需要解决这个问题;所以我想到了JSONSpec。

试想这样一个场景,我们像服务器获取一本书的信息,服务器会返回给我们如下格式的文档:

{
    "title": "Life, Universe and Everything",
    "author": [ "Douglas Adam" ],
    "isbn": "...."
    ....
}

对于这条数据,我们怎么来定义他的spec如下:

//file: Book.type.json
{
    "title": "string",
    "subtitle?": "string",
    "author": "[Person | string]",
    "isbn": "ISBN"
}

我们逐行解读这条信息:

首先,我们就定义了title这个属性,对应的值是string,其实这就是spec的格式:属性,和属性对应的类型。因为对于data-type这种spec我们依然作为JSON来处理,所以对应的类型也以字符串的方式保存。所以,对于这一项我们可以简单的理解为:title属性的类型是string。

然后就是subtitle这个属性,属性名后面有一个问号,表示这项属性是可选的,同样的,其类型也是string。

接下来是author,看类型这是一个数组,其中的每一个元素都是Person | string类型,这个类型类似于C/Pascal中的Union/Variant,意思是说,对于author这个属性,其类型是一个数组,该数组中的元素可以是Person,或者只是一个string(就像"Douglas Adam”)。

所以,可以看到,在这条spec里面出现了两个特殊的类型,Person和ISBN。也就是说,我们可以在spec里面添加自定义类型,同时,也需要给出他们所对应的spec。

比如:

//file: Person.type.json
{
    "first_name": "string",
    "last_name": "string",
    "born_of": "Date",
    ...
}

//file ISBN.type.json
"string & /[0-9]{10}[0-9]{3}?/"

也即:Person是另外一个JSON Object, 更丰富的属性和内容,用来表示一个人(Person)。而,ISBN则非常的简单,就是一个string,只是用了一个正则表达式来做数据格式上的约束:10位或者13位数字。

于是我们就可以很详细的表示我们要用到的数据究竟长成什么样子的了。

同样地,我们还可以把这些格式映射到具体的编程语言里面,比如一个type spec可能就表示一个Java POJO或者一个.Net POCO,这样只要对应的语言提供了序列化和反序列化的服务和约束检查,那么就可以很轻松地把JSONSpec应用到服务的设计和构建中去了。

我会在后面的文章里面讨论更多详细的内容。