为了提高服务编排模块的性能和代码可维护性,在新版本中我们提供了@lingxiteam/service这个包作为服务编排模块底层数据操作框架。该框架提供了一个通用的数据存储,修改以及响应的接口。
在新版本,我们将服务编排模块的代码调整至ServiceOrchestrator目录,业务相关的代码以及组件和资源都放置在此目录,以便于将来模块解耦。
当前@lingxiteam/service还未发包,本地使用软连接使用,包的位置位于packages/service/src/
不建议直接修改包中的内容,在使用的过程中如果觉得框架有需要修改,需要先做评估。
-
请确保你已经从git上拉取了该分支的全部代码,当前分支为
opt-service-performance-11146372
-
使用
yarn start:app
运行主程序,同时切换到packages/service
目录执行yarn watch
如果不出意外,此时项目应该可以正常启动了。
服务编排框架的核心处理类,负责数据的处理以及事件的分法。
const processor = new ServiceProcessor({ // 类实例化的时候可以传入默认数据到store中去
baseInfo: {
serviceName: `新增服务${getCurrentDateTime('MMDDhhmm')}`,
serviceCode: `new_${createId(10)}`,
serviceType: 'orchestration',
},
serviceParam: {},
serviceNodeList: [
{ nodeCode: 'input', nodeName: '服务入参', nodeType: 'input', children: [] },
{ nodeCode: 'output', nodeName: '服务出参', nodeType: 'output', children: [] },
],
nodeListStructure: [],
nodeDictionary: {},
selectedNodeCode: 'input',
graphInstance: null,
selectedNode: null,
cache: {},
addStepInfo: {},
activeTab: null, // 'choreography',
status: 0, // 0:未初始化 1: 初始化完成 >1 更新数据
})
实际上对于ServiceProcessor的实例化,也包含在框架中了,我们会将实例processor暴露出来,以供大家通过processor几个简单的API封装出更多的符合业务需求的工具代码。
-
public getData(): any;
-
参数: 不传入任何参数
-
返回值: 返回整个store中的数据
-
-
public getData(key: string | string[]): any;
-
参数: 传入从store后开始的路径字符串活路径字符串的数据
-
返回值: 返回指定路径的数据或者指定多个路径的数据集合
getData('baseInfo') // 返回store中baseInfo的最新值 getData('baseInfo.serviceName') // 返回store中baseInfo.serviceName的最新值 getData(['baseInfo.serviceCode', 'status']) // 返回对应值的数组[serviceCode新值, status新值]
-
-
public getData(selector: (store: any) => any): any;
- 参数:使用函数(selector的方式)获取值,支持数组
- 返回值:返回指定选择器的获取的数据或者数据集合
getData(store => store.baseInfo) // 返回store中baseInfo的最新值 getData(store => store.baseInfo.serviceName) // 返回store中baseInfo.serviceName的最新值 // store不是固定的,可以进行简化 getData(s => [s.serviceCode, s.status]) // 返回对应值的数组[serviceCode新值, status新值]
-
public setData(newData: object): void;
-
参数: 不传入路径,只传入新的数据
-
说明:将newData合并到store中
-
-
setData = (path: string | string[], value: any | ((currentValue: any) => any)): void
- 参数:
path
路径或者路径的数组,value
要传入的值或者一个回调函数
const instance = {}; setData('graphInstance', instance); // store下跟节点单个值设置 setData('baseInfo.serviceType', 'rule'); // 根据路径设置值 setData(['graphInstance', 'baseInfo.serviceType'], [instance, 'rule']); //保证顺序一致 // 使用回调函数的方式设置值 setData('status', (value) => { // value是当前仓库中status的最新值 return value + 1; // 将其+1后更新原值 });
- 参数:
通过以上getData和setData方法的组合使用,就可以组合出我们自己业务上需要用到的业务函数
举个🌰 删除画布中指定节点
deleteServiceNode: (processor: any, params: any) => {
const { path, nodeCode } = params;
const [serviceNodeList, nodeDictionary] =
processor.getData((s: any) => [s.serviceNodeList, s.nodeDictionary]);
deleteNodeByPath(serviceNodeList, path);
delete nodeDictionary[nodeCode];
processor.setData(['nodeDictionary', 'status'], ([_, num]) => {
return [nodeDictionary, num + 1];
});
},
-
subscribe(selector: (store: any) => any, listener: Listener): Unsubscribe
-
参数:
-
selector
: 函数,从 store 中选择部分数据的函数。 -
listener
: 函数,当选定的数据变化时调用的回调函数。
-
-
返回值:
Unsubscribe
,调用此函数可取消订阅。
-
-
示例1:单独订阅一个数据
useEffect(() => {
const unsubscribe = subscribe(store => store.serviceParam, (value) => {
// 这里的value就是store中serviceParam的值
// 当serviceParam发生了变化
// 可以在这里做一些事情....
});
return () => unsubscribe();
}, []);
- 示例2:单独订阅一个数据,并触发当前页面更新
const [serviceParam, setServiceParam] = useState({});
useEffect(() => {
const unsubscribe = subscribe(store => store.serviceParam, setServiceParam); // v => setService(v)简写
return () => unsubscribe();
}, []);
return (<MakeupParames serviceParam={serviceParam} />);
- 示例3:单独订阅一个数据,并且通过比较新旧值进行再进行下一步动作
useEffect(() => {
const unsubscribe = subscribe(store => store.serviceParam, (currValue, prevValue) => {
if(currValue.editStatus !== prevValue.editStatus) {
// 可以精确控制新旧值中具体某个值发生了变化再执行某些业务
// ...
}
});
return () => unsubscribe();
}, []);
- 示例4:同时订阅多个数据,并且通过数据判断决定下一步动作
useEffect(() => {
const unsubscribe = subscribe(
s => [s.selectedNodeCode, s.nodeDictionary],
([code, nodeMap],[oldCode,oldNodeMap]) => {
// 多个值的订阅,返回新值和就值的数组,和订阅顺序一致
// ...
});
return () => unsubscribe();
}, []);
在大多数情况下,如果没有要手动控制组件的渲染,而是希望直接取回一个具备响应性的数据,按照示例2
,需要写不少样板代码,因此我们提供了一个hooks,可以简化这些样板代码:
- 示例5:通过getStateData直接获取状态值
// 可以对照示例2,代码简化了不少
const serviceParam = getStateData(store => store.serviceParam);
return (<MakeupParames serviceParam={serviceParam} />);
init = (newData: { [key: string]: any }): void
update = (newData: { [key: string]: any }): void
public setData(newData: object): void;
三者功能相同,都是直接往store里直接塞数据,区别是init不会触发事件监听,一般用于初始设置默认数据,后两者会触发监听。
destroy = (): void
销毁当前仓库以及事件监听,在组件卸载时可以调用,已达到快速回收内存的目的
在serviceProvider中会完成对ServiceProcessor的实例化,并且在Context中共享这个这个实例processor,通过这个实例,我们可以组合出更加贴近业务的通用代码,并且将其注册到框架中。ServiceProvider提供了模块内状态共享的能力,并且多个状态相互不受影响,比如打开多个服务标签。当标签被关闭后,会自动调用processor提供的destroy()方法销毁该实例内的数据和监听。
render(): React.ReactNode {
return (
<ServiceContext.Provider
value={{
subscribe: this.processor.subscribe,
setData: this.processor.setData,
getData: this.processor.getData,
...this.executeInjectedMethods(),
}}
>
{this.props.children}
</ServiceContext.Provider>
);
}
通过上面的代码不难发现,Provider本身暴露的三个subscribe
、getData
、setData
都是实例processor提供的,其中this.executeInjectedMethods()
用户注入用户自定义函数,以满足开发需求。
getDataByNodeCode: (processor: any, nodeCode: string) => {
return processor.getData('nodeDictionary')[nodeCode];
},
setGraphInstance: (processor: any, instance: any) => {
processor.setData('graphInstance', instance);
},
getGraphInstance: (processor: any) => {
return processor.getData('graphInstance');
},
setScaleRatio: (processor: any, instance: any) => {
processor.setData('scaleRatio', transZoomRatio(instance.zoom()));
},
getStateData: (_: unknown, selector: Function) => useStateData(selector),
addServiceNode: (processor: any, params: any) => {
const { nodeName, nodeType, sourceStep } = params;
const { path } = sourceStep;
const [serviceNodeList, nodeDictionary] = processor.getData((s: any) => [s.serviceNodeList, s.nodeDictionary]);
const suffix = Math.floor(Math.random() * 10000 + 1);
const nodeCode = `${nodeType}${suffix}`;
const newPath = path.split('/');
newPath.pop();
const newNode = {
nodeName,
nodeType,
nodeCode,
path: `${newPath.join('/')}/${nodeCode}`,
children: GATEWAY_NODES.includes(nodeType) ? [] : null,
};
nodeDictionary[nodeCode] = newNode;
if (nodeType === 'ifElse') {
newNode.children = [
{
nodeCode: `if-${suffix}-1`,
nodeName: '分支1',
nodeType: 'if',
children: [],
},
{
nodeCode: `if-${suffix}-else`,
nodeName: '其他',
nodeType: 'if',
children: [],
isElse: true,
},
];
newNode.children.forEach(c => {
nodeDictionary[c.nodeCode] = { ...c, path: `${newNode.path}/${c.nodeCode}` };
});
}
insertNodeAfterPath(serviceNodeList, path, newNode);
nodeDictionary[nodeCode] = newNode;
processor.setData(['nodeDictionary', 'status'], ([_, num]) => {
return [nodeDictionary, num + 1];
});
return [newNode];
},
// 使用方法
const newNode = addServiceNode(params);
deleteServiceNode: (processor: any, params: any) => {
if (!params) return;
const { path, nodeCode } = params;
const [serviceNodeList, nodeDictionary] = processor.getData((s: any) => [s.serviceNodeList, s.nodeDictionary]);
deleteNodeByPath(serviceNodeList, path);
delete nodeDictionary[nodeCode];
processor.setData(['nodeDictionary', 'status'], ([_, num]) => {
return [nodeDictionary, num + 1];
});
},