Em um de nossos projetos, usamos IPC (comunicação entre processos) em soquetes. Um projeto bem grande, um robô comercial, onde havia muitos módulos que interagiam entre si. Com o aumento da complexidade, tornou-se uma questão de monitorar o que estava acontecendo nos microsserviços. Decidimos criar o nosso próprio aplicativo para rastrear dados de fluxo usando apenas duas bibliotecas reagir e redoor . Eu gostaria de compartilhar nossa abordagem com você.
Os microsserviços trocam objetos JSON entre si, com dois campos: nome e dados. O nome é o identificador a que serviço o objeto se destina e o campo de dados é a carga útil. Exemplo:
{ name:'ticket_delete', data:{id:1} }
Como o serviço é bastante rudimentar e os protocolos mudam todas as semanas, o monitoramento deve ser o mais simples e modular possível. Assim, na aplicação, cada módulo deve exibir os dados que se destinam a ele, e assim, adicionando, excluindo dados, devemos obter um conjunto de módulos independentes para monitorar processos em microsserviços.
Então, vamos começar. Por exemplo, vamos fazer um aplicativo simples e um servidor web. O aplicativo será composto por três módulos. Eles são indicados por linhas pontilhadas na imagem. Botões de cronômetro, estatísticas e controle de estatísticas.
Vamos criar um servidor Web Socket simples.
/** 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,
})
}
Assim, temos três módulos independentes, onde cada módulo monitora apenas o seu próprio evento e exibe apenas ele. O que é bastante conveniente quando a estrutura e os protocolos no estágio de prototipagem ainda não estão completamente claros. É apenas necessário acrescentar que, uma vez que todos os eventos têm uma estrutura ponta a ponta, devemos seguir claramente o modelo para a criação de um evento, escolhemos o seguinte para nós: ( MODULEN AME)_(FUNCTION NAME)_(VAR NAME)
.
Espero que tenha sido útil. Os códigos-fonte do projeto, como de costume, estão no github.