Monitoreo de React + Redoor IPC

En uno de nuestros proyectos, utilizamos IPC (comunicación entre procesos) en sockets. Un proyecto bastante grande, un robot comercial, donde había muchos módulos que interactuaban entre sí. A medida que crecía la complejidad, se convirtió en una cuestión de monitorear lo que estaba sucediendo en los microservicios. Decidimos crear nuestra propia aplicación para rastrear el flujo de datos usando solo dos bibliotecas reaccionar y abrir . Me gustaría compartir nuestro enfoque con ustedes.





Los microservicios intercambian objetos JSON entre sí, con dos campos: nombre y datos. El nombre es el identificador a qué servicio está destinado el objeto y el campo de datos es la carga útil. Ejemplo:





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



Dado que el servicio es bastante burdo y los protocolos cambian cada semana, el monitoreo debe ser lo más simple y modular posible. En consecuencia, en la aplicación, cada módulo debe mostrar los datos destinados a él, por lo que al agregar, eliminar datos, debemos obtener un conjunto de módulos independientes para monitorear procesos en microservicios.





Vamos a empezar. Por ejemplo, hagamos una aplicación simple y un servidor web. La aplicación constará de tres módulos. Están indicados por líneas de puntos en la imagen. Botones de control de temporizador, estadísticas y estadísticas.





Creemos un servidor Web Socket simple.





/** 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,
  })
}

      
      



Entonces, obtuvimos tres módulos independientes, donde cada módulo monitorea solo su propio evento y solo lo muestra. Lo cual es bastante conveniente cuando la estructura y los protocolos en la etapa de creación de prototipos aún no están completamente claros. Solo es necesario agregar que dado que todos los eventos tienen una estructura de extremo a extremo, debemos adherirnos claramente a la plantilla para crear un evento, hemos elegido lo siguiente para nosotros: ( MODULEN AME)_(FUNCTION NAME)_(VAR NAME)



.





Espero que haya sido de ayuda. Los códigos fuente del proyecto, como de costumbre, están en github.








All Articles