Releases: moleculerjs/moleculer
v0.11.5
v0.11.4
v0.11.3
v0.11.2
New
Service dependencies #102
The Service
schema has a new dependencies
property. The serice can wait for other dependening ones when it starts. This way you don't need to call waitForServices
in started
any longer.
module.exports = {
name: "posts",
settings: {
$dependencyTimeout: 30000 // Default: 0 - no timeout
},
dependencies: [
"likes", // shorthand w/o version
{ name: "users", version: 2 }, // with numeric version
{ name: "comments", version: "staging" } // with string version
],
started() {
this.logger.info("Service started after the dependent services available.");
}
....
}
The started
service handler is called once the likes
, users
and comments
services are registered (on the local or remote nodes).
Pending request queue size limit #111
The ServiceBroker
has a new maxQueueSize
option under transit
key. The broker protects the process to avoid crash during a high load with it. The maxQueueSize
default value is 50,000. If pending request queue size reaches it, broker rejects the request with a QueueIsFull
(retryable) error.
let broker = new ServiceBroker({
transporter: "NATS",
transit: {
maxQueueSize: 10 * 1000
}
}
Changes
The waitForServices
method supports service versions #112
By @imatefx, the waitForServices
broker & service methods support service versions. Use the following formats to define version in a dependency:
module.exports = {
name: "test",
dependencies: { name: "users", version: 2 }
};
v0.11.1
New
Service metadata #91
The Service
schema has a new metadata
property. The Moleculer modules doesn't use it, so you can use it whatever you want.
broker.createService({
name: "posts",
settings: {},
metadata: {
scalable: true,
priority: 5
},
actions: { ... }
});
The
metadata
is transferred between nodes, you can access it via$node.services
. Or inside service withthis.metadata
like settings.
NATS transporter supports to use the built-in balancer
The NATS transporter has been changed. It supports to use the NATS built-in balancer instead of Moleculer balancer. In this case every call
& emit
will be transferred through NATS message broker.
let broker = new ServiceBroker({
transporter: "NATS",
disableBalancer: true
});
Changes
- ping nodes with
broker.sendPing
instead ofbroker.transit.sendPing
. index.d.ts
updated to v0.11- AMQP integration tests has been rewritten.
- process exit code changed from
2
to1
inbroker.fatal
. Reason:2
is reserved by Bash for builtin misuse. More info
v0.11.0
Breaking changes
Protocol changed #86
The Moleculer transportation protocol has been changed. It means, the new (>= v0.11) versions can't communicate with the old (<= v0.10.x) ones.
You can find more information about changes in #86 issue.
Balanced events
The whole event handling has been rewritten. By now Moleculer supports event driven architecture. It means that event emits are balanced like action calls are.
For example, you have 2 main services: users
& payments
. Both subscribe to the user.created
event. You start 3 instances from users
service and 2 instances from payments
service. If you emit the event with broker.emit('user.created')
, broker groups & balances the event, so only one users
and one payments
service receive the event.
You can also send broadcast events with the broker.broadcast('user.created
) command. This way every service instance on every node receives the event.
The broker.broadcastLocal('user.created')
command sends events only to the local services.
Renamed & new internal events
Every internal event name starts with '$'. These events are not transferred to remote nodes.
Renamed events:
node.connected
->$node.connected
node.updated
->$node.updated
node.disconnected
->$node.disconnected
services.changed
->$services.changed
. It is called if local or remote service list is changed.circuit-breaker.closed
->$circuit-breaker.closed
circuit-breaker.opened
->$circuit-breaker.opened
circuit-breaker.half-opened
->$circuit-breaker.half-opened
New events:
- global circuit breaker events for metrics:
metrics.circuit-breaker.closed
,metrics.circuit-breaker.opened
,metrics.circuit-breaker.half-opened
Switchable built-in load balancer
The built-in Moleculer load balancer is switchable. You can turn it off, if the transporter has internal balancer (currently AMQP has it).
const broker = new ServiceBroker({
disableBalancer: false
});
Please note! If built-in balancer is disabled, every call & emit (including local ones too) are transferred via transporter.
Removed broker methods
Some internal broker methods have been removed or renamed.
broker.bus
has been removed.broker.on
has been removed. Useevents
in service schema instead.broker.once
has been removed.broker.off
has been removed.broker.getService
has been renamed tobroker.getLocalService
broker.hasService
has been removed.broker.hasAction
has been removed.broker.getAction
has been deprecated.broker.isActionAvailable
has been removed.
Changed local service responses
Internal action ($node.list
, $node.services
, $node.actions
, $node.health
) responses are changed. New internal action ($node.events
) to list event subscriptiion is added.
Broker option changes
heartbeatInterval
default value is changed from10
to5
.heartbeatTimeout
default value is changed from30
to15
.circuitBreaker.maxFailures
default value is changed from5
to3
.logFormatter
accepts string. Thesimple
value is a new formatter to show only log level & log messages.
New
Ping command
New PING & PONG feature has been implemented. Ping remite nodes to measure the network latency and system time differences.
broker.createService({
name: "test",
events: {
"$node.pong"({ nodeID, elapsedTime, timeDiff }) {
this.logger.info(`Pong received from '${nodeID}' - Time: ${elapsedTime}ms, System time difference: ${timeDiff}ms`);
}
}
});
broker.start().then(() => broker.transit.sendPing(/*nodeID*/));
Pluggable validator
The Validator in ServiceBroker is plugable. So you can change the built-in fastest-validator
to a slower one :) Example Joi validator
Waiting for other services feature
If your services depend on other ones, use the waitForService
method to make services wait until dependencies start.
let svc = broker.createService({
name: "seed",
started() {
return this.waitForServices(["posts", "users"]).then(() => {
// Do work...
});
}
});
Signature:
this.waitForServices(serviceNames: String|Array<String>, timeout: Number/*milliseconds*/, interval: Number/*milliseconds*/): Promise
New error types
We added some new Moleculer error classes.
MoleculerRetryableError
- Common Retryable error. Caller retries the request ifretryCount > 0
.MoleculerServerError
- Common server error (5xx).MoleculerClientError
- Common client/request error (4xx).ServiceNotAvailable
- Raises if the service is registered but isn't available (no live nodes or CB disabled them).ProtocolVersionMismatchError
- Raises if connect a node with an older client (<= v0.10.0)).
Other changes
- The cachers don't listen "cache.clean" event.
v0.10.0
Breaking changes
No more nodeID == null
in local stuff
In all core modules removed the nullable nodeID
. Every places (context, events, $node.* results) the nodeID contains a valid (local or remote) nodeID. On local nodes it equals with broker.nodeID
.
Migration guide
Before:
if (ctx.nodeID == null) { ... }
// ---------
events: {
"users.created"(payload, sender) {
if (sender == null) { ... }
}
}
After:
if (ctx.nodeID == ctx.broker.nodeID) { ... }
// ---------
events: {
"users.created"(payload, sender) {
if (sender == this.broker.nodeID) { ... }
}
}
internalActions
is renamed to internalServices
The internalActions
broker option is renamed to internalServices
.
Removed broker.createNewContext
method
The createNewContext
broker method is moved to Context
class as a static method.
Migration guide:
Before:
let ctx = broker.createNewContext(action, nodeID, params, opts);
After:
let ctx = Context.create(broker, action, nodeID, params, opts);
// or better
let ctx = broker.ContextFactory.create(broker, action, nodeID, params, opts);
Removed LOCAL_NODE_ID
constant
The recently added LOCAL_NODE_ID
constant is removed. If you want to check the nodeID is local, please use the if (nodeID == broker.nodeID)
syntax.
Class based pluggable Service registry strategies #75
By @WoLfulus, the service registry balancer strategy is now pluggable.
New syntax:
let Strategies = require("moleculer").Strategies;
let broker = new ServiceBroker({
registry: {
strategy: new Strategies.RoundRobin()
}
});
Custom strategy
You can create you custom strategy.
let BaseStrategy = require("moleculer").Strategies.Base;
class CustomStrategy extends BaseStrategy {
select(list) {
return list[0];
}
};
let broker = new ServiceBroker({
registry: {
strategy: new CustomStrategy()
}
});
Metrics event payloads are changed
The metrics payload contains remoteCall
and callerNodeID
properties. The remoteCall
is true if the request is called from a remote node. In this case the callerNodeID
contains the caller nodeID.
metrics.trace.span.start
:
{
"action": {
"name": "users.get"
},
"id": "123123123",
"level": 1,
"parent": 123,
"remoteCall": true,
"requestID": "abcdef",
"startTime": 123456789,
"nodeID": "node-1",
"callerNodeID": "node-2"
}
metrics.trace.span.start
:
{
"action": {
"name": "users.get"
},
"duration": 45,
"id": "123123123",
"parent": 123,
"requestID": "abcdef",
"startTime": 123456789,
"endTime": 123456795,
"fromCache": false,
"level": 1,
"remoteCall": true,
"nodeID": "node-1",
"callerNodeID": "node-2"
}
New
Hot reload services #82
The ServiceBroker supports hot reloading services. If you enable it broker will watch file changes. If you modify service file, broker will reload it on-the-fly.
Demo video
Note: Hot reloading is only working with Moleculer Runner or if you load your services with
broker.loadService
orbroker.loadServices
.
Usage
let broker = new ServiceBroker({
logger: console,
hotReload: true
});
broker.loadService("./services/test.service.js");
Usage with Moleculer Runner
Turn it on with --hot
or -H
flags.
$ moleculer-runner --hot ./services/test.service.js
Protocol documentation
Moleculer protocol documentation is available in docs/PROTOCOL.md file.
AMQP transporter #72
By @Nathan-Schwartz, AMQP (for RabbitMQ) transporter added to Moleculer project.
let broker = new ServiceBroker({
transporter: "amqp://guest:guest@rabbitmq-server:5672"
});
let broker = new ServiceBroker({
transporter: new AmqpTransporter({
amqp: {
url: "amqp://guest:guest@localhost:5672",
eventTimeToLive: 5000,
prefetch: 1
}
});
});
v0.9.0
Breaking changes
Namespace support, removed prefix
options #57
The broker has a new namespace
option to segment your services. For example, you are running development & production services (or more production services) on the same transporter. If you are using different namespace
you can avoid collisions between different environments.
You can reach it in your services as
this.broker.namespace
.
Thereupon the prefix
option in transporters & cachers is removed.
Example
const broker = new ServiceBroker({
logger: console,
namespace: "DEV",
transporter: "NATS",
cacher: "Redis"
});
In this case the transporter & cacher prefix will be MOL-DEV
.
Renamed internal service settings
The useVersionPrefix
is renamed to $noVersionPrefix
. The serviceNamePrefix
is renamed to $noServiceNamePrefix
. Both settings logical state is changed.
The cache
setting is renamed to $cache
.
Migration guide
Before
broker.createService({
name: "test",
settings: {
useVersionPrefix: false,
serviceNamePrefix: false,
cache: true
}
});
After
broker.createService({
name: "test",
settings: {
$noVersionPrefix: true,
$noServiceNamePrefix: true,
$cache: true
}
});
Changed versioned action names #58
Based on #58 if service version is a String
, the version in action names won't be prefixed with v
, expect if it is a Number
.
Example
broker.createService({
name: "test",
version: 3,
actions: {
hello(ctx) {}
}
});
broker.call("v3.test.hello");
broker.createService({
name: "test",
version: "staging",
actions: {
hello(ctx) {}
}
});
broker.call("staging.test.hello");
Module log level configuration is removed
The module log level is not supported. The logLevel
option can be only String
. It is used if the logger is the console
. In case of external loggers you have to handle log levels.
New
Better logging #61
The whole Moleculer logger is rewritten. It supports better the external loggers. The built-in log message format is also changed.
Built-in console
logger
const broker = createBroker({
logger: console,
logLevel: "info"
});
With custom logFormatter
const broker = new ServiceBroker({
logger: console,
logFormatter(level, args, bindings) {
return level.toUpperCase() + " " + bindings.nodeID + ": " + args.join(" ");
}
});
broker.logger.warn("Warn message");
broker.logger.error("Error message");
Output:
WARN dev-pc: Warn message
ERROR dev-pc: Error message
External loggers
const pino = require("pino")({ level: "info" });
const broker = new ServiceBroker({
logger: bindings => pino.child(bindings)
});
const bunyan = require("bunyan");
const logger = bunyan.createLogger({ name: "moleculer", level: "info" });
const broker = new ServiceBroker({
logger: bindings => logger.child(bindings)
});
const broker = new ServiceBroker({
logger: bindings => new winston.Logger({
transports: [
new (winston.transports.Console)({
timestamp: true,
colorize: true,
prettyPrint: true
})
]
})
});
const WinstonContext = require("winston-context");
const winston = require("winston");
const broker = createBroker({
logger: bindings => new WinstonContext(winston, "", bindings)
});
Please note! Some external loggers have not
trace
&fatal
log methods (e.g.: winston). In this case you have to extend your logger.
const WinstonContext = require("winston-context");
const winston = require("winston");
const { extend } = require("moleculer").Logger;
const broker = createBroker({
logger: bindings => extend(new WinstonContext(winston, "", bindings))
});
The bindings
contains the following properties:
ns
- namespacenodeID
- nodeIDmod
- type of core module:broker
,cacher
,transit
,transporter
svc
- service namever
- service version
Please avoid to use these property names when you log an Object
. For example: the broker.logger.error({ mod: "peanut" })
overrides the original mod
value!
Dynamic service load & destroy
Available to load & destroy services after the broker started. For example you can hot-reload your services in runtime. The remote nodes will be notified about changes and the broker will emit a services.changed
event locally.
Example
broker.start().then(() => {
setTimeout(() => {
// Create a new service after 5s
broker.createService({
name: "math",
actions: {
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b);
},
}
});
}, 5000);
setTimeout(() => {
// Destroy a created service after 10s
let svc = broker.getService("math");
broker.destroyService(svc);
}, 10000);
});
Multiple service calls #31
With broker.mcall
method you can call multiple actions (in parallel).
Example with Array
broker.mcall([
{ action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
{ action: "users.find", params: {limit: 5, sort: "username"} }
]).then(results => {
let posts = results[0];
let users = results[1];
})
Example with Object
broker.mcall({
posts: { action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
users: { action: "users.find", params: {limit: 5, sort: "username"} }
}).then(results => {
let posts = results.posts;
let users = results.users;
})
v0.8.4
v0.8.3
New
Removable actions in mixins
You can remove an existing action when mixing a service.
broker.createService({
name: "test",
mixins: [OtherService],
actions: {
dangerAction: false
}
});
In the test
service the dangerAction
action won't be registered.
Support NPM modules in moleculer-runner
You can load services from NPM module in moleculer-runner
.
With CLI arguments
$ moleculer-runner -r npm:moleculer-fake npm:moleculer-twilio
With env
$ SERVICES=posts,users,npm:moleculer-fale,npm:moleculer-twilio
$ moleculer-runner