Dalam salah satu proyek kami, kami menggunakan IPC (komunikasi antar proses) pada soket. Proyek yang cukup besar, bot perdagangan, di mana terdapat banyak modul yang saling berinteraksi. Seiring dengan meningkatnya kompleksitas, ini menjadi pertanyaan untuk memantau apa yang terjadi di layanan mikro. Kami memutuskan untuk membuat aplikasi kami sendiri untuk melacak aliran data dengan hanya menggunakan dua perpustakaan react dan redoor . Saya ingin berbagi pendekatan kami dengan Anda.
Layanan mikro bertukar objek JSON satu sama lain, dengan dua bidang: nama dan data. Nama adalah pengidentifikasi ke layanan mana objek dimaksudkan dan bidang data adalah payload. Contoh:
{ name:'ticket_delete', data:{id:1} }
Karena layanan ini sangat kasar dan protokolnya berubah setiap minggu, jadi pemantauan harus sesederhana dan semodular mungkin. Karenanya, dalam aplikasi, setiap modul harus menampilkan data yang dimaksudkan untuknya, dan dengan menambahkan, menghapus data, kita harus mendapatkan satu set modul independen untuk memantau proses di layanan mikro.
Jadi, mari kita mulai. Misalnya mari kita buat aplikasi sederhana dan web server. Aplikasi akan terdiri dari tiga modul. Mereka ditunjukkan dengan garis putus-putus pada gambar. Timer, statistik, dan tombol kontrol statistik.
Mari buat server Web Socket sederhana.
/** 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,
})
}
Jadi, kami mendapat tiga modul independen, di mana setiap modul hanya memantau kejadiannya sendiri dan hanya menampilkannya. Yang cukup nyaman ketika struktur dan protokol pada tahap pembuatan prototipe belum sepenuhnya jelas. Anda hanya perlu menambahkan bahwa karena semua peristiwa memiliki struktur ujung ke ujung, kita harus mematuhi template dengan jelas untuk membuat acara, kita telah memilih yang berikut untuk diri kita sendiri: ( MODULEN AME)_(FUNCTION NAME)_(VAR NAME)
.
Semoga bermanfaat. Kode sumber proyek, seperti biasa, ada di github.