/dhp-model

Primary LanguageJavaScript

项目简介

这个是 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 中。初始化流程如下。

  1. 先调用一些基本的 API,比如 apiInfo,然后保存在 α$$ 中。α$$ 是整个应用最顶层的 Subject。
  2. α$$ 发布数据时,代表应用基本的 API 调用完毕。可以开始启动 socket 了。
  3. socket 的事件推送会被包装成 hub$$hub$$ 是整个应用次顶层的 Subject,它收集所有 socket 事件以及自定义事件,并且把对应的事件分发给对应的数据流。
  4. 最后 hub$$ 会分离出很多 Observable,比如 app$service$,由此产生各类数据的数据流。
  5. 最开始 hub$$ 会默认发布一个 init 事件,来获取所有数据。

数据流

hub$$ 衍生出来的数据流也有各自的逻辑。hub$$ 的作用仅仅是通知数据流去更新,但不会告诉数据流怎么更新。数据流的逻辑一般如下:

  1. 接收到 hub$$ 的更新通知。
  2. 调用 API 发送请求。
  3. merge 或者 combineLatest 其他数据流中的数据,然后把数据通过工场函数格式化。
  4. 格式化好的数据就是最后要暴露出来的结果,保存在 BehaviorSubject 中,比如 appsVm$$。视图层只要直接订阅这个 BehaviorSubject 就可以了。

工厂函数

整个数据层最关键的部分就是数据格式化的部分,它有这么几个难点:

  1. 格式化一个数据往往需要多个数据。
  2. 格式化的时候往往要发请求获取数据。
  3. 当数据更新时,难以复用原来的格式化逻辑。

这些难点统统可以通过 RxJs 解决,请看它是如何解决这些问题的:

  1. 通过 merge、zip、combineLatest 等 operator 来合成数据。
  2. 在一开始获取所有数据,形成数据池。这样就能最大程度地重用数据,避免大部分请求。即使要发,由于 RxJs 强大的异步控制能力,也并不是难事。
  3. 整个 RxJs 数据流形成了一个闭环,数据流是单一、可预测的。从始至终,每种数据都只有一套逻辑。

为了确保这些功能,工场函数必须要是纯函数

  1. 工厂函数使用 class。虽然名字不同,但结果是一样的。
  2. class 中的所有方法必须是纯函数。即使有地方不纯,也要注释标明。
  3. 如果上面一点做不到,那至少要保证整个 class 是纯的。如果格式化过程中需要发请求,就在数据传入 class 之前发好,然后把结果和元数据一起作为参数传入 class 中。
  4. 如果 class 是纯的,那么即使数据出了问题,也很容易定位。如果 class 不纯,那么逻辑就乱了,出了问题很难追踪。

命名约定

  • 以 $ 结尾代表这个变量是 Observable。
  • 以 $$ 结尾代表这个变量是 Subject,或者 BehaviorSubject。
  • 以 _ 结尾代表这个变量时 Subscription。

添加新数据流的流程

由于架构有点复杂,所以把流程写下来,以后这部分应该可以自动化。下面以添加 app 的数据流为例。

  1. 在 api 目录中,新建 app.js 文件,在里面添加好 app 的 api。
  2. 在 hub 中分出一个 app$,并且记得要在 init 的时候初始化它。
  3. 在 factory 目录中,新建 app.js 文件,暴露出一个 AppClass。
  4. 在 stream 目录中,新建 app.stream 文件,引用 app 的 api、AppClass、app$,以及其他必要的数据,然后定义好数据流,最终暴露出 appVm$$

常见问题

  1. 现在 hub$$ 完全依赖 socket 更新数据,未来要手动更新数据怎么办呢?

    未来如果要手动更新数据应该会采用类似 redux 那样的形式,并且把它封装成一个 Observable,merge 进 hub$$ 中。这样整个应用就能形成一个闭环。

待办事项

  1. 单元测试
  2. 进一步完善注释

维护者

Bowen