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…

參考

相關文章
相關標籤/搜索