这个是 DCE-UI 的数据层。整体使用了 RxJS 5 作为主要架构。目前的形式是通过 restify 框架暴露 API 接口来使用。但是由于本项目依赖很轻量,而且核心的数据层和 API 的耦合很低,所以也可以很方便地迁移到前端使用,并且理论上可以兼容所有主流前端框架。
npm start
:在本地的 4000 端口启动服务器,具体路由参看 index.js 里的内容。
npm run build
:用 babel 编译代码,结果在 dist 目录中。
npm run webpack
:用 wenpack 打包代码,结果在 build 目录中。
npm run watch
:持续用 babel 编译源代码。
├── app 应用目录
│ ├── api 数据的 API 接口
│ ├── constant 一些常量
│ ├── factory 数据的工厂函数
│ ├── stream 数据流
│ ├── util 一些方便的函数
│ ├── index.js 应用入口文件
│ └── index.js 用来编译浏览器版代码,可以无视
├── build
│ └── bundle.js webpack 打包结果
├── dist babel 编译后的结果
├── gulpfile.js
├── package.json
├── index.html 用来测试编译出来的浏览器版代码,可以无视
└── webpack.config.js webpack 配置文件
整个应用的入口在 app/index.js,因为 restify 服务器是在这个文件中启动的。但事实上它不是整个数据层的入口,因为 restify 只是暴露数据层的一种形式,未来也可以直接把数据层移植到前端。
整个数据层的入口初始化在 stream/hub.js 中。初始化流程如下。
- 先调用一些基本的 API,比如 apiInfo,然后保存在
α$$
中。α$$
是整个应用最顶层的 Subject。 - 当
α$$
发布数据时,代表应用基本的 API 调用完毕。可以开始启动 socket 了。 - socket 的事件推送会被包装成
hub$$
。hub$$
是整个应用次顶层的 Subject,它收集所有 socket 事件以及自定义事件,并且把对应的事件分发给对应的数据流。 - 最后
hub$$
会分离出很多 Observable,比如app$
、service$
,由此产生各类数据的数据流。 - 最开始
hub$$
会默认发布一个init
事件,来获取所有数据。
从 hub$$
衍生出来的数据流也有各自的逻辑。hub$$
的作用仅仅是通知数据流去更新,但不会告诉数据流怎么更新。数据流的逻辑一般如下:
- 接收到
hub$$
的更新通知。 - 调用 API 发送请求。
- merge 或者 combineLatest 其他数据流中的数据,然后把数据通过工场函数格式化。
- 格式化好的数据就是最后要暴露出来的结果,保存在 BehaviorSubject 中,比如
appsVm$$
。视图层只要直接订阅这个 BehaviorSubject 就可以了。
整个数据层最关键的部分就是数据格式化的部分,它有这么几个难点:
- 格式化一个数据往往需要多个数据。
- 格式化的时候往往要发请求获取数据。
- 当数据更新时,难以复用原来的格式化逻辑。
这些难点统统可以通过 RxJs 解决,请看它是如何解决这些问题的:
- 通过 merge、zip、combineLatest 等 operator 来合成数据。
- 在一开始获取所有数据,形成数据池。这样就能最大程度地重用数据,避免大部分请求。即使要发,由于 RxJs 强大的异步控制能力,也并不是难事。
- 整个 RxJs 数据流形成了一个闭环,数据流是单一、可预测的。从始至终,每种数据都只有一套逻辑。
为了确保这些功能,工场函数必须要是纯函数:
- 工厂函数使用 class。虽然名字不同,但结果是一样的。
- class 中的所有方法必须是纯函数。即使有地方不纯,也要注释标明。
- 如果上面一点做不到,那至少要保证整个 class 是纯的。如果格式化过程中需要发请求,就在数据传入 class 之前发好,然后把结果和元数据一起作为参数传入 class 中。
- 如果 class 是纯的,那么即使数据出了问题,也很容易定位。如果 class 不纯,那么逻辑就乱了,出了问题很难追踪。
- 以 $ 结尾代表这个变量是 Observable。
- 以 $$ 结尾代表这个变量是 Subject,或者 BehaviorSubject。
- 以 _ 结尾代表这个变量时 Subscription。
由于架构有点复杂,所以把流程写下来,以后这部分应该可以自动化。下面以添加 app 的数据流为例。
- 在 api 目录中,新建 app.js 文件,在里面添加好 app 的 api。
- 在 hub 中分出一个
app$
,并且记得要在init
的时候初始化它。 - 在 factory 目录中,新建 app.js 文件,暴露出一个 AppClass。
- 在 stream 目录中,新建 app.stream 文件,引用 app 的 api、AppClass、
app$
,以及其他必要的数据,然后定义好数据流,最终暴露出appVm$$
。
-
现在
hub$$
完全依赖 socket 更新数据,未来要手动更新数据怎么办呢?未来如果要手动更新数据应该会采用类似 redux 那样的形式,并且把它封装成一个 Observable,merge 进
hub$$
中。这样整个应用就能形成一个闭环。
- 单元测试
- 进一步完善注释
Bowen