@lingxiteam/service
TypeScript icon, indicating that this package has built-in type declarations

1.0.5 • Public • Published

服务编排框架及模块调整说明

为了提高服务编排模块的性能和代码可维护性,在新版本中我们提供了@lingxiteam/service这个包作为服务编排模块底层数据操作框架。该框架提供了一个通用的数据存储,修改以及响应的接口。

在新版本,我们将服务编排模块的代码调整至ServiceOrchestrator目录,业务相关的代码以及组件和资源都放置在此目录,以便于将来模块解耦。

快速开始

当前@lingxiteam/service还未发包,本地使用软连接使用,包的位置位于packages/service/src/

不建议直接修改包中的内容,在使用的过程中如果觉得框架有需要修改,需要先做评估。

项目的启动

  • 请确保你已经从git上拉取了该分支的全部代码,当前分支为opt-service-performance-11146372

  • 使用yarn start:app 运行主程序,同时切换到packages/service目录执行yarn watch

如果不出意外,此时项目应该可以正常启动了。

API参考及说明

ServiceProcessor

服务编排框架的核心处理类,负责数据的处理以及事件的分法。

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封装出更多的符合业务需求的工具代码。

ServiceProcessor实例方法使用说明
方法
getData方法支持多种方式取值
  • 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新值]
setData方法同样支持多中方式设置值
  • 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订阅数据更新
  • 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

在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本身暴露的三个subscribegetDatasetData都是实例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];
    });
  },

Readme

Keywords

Package Sidebar

Install

npm i @lingxiteam/service

Weekly Downloads

335

Version

1.0.5

License

ISC

Unpacked Size

133 kB

Total Files

38

Last publish

Collaborators

  • brinkr
  • mishi-brother
  • babelczx
  • yaozanwen
  • duan_duan
  • wanerrr
  • goulili
  • diyc
  • cenxiaolian
  • taiyangteng
  • pengyh
  • xiaohuoni
  • wujian666
  • limanpm
  • zhang.guoyong
  • zzzzjq
  • hammersjs