Drover module - run, manage and scale nodejs solution that helps utilize multi-core systems
Drover is native nodejs solution that takes away pain when orchestrating composite application and providers all-in-one point for graceful cluster control
Key features:
- run and manage multiple app processes as simple app
- runtime scale
- graceful reload (helpful for zero-downtime releases)
Installation
Using npm:
$ npm i --save drover
Using yarn:
$ yarn add drover
Simple usage
main.js
const MasterFactory = ; const master = MasterFactory; process; masterstart;
app.js
const express = ;const MessageBus = ; const mb = ; mb; mb; const app = ; const server = app;
Basic concepts
Glossary
- Main context (facade) - facade part of application, which is responsible for launching logic and managing the lifecycle of functional parts of the application
- Functional context (business logic) - functional parts of application that directly contain the business logic
- Master - entity of drover, which takes the role of creating and maintaining a given number of workers. This entity is always located only in
main context
of application. - Worker - entity of drover, which assumes a role in the actual up-to-date presentation of state of
functional context
process onmain context
side. - MessageBus - entity of drover, which connects the
functional context
of application withmain context
provides bidirectional commands via IPC channel. Strongly associated withWorker
and appear as it's representation on side of thefunctional context
.
Main idea
-
in
main context
we should control worker processes of application. Here we can describe the logic of restarting falling workers, log them or even implement API that will allow us change workers state from the outside process context. E.g. to bring the workers in maintenance mode and suspend the application without stopping the master process itself. -
in
main context
, we get a guarantee that all workers have successfully completed the start, according to the internal business logic of the functional parts of the application. When you use defaultcluster
module, the only signal for you about raising workers is to establish a IPC channel betweenworker
andmaster
. This is not very convenient and not very informative. Indeed, it does not guarantee that the worker is started up functionally correct up to bootstrap steps. -
in
main context
, we get access to the direct signaling offunctional context
, including the ability to correctly stop worker process or perform full controlled restart
Principles
- Logic segregation -
main context
of the applicationMUST NOT
contain business logic and vice versa -functional context
of the applicationMUST NOT
contain logic of monitoring and controlling lifecycle of composite parts of the application itself.
Exit reasons
To distinguish exit reasons you may import ExitReasons
object.
const ExitReasons = ;
There are such available classes:
-
ExitReasons.ExternalSignal
-
ExitReasons.NormalExit
-
ExitReasons.AbnormalExit
worker-exit
event
Underlying process may not start at all, it may fail after some time or it may be killed with signal. Each instance has its own reason-specific payload field. There is a list of reasons:
-
ExitReasons.ExternalSignal
- worker process was killed by someone or by another process with the signal, the signal name can be found inpayload.signal
field. -
ExitReasons.NormalExit
- worker process has exited with0
code.payload.code
is provided -
ExitReasons.AbnormalExit
- worker process has exited with non-zero code.payload.code
is provided.
Usage
FYI: You can find and run every case listed below in examples.
1. Run single app in cluster mode
In this case, the logic in the approach will be absolutely identical to the usual use of the cluster nodejs module. However, in the case of a drover, we get a number of important advantages and convenience of working with a complex application using understandable entities and a transparent interface.
Main context
(e.g main.js)
- Create instance of
Master
const master = MasterFactory;
- Add listener on
worker-exit
event ofMaster
master;
- Handle terminating of
main context
process
const quit = async { try if force // in case of emergency stop we just hard quit all worker processes await master; else // for default app exit we do it in more graceful way await master; catch err // your stop-failed handler logic here console; ;}; // handle main process SIGINT (default signal in Unix when "ctrl+c" terminal interruption happened)process;
- Start application. This part will fork
functional context
(app.js
) and run 4 instances of it.
const run = async { try await masterstart; // right here we have a guarantee that all 4 app instances // already did their business logic (raised connects, started servers, etc) catch err // your start-failed handler logic here console; return; // primitive health-check from master every 2s ;}; ;
Functional context
(e.g. app.js)
- Create instance of
MessageBus
:
const mb = ;
- Setup listeners on
stop
andquit
events:
mb; mb;
- Send start signal to master when all bootstrap part is done:
const app = ; const server = app;
For that example above we will expect next output in console repeated every 2s:
[app-0]: STARTED[app-1]: STARTED[app-2]: STARTED[app-3]: STARTED---
2. Run multiple apps in cluster mode
In this case, the logic will same as if you are using several cluster modules within one main context
without loss of ease of management.
Most parts of context building are similar to previous example, but with little different points.
Main context
(e.g main.js)
Here we instantiate N different masters.
const fooMaster = MasterFactory; const barMaster = MasterFactory; const bazMaster = MasterFactory;
After that we subscribe listeners on worker-exit
event and start all masters.
You can start them parallel with Promise.all
, or consistently one by one. It totally depends on your business logic.
3. Run single app instance
If you don't need cluster multi-instances of your app, you still can run you application with drover. Most parts of
context building are similar to first example with little difference in master's option count
when you instantiate it.
Main context
(e.g main.js)
const master = MasterFactory;
In this case you still got advantages of graceful reloads with zero-downtime of your app. When reload begins - one more instance of app will be added right before previous one shut down.
Difference with PM2
License
drover
covered with MIT license, so it's free to use for any kind of your private or commercial projects without
any restrictions and obligations to be open-sourced.
Clear and flexible programmatic flow
pm2
has programmatic flow, but it is still just API to pm2
demon process and it brings some usage restrictions.
With drover
you've got more options, so flexibility for your business logic raises a lot.
More control
pm2
uses "let if fail" concept, but drover
gives you control instead. You've got not just exits as fact, but you can
manage different ExitReasons
and handle each case according to your needs.
Debug
drover
module use debug
module for this this purpose.
Just run your app with DEBUG
env var like example below:
DEBUG=drover:* node mainjs
Sample output for simple-app
start:
And changes with SIGINT
signal to main.js
process triggered by Ctrl+C
:
As you see, you have transparent access to all events, state changes and errors described behaviour even via IPC communication.