Решением многих из этих проблем является (или) использование 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": {}
}

В демонстрационных целях давайте создадим стим-бота с двумя пакетами:

  1. broadcaster: Этот пакет будет прослушивать цепочку блоков steem и внутренне транслировать все транзакции, комментарии и голоса другим микропроцессам.
  2. 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"
  }
}

Здесь мы определяем несколько скриптов:

  1. Я использую функции ES6 / ES7, поэтому мне нужно перенести его с помощью babel, для чего предназначен сценарий build. Я также выполняю этап сборки в сценарии предварительной публикации, поскольку lerna выполняет все сценарии предварительной публикации во время начальной загрузки.
  2. 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