一点关于Console插件前后端通信的想法

一点碎碎念:

  • 为什么client::receive 没有写完整的type呢, 是因为被认为是internal了嘛
  • 以及我没有细看但没有找到Context和socket的联动,看起来只是一个纯粹混入的client限定的cordis,是不是可以把这两者关联起来?虽然我也没想到有什么用x 如果我看漏了请敲我

虽然暂时没有以上问题的答案,不过我先来说说我的想法

目前的文档叙述的数据交换模式(DataService / send event)在不主动使用receive的前提下受限非常大,主要体现在两个部分:
1、难以向前端主动推送
2、难以向不同的Client推送不同的信息

第一点的例子比如sanbox中的sandbox/message, sandbox/clear(此处使用了receive,而且并没有将二者声明为Event,不知道有什么考虑?),再比如dataview中table.count不能被及时更新以及size为undefined时的问题(本来想单开个issue的,但测起来非常麻烦影响也没那么大故作罢),也是由于database DataService 不能及时更新(准确的说是,因为成本过高所以选择不去进行更新)导致的,除此之外还有非常多的场景需求后端对前端的主动推送

第二点是对第一点的补充。client或者说client session是有状态的,具体来说比如用户停留在哪个activity,选中了哪个tab(侧边栏),或者其他自行定义的前端状态。而在不同的状态下,后端可以向前端推送与当前状态,或者说当前所在页面相关的信息。

由于我后续有写比较复杂的后台系统的计划,但我又是完全的前端废物,所以前两天写了一个非常粗糙im demo,对应上述几点大概就是做了:收到消息时如用户在目标聊天窗则直接更新,在其他聊天窗则更新侧边栏,在其他activity则弹出通知。这样几点。

仅为抛砖引用,想听听各位的想法。

2 个赞

大佬你好,我也是最近刚刚接触 Koishi,正在学习 Koishi 的设计,我来分享一下我对这个问题的看法,同时也给后来的小伙伴一个参考:


首先直接说两个问题的解法:

  • 难以向前端主动推送:使用 ctx.console.broadcast() 即可直接推送
  • 难以向不同的Client推送不同的信息:在 主动获取 一节中可以看到服务端代码中的 ctx.console.addListener() 使用,其二参是一个函数;在函数内直接调用 this.send() 即可完成主动推送。

了解了上述两种主动推送的方式之后,你应该已经理解为什么服务端主动推送的方法在「主动获取」一节中被使用;这不是 API 设计问题,而是逻辑上如此——如果要向不同的 Client 推送不同的信息,服务端必须先知道 Client 的诞生。这通过客户端的事件推送,也就是「数据获取」的逻辑实现。

相关 API 在文档中均有列出,应该不会存在任何难度。

此外,如果你仍然觉得 Koishi 提供的前后端通信的逻辑不符合你的想法,你始终可以使用 ctx.router 自己开启一个 WebSocket 服务器进行推送,这样也不会受到 send() 方法对象结构的限制。


下面讨论主动推送尚未在指南中给出示例的原因。

可以看到,在官方文档中只给出了「被动推送」一种前端呈现后端数据的方案;我们希望绝大多数插件均能够使用这种方案获取和呈现状态,而非由服务端推送状态。

「被动推送」方案的基础是前端页面背后的响应式状态系统,这是现代前端区别于传统前端(如 jQuery)的一个重要标志。如果你对「响应式状态系统」尚不了解,那么我推荐阅读 Vue 的介绍以快速入教——Koishi 前端使用 Vue 框架:


倡导使用「被动推送」方案的原因是,绝大多数客户端逻辑均应当是无状态的,状态应当由服务端的继承了 DataService 的 Provider 所持有(需要注意每个 Provider 同时也是一个 Koishi 插件),前端页面应当是这些后端状态的纯(pure)呈现。

实现方面,Vue 使用 @vue/reactivity 包实现响应式状态系统,而 @koishijs/client 在此之上完成了服务端状态的响应式呈现。@koishijs/client 导出的 store 对象是一个响应式对象,可以直接在 SFC Template 中使用。这使得绝大多数 Koishi 控制台插件的开发者无需考虑前后端的通信问题,而将 store 对象的成员直接视为 Provider 插件的状态的复刻。

在实践中,我们发现大多数插件均可以将核心状态置于服务端进行维护。诸如 config、market 和 commands 这样的拥有巨量逻辑的插件仍然能够使用这种方式完成大多数的功能实现,并使用指南章节中提供的另一个示例——上文讨论过的「主动获取」,完成客户端到服务端的请求。

顺带一提,使用「主动获取」方案需要将客户端的用户操作抽象为统一的「客户端事件」(Event,由 @koishijs/plugin-console 导出),这一点在文档中也有体现。抽象为事件的另一个好处是,页面在 UX 层能将用户操作更加提前地抽象为「客户端操作」(Action,虽然 @koishijs/client 好像没有导出),而客户端操作在未来的 Koishi 控制台中会发挥更多的作用。


最后来讨论 Opt-Out。

这个例子非常有代表性——sandbox 和 dataview 这两个插件正是不应当使用「被动推送」模式的两个典型插件。二者 Opt-Out 的原因也显而易见——它们都具有复杂的、客户端 separate 的(吐槽一下 CN 语里似乎没有 separate 这个形容词的同等表述)、耗时的,客户端状态。这样的状态应当由客户端而非服务端持有,因此两个插件选择在用户操作时提交数据请求,然后等待服务端的推送。

客户端相关的

1 个赞

relevant 和 separate 的语感似乎还是有那么一丁点的差别,没有把「別々」的感觉体现出来

不过这里用「相关」确实足够表意

Koishi 的设计基本都是需求驱动的。目前你看到的控制台 API 之所以长成这样,是因为 Koishi 的绝大多数控制台插件都是被动推送模式。如果未来主动推送的需求增加并复杂化,也会提供专用的 API 并完善相应文档。

1 个赞