YodaOS 中是如何生成 API 的?

在 Node.js 社区中,其实不乏经过 Markdown 生成 RESTful API 的框架,按照必定的格式约定好 API 所须要的数据,而后再经过解析 Markdown 文档,将这些关键数据提取出来,最后生成数据库模型和 HTTPS 服务。html

YodaOS 做为一个前端操做系统,一样使用了相似的技术。YodaOS 中的应用分为:lightapp 和 extapp,前者是集成在语音交互运行时(Vui-daemon)进程内部的轻应用,它主要是用于一个交互简单,须要快速响应的场景,好比音量控制、系统控制等。后者做为一个独立的进程,经过 Child Process 与主进程通信,使用场景主要是音乐、游戏、电话等须要长时期使用的应用。前端

为何要有轻应用? 轻应用更像是一个脚本,每当用户一次进行一次交互,只须要从预先加载的脚本中调用定义在对应脚本的函数便可完成一次响应,每每这类应用交互比较简单,若是为此要建立在每次交互的过程当中进行一次 ipc 甚至 fork 时,不管对性能仍是内存来讲,都是比较浪费的。node

在设计之初,咱们指望对于开发者来讲,并不须要针对不一样类型的应用,只须要在 package.json 中修改类型便可,YodaOS API 应当保持彻底一致。这样的话,咱们则面对一个问题,即便是能作到高度抽象,也须要在每次新增一个接口时,修改两处代码,这实际上是有违咱们的设计初衷的。git

API Descriptor

为此,咱们引入了 API Descriptor 的概念:github.com/yodaos-proj… JavaScript 写的 DSL,它用于描述每一个 YodaOS API,包括命名空间、事件、方法等定义。系统在初始化时,会加载全部 API Descriptor,而后分别在 lightapp 和 extapp 生成对应的 API。github

Object.assign(ActivityDescriptor.prototype,
  {
    /** * When the app is active. * @event yodaRT.activity.Activity#active */
    active: {
      type: 'event'
    },
    /** * When the Activity API is ready. * @event yodaRT.activity.Activity#ready */
    ready: {
      type: 'event'
    },
    /** * When an activity is created. * @event yodaRT.activity.Activity#create */
    created: {
      type: 'event'
    }
  }
)
复制代码

上面的代码分别定义了 Activity 中的几个事件:activereadycreate。所以,在任何应用中均可以这样写:数据库

module.exports = activity => {
  activity.on('active', () => console.log('app activated'))
  activity.on('ready', () => console.log('app is ready'))
  activity.on('created', () => console.log('app is created'))
}
复制代码

接下来咱们再看看“方法”是如何定义:json

Object.assign(ActivityDescriptor.prototype,
  {
    /** * Get all properties, it contains the following fields: * - `deviceId` the device id. * - `deviceTypeId` the device type id. * - `key` the cloud key. * - `secret` the cloud secret. * - `masterId` the userId or masterId. * * @memberof yodaRT.activity.Activity * @instance * @function get * @returns {Promise<object>} * @example * module.exports = function (activity) { * activity.on('ready', () => { * activity.get().then((props) => console.log(props)) * }) * } */
    get: {
      type: 'method',
      returns: 'promise',
      fn: function get () {
        return Promise.resolve(this._runtime.getCopyOfCredential())
      }
    },
  }
)
复制代码

能够看到,与定义事件的方式同样,只须要在 Descriptor 的原型链中,增长对应的对象,而后设置类型(type)为 method 便可,而后在 fn 中实现函数。promise

module.exports = activity => {
  activity.get().then(
    (data) => console.log('credentialse is', data),
    (err) => console.error('something went wrong', err))
}
复制代码

这样除了 API 定义能够统一块儿来了,也能比较方便地基于 JSDoc 生成统一的 API Reference 给开发者,使得整个 API 的修改能作到简单易读、门槛低和修改为本低等。app

API Translator

那么在 YodaOS 中,又是如何将上述的 Descriptor 生成为开发者直接使用的接口的呢?下面就为你们介绍咱们引入的 Translator。框架

Translator 是按照咱们支持的应用类型对应的,所以对于 lightapp 和 extapp 来讲,咱们也分为两个 translator:

本文并不具体展开每一个 translator 的工做原理,但会作一些简单的流程介绍。以 translator-ipc 为例:

module.exports.translate = translate
function translate (descriptor) {
  if (typeof process.send !== 'function') {
    throw new Error('IpcTranslator must work in child process.')
  }
  var activity = PropertyDescriptions.namespace(null, descriptor, null, null)
  listenIpc()
  return activity
}
复制代码

每一个 translator 提供一个函数,即 translate(descriptor)。它接受一个 descriptor 对象,而后会遍历原型链中的对象,而且分别按照 namespace、event 和 method 去生成一个叫 activity 的对象,最后将这个对象返回给开发者。

当开发者在使用某个 API 时,activity 对象会按照 translator 预先生成(约定)好的逻辑调用到服务端(Vui-daemon),最后再经过 Promise 返回调用后的结果,从而完成一次接口调用。

后记

本文简单介绍了 YodaOS 在 API 设计过程当中,如何利用 DSL,解决 YodaOS API 在多种应用形态保持一致性。以此,咱们但愿抛砖引玉:

  • 帮助读者更好地了解 YodaOS API 的生成过程
  • 帮助读者了解到 DSL,也能将这种思路应用在本身的项目中

若有更多问题,欢迎评论,或者直接在 GitHub 上给咱们提问题:github.com/yodaos-proj…

参考

相关文章
相关标签/搜索