Skip to content

Latest commit

 

History

History
580 lines (426 loc) · 27.1 KB

3.实现自己的留言服务.md

File metadata and controls

580 lines (426 loc) · 27.1 KB

简介

在本章中,你将学习到如何从零开始搭建一个基础 DEC App 的模板项目、了解项目的工程结构、学习有关对象建模的知识以及实现留言服务的增删改查功能。

创建项目

  1. 请确保你已经通过 CyberChat 导出自己的身份到电脑上。
  • mac 系统,在 ~/.cyfs_profile 目录下分别有 poeple.desc 和 people.sec 文件

  • windows 系统,在 C:\Users<username>\.cyfs_profile 目录下分别有 poeple.desc 和 people.sec 文件

  1. 请确保已经在系统中安装好 nodejs 最新稳定版。
  2. 全局安装 cyfs-sdk、cyfs-tool 和 cyfs-dapp-cli 脚手架。
  3. 在你觉得合适的文件夹下,使用 cyfs-dapp-cli 创建 dec app 工程模板,命令如下:
npm i -g cyfs-sdk cyfs-tool cyfs-dapp-cli
cyfs-dapp-cli create message-board -t message-board-demo
cd message-board
npm i --force

cyfs-dapp-cli 使用注意

如果你在使用 CyberChat 导出自己的身份到电脑上的时候,更改了身份文件的储存路径,在用 cyfs-dapp-cli 创建项目时需要使用 -o <peoplePath>来指定身份文件,如 -o ~/.cyfs_profile/people,否则会因找不到身份文件而报错。


工程结构

关键阶段的文件/文件夹

开发阶段:在 src 文件夹下完成前端和 Service 端的开发。 发布阶段:打包前端和 Service 端所需的文件资源,依赖 cyfs.config.json 的配置。 安装和运行阶段:DEC Service 在 OOD 上从启动、运行到终止的各阶段,依赖 service_package.cfg 的配置。

cyfs.config.json

  1. config_version: 配置文件的版本号
  2. app_name: 在 cyfs create -n 中输入的名字,也是该 app 的名字。同一个 owner 的 app 名字不能相同。支持中文等非 ASCII 字符,这个名字会显示在应用商店和 App 管理界面上
  3. version: 表示 App 的当前版本,当执行 cyfs deploy 时,会打包并部署 App 的当前版本,部署后,会将该版本的第三位自动加一,作为新的当前版本
  4. description:App 描述,每次部署时,此处填写的描述文字会更新到 App 对象中,在应用商店上显示。
  5. icon: App 图标,只支持 cyfs 链接。每次部署时更新,在应用商店和 App 管理界面上展示
  6. service:配置 Dec App Service 相关打包和运行配置,详情下述
    • pack: 指示打包 Service 时,需要打包哪些目录
    • type: 指定 service 的类型,目前支持"node"和"rust"两种 当指定为"node"时,会将 service 的打包目录,cyfs 目录,package.json 文件一起拷贝到${dist}/service/${target}目录下 当指定为"rust"时,只将 service 的打包目录拷贝到${dist}/service 下,service 的编译等工作需用户事先手动执行
    • dist_targets:指定 service 支持哪些平台,默认和 ood 支持的平台一致。如果在 OOD 支持,但 service 不支持的平台上安装该 Dec App,会返回安装失败错误
    • app_config: 配置 service 的运行配置。可以通过添加[target_name]:[config_path]字段的形式,给每个平台配置不同的运行配置。默认的 default 表示当没有该平台对应配置时,使用的默认配置
  7. web: 配置 Dec App Web 部分的相关打包和运行配置,详情下述
    • folder: 配置 Dec App Web 部分的根目录
    • entry: 可配置 Web 首页的文件名,当前该配置修改无效,首页文件名必须为 index.html
  8. dist: 打包临时目录的名字,默认为"dist"。如果该默认值与其他本地目录冲突,可以修改为其他不冲突的目录名
  9. owner_config: owner 配置文件
  10. ext_info:与 App 部署,安装,运行无关的其他配置,一般与 App 的展示有关。详情下述
    • medias: 配置 App 相关的媒体信息,目前应用商店的 DecApp 详情页会使用该信息
  11. app_id:由 owner id 和 app_name 计算得来的 app id,不能自行修改。在代码中任何需要使用 Dec App Id 的地方,需要用这里展示的 ID。

service_package.cfg

  1. id: 标识
  2. version: 版本号
  3. install:安装脚本
  4. start:启动脚本
  5. stop:停止脚本
  6. status:运行状态查询脚本

文件结构

在项目根目录下,有 4 个文件夹及 11 个文件:

  1. .cyfs: dec app 的元信息
  2. doc: 项目有关的文档
  3. src: 项目的主文件夹
  4. tools: 项目有关的工具程序。
  5. .eslintignore: eslintignore 文件
  6. .eslintrc.json: eslint 配置文件
  7. .gitignore: gitignore 文件
  8. cyfs.config.json: 发布 dec app 到 ood 的配置文件
  9. move_deploy.js: 发布 dec app 到 ood 前必要的文件转移
  10. package.json: 项目配置文件
  11. prettier.config.js: prettier 配置文件
  12. README.md: README 文件
  13. service_package.cfg: dec service 在 ood 的 运行配置文件
  14. tsconfig.json: typescript 配置文件
  15. typing.d.ts: 全局声明文件

注意 cyfs.config.json 文件中有个 app_id 的属性,app_id 是由 owner id 和 app_name 计算得来的 app id,不能自行修改。在代码中有一些地方必须使用 Dec App Id ,请使用这里展示的 ID。


在 src 目录下,有 3 个子文件夹:

  1. common: 前端和 Service 端可以共同复用的代码
  2. service: Service 工程代码
  3. www: 前端工程代码

cyfs_helper

src/common/cyfs_helper 文件夹下的几个文件,分别对 MetaClient、Stack 以及各类常见的较复杂的操作进行了一些封装,方便简单使用。

DECApp 标识修改

  1. 将 src/common/constant.ts 源码中的 DEC_ID_BASE58 常量值修改为自己的 dec app id,在 cyfs.config.json -> app_id
  2. 将 src/common/constant.ts 源码中的 APP_NAME 常量值修改为自己的 dec app 名字,在 cyfs.config.json -> app_name

对象建模

在 CYFS 网络中,所有的自定义对象都必须继承自 cyfs 的基础对象 NamedObject,这个 NamedObject 会提供一些通用属性,每个对象都会有这些属性。 NamedObject 是有具体意义,不可篡改,可被确权的对象数据。 现在,我们先来回顾和学习一些关于 NamedObject 的知识。

命名对象的基础结构

在 CYFS 网络中,结构化数据都是以”命名对象“的格式存储的,这是一种通用的数据格式,是 CYFS 对互联网数据的一种抽象。任何结构数据,都可以被解释为命名对象(NamedObject)。NamedObject 在结构上分:不可变部分 Desc、可变部分 Body,以及这两部分的签名 DescSign,BodySign。

每一个 NamedObject,都有一个全网唯一的对象 Id(ObjectId),我们用 ObjectId 来区分每个对象。这个 ObjectId,就是对 Desc 部分做 Hash 计算得到的。如果 Desc 部分发生了变化,那么 ObjectId 也发生了变化,这是”创建了一个新对象“而不是”修改了一个旧对象“

与之相对,一个 NamedObject 的 Body 部分发生了变化,它的 ObjectId 不变,当这个更新后的对象在 CYFS 网络中传播的时候,CYFS 节点会根据 Body 的 update_time 来决定是否更新本节点上的这个对象。为了保障在去中心系统里 NamedObject Body 部分的一致性,拥有 Body 的对象要么上链,通过区块链来保障其一致性,要么只在 Zone 内使用,通过传统的中心化方法来保障其一致性。在开发实践中,我们一般不鼓励使用 Body。

命名对象的标准属性

NamedObject 的 Desc 和 Body 部分,也是由两部分组成的:标准属性,和自定义的 DescContent、BodyContent。当自定义一个 NamedObject 时,我们只能自定义它的 Content 部分。原则上应尽量使用 NamedObject 的标准属性头部分存储数据,标准属性头中不包含的数据才应存储在自定义的 Content 中。

存储在标准属性中的数据会被 CYFS 的存储引擎和协议自动识别,会带来在存储和查询上的一些便利。

Desc 标准属性:

  • obj_dec_type: number, 必有,不可修改,表示一个 NamedObject 的类型。CYFS 标准类型的 obj_type 恒为 0,通过 obj_type_code()区分标准类型中的各个类型
  • dec_id: ObjectId, 可选,如设置,表示这个命名对象类型是由哪个 Dec App 创立的。通过 dec_id 和 obj_dec_type 可以唯一确定一个 DecApp 对象的类型
  • prev: ObjectId,可选,含义一般为"该对象的前置对象",如果一系列对象间有确定的前后关系,可以使用这个字段
  • create_timestamp: HashValue,可选,一般表示该对象创建时,某个外部参照物的 Hash 值。常见选择是使用当前 BTC 或 ETH 等公链的最新块 Hash
  • create_time: u64, 必选,表示该对象创建时的时间戳,默认为当前时间戳。如果显式设置该值为 0,表示这个对象的 ObjectId 与创建时间无关,只与对象的内容有关。
  • expired_time:u64, 可选,表示该对象的过期时间
  • owner: ObjectId,可选,表示该对象的"所有者"。是最重要的一个标准属性,除了 People、Device 等少数标准有权对象外,任何对象都应从产权的角度设置一个所有者,并用所有者的私钥对其签名。需要说明的是,Device 对象的 owner 是 People 对象,因此,被 People 对象用户和被 Device 对象拥有的区别在于,Device 对象是个二级身份对象。
  • area: ObjectId,可选,表示对象创建时所属的地区。地区的填写按真实情况填写即可,CYFS 围绕地区有数据产权的管辖权设计。
  • author: ObjectId,可选,表示这个内容的作者。比如一本旧书,其 Owner 是出版社,但作者一定是一个故人。
  • public_key: PublicKeyValue,可选,只有有权对象有这个值

Body 标准属性:

  • prev_version: HashValue,可选,表示这个 Body 的前置 Body Hash
  • update_time: u64,必选,表示 Body 内容的修改时间,CYFS 基础设施默认会按照 update_time 判定对象是否要更新。如果新的对象的 update_time 比当前的新,则更新本地存储的对象,否则不更新

命名对象的类型

在 cyfs 体系中,我们把对象分成 3 大类:

  • 标准对象 StandardObject:cyfs sdk 内置对象,共 15 种,占用 ObjectTypeCode 的 1-15 号位,区间范围[1, 15],这些对象代表了我们对 CYFS Network 的基础抽象,应熟练掌握这些标准对象的用法。
  • 核心(语义)对象 CoreObject:cyfs sdk 内置对象,统一占用 ObjectTypeCode 的第 16 号位,区间范围[32, 32767],通过 obj_type 字段来区分对象。这些对象是未来在不同应用程序之间有共同语义的对象,由标准化组织维护。通常一个流行应用定义的 DECObject,会在成为事实标准后升级成 CoreObject。
  • 应用对象 DECObject: 由 App 开发者自行扩展,并设置了 dec_id 的对象,也占用 ObjectTypeCode 的第 16 号位(大于 32767),通过 obj_type 字段来区分对象。注意:一个对象在设置了 dec_id 字段后,才会被看做一个 DecObject,并得到对应处理。如果没有设置,会被错误地当作 CoreObject

使用稳定编码实现 DEC Object

由于 NamedObject 的 Desc 部分和 ObjectId 的关系,这就要求我们必须引入稳定编码技术。 protobuf 是一种广泛使用的用于序列化结构数据的协议,其稳定编码的特性刚好符合我们的需要。 使用 proto 描述文件定义数据的结构,然后使用 protoc 编译器生成源代码,在各种数据流中使用各种语言进行编写和读取结构数据。甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。 关于 protobuf 的 2 个重要概念:

  1. 序列化:将结构数据或者对象转换成能够用于存储和传输的格式。
  2. 反序列化:在其他的计算环境中,将序列化后的数据还原为结构数据和对象。

结构化数据的 64KB 编码长度限制

在 NamedObject 中,desc 长度目前限制为 64KB,但今天很多场景的数据大小都很难超出这个限制。我们通过这个限制希望帮助开发者严肃的区分结构化数据和非结构化数据。很多时候你不是需要更大的 NamedObject,而是应该使用非结构化数据(Chunk)。比如一本书的内容或者一张图片的 base64 编码,可以考虑使用以下参考方法进行解决:

  • 将数据对象本身保存成 chunk,应用扩展的 EBookObject 保存对应的 chunkid。

使用 protobuf 编写留言对象的描述文件

  • 完整代码见 src/common/objs/obj_proto.proto

留言板中的每一条留言就是一个 NamedObject,我们把这种代表留言的 NameObject 叫留言对象。 留言对象包含两个属性:key 和 content。key 是留言对象的唯一标识,content 是留言的文本内容。 按这个设计,受到前面 64KB 的限制,一个留言不会特别长。 在 porto 文件中的定义如下:

message Message {
	string key  = 1;
	string content = 2;
}

利用 protobuf 生成静态桩代码

在利用 protobuf 生成静态桩代码时,不同的系统在操作上有所差异。

  • mac 系统,在项目根目录下,执行指令如下:
npm run proto-mac-pre
npm run proto-mac

解释一下这 2 个命令:

  1. proto-mac-pre:全局安装ts-protoc-gen,这可以为我们生成对应的声明文件。之后,修改 tools 目录下的 protoc 程序的可执行权限。
  2. proto-mac:使用 protoc 来生成静态桩代码。

由于是直接执行的 protoc 执行程序,可能会弹窗提示 无法打开“protoc”,因为无法验证开发者,需要开发者按照以下路径去设置: 系统偏好设置 -> 安全性与隐私 -> 允许从以下位置下载的 App -> 选择 App Store 和认可的开发者 -> 点击 仍然允许 按照这个路径设置好,重新执行指令即可。


  • windows 系统,在项目根目录下,运行如下指令:
npm run proto-windows

运行完毕,在 src/common/objs 文件夹下,生成了 obj_proto_pb.d.ts 和 obj_proto_pb.js 这两个文件。在 obj_proto_pb.d.ts 声明文件中,我们可以看到 Message 对象的桩代码如下:

export class Message extends jspb.Message {
	getKey(): string;
	setKey(value: string): void;

	getContent(): string;
	setContent(value: string): void;

	serializeBinary(): Uint8Array;
	toObject(includeInstance?: boolean): Message.AsObject;
	static toObject(includeInstance: boolean, msg: Message): Message.AsObject;
	static extensions: { [key: number]: jspb.ExtensionFieldInfo<jspb.Message> };
	static extensionsBinary: {
		[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>;
	};
	static serializeBinaryToWriter(
		message: Message,
		writer: jspb.BinaryWriter
	): void;
	static deserializeBinary(bytes: Uint8Array): Message;
	static deserializeBinaryFromReader(
		message: Message,
		reader: jspb.BinaryReader
	): Message;
}

针对上面的 Message 对象的桩代码中比较重要的 4 个内容进行说明:

  1. 读取和设置 key 值分别使用 getKey 和 setKey
  2. 读取和设置 content 值分别使用 getContent 和 setContent
  3. 将 Message 对象编码为 Uint8Array,使用 serializeBinary
  4. 将 Message 对象的 Uint8Array 格式解码为 Message 对象,使用 deserializeBinary

cyfs-objs-generator 自定义对象文件生成器

前面,我们已经使用 protobuf 生成了静态桩代码,但对象要能在 CYFS 中使用,还需要针对每个对象创建对象自己的定义文件,我们使用 cyfs-objs-generator 可以很方便的实现这个目的。 cyfs-objs-generator 可以用来将定义好的 .proto 文件转换为一个或多个自定义对象的 .ts 文件。 目前支持的 .proto 类型有:double, float, int32, int64, uint32, uint64, bool, string, bytes 和 enum。


cyfs-objs-generator 暂时不支持嵌套定义,需要嵌套定义,请自行修改自定义对象的 .ts 文件。


安装

npm i -g cyfs-objs-generator

使用

使用 cyfs-objs-generator -i <proto_file_path> -o <output_dir_path>,例如:

cyfs-objs-generator -i ./obj_proto.proto -o ./test_out

完成 MessageObject

你可以使用 cyfs-objs-generator 工具来自动生成自定义对象文件的全部代码。不过,掌握原理总没有坏处。定义对象文件的过程比较机械和繁琐,基本上=按模板填写就好,这里简单的说明一下:

  1. 定义不可变部分 Desc
    • 定义对象 ExampleDescTypeInfo extends cyfs.DescTypeInfo object_type: 应用对象类型 sub_desc_type:desc 系统字段的存在形态(disable-没有,option-可选,其他)
    • 定义具体的 ExampleDescContent extends cyfs.ProtobufDescContent
    • 定义解码器 ExampleDescContentDecoder extends cyfs.ProtobufDescContentDecoder<ExampleDescContent, protos.Example>
    • 定义 ExampleDesc extends cyfs.NamedObjectDesc
    • 定义 ExampleDescDecoder extends cyfs.ProtobufBodyContentDecoder
  2. 定义可变部分 Body
    • 定义 ExampleBodyContent extends cyfs.ProtobufBodyContent
    • 定义 ExampleBodyContentDecoder extends cyfs.BodyContentDecoder<ExampleBodyContent, protos.NoneObject>
  3. 定义 ID 辅助类
    • ID 辅助转换 ExampleId extends cyfs.NamedObjectId<ExampleDescContent, ExampleBodyContent>
    • 二进制 ID 解码器 ExampleIdDecoder extends cyfs.NamedObjectIdDecoder<ExampleDescContent, ExampleBodyContent>
  4. 整合不可变 desc 和可变 body
    • 定义对象构造器 ExampleBuilder extends cyfs.NamedObjectBuilder<ExampleDescContent, ExampleBodyContent>
    • 定义对象本身 Example extends cyfs.NamedObject<ExampleDescContent, ExampleBodyContent>
    • 定义对象解码器 ExampleDecoder extends cyfs.NamedObjectDecoder<ExampleDescContent, ExampleBodyContent, Example>

至此,对象定义部分算是实现完全,但是,你是不是发现为了定义一个对象居然定义了多达 12 个类,心理压力巨大,下面我们给这些类从更宏观的角度归下类:

  • 按处理目标分类,可以分为 Desc、DescContent、Body、ID、整合对象。

  • 按功能分,可以分为:

    1. 编解码规则说明,这里告诉编解码器如何编解码特定字段
    DescTypeInfo
    
    1. 对象内容定义,定义对象包含哪些字段,如何编解码,DescContent/Body/ID 都有相对应的编解码类
    // Desc
    DescContent
    DescContentDecoder
    // Body
    BodyContent
    BodyContentDecoder
    // ID
    NamedObjectId
    NamedObjectIdDecoder
    
    1. 类别名,定义好以上类型后,基本就可以用了,系统提供了一系列模板来完成剩余的整合工作,但是系统提供的都是一些通用命名,非常难读而且冗长,所以一般推荐给它们都定义一个别名,让代码可读性更好,这些类通常直接用默认接口就可以,无须增加其他内容;当然为了实现其他效果,可以为它们定义一些辅助接口
    // Desc
    NamedObjectDesc
    NamedObjectDescDecoder
    // 完整Object
    NamedObjectBuilder
    NamedObject
    NamedObjectDecoder
    

总结一下,这些类型主要提供对象各个部分(Desc/DescContent/Body/ID/完整对象)的字段定义和编解码规则,基本方法就是继承系统提供的对应基类,实现对应的虚接口,并增加一些自己定义的字段,无须自己完全从头定义它们的内容和编解码方式。

简单的说,其实就是个填空题,完整源码见 src/common/objs/message_object.ts
更多自定义对象建模,可参考此文件进行填空题式的套用。

设计留言对象在 RootState 上的存储路径

通过前面基于 ObjectMap 的 RootState 介绍小节的学习,我们知道,在 CYFS 中,包含了 ObjectMap,ObjectSet...等标准命名容器对象,这些标准命名容器对象用来存放命名对象。每一个命名对象都是通过一个"/key1/key2/key3"的唯一路径来表示。 留言对象也是命名对象的一种实例,这些对象应当保存在一个能代表留言对象列表命名容器对象中,这个命名容器对象也有自己的路径,我们取名为messages_list。 也就是说,只有存放在特定ObjectMap<MessageObject>中的 MessageObject,才是留言对象列表里要展现的留言。 因此,标题对应对象在 RootState 上的存储结构如下所示:

|--messages_list
|   |--message1.key => Message object
|   |--message2.key => Message object
  • 包含其他对象的完整存储路径设计,见 src/doc/object_model.md

使用 RootState API 实现留言的增删改查

通过前面的学习,你已经了解了 RootState 的基本原理、完成了为留言对象建模以及设计好了留言对象在 RootState 上的存储路径。 不过,你可能会问,该如何对 RootState 上的对象进行增删改查操作呢? 答案:使用 PathOpEnv (Root Path Operation Env) 在 CYFS 中,对于任意一个 RootState 的状态,比如/a/b/c,除了叶子节点,每一级都是一个 ObjectMap,叶子节点可以是任意 ObjectId 或 ObjectMap、 ObjectSet 格式的容器等。PathOpEnv 实现了对 DEC 的 RootState 进行基于路径的操作。

为解决数据的一致性问题,PathOpEnv 采用基于路径锁事务机制来实现对 RootState 上的对象数据的操作。 事务是指单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。一个逻辑工作单元要成为事务,必须满足 ACID(原子性、一致性、隔离性和持久性)属性。

如果要对 RootState 上的对象进行 CRUD 操作,一个完整的的 PathOpEnv 事务会包含 3 个步骤:

  1. 锁定路径(lock): 锁定一组 path,只有当前 PathOpEnv 才可以操作。一个路径被锁定后,该路径的父路径和子路径都会被锁定,别的 PathOpEnv 不可重复加锁,但兄弟路径不会被锁定,比如锁定路径/a/b/c,那么/a, /a/b, /a/d/c/d 都不可以再重复加锁,但/a/c, /a/b/d 这些路径都可以加锁。
  2. 事务操作(CURD): 在指定路径创建对象(insert_with_path)、在指定路径更新对象(set_with_key)、从指定路径读取对象(get_by_path)以及从指定路径删除对象(remove_with_key)
  3. 事务提交(commit): 使用 PathOpEnv 完成操作后,需要 commit 才会把所有修改操作更新到 RootState 上。如果只是读取操作,那么 commit 不会做任何事情。一个 PathOpEnv 只可以 commit/abort 一次,之后不可以再继续使用了。提交成功后,会返回对应的 DEC 和 Global 的 RootState 状态。

现在,我们小试牛刀一下,使用 RootState 实现对留言的增删改查功能。

新增留言

  • 完整代码见 src/service/routers/publish_message.ts

要新增一条留言,核心是需要 5 个步骤:

  1. 接收新的留言对象
const { object, object_raw } = req.request.object;
// ...ignore
const decoder = new MessageDecoder();
const dr = decoder.from_raw(object_raw);
// ...ignore
const messageObject: PublishMessageRequestParam = dr.unwrap();
  1. 使用 PathOpEnv 根据留言对象的 key 锁定新路径
const createMessagePath = `/messages_list/${messageObject.key}`;
const paths = [createMessagePath];
// ...ignore
const lockR = await pathOpEnv.lock(paths, cyfs.JSBI.BigInt(30000));
  1. 将该留言对象保存到 NON 中
// ...ignore
const nonObj = new cyfs.NONObjectInfo(
	messageObject.desc().object_id(),
	messageObject.encode_to_buf().unwrap()
);
const putR = await stack.non_service().put_object({
	common: {
		dec_id: decId,
		level: cyfs.NONAPILevel.NOC, // Local operation only, no network
		flags: 0, // operation will be initiated
	},
	object: nonObj,
});
// ...ignore
  1. 使用 PathOpEnv 执行 insert_with_path 操作,将新留言对象加入到 message_list
const objectId = nonObj.object_id;
const rp = await pathOpEnv.insert_with_path(createMessagePath, objectId);
// ...ignore
  1. 提交事务
const ret = await pathOpEnv.commit();
// ...ignore

修改留言

  • 完整代码见 src/service/routers/update_message.ts

要修改一条留言,核心是需要 5 个步骤:

  1. 接收新的留言对象(指定要修改的 key 值)
const { object, object_raw } = req.request.object;
// ...ignore
const decoder = new MessageDecoder();
const dr = decoder.from_raw(object_raw);
// ...ignore
const messageObject: UpdateMessageRequestParam = r.unwrap();
  1. 使用 PathOpEnv 根据留言对象的 key 锁定对象路径
const updateMessagePath = `/messages_list/${messageObject.key}`;
const paths = [updateMessagePath];
// ...ignore
const lockR = await pathOpEnv.lock(paths, cyfs.JSBI.BigInt(30000));
  1. 将该新的留言对象保存到 NON 中
const nonObj = new cyfs.NONObjectInfo(
	messageObject.desc().object_id(),
	messageObject.encode_to_buf().unwrap()
);
// ...ignore
const putR = await stack.non_service().put_object({
	common: {
		dec_id: decId,
		level: cyfs.NONAPILevel.NOC,
		flags: 0,
	},
	object: nonObj,
});
// ...ignore
  1. 使用 PathOpEnv 执行 set_with_path 执行更新操作
const newObjectId = nonObj.object_id;
const rs = await pathOpEnv.set_with_path(updateMessagePath, newObjectId!);
// ...ignore
  1. 提交事务
const ret = await pathOpEnv.commit();
// ...ignore

删除留言

  • 完整代码见 src/service/routers/delete_message.ts

要删除一条留言,核心是需要 4 个步骤:

  1. 接收要删除的留言对象(指定 key 值)
const { object, object_raw } = req.request.object;
// ...ignore
const decoder = new MessageDecoder();
const dr = decoder.from_raw(object_raw);
// ...ignore
const messageObject: DeleteMessageRequestParam = r.unwrap();
  1. 使用 PathOpEnv 根据留言对象的 key 锁定对象路径
const deleteMessagePath = `/messages_list/${messageObject.key}`;
const paths = [deleteMessagePath];
// ...ignore
const lockR = await pathOpEnv.lock(paths, cyfs.JSBI.BigInt(30000));
  1. 使用 PathOpEnv 执行 remove_with_path 操作
const rm = await pathOpEnv.remove_with_path(deleteMessagePath);
// ...ignore
  1. 提交事务
const ret = await pathOpEnv.commit();
// ...ignore

查询留言

  • 完整代码见 src/service/routers/retrieve_message.ts

要新增一条留言,核心是需要 3 个步骤:

  1. 接收要查询的留言对象(指定 key 值)
const { object, object_raw } = req.request.object;
// ...ignore
const decoder = new MessageDecoder();
const dr = decoder.from_raw(object_raw);
// ...ignore
const messageObject: RetrieveMessageRequestParam = dr.unwrap();
  1. 使用 PathOpEnv 读取要查询的留言对象 objectId
const queryMessagePath = `/messages_list/${messageObject.key}`;
const idR = await pathOpEnv.get_by_path(queryMessagePath);
// ...ignore
const objectId = idR.unwrap();
// ...ignore
  1. 在 non 上获取留言对象
const gr = await stack.non_service().get_object({
	common: { level: cyfs.NONAPILevel.NOC, flags: 0 },
	object_id: objectId,
});
// ...ignore

小结

本章内容比较多,我们学习了 CYFS 命名对象建模的原理,并实战了 DEC Service 中保存状态和读取状态的方法。了解了 RootState 协议 API 的使用。但这还不是一个能运行的 DEC Service。要让我们的 DEC Service 运行起来以及进行必要的调试,请继续阅读下一章内容。