0.0.12 • Published 9 months ago

block-proto v0.0.12

Weekly downloads
-
License
-
Repository
-
Last release
9 months ago

简介

koko-server 在服务端配合 koko 完成协同能力,它的 3 大核心功能很简单明了:

  • 保存 block 变更
  • 读取 block 内容
  • 订阅 block 的变化

koko-server 的功能本身很简单,因此,该仓库旨在用 node-js 的代码呈现一下逻辑,主要供学习探讨,不建议直接在 production 环境作为使用

数据协议

koko 在前后端交互上,做了必要的约定

案例:

读取协议

// 客户端发起请求
const loadRequestBody = {
  // 标识一次请求,由客户端生成,如果用 websocket 协议,是必须的,以便与响应关联上
  requestId: "e29882ab6ce3445f847b24e9784d197e",
  // 标识客户端 id,页面每次打开会生成一个
  clientId: "0308ac9cc10a41fc91844d117a3a91be",
  // 以数组承载多个 block 查询
  body: [
    {
      // pointer 里可指明查询的信息,比如 id,也可包括其他辅助信息,例如 spaceId
      pointer: {
        // 查询的 blockId
        id: "de98096af0ac42b19d6087cc4b6ba134-00b",
      },
    },
    {
      pointer: {
        // 查询的另一个 blockId
        id: "082d4495f2c54252bbba4a623708f9d0-00b",
      },
    },
  ],
};

// 服务端响应
const loadResponseBody = {
  status: 0,
  message: "",
  data: {
    // block 类型,这里增加一次,便于服务端扩展分类
    block: {
      // 返回的 block 信息,与请求的对应
      "de98096af0ac42b19d6087cc4b6ba134-00b": {
        // 可选,比如 对 block 的权限界定
        role: "manager",
        // 数据示例
        value: {
          id: "de98096af0ac42b19d6087cc4b6ba134-00b",
          version: 26,
          type: "text",
          properties: { text: "world" },
        },
      },
      "082d4495f2c54252bbba4a623708f9d0-00b": {
        role: "reader",
        // 数据示例
        value: {
          id: "082d4495f2c54252bbba4a623708f9d0-00b",
          version: 26,
          type: "text",
          properties: { text: "hello" },
        },
      },
    },
  },
};

保存协议

const saveRequestBody = {
  requestId: "d77f4d53117a4a94a3c971591d8a212f",
  clientId: "0308ac9cc10a41fc91844d117a3a91be",
  transactions: [
    {
      // 由客户端随机生成一个 transaction id
      id: "3a35264ea26c4e73bd5b2853447c7aa0",
      // 具体操作可以有 1 个,或者多个;操作 command 包括 set、update、listBefore、listAdd、listRemove 5 种,囊括了增删改的范畴
      operations: [
        {
          // 改动表的相关信息
          pointer: {
            // 需要修改的 blockId
            id: "de98096af0ac42b19d6087cc4b6ba134-00b",
          },
          // 修改协议 command,set 表示设置
          command: "set",
          // 修改路径
          path: ["properties", "user"],
          // 协议参数示例
          args: "xiaoming",
        },
        {
          pointer: {
            id: "de98096af0ac42b19d6087cc4b6ba134-00b",
          },
          // 修改协议 command,update 表示设置 kv 合并进入
          command: "update",
          path: ["properties"],
          // 协议参数示例
          args: {
            modified: "2022-05",
          },
        },
      ],
    },
  ],
};

// 响应
const saveResponseBody = {
  status: 0,
};

关于 5 类 command,下文中会有具体释义

订阅(取消订阅)协议

// 请求
const subscribeRequestBody = {
  requestId: "c5f4d996fdf44696b40bf275a76b7318",
  // action 表示请求的类型,subscribe 表示订阅,unsubscribe 表示取消订阅
  action: "subscribe",
  // 可同时多个订阅,比如案例里订阅了 3 个
  batchEvents: [
    // version 表示订阅类型,后面为订阅的 blockId;表示对特定 block 产生变化的订阅
    "version:3be6e8ebff8d4ef7be074473dedece07-00b",
    "version:99ddc78d57dc453bb8bfae64b4d2539f-00b",
    // custom 表示自定义类型,hello 代表具体子类,该类型非服务端自动触发
    "custom:hello",
  ],
};

// 响应
const subscribeResponseBody = {
  requestId: "",
};
// 响应-失败
const subscribeResponseBodyFail = {
  requestId: "",
  code: 1,
};
// 注:对于所有响应,客户端判断失败的标准为 → 「status 和 code 有任意一个存在且非 0」

发送自定义事件

我们知道 block 内容变化后,服务端会察觉并推送给订阅者,因此推送行为由服务端触发; 当我们需要由客户端触发一些推送,可以借助自定义事件

const triggerCustomEventBody = {
  requestId: "c5f4d996fdf44696b40bf275a76b7318",
  action: "sendCustomEvent",
  // 自定义事件名称
  event: "custom:hello",
  eventType: "custom",
  // 自定义内容,透传给订阅者
  body: {},
};

订阅通知(下行)协议

当订阅的内容触发时,由服务端下行通知 (比如基于 socket)

// version 类的通知
const versionPushBody = {
  // type 代表通知的大类别,version 类属于 content
  type: "content",
  // event 对应我们之前订阅的内容
  event: "version:93ffa93d0e0242afbde2b5b41bb9448b-00b",
  // 表示该 block 目前最新的版本号,用于对比本地新旧
  body: { version: 4 },
  // 表示是否由自己触发的这次变更
  fromSelfClientId: false,
};

// 自定义事件(custom) 类的通知
const custionPushBody = {
  type: "custom",
  event: "custom:hello",
  // body 可以是任意内容,由触发者提供
  body: {},
};

5 种 command

在保存一个 block 的时候,我们提供 5 种 command:

  • set 设置
  • update 部分更新
  • listBefore 在列表里添加(前)
  • listAfter 在列表里添加(后)
  • listRemove 在列表里移除

set

类似 loadash.set,指定 path,设置为指定 value,例如

例如,设置前数据为:

{
  "name": "xiaoming",
  "age": 20
}

我们进行如下的 set 命令:

{
  "command": "set",
  "path": ["friends"],
  "args": ["zhangsan", "lisi"]
}

设置后的数据为:

{
  "name": "xiaoming",
  "age": 20,
  "friends": ["zhangsan", "lisi"]
}

set 可以覆盖原有的数据,如果路径不存在,支持递归创建

update

update 支持对原有的对象进行 kv 的更新,类似 js 中的 Object.assign 的效果

例如,设置前数据为:

{
  "name": "xiaoming",
  "age": 20,
  "properties": {
    "level": 1,
    "rate": "10%"
  }
}

我们进行如下的 update 命令:

{
  "command": "update",
  "path": ["properties"],
  "args": {
    "level": 2,
    "score": 100
  }
}

设置后的数据为:

{
  "name": "xiaoming",
  "age": 20,
  "properties": {
    "level": 2,
    "score": 100,
    "rate": "10%"
  }
}

如上述示例,update 只能针对 map 类型的数据

listBefore/listAfter

listBefore/listAfter 支持向列表里追加 item

例如,设置前数据为:

{
  "name": "dad",
  "children": ["x1", "x2", "x3"]
}

我们进行如下的 listBefore 命令 (listAfter 类似):

{
  "command": "listBefore",
  "path": ["children"],
  "args": {
    "before": "x2",
    "id": "yyyyyy"
  }
}

设置后的数据为:

{
  "name": "dad",
  "children": ["x1", "yyyyyy", "x2", "x3"]
}

注意的是,listBefore/listAfter 使用时,有以下的约定:

  • 只能操作数组
  • 数组的 item 需要是字符串,并且都不相同,否则可能插入位置不符合预期
  • before 或者 after 指定的参照 item,如果实际不存在,则会放在第一个/最后一个

listRemove

支持从列表里移除 item

例如,设置前数据为:

{
  "name": "dad",
  "children": ["x1", "x2", "x3"]
}

我们进行如下的 listRemove 命令:

{
  "command": "listRemove",
  "path": ["children"],
  "args": {
    "id": "x2"
  }
}

设置后的数据为:

{
  "name": "dad",
  "children": ["x1", "x3"]
}

注意的是,listBefore/listAfter 使用时,有以下的约定:

  • 只能操作数组
  • 数组的 item 需要是字符串,并且都不相同,否则删除的不符合预期
  • 如果删除的内容不存在,则不会引起任何变化

聊一聊 command 的设计思路

实际上我们如果使用 set 命令能满足一切能力,但并不推荐这么使用

update 和 list 系列命令,更像是一种增量操作,我们更推荐这么做,这样在协同冲突的时候,能尽量保持双方的意图

demo server 的模块划分

本仓库用 nodejs 实现了一个服务端,遵从 koko 的前后端协议

各个模块的主要功能如下

  • koa-inst:基于 koa 实现了一个 server
  • midware:负责协议的包装和解析
  • netapp:处理请求并管理连接的 clients
  • store:实现存储与读取,应用 commands

代码地址 : https://h1.static.yximgs.com/udata/pkg/IS-DOCS-MD/d/koko/koko-base-url.v1.js 使用前配置 baseURL,否则回退到 gifshow,例如:

baseURL = "http://localhost:8082";
0.0.10

9 months ago

0.0.11

9 months ago

0.0.12

9 months ago

0.0.9

9 months ago

0.0.8

9 months ago

0.0.5

9 months ago

0.0.4

9 months ago

0.0.7

9 months ago

0.0.6

9 months ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago