讨论结论
1. 开发 DataClient
- 模块开发者开发 DataClient(原来的 RealClient),只做异步 API,负责和远端服务通讯。
- 一般只有 publish subscribe 和 generator/promise API
- 开发这个客户端时无需了解多进程知识,只和服务端交互
// DataClient
const Base = require('sdk-base');
class DataClient extends Base {
constructor(options) {
super(options);
this.ready(true);
}
subscribe(info, listener) {
// 向服务端 subscribe
}
publish(info) {
// 向服务端 publish
}
* getData(id) {}
}
2. 包装出 clusterClient
cluster-client 模块保持不变,通过 cluster(DataClient).create()
生成支持多进程的 ClusterClient
const cluster = require('cluster-client');
const clusterClient = cluster(DataClient).create();
3. 开发 APIClient
- 对于有数据缓存等同步 API 需求的模块,再额外封装一个 APIClient
- 用户使用到的是这个 Client 的实例
- 异步数据获取,通过调用 ClusterClient 的 API 实现。
- 由于 clusterClient 的 API 已经被抹平了多进程差异,所以在开发 APIClient 时,也无需关心多进程知识。
例如增加带缓存的 get 同步方法:
const cluster = require('cluster-client');
const DataClient = require('./data_client');
class APIClient extends Base {
constructor(options) {
super(options);
this._client = (options.cluster || cluster)(DataClient).create(options);
this._client.ready(() => this.ready(true));
this._cache = {};
// config.subMap:
// {
// foo: reg1,
// bar: reg2,
// }
for (const key in config.subMap) {
this.subscribe(onfig.subMap[key], value => {
this._cache[key] = value;
});
}
}
subscribe(reg, listener) {
this._client.subscribe(reg, listener);
}
publish(reg) {
this._client.publish(reg);
}
get(key) {
return this._cache[key];
}
}
4. 模块向外暴露 API
module.exports = APIClient;
5. plugin 开发
// app.js || agent.js
const APIClient = require('client-module'); // 上面那个模块
module.exports = app => {
const config = app.config.client;
app.client = new APIClient(Object.assign({}, config, { cluster: app.cluster.bind(this) });
app.beforeStart(function* () {
yield app.client.ready();
});
};
附:
|------------------------------------------------|
| APIClient |
| |----------------------------------------|
| | clusterClient |
| | |---------------------------------|
| | | DataClient |
|-------|------|---------------------------------|
issue 原始内容如下
cluster-client 模块给我们提供了强大的功能,在 sub/pub 模式下使得我们能够只开发一个客户端,在不同的进程中运行同样的 API,且复用同一个远端连接。
一个最简单的例子:
const Base = require('sdk-base');
const cluster = require('cluster-client');
class RealClient extends Base {
constructor(options) {
super(options);
this.ready(true);
}
subscribe(info, listener) {
// 向服务端 subscribe
}
publish(info) {
// 向服务端 publish
}
* getData(id) {}
}
const client = cluster(RealClient).create({});
这段代码运行于多个进程,我们在每个进程都可以使用:
client.subscribe()
client.publish()
brilliant!
但 ClusterClient 同时带来了一些约束,如果想在各进程暴露同样的方法,那么只能是 subscribe publish 和 generator 函数。
假设我要实现一个同步的 get 方法,sub 过的数据直接放入内存,使用 get 方法时直接返回。要怎么实现呢?
cluster-client 提供了 override 方法,你可能会想:
const client = cluster(RealClient)
.overide('get', function() {})
.create({});
这时你会发现不可行,因为还没有 create 之前,我们无法拿到实例进行提前 subscribe,进行缓存。
所以这里建议一种模式,再包一个 API class:
- 异步方法可以直接转调 clusterClient 实例
- 同步方法直接实现
class APIClient extends Base {
constructor(options) {
super(options);
this._client = cluster(RealClient).create({});
this._client.ready(() => this.ready(true));
this._cache = {};
// config.subMap:
// {
// foo: reg1,
// bar: reg2,
// }
for (const key in config.subMap) {
this.subscribe(onfig.subMap[key], value => {
this._cache[key] = value;
});
}
}
subscribe(reg, listener) {
this._client.subscribe(reg, listener);
}
publish(reg) {
this._client.publish(reg);
}
get(key) {
return this._cache[key];
}
}
这样我们就可以:
client.get('foo')
当然这个例子太简单,看起来收益并不大。但是有些功能不仅仅是 sub 到数据就可以了,还需要做出复杂的处理,这种方式就变得有意义了。
好处还有提供一种统一的拓展模式,否则只要是同步的 API,开发者都需要实现一种 patch 的方式,写法太灵活了。
总结一下:
|------------------------------------------------|
| SDKClient |
| |----------------------------------------|
| | ClusterClient |
| | |---------------------------------|
| | | RealClient |
|-------|------|---------------------------------|
这种增强可以进一步固化到 cluster-client 中。
const client = cluster(RealClient, SDKClient)
通过这种方式一键返回最后需要使用的客户端。当然,对于只需要 pub/sub 的简单客户端,直接 cluster(RealClient)
即可,和原来是一样的。
type: feature core: agent