Решением многих из этих проблем является (или) использование lerna. Lerna - это инструмент для управления несколькими пакетами в одном репозитории git. Задача, которую он пытается решить, - упростить публикацию взаимозависимых пакетов. Он часто используется в библиотеках пользовательского интерфейса, содержащих множество настраиваемых компонентов, которые могут зависеть друг от друга.
Хотя мы не собираемся публиковать наши отдельные микропроцессы в npm, злоупотребление lerna и превращение каждого микропроцесса в отдельный пакет все же дает нам преимущество начальной загрузки всех пакетов: он устанавливает все их зависимости, связывает пакеты, зависящие друг от друга, и запускает собственные сценарии для каждого пакета.
Начните с глобальной установки lerna: npm install -g lerna
Затем запустите приложение, создав новую папку и инициализировав ее:
mkdir steem-bot
cd steem-bot
git init
lerna init
Это создаст lerna.json
файл, который вы можете пока игнорировать, и создаст папку packages
, в которой будут жить наши микропроцессы. В репо есть глобальный package.json
файл, и каждый отдельный пакет в packages
- это просто еще одно приложение NPM со своим собственным package.json
файлом.
Вы можете установить все инструменты сборки и сценарии тестирования в корень package.json
. Например, если вы используете babel
, eslint
, prettier
и jest
, это будет выглядеть так:
{
"name": "@cmichel/steem-bot",
"version": "1.0.0",
"description": "",
"main": "",
"scripts": {
"test": "jest"
},
"license": "MIT",
"devDependencies": {
"babel-eslint": "^8.0.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"eslint": "^4.11.0",
"eslint-plugin-react": "^7.5.1",
"jest": "^22.1.4",
"lerna": "^2.8.0",
"prettier": "^1.8.2"
},
"dependencies": {}
}
В демонстрационных целях давайте создадим стим-бота с двумя пакетами:
broadcaster
: Этот пакет будет прослушивать цепочку блоков steem и внутренне транслировать все транзакции, комментарии и голоса другим микропроцессам.bot
: Сам бот, который прослушивает данные отbroadcaster
, проверяет, относятся ли данные к нему, а затем выполняет свою логику бота.
Создание микропроцесса вещателя
Создание микропроцесса вещателя несложно. Создайте новый пакет NPM с именем broadcaster
в packages
и поместите туда package.json
со следующим содержимым:
{
"name": "@cmichel/broadcaster",
"version": "1.0.0",
"main": "dist/config.js",
"scripts": {
"prepublish": "npm run build",
"build": "npx babel src --out-dir dist",
"start": "node dist/broadcast.js",
"start:dev": "babel-node src/broadcast.js"
},
"dependencies": {
"dotenv": "^4.0.0",
"node-ipc": "^9.1.1",
"steem": "^0.6.7"
}
}
Здесь мы определяем несколько скриптов:
- Я использую функции ES6 / ES7, поэтому мне нужно перенести его с помощью babel, для чего предназначен сценарий build. Я также выполняю этап сборки в сценарии предварительной публикации, поскольку lerna выполняет все сценарии предварительной публикации во время начальной загрузки.
start
скрипты, запускающие микропроцесс.
Обратите внимание, что здесь мы используем частные репозитории NPM (обозначенные @ name / перед названием приложения). Я никогда не собираюсь публиковать эти пакеты в NPM, я просто злоупотребляю им как пространством имен, чтобы все мои пакеты lerna для конкретного приложения оставались под тем же именем.
Создание микропроцесса бота
Так же создаем микропроцесс бота, с теми же скриптами.
{
"name": "@cmichel/bot",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"prepublish": "npm run build",
"build": "npx babel src --out-dir dist",
"start": "node dist/index.js",
"start:dev": "babel-node src/index.js"
},
"dependencies": {
"dotenv": "^4.0.0",
"node-ipc": "^9.1.1",
"steem": "^0.6.7",
"@cmichel/broadcaster": "*"
}
}
Обратите внимание, как мы добавили broadcaster
как зависимость к bot
. Мы можем использовать это, чтобы поделиться конфигурацией вещателя со всеми микропроцессами, которые его используют.
А теперь вот замечательные вещи: Когда мы запускаем lerna bootstrap --hoist
, lerna установит все зависимости для пакетов, запустит наши prepublish
скрипты, которые собирают исходные коды, и создаст символьную ссылку на вещательную станцию , так что это будет просто еще один внешняя зависимость для bot
. Флаг --hoist
устанавливает общие зависимости всех пакетов (например, dotenv
, node-ipc
, steem
) только один раз в node_modules
корневой папки lerna. Таким образом, зависимость устанавливается только один раз для всего приложения, а не один раз для каждого пакета.
Вот что у нас есть на данный момент:
Вы можете ознакомиться со структурой кода на GitHub.
Как процессы взаимодействуют
Мы уже добавили node-ipc
как зависимость к нашим пакетам. Он использует локальные сокеты UNIX / Windows для межпроцессного взаимодействия (IPC). Есть один или несколько серверов и клиентов, которые к ним подключаются. Затем сообщения могут транслироваться с сервера всем подключенным клиентам или клиентам на индивидуальной основе.
В нашем случае broadcaster
действует как сервер, к которому подключен bot
.
Итак, давайте начнем с настройки сервера в broadcaster/src/broadcast.js
для подключения к блокчейну Steem и широковещательной рассылки его транзакций всем подключенным локальным клиентам:
import config from './config'
import ipc from 'node-ipc'
import steem from 'steem'
ipc.config.id = config.id
ipc.config.retry = config.retry
ipc.serve(function() {
ipc.server.on(`socket.disconnected`, function(socket, destroyedSocketID) {
ipc.log(`client ` + destroyedSocketID + ` has disconnected!`)
})
})
ipc.server.start()
steem.api.streamOperations((err, res) => {
if (err) {
return
}
const type = res[0]
const data = res[1]
ipc.server.broadcast(type, data)
})
Мы используем ipc.server.broadcast(type, data)
для широковещательной передачи данных Steem по их типу, т.е. подключенные клиенты могут выбрать прослушивание только определенных типов, например comment
или vote
.
Затем мы можем обработать сообщения в bot/src/index.js
:
import ipc from 'node-ipc'
import ipcServerConfig from '@cmichel/broadcaster'
ipc.config.id = `steem.bot`
ipc.config.retry = 1500
ipc.config.silent = true
ipc.connectTo(ipcServerConfig.id, function() {
ipc.of[ipcServerConfig.id].on(`connect`, function() {
ipc.log(
`## connected to ${ipcServerConfig.id} ##`.rainbow,
ipc.config.delay
)
})
ipc.of[ipcServerConfig.id].on(`disconnect`, function() {
ipc.log(`disconnected from ${ipcServerConfig.id}`.notice)
})
})
// listen to all comments (= posts + replies)
ipc.of[ipcServerConfig.id].on(`comment`, (data) => {
ipc.log(`got a message from ${ipcServerConfig.id} : `.debug, data)
// check for new posts only, no replies
if (data.parent_author === ``) {
console.log(`https://steemit.com/@${data.author}/${data.permlink}`)
}
})
Управление микропроцессами
Запуск всех микропроцессов вручную утомительно, поэтому мы используем диспетчер процессов узла. Мне нравится PM2 из-за его простоты и приятных функций: вы можете определить, какие npm scripts
и какие пакеты запускать в файле конфигурации, и он автоматически перезапускает сбойные процессы.
Чтобы его получить, запустите:
npm install pm2@latest -g
Мы создаем файл конфигурации pm2.json
в корневом каталоге lerna, который запускает два наших микропроцесса с их командой start
:
{
"apps": [
{
"name": "bot",
"cwd": "./packages/bot",
"kill_timeout": 3000,
"restart_delay": 3000,
"script": "npm",
"args": "run start"
},
{
"name": "steem-broadcaster",
"cwd": "./packages/broadcaster",
"kill_timeout": 3000,
"restart_delay": 3000,
"script": "npm",
"args": "run start"
}
]
}
Теперь наш рабочий процесс выглядит следующим образом: Соберите все пакеты, запустив:
lerna bootstrap --hoist
Запустите микропроцессы, запустив:
pm2 start pm2.json
И вы сделали. У вас есть надежный самовосстанавливающийся рой взаимодействующих узловых процессов. 🤖🤖🤖
Я создал репозиторий GitHub с этим шаблоном.
Первоначально опубликовано на cmichel.io