React + RedoorIPCモニタリング

私たちのプロジェクトの1つでは、ソケットでIPC(プロセス間通信)を使用しました。非常に大規模なプロジェクトであるトレーディングボットでは、相互に作用するモジュールが多数ありました。複雑さが増すにつれて、マイクロサービスで何が起こっているかを監視することが問題になりました。2つのライブラリreactredoorのみを使用して、データフローを追跡するための独自のアプリケーションを作成することにしました私たちのアプローチを皆さんと共有したいと思います。





マイクロサービスは、名前とデータの2つのフィールドを使用して、JSONオブジェクトを相互に交換します。名前はオブジェクトが対象とするサービスの識別子であり、データフィールドはペイロードです。例:





{ name:'ticket_delete', data:{id:1} }
      
      



サービスは非常に粗雑であり、プロトコルは毎週変更されるため、監視は可能な限りシンプルでモジュール化する必要があります。したがって、アプリケーションでは、各モジュールが目的のデータを表示する必要があるため、データを追加、削除することで、マイクロサービスのプロセスを監視するための独立したモジュールのセットを取得する必要があります。





それでは、始めましょう。たとえば、簡単なアプリケーションとWebサーバーを作成してみましょう。アプリケーションは3つのモジュールで構成されます。写真では点線で示されています。タイマー、統計および統計制御ボタン。





簡単なWebSocketサーバーを作成しましょう。





/** src/ws_server/echo_server.js */

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8888 });

function sendToAll( data) {
  let str = JSON.stringify(data);
  wss.clients.forEach(function each(client) {
    client.send(str);
  });
}

//    
setInterval(e=>{
  let d = new Date();
  let H = d.getHours();
  let m = ('0'+d.getMinutes()).substr(-2);
  let s = ('0'+d.getSeconds()).substr(-2);
  let time_str = `${H}:${m}:${s}`;
  sendToAll({name:'timer', data:{time_str}});
},1000);
      
      



. :





node src/ws_server/echo_server.js
      
      



. rollup .





rollup.config.js
import serve from 'rollup-plugin-serve';
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import hmr from 'rollup-plugin-hot'
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer'
import replace from '@rollup/plugin-replace';

const browsers = [  "last 2 years",  "> 0.1%",  "not dead"]

let is_production = process.env.BUILD === 'production';

const replace_cfg = {
  'process.env.NODE_ENV': JSON.stringify( is_production ? 'production' : 'development' ),
  preventAssignment:false,
}

const babel_cfg = {
    babelrc: false,
    presets: [
      [
        "@babel/preset-env",
        {
          targets: {
            browsers: browsers
          },
        }
      ],
      "@babel/preset-react"
    ],
    exclude: 'node_modules/**',
    plugins: [
      "@babel/plugin-proposal-class-properties",
      ["@babel/plugin-transform-runtime", {
         "regenerator": true
      }],
      [ "transform-react-jsx" ]
    ],
    babelHelpers: 'runtime'
}


const cfg = {
  input: [
    'src/main.js',
  ],
  output: {
    dir:'dist',
    format: 'iife',
    sourcemap: true,
    exports: 'named',
  },
  inlineDynamicImports: true,
  plugins: [
    replace(replace_cfg),
    babel(babel_cfg),
    postcss({
      plugins: [
        autoprefixer({
          overrideBrowserslist: browsers
        }),
      ]
    }),
    commonjs({
        sourceMap: true,
    }),
    nodeResolve({
        browser: true,
        jsnext: true,
        module: false,
    }),
    serve({
      open: false,
      host: 'localhost',
      port: 3000,
    }),
  ],
} ;


export default cfg;

      
      



main.js



.





/** src/main.js */
import React, { createElement, Component, createContext } from 'react';
import ReactDOM from 'react-dom';
import {Connect, Provider} from './store'
import Timer from './Timer/Timer'

const Main = () => (
  <Provider>
    <h1>ws stats</h1>
    <Timer/>
  </Provider>
);
const root = document.body.appendChild(document.createElement("DIV"));
ReactDOM.render(<Main />, root);
      
      







/** src/store.js */
import React, { createElement, Component, createContext } from 'react';
import createStoreFactory from 'redoor';
import * as actionsWS from './actionsWS'
import * as actionsTimer from './Timer/actionsTimer'

const createStore = createStoreFactory({Component, createContext, createElement});
const { Provider, Connect } = createStore(
  [
    actionsWS,     // websocket actions
    actionsTimer,  // Timer actions
  ]
);
export { Provider, Connect };
      
      



. .





/** src/actionsWS.js */
export const  __module_name = 'actionsWS'
let __emit;
//   emit  redoor
export const bindStateMethods = (getState, setState, emit) => {
  __emit = emit
};
//   
let wss = new WebSocket('ws://localhost:8888')
//           redoor
wss.onmessage = (msg) => {
  let d = JSON.parse(msg.data);
  __emit(d.name, d.data);
} 
      
      



. : . redoor . :





   
+------+    
| emit | --- events --+--------------+----- ... ------+------------->
+------+              |              |                |
                      v              v                v
                 +----------+   +----------+     +----------+
                 | actions1 |   | actions2 | ... | actionsN |
                 +----------+   +----------+     +----------+
      
      



"" .





. Timer



Timer.js



actionsTimer.js







/** src/Timer/Timer.js */

import React from 'react';
import {Connect} from '../store'
import s from './Timer.module.css'

const Timer = ({timer_str}) => <div className={s.root}>
  {timer_str}
</div>

export default Connect(Timer);
      
      



, timer_str



actionsTimer.js



. Connect



redoor.





/** src/Timer/actionsTimer.js */
export const  __module_name = 'actionsTimer'
let __setState;

//     
export const bindStateMethods = (getState, setState) => {
  __setState = setState;
};

//   
export const initState = {
  timer_str:''
}

// ""     "timer"
export const listen = (name,data) =>{
  name === 'timer' && updateTimer(data);
}
//   
function updateTimer(data) {
  __setState({timer_str:data.time_str})
}
      
      



, "" timer



( listen



) .





redoor:





__module_name



- .





bindStateMethods



- setState



, .





initState



- timer_str







listen



- redoor.





. http://localhost:3000







npx rollup -c rollup.config.js --watch
      
      



. . . echo_server.js







/** src/ws_server/echo_server.js */

...
let g_interval = 1;
//  
setInterval(e=>{
  let stats_array = [];
  for(let i=0;i<30;i++) {
    stats_array.push((Math.random()*(i*g_interval))|0);
  }
  let data  = {
    stats_array
  }
  sendToAll({name:'stats', data});
},500);

...

      
      



. Stats



Stats.js



actionsStats.js







/** src/Stats/Stats.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'

const Bar = ({h})=><div className={s.bar} style={{height:`${h}`px}}>
  {h}
</div>

const Stats = ({stats_array})=><div className={s.root}>
  <div className={s.bars}>
    {stats_array.map((it,v)=><Bar key={v} h={it} />)}
  </div>
</div>

export default Connect(Stats);
      
      



/** src/Stats/actionsStats.js */
export const  __module_name = 'actionsStats'
let __setState = null;

export const bindStateMethods = (getState, setState, emit) => {
  __setState = setState;
}

export const initState = {
  stats_array:[],
}

export const listen = (name,data) =>{
  name === 'stats' && updateStats(data);
}

function updateStats(data) {
  __setState({
    stats_array:data.stats_array,
  })
}
      
      







/** src/store.js */
...
import * as actionsStats from './Stats/actionsStats'

const { Provider, Connect } = createStore(
  [
    actionsWS,
    actionsTimer,
    actionsStats //<--  Stats
  ]
);
...
      
      



:





Stats



Timer



, , . , ? .





g_interval . .





. interval



.





/** src/Stats/Stats.js */
...
import Buttons from './Buttons' //  
...
const Stats = ({cxRun, stats_array})=><div className={s.root}>
  <div className={s.bars}>
    {stats_array.map((it,v)=><Bar key={v} h={it} />)}
  </div>
  <Buttons/> {/* */}
</div>
...
      
      







/** src/Stats/Buttons.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'

const DATA_INTERVAL_PLUS = {
  name:'change_interval',
  interval:1
}
const DATA_INTERVAL_MINUS = {
  name:'change_interval',
  interval:-1
}

const Buttons = ({cxEmit, interval})=><div className={s.root}>
  <div className={s.btns}>
      <button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_PLUS)}>
        plus
      </button>

      <div className={s.len}>interval:{interval}</div>

      <button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_MINUS)}>
        minus
      </button>
  </div>
</div>

export default Connect(Buttons);
      
      



:





actionsWS.js





/** src/actionsWS.js */
...

let wss = new WebSocket('ws://localhost:8888')

wss.onmessage = (msg) => {
  let d = JSON.parse(msg.data);
  __emit(d.name, d.data);
}

// ""     
export const listen = (name,data) => {
  name === 'ws_send' && sendMsg(data);
}
//  
function sendMsg(msg) {
  wss.send(JSON.stringify(msg))
}
      
      



Buttons.js



(cxEmit



) redoor. ws_send



"" actionsWS.js



. data



- : DATA_INTERVAL_PLUS



DATA_INTERVAL_MINUS



. { name:'change_interval', interval:1 }











/** src/ws_server/echo_server.js */
...

wss.on('connection', function onConnect(ws) {
  // ""    "change_interval"
  //   Buttons.js
  ws.on('message', function incoming(data) {
    let d = JSON.parse(data);
    d.name === 'change_interval' && change_interval(d);
  });
});

let g_interval = 1;
//  
function change_interval(data) {
  g_interval += data.interval;
  //  ,   
  sendToAll({name:'interval_changed', data:{interval:g_interval}});
}

...

      
      



Buttons.js. actionsStats.js "interval_changed



" interval







/** src/Stats/actionsStats.js */
...

export const initState = {
  stats_array:[],
  interval:1 //   
}

export const listen = (name,data) =>{
  name === 'stats' && updateStats(data);
  
  // ""   
  name === 'interval_changed' && updateInterval(data);
}
//  
function updateInterval(data) {
  __setState({
    interval:data.interval,
  })
}

function updateStats(data) {
  __setState({
    stats_array:data.stats_array,
  })
}

      
      



したがって、3つの独立したモジュールがあり、各モジュールは独自のイベントのみを監視し、それのみを表示します。これは、プロトタイピング段階の構造とプロトコルがまだ完全に明確でない場合に非常に便利です。追加する必要があるのは、すべてのイベントがエンドツーエンドの構造であるため、イベントを作成するためのテンプレートに明確に準拠する必要があるため、以下を選択したことだけですMODULEN AME)_(FUNCTION NAME)_(VAR NAME)







お役に立てば幸いです。プロジェクトのソースコードは、いつものようにgithubにあります。








All Articles