YodaOS 中是如何生成 API 的?

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

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

为什么要有轻应用?轻应用更像是一个脚本,每当用户一次进行一次交互,只需要从预先加载的脚本中调用定义在对应脚本的函数即可完成一次响应,往往这类应用交互比较简单,如果为此要创建在每次交互的过程中进行一次 ipc 甚至 fork 时,无论对性能还是内存来说,都是比较浪费的。

在设计之初,我们期望对于开发者来说,并不需要针对不同类型的应用,只需要在 package.json 中修改类型即可,YodaOS API 应当保持完全一致。这样的话,我们则面对一个问题,即使是能做到高度抽象,也需要在每次新增一个接口时,修改两处代码,这其实是有违我们的设计初衷的。

API Descriptor

为此,我们引入了 API Descriptor 的概念:https://github.com/yodaos-project/yodart/blob/master/runtime/lib/descriptor/activity-descriptor.js。可以把它看作是用 JavaScript 写的 DSL,它用于描述每个 YodaOS API,包括命名空间、事件、方法等定义。系统在初始化时,会加载所有 API Descriptor,然后分别在 lightapp 和 extapp 生成对应的 API。

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 中的几个事件:active、ready 和 create。因此,在任何应用中都可以这样写:

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'))}

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

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:functionget(){returnPromise.resolve(this._runtime.getCopyOfCredential())}},})

可以看到,与定义事件的方式一样,只需要在 Descriptor 的原型链中,增加对应的对象,然后设置类型(type)为 method 即可,然后在 fn 中实现函数。

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

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

API Translator

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

Translator 是按照我们支持的应用类型对应的,因此对于 lightapp 和 extapp 来说,我们也分为两个 translator:

进程内的 https://github.com/yodaos-project/yodart/blob/master/runtime/client/translator-in-process.js

进程间的 https://github.com/yodaos-project/yodart/blob/master/runtime/client/translator-ipc.js

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

module.exports.translate=translatefunctiontranslate(descriptor){if(typeofprocess.send!=='function'){thrownewError('IpcTranslator must work in child process.')}varactivity=PropertyDescriptions.namespace(null,descriptor,null,null)listenIpc()returnactivity}

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

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

后记

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

帮助读者更好地了解 YodaOS API 的生成过程

帮助读者了解到 DSL,也能将这种思路应用在自己的项目中

如有更多问题,欢迎评论,或者直接在 GitHub 上给我们提问题:Build software better, together

参考

D-Bus introspection:Introspection - Using of D-Bus

YodaOS:YODAOS Project

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容