Changes
- allow to work with pino v9 #1282
- Runner: log runner error through util.inspect #1304
- Update default type for ServiceSchema generic to Service #1299
- fix: updated removePendingRequestByNodeID #1306
Changes
- expand the type for ServiceSchema to allow for typed lifecycle handlers #1272
- fix runner use of moleculer.config function with default export #1284
- improve broker error handler types #1286
- don't plugging third-party Promise library for ioredis in RedisTransporter #1290
- fixed numeric cache key issue #1289
Changes
- autodetect Redis type discoverer when using redis SSL URI #1260
- change redis client events #1269
- fix transit connecting state flag #1258
- add hook middlewares interceptors to preserve call context with call middlewares #1270
- remove unnecessary clone in node update method #1274
Changes
- update peer dependency for mqtt to 5.0.2 #1236
- update d.ts #1245, #1246, #1248
- fix stream sending logic to avoid memory leak #1243
Changes
- fix prometheus reporter to accept host option #1221
- remove usage of _.uniq #1211
- fix: started not being called when createService is used #1229
Changes
- Improve d.ts files #1208, #1210, #1225
- replace deprecated redis.setex with redis.set method #1191
- rewrite
_.random
without lodash #1212
Changes
- Improve lifecycle handler types #1179
- add service list options & define getActionList type #1182
- mergeSchema can be used to merge 2 mixins #1187
- fix: do not emit send node info during broker stopping event #1186
- Allow opt out of redlock in redis cacher #1192
Changes
- fix no clean exit on shutdown, with disableBalancer: true #1168
- change
__proto__
to Object.getProtoTypeOf
#1170
- fix merge schemas (handling nulls) #1172
- fix hot reload error handling #1174
- update d.ts file
- update dependencies
Changes
- fix typescript definitions in may commits.
- prevent registerInternalServices deprecation warning #1163
Changes
- fix typescript definitions for the Service class #1139
- allow matching hooks to multiple actions with "|" #1149
- fix serializers datetime flaky test #1151
Changes
- fix Node 19 compatibility
Changes
- allow moleculer-runner to resolve configuration files from node_modules #1126
- fixed slower broker startup time issue #1132
- fixed memory leak at dynamic service creation #1121
- fixed invalid 'undefined' type in validator schema. #1137
- update dependencies
Changes
- fixed timeout issue in
waitForServices
method #1123
- fixed metadata issue when compression enabled #1122
35 commits from 11 contributors.
Changes
- fixed 'Ctx is undefined when using shard strategy and preferLocal is false, throws error on emit' #1072
- fixed info packet send at broker stop #1101
- added support for either-or versions to waitForServices #1030
- fixed streaming issue with compression #1100
- add requestID to debug logs in transit #1104
- removed static on methods for the use of ServiceFactory #1098
- fixed the issue with setting tracing and metrics options with env variables #1112
- added dependencyTimeout broker option #1118
- improved d.ts #1099 #1111 #1115
- updated dependencies
20 commits from 2 contributors.
ESM support #1063
This version contains an ESM-based Moleculer Runner. This Runner is able to load ESM configuration file and ESM services. It can load the CJS services, as well
Example usage
moleculer-runner-esm --repl services*.service.mjs
Moreover, the index.js
file is wrapped into index.mjs
, so you can import internal modules from the core in ESM modules. E.g.:
import { ServiceBroker, Errors } from "moleculer";
Please note, the hot-reload function doesn't work with this ESM Runner. The cause: https://github.com/nodejs/modules/issues/307
Node maintainers try to solve the missing features (module cache and module dependency tree) with loaders but this API is not stable yet.
Other Changes
broker.stopping
property is created to indicate that broker is in stopping state.
52 commits from 8 contributors.
Dependency logic changed #1077
In mixed architecture, it's not hard to create a circular service dependency that may cause a dead-lock during the start of Moleculer nodes. The problem is that Moleculer node only sends the local service registry to remote nodes after all local services started properly.
As of 0.14.20, this behavior has changed. The new logic uses a debounced registry sending method which is triggered every time a local service, that the node manages, has started()
.
Note that the new method generates more INFO packets, than early versions, during the start of the node. The number of INFO packets depends on the number of the services that the node manages. The debounce timeout, between sending INFO packets, is 1 second.
Other Changes
- fix ActionLogger and TransitLogger middlewares.
- update Datadog Logger using v2 API. #1056
- update dependencies.
- update d.ts file. #1064, #1073
- fix pino child logger bindings. #1075
69 commits from 7 contributors.
Custom error recreation feature #1017
You can create a custom Regenerator
class that is able to serialize and deserialize your custom errors. It's necessary when the custom error is created on a remote node and must be serialized to be able to send it back to the caller.
Create a custom Regenerator
const { Regenerator, MoleculerError } = require("moleculer").Errors;
class TimestampedError extends MoleculerError {
constructor(message, code, type, data, timestamp) {
super(message, code, type, data);
this.timestamp = timestamp;
}
}
class CustomRegenerator extends Regenerator {
restoreCustomError(plainError, payload) {
const { name, message, code, type, data, timestamp } = plainError;
switch (name) {
case "TimestampedError":
return new TimestampedError(message, code, type, data, timestamp);
}
}
extractPlainError(err) {
return {
...super.extractPlainError(err),
timestamp: err.timestamp
};
}
}
module.exports = CustomRegenerator;
Use it in broker options
const CustomRegenerator = require("./custom-regenerator");
module.exports = {
errorRegenerator: new CustomRegenerator()
}
Error events #1048
When an error occurred inside ServiceBroker, it's printed to the console, but there was no option that you can process it programmatically (e.g. transfer to an external monitoring service). This feature solves it. Every error inside ServiceBroker broadcasts a local (not transported) event ($transporter.error
, $broker.error
, $transit.error
, $cacher.error
, $discoverer.error
) what you can listen in your dedicated service or in a middleware.
Example to listen in an error-tracker service
module.exports = {
name: "error-tracker",
events: {
"$**.error": {
handler(ctx) {
this.sendError(ctx.params.error);
}
}
}
}
Example to listen in a middleware or in broker options
module.exports = {
created(broker) {
broker.localBus.on("*.error", payload => {
this.sendError(payload.error);
});
}
}
Wildcards in Action Hooks #1051
You can use *
wildcard in action names when you use it in Action Hooks.
Example
hooks: {
before: {
"create-*": [],
"*-user": [],
}
}
Other Changes
- update dependencies.
- update d.ts file. #1025, #1028, #1032
- add
safetyTags
option to tracing exporters. #1052
20 commits from 7 contributors.
Changes
- update dependencies.
- expose Cacher and Validator middlewares. #1012
- update d.ts file. #1013
- parse user & password from NATS server urls. #1021
61 commits from 10 contributors.
Changes
- reformat codebase with Prettier.
- fix binding issue in Pino logger. #974
- update d.ts file. #980 #970
- transit message handler promises are resolved. #984
- fix cacher issue if cacher is not connected. #987
- fix Jest open handlers issue. #989
- fix cacher cloning issue. #990
- add custom headers option to Zipkin trace exporter. #993
- fix
heartbeatTimeout
option in BaseDiscoverer. #985
- add cacher
keygen
option action definition. #1004
- update dependencies
11 commits from 4 contributors.
Changes
- fix
nats-streaming
version in peerDependencies.
- RedisCacher
pingInterval
option. #961
- Update NATS transporter messages to debug. #963
- update d.ts file. #964 #966
- update dependencies
15 commits from 5 contributors.
Changes
- fix
nats
version in peerDependencies.
- convert
url
to servers
in nats@2. #954
- add typing for
mcall
settled
option. #957
- revert TS
ThisType
issue in 0.14.14. #958
- update dependencies
- add
useTag259ForMaps: false
default option for CBOR serializer to keep the compatibility.
105 commits from 11 contributors.
New CBOR serializer #905
CBOR (cbor-x) is a new serializer but faster than any other serializers.
Example
module.exports = {
logger: true,
serializer: "CBOR"
};
Benchmark
Suite: Serialize packet with 10bytes
√ JSON 509,217 rps
√ Avro 308,711 rps
√ MsgPack 79,932 rps
√ ProtoBuf 435,183 rps
√ Thrift 93,324 rps
√ Notepack 530,121 rps
√ CBOR 1,016,135 rps
JSON (#) 0% (509,217 rps) (avg: 1μs)
Avro -39.38% (308,711 rps) (avg: 3μs)
MsgPack -84.3% (79,932 rps) (avg: 12μs)
ProtoBuf -14.54% (435,183 rps) (avg: 2μs)
Thrift -81.67% (93,324 rps) (avg: 10μs)
Notepack +4.11% (530,121 rps) (avg: 1μs)
CBOR +99.55% (1,016,135 rps) (avg: 984ns)
settled
option in broker.mcall
The broker.mcall
method has a new settled
option to receive all Promise results. Without this option, if you make a multi-call and one call is rejected, the response will be a rejected Promise
and you don't know how many (and which) calls were rejected.
If settled: true
, the method returns a resolved Promise
in any case and the response contains the statuses and responses of all calls.
Example
const res = await broker.mcall([
{ action: "posts.find", params: { limit: 2, offset: 0 },
{ action: "users.find", params: { limit: 2, sort: "username" } },
{ action: "service.notfound", params: { notfound: 1 } }
], { settled: true });
console.log(res);
The res
will be something similar to
[
{ status: "fulfilled", value: [] },
{ status: "fulfilled", value: [] },
{ status: "rejected", reason: {} }
]
New MOLECULER_CONFIG
environment variable in Runner
In the Moleculer Runner, you can configure the configuration filename and path with the MOLECULER_CONFIG
environment variable. It means, no need to specify the config file with --config
argument.
Supporting `nats@2.x.x` in NATS transporter
The new nats 2.x.x
version has a new breaking API which has locked the NATS transporter for `nats@1.x.xlibrary. As of this release, the NATS transporter supports both major versions of the
nats` library.
The transporter automatically detects the version of the library and uses the correct API.
Since `fastest-validator@1.11.0, the FastestValidator supports async custom validators and you can [pass metadata for custom validator functions](https://github.com/icebob/fastest-validator/blob/master/CHANGELOG.md#meta-information-for-custom-validators).
In Moleculer, the
FastestValidatorpasses the
ctx` as metadata. It means you can access the current context, service, broker and you can make async calls (e.g calling another service) in custom checker functions.
Example
module.exports = {
name: "posts",
actions: {
params: {
$$async: true,
owner: { type: "string", custom: async (value, errors, schema, name, parent, context) => {
const ctx = context.meta;
const res = await ctx.call("users.isValid", { id: value });
if (res !== true)
errors.push({ type: "invalidOwner", field: "owner", actual: value });
return value;
} },
},
}
}
Changes
- fix node crash in encryption mode with TCP transporter. #849
- expose
Utils
in typescript definition. #909
- other d.ts improvements. #920, #922, #934, #950
- fix etcd3 discoverer lease-loss issue #922
- catch errors in Compression and Encryption middlewares. #850
- using optional peer dependencies. #911
- add relevant packet to to serialization and deserialization calls. #932
- fix disabled balancer issue with external discoverer. #933
62 commits from 12 contributors.
Changes
- update dependencies
- logging if encryption middleware can't decrypt the data instead of crashing. #853
- fix
disableHeartbeatChecks
option handling. #858
- force scanning only master redis nodes for deletion. #865
- add more info into
waitForServices
debug log messages. #870
- fix
EVENT
packet Avro schema. #856
- fix array & date conversion in cacher default key generator. #883
- fix Datadog tracing exporter. #890
- better elapsed time handling in tracing. #899
- improve type definitions. #843, #885, #886
- add E2E tests for CI (test all built-in transporter & serializers)
Other changes
- update dependencies
- improved type definitions. #816 #817 #834 #840
- support
rediss://
cacher URI. #837
- fix Event Trace exporter generated events loop. #836
- change log level of node disconnected message. #838
- improve the
broker.waitForServices
response. #843
- fix recursive hot-reload issue on Linux OS. #848
New merged
service lifecycle hook
Service has a new merged
lifecycle hook which is called after the service schemas (including mixins) have been merged but before service is registered. It means you can manipulate the merged service schema before it's processed.
Example
module.exports = {
name: "posts",
settings: {},
actions: {
find: {
params: {
limit: "number"
}
handler(ctx) {
}
}
},
merged(schema) {
schema.settings.myProp = "myValue";
schema.actions.find.params.offset = "number";
}
};
Other changes
- add
requestID
tag to all action and event spans #802
- fix bug in second level of mixins with $secureSettings #811
Changes
- update dependencies
- fix issues in index.d.ts
- fix broadcast event sending issue when
disableBalancer: true
#799 (thanks for ngraef)
0.14.9 (2020-08-06)
Register method in module resolver
If you create a custom module (e.g. serializer), you can register it into the built-in modules with the register
method. This method is also available in all other built-in module resolvers.
Example
const { Serializers } = require("moleculer");
class SafeJsonSerializer {}
Serializers.register("SafeJSON", SafeJSON);
module.exports = SafeJsonSerializer;
require("./SafeJsonSerializer");
module.exports = {
nodeID: "node-100",
serializer: "SafeJSON"
});
Changeable validation property name
You can change the params
property name in validator options. It can be useful if you have a custom Validator implementation.
const broker = new ServiceBroker({
validator: {
type: "Fastest",
options: {
paramName: "myParams"
}
}
});
broker.createService({
name: "posts",
actions: {
create: {
myParams: {
title: "string"
}
},
handler(ctx) { }
}
});
Thanks for @kthompson23, you can configure the action & events tracing span tags globally. These tags will be used for all actions & events where tracing is enabled. Of course, you can overwrite them locally in the action & event schema.
Example
module.exports = {
tracing: {
enabled: true,
exporter: 'Zipkin',
tags: {
action: {
meta: ['app.id', 'user.email', 'workspace.name'],
params: false,
response: true,
},
event: (ctx) {
return {
caller: ctx.caller,
}
}
}
}
}
Other changes
- fix multiple trace spans with same ID issue in case of retries.
- update dependencies
- add
lastValue
property to histogram metric type.
- update return type of context's copy method.
- add configuration hotReloadModules #779
- Remove Zipkin v1 annotations and change kind to SERVER
0.14.8 (2020-06-27)
We have been approved in the Github Sponsors program, so you can sponsor the Moleculer project via Github Sponsors.
If you have taxing problem with Patreon, change to Github Sponsors.
New Validator configuration
The validator
has the same module configuration in broker options as other modules. It means you can configure the validation constructor options via broker options (moleculer.config.js).
Default usage:
module.exports = {
nodeID: "node-100",
validator: true
}
Using built-in validator name:
module.exports = {
nodeID: "node-100",
validator: "FastestValidator"
}
Example with options:
module.exports = {
nodeID: "node-100",
validator: {
type: "FastestValidator",
options: {
useNewCustomCheckerFunction: true,
defaults: { },
messages: { },
aliases: { }
}
}
}
Example with custom validator
const BaseValidator = require("moleculer").Validators.Base;
class MyValidator extends BaseValidator {}
module.exports = {
nodeID: "node-100",
validator: new MyValidator()
}
New metrics
Added the following new metrics:
moleculer.event.received.active
: Number of active event executions.
moleculer.event.received.error.total
: Number of event execution errors.
moleculer.event.received.time
: Execution time of events in milliseconds.
os.memory.total
: OS used memory size.
Other changes
- support using
moleculer.config.js
with export default
.
- remove some lodash methods.
- upgrade to the latest
tsd
.
- new
dependencyInterval
broker option. Using as default value for broker.waitForServices
#761
- fix Datadog traceID, spanID generation logic to work with latest
dd-trace
.
- add error stack trace to
EventLegacy
trace exporter.
- fix INFO key issue in Redis Discoverer after Redis server restarting.
0.14.7 (2020-05-22)
New Discoverer module
The Discoverer is a new built-in module in Moleculer framework. It's responsible for that all Moleculer nodes can discover each other and check them with heartbeats. In previous versions, it was an integrated module inside ServiceRegistry
& Transit
modules. In this version, the discovery logic has been extracted to a separated built-in module. It means you can replace it with other built-in implementations or a custom one. The current discovery & heartbeat logic is moved to the Local
Discoverer.
Nevertheless, this version also contains other discoverers, like Redis & etcd3 discoverers. Both of them require an external server to make them work.
One of the main advantages of the external discoverers, the node discovery & heartbeat packets don't overload the communication on the transporter. The transporter transfers only the request, response, event packets.
By the way, the external discoverers have some disadvantages, as well. These discoverers can detect lazier the new and disconnected nodes because they scan the heartbeat keys periodically on the remote Redis/etcd3 server. The period of checks depends on the heartbeatInterval
broker option.
The Local Discoverer (which is the default) works like the discovery of previous versions, so if you want to keep the original logic, you'll have to do nothing.
Please note the TCP transporter uses Gossip protocol & UDP packets for discovery & heartbeats, it means it can work only with Local Discoverer.
Local Discoverer
It's the default Discoverer, it uses the transporter module to discover all other moleculer nodes. It's quick and fast but if you have too many nodes (>100), the nodes can generate a lot of heartbeat packets which can reduce the performance of request/response packets.
Example
module.exports = {
registry: {
discoverer: "Local"
}
}
Example with options
module.exports = {
registry: {
discoverer: {
type: "Local",
options: {
heartbeatInterval: 10,
heartbeatTimeout: 30,
disableHeartbeatChecks: false,
disableOfflineNodeRemoving: false,
cleanOfflineNodesTimeout: 10 * 60
}
}
}
}
Redis Discoverer
This is an experimental module. Do not use it in production yet!
This Discoverer uses a Redis server to discover Moleculer nodes. The heartbeat & discovery packets are stored in Redis keys. Thanks to Redis key expiration, if a node crashed or disconnected unexpectedly, the Redis will remove its heartbeat keys from the server and other nodes can detect it.
Example to connect local Redis server
module.exports = {
registry: {
discoverer: "Redis"
}
}
Example to connect remote Redis server
module.exports = {
registry: {
discoverer: "redis://redis-server:6379"
}
}
Example with options
module.exports = {
registry: {
discoverer: {
type: "Redis",
options: {
redis: {
port: 6379,
host: "redis-server",
password: "123456",
db: 3
}
serializer: "JSON",
fullCheck: 10,
scanLength: 100,
monitor: true,
heartbeatInterval: 10,
heartbeatTimeout: 30,
disableHeartbeatChecks: false,
disableOfflineNodeRemoving: false,
cleanOfflineNodesTimeout: 10 * 60
}
}
}
}
To be able to use this Discoverer, install the ioredis
module with the npm install ioredis --save command.
Tip: To further network traffic reduction, you can use MsgPack/Notepack serializers instead of JSON.
Etcd3 Discoverer
This is an experimental module. Do not use it in production yet!
This Discoverer uses an etcd3 server to discover Moleculer nodes. The heartbeat & discovery packets are stored in the server. Thanks to etcd3 lease solution, if a node crashed or disconnected unexpectedly, the etcd3 will remove its heartbeat keys from the server and other nodes can detect it.
Example to connect local etcd3 server
module.exports = {
registry: {
discoverer: "Etcd3"
}
}
Example to connect remote etcd3 server
module.exports = {
registry: {
discoverer: "etcd3://etcd-server:2379"
}
}
Example with options
module.exports = {
registry: {
discoverer: {
type: "Etcd3",
options: {
etcd: {
hosts: "etcd-server:2379",
auth: "12345678"
}
serializer: "JSON",
fullCheck: 10,
heartbeatInterval: 10,
heartbeatTimeout: 30,
disableHeartbeatChecks: false,
disableOfflineNodeRemoving: false,
cleanOfflineNodesTimeout: 10 * 60
}
}
}
}
To be able to use this Discoverer, install the etcd3
module with the npm install etcd3--save command.
Tip: To further network traffic reduction, you can use MsgPack/Notepack serializers instead of JSON.
Custom Discoverer
You can create your custom Discoverer. We recommend to copy the source of Redis Discoverer and implement the necessary methods.
Other changes
- fixed multiple heartbeat sending issue at transporter reconnecting.
- the
heartbeatInterval
default value has been changed to 10
seconds.
- the
heartbeatTimeout
default value has been changed to 30
seconds.
- the heartbeat check logics uses process uptime instead of timestamps in order to avoid issues with time synchronization or daylight saving.
- Notepack serializer initialization issue fixed. It caused a problem when Redis cacher uses Notepack serializer.
- new
removeFromArray
function in Utils.
mcall
method definition fixed in Typescript definition.
- dependencies updated.
0.14.6 (2020-04-11)
New NewRelic zipkin tracing exporter
Thanks for @jalerg, there is a NewRelic tracing exporter. PR #713
{
tracing: {
enabled: true,
events: true,
exporter: [
{
type: 'NewRelic',
options: {
insertKey: process.env.NEW_RELIC_INSERT_KEY,
interval: 5,
payloadOptions: {
debug: false,
shared: false,
},
defaultTags: null,
},
},
],
},
}
Other changes
- fix stream chunking issue. PR #712
- fix INFO packet sending issue after reconnecting
- safely handling disconnected state for transporters (heartbeat queuing issue). PR #715
- fix orhpan response issue in case of streaming with disabled balancer. #709
- update dependencies, audit fix
0.14.5 (2020-03-25)
Wrapping service methods with middlewares
New localMethod
hook in middlewares which wraps the service methods.
Example
module.exports = {
name: "MyMiddleware",
localMethod(next, method) {
return (...args) => {
console.log(`The '${method.name}' method is called in '${method.service.fullName}' service.`, args);
return handler(...args);
}
}
}
Schema for service methods
Similar to action schema, you can define service methods with schema. It can be useful when middleware wraps service methods.
Example for new method schema
module.exports = {
name: "posts",
methods: {
list: {
async handler(count) {
return posts;
}
}
}
};
Changes
- add chunk limit for streams in message transporting. #683
- add
baseUrl
option to Datadog metric reporter. #694
- fix open handles in unit tests. #695
- update d.ts #699 #700 #703
0.14.4 (2020-03-08)
Changes
- add
maxSafeObjectSize
to service broker options. Fixes #697
- add
stop
method to tracing exporters. Fixes #689
- fix
EventLegacy
tracing exporter. Fixes #676
- the
defaultTags
property in tracer
options can be a Function
, as well.
0.14.3 (2020-02-24)
Changes
- fix issue in AMQP 1.0 transporter
0.14.2 (2020-02-14)
Support custom loggers
If you have your custom logger you should wrap it into a Logger
class and implement the getLogHandler
method.
Using a custom logger
const BaseLogger = require("moleculer").Loggers.Base;
class MyLogger extends BaseLogger {
getLogHandler(bindings) {
return (type, args) => console[type](`[MYLOG-${bindings.mod}]`, ...args);
}
}
module.exports = {
logger: new MyLogger()
};
0.14.1 (2020-02-12)
Changes
- fix bluebird import issue #674
0.14.0 (2020-02-12)
Migration guide from 0.13 to 0.14
Breaking changes
Minimum Node version is 10
The Node version 8 LTS lifecycle has been ended on December 31, 2019, so the minimum required Node version is 10.
Bluebird dropped
The Bluebird Promise library has been dropped from the project because as of Node 10 the native Promise
implementation is faster (2x) than Bluebird.
Nonetheless, you can use your desired Promise library, just set the Promise
broker options.
Using Bluebird
const BluebirdPromise = require("bluebird");
module.exports = {
Promise: BluebirdPromise
};
Please note, the given Promise library will be polyfilled with delay
, method
, timeout
and mapSeries
methods (which are used inside Moleculer core modules).
Communication protocol has been changed
The Moleculer communication protocol has been changed. The new protocol version is 4
.
It means the new Moleculer 0.14 nodes can't communicate with old <= 0.13 nodes.
Fastest validator upgraded to 1.x.x
Fastest-validator, the default validation has been upgraded to the 1.0.0 version. It means breaking changes but the new version more faster and contains many sanitization functions.
If you use custom rules, you should upgrade your codes. Check the changes here.
Logger settings changed
The whole logging function has been rewritten in this version. It means, it has a lot of new features, but the configuration of loggers has contains breaking changes. You can't use some old custom logger configuration form. The new configuration is same as the other Moleculer module configurations. This new version supports all famous loggers like Pino, Winston, Bunyan, Debug & Log4js.
If you are using the built-in default console logger, this breaking change doesn't affect you.
The logFormatter
and logObjectPrinter
broker options have been removed and moved into the Console
and File
logger options.
Not changed usable configurations
module.exports = {
logger: true,
logger: false
};
You CANNOT use these legacy configurations
module.exports = {
logger: bindings => pino.child(bindings),
logger: {
error: () => { ... },
warn: () => { ... },
info: () => { ... },
debug: () => { ... }
}
};
Console logger
This logger prints all log messages to the console
. It supports several built-in formatters or you can use your custom formatter, as well.
Shorthand configuration with default options
module.exports = {
logger: "Console",
};
Full configuration
module.exports = {
logger: {
type: "Console",
options: {
level: "info",
colors: true,
moduleColors: false,
formatter: "full",
objectPrinter: null,
autoPadding: false
}
}
};
File logger
This logger saves all log messages to file(s). It supports JSON & formatted text files or you can use your custom formatter, as well.
Shorthand configuration with default options
module.exports = {
logger: "File",
};
It will save the log messages to the logs
folder in the current directory with moleculer-{date}.log
filename.
Full configuration
module.exports = {
logger: {
type: "File",
options: {
level: "info",
folder: "./logs",
filename: "moleculer-{date}.log",
formatter: "json",
objectPrinter: null,
eol: "\n",
interval: 1 * 1000
}
}
};
Pino logger
This logger uses the Pino logger.
Shorthand configuration with default options
module.exports = {
logger: "Pino",
};
Full configuration
module.exports = {
logger: {
type: "Pino",
options: {
level: "info",
pino: {
options: null,
destination: "/logs/moleculer.log",
}
}
}
};
To use this logger please install the pino
module with npm install pino --save
command.
Bunyan logger
This logger uses the Bunyan logger.
Shorthand configuration with default options
module.exports = {
logger: "Bunyan",
};
Full configuration
module.exports = {
logger: {
type: "Bunyan",
options: {
level: "info",
bunyan: {
name: "moleculer"
}
}
}
};
To use this logger please install the bunyan
module with npm install bunyan --save
command.
Winston logger
This logger uses the Winston logger.
Shorthand configuration with default options
module.exports = {
logger: "Winston",
};
Full configuration
const winston = require("winston");
module.exports = {
logger: {
type: "Winston",
options: {
level: "info",
winston: {
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "/logs/moleculer.log" })
]
}
}
}
};
To use this logger please install the winston
module with npm install winston --save
command.
debug
logger
This logger uses the debug logger.
To see messages you have to set the DEBUG
environment variable to export DEBUG=moleculer:*
.
Shorthand configuration with default options
module.exports = {
logger: "Debug",
};
Full configuration
module.exports = {
logger: {
type: "Debug",
options: {
level: "info",
}
}
};
To use this logger please install the debug
module with npm install debug --save
command.
Log4js logger
This logger uses the Log4js logger.
Shorthand configuration with default options
module.exports = {
logger: "Log4js",
};
Full configuration
module.exports = {
logger: {
type: "Log4js",
options: {
level: "info",
log4js: {
appenders: {
app: { type: "file", filename: "/logs/moleculer.log" }
},
categories: {
default: { appenders: [ "app" ], level: "debug" }
}
}
}
}
};
To use this logger please install the log4js
module with npm install log4js --save
command.
Datadog logger
This logger uploads log messages to the Datadog server.
Please note, this logger doesn't print any messages to the console, just collects & uploads. Use it beside another logger which also prints the messages.
Shorthand configuration with default options
module.exports = {
logger: "Datadog",
};
Full configuration
module.exports = {
logger: {
type: "Datadog",
options: {
level: "info",
url: "https://http-intake.logs.datadoghq.com/v1/input/",
apiKey: process.env.DATADOG_API_KEY,
ddSource: "moleculer",
env: undefined,
hostname: os.hostname(),
objectPrinter: null,
interval: 10 * 1000
}
}
};
Multiple loggers
This new logger configuration admits to use multiple loggers even from the same logger type and different logging levels.
Define multiple loggers with different logging levels
This configuration demonstrates how you can define a Console
logger, a File
logger to save all log messages in formatted text file and another File
logger to save only error messages in JSON format.
module.exports = {
logger: [
{
type: "Console",
options: {
level: "info",
}
},
{
type: "File",
options: {
level: "info",
folder: "/logs/moleculer",
filename: "all-{date}.log",
formatter: "{timestamp} {level} {nodeID}/{mod}: {msg}"
}
},
{
type: "File",
options: {
level: "error",
folder: "/logs/moleculer",
filename: "errors-{date}.json",
formatter: "json"
}
}
]
};
Using different loggers for different modules
This configuration demonstrates how you can define loggers for certain modules.
module.exports = {
logger: [
"Console",
{
type: "File",
options: {
level: {
"GREETER": false,
"**": "info"
},
filename: "moleculer-{date}.log"
}
},
{
type: "File",
options: {
level: {
"GREETER": "debug",
"**": false
},
filename: "greeter-{date}.log"
}
}
],
logLevel: "info"
};
Logging level setting.
To configure logging levels, you can use the well-known logLevel
broker option which can be a String
or an Object
. But it is possible to overwrite it in all logger options
with the level
property.
Complex logging level configuration
module.exports = {
logger: [
"Console",
{
type: "File",
options: {
level: {
"GREETER": false,
"**": "warn"
}
}
}
],
logLevel: {
"TRACING": "trace",
"TRANS*": "warn",
"GREETER": "debug",
"**": "info",
}
};
Validation settings changed
The validation: true
broker options was removed to follow other module configuration. Use validator
option, instead.
Enable validation with built-in validator (default option)
const broker = new ServiceBroker({
validator: true
});
Disable validation/validator
const broker = new ServiceBroker({
validator: false
});
Use custom validation
const broker = new ServiceBroker({
validator: new MyCustomValidator()
});
The broker.use
removed
The broker.use
has been deprecated in version 0.13 and now it is removed. Use middleware: []
broker options to define middlewares.
loading middleware after the broker has started is no longer available.
The $node.health
response changed
The $node.health
action's response has been changed. The transit
property is removed. To get transit metrics, use the new $node.metrics
internal action.
Middleware shorthand definition is dropped
In previous versions you could define middleware which wraps the localAction
hook with a simple Function
.
In version 0.14 this legacy shorthand is dropped. When you define a middleware as a Function
, the middleware handler will call it as an initialization and pass the ServiceBroker instance as a parameter.
Old shorthand middleware definition as a Function
const MyMiddleware = function(next, action) {
return ctx => next(ctx);
};
const broker = new ServiceBroker({
middlewares: [MyMiddleware]
});
New middleware definition as a Function
const MyMiddleware = function(broker) {
const myLogger = broker.getLogger("MY-LOGGER");
return {
localAction: function(next, action) {
return ctx => {
myLogger.info(`${action.name} has been called`);
return next(ctx);
}
}
}
};
const broker = new ServiceBroker({
middlewares: [MyMiddleware]
});
The localEvent
middleware hook signature changed
Old signature
module.exports = {
localEvent(next, event) {
return (payload, sender, event) => {
return next(payload, sender, event);
};
},
};
New context-based signature
module.exports = {
localEvent(next, event) {
return (ctx) => {
return next(ctx);
};
},
};
New
Experimental transporters have become stable
The Kafka, NATS Streaming & TCP transporter have become stable because we didn't find and receive any issue about them.
Context-based events
The new 0.14 version comes context-based event handler. It is very useful when you are using event-driven architecture and you would like to tracing the event. The Event Context is same as Action Context. They are the same properties except a few new properties related to the event.
It doesn't mean you should rewrite all existing event handlers. Moleculer detects the signature of your event handler. If it finds that the signature is "user.created(ctx) { ... }
, it will call it with Event Context. If not, it will call with old arguments & the 4th argument will be the Event Context, like "user.created"(payload, sender, eventName, ctx) {...}
Use Context-based event handler & emit a nested event
module.exports = {
name: "accounts",
events: {
"user.created"(ctx) {
console.log("Payload:", ctx.params);
console.log("Sender:", ctx.nodeID);
console.log("We have also metadata:", ctx.meta);
console.log("The called event name:", ctx.eventName);
ctx.emit("accounts.created", { user: ctx.params.user });
}
}
};
If you want to use different variable name or the service can't detect properly the signature, use context: true
in the event definition:
module.exports = {
name: "accounts",
events: {
"user.created": {
context: true,
handler({ params, nodeID }) {
console.log("Payload:", ctx.params);
console.log("Sender:", ctx.nodeID);
}
}
}
};
Event parameter validation
Similar to action parameter validation, the event parameter validation is supported.
Like in action definition, you should define params
in even definition and the built-in Validator
validates the parameters in events.
module.exports = {
name: "mailer",
events: {
"send.mail": {
params: {
from: "string|optional",
to: "email",
subject: "string"
},
handler(ctx) {
this.logger.info("Event received, parameters OK!", ctx.params);
}
}
}
};
The validation errors are not sent back to the caller, they are logged or you can catch them with the new global error handler.
New built-in metrics
Moleculer v0.14 comes with a brand-new and entirely rewritten metrics module. It is now a built-in module. It collects a lot of internal Moleculer & process metric values. You can easily define your custom metrics. There are several built-in metrics reporters like Console
, Prometheus
, Datadog
, ...etc.
Multiple reporters can be defined.
Enable metrics & define console reporter
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
"Console"
]
}
});
Define custom metrics
module.exports = {
name: "posts",
actions: {
get(ctx) {
this.broker.metrics.increment("posts.get.total");
return posts;
}
},
created() {
this.broker.metrics.register({ type: "counter", name: "posts.get.total" });
}
};
Enable metrics & define Prometheus reporter with filtering
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "Prometheus",
options: {
port: 3030,
includes: ["moleculer.**"],
excludes: ["moleculer.transit.**"]
}
}
]
}
});
Supported metric types
counter
- A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero. For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
gauge
- A gauge is a metric that represents a single numerical value that can arbitrarily go up and down. Gauges are typically used for measured values like current memory usage, but also "counts" that can go up and down, like the number of concurrent requests.
histogram
- A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets. It also provides a sum of all observed values and calculates configurable quantiles over a sliding time window.
info
- An info is a single string or number value like process arguments, hostname or version numbers.
Internal metrics
Process metrics
process.arguments
(info)
process.pid
(info)
process.ppid
(info)
process.eventloop.lag.min
(gauge)
process.eventloop.lag.avg
(gauge)
process.eventloop.lag.max
(gauge)
process.eventloop.lag.count
(gauge)
process.memory.heap.size.total
(gauge)
process.memory.heap.size.used
(gauge)
process.memory.rss
(gauge)
process.memory.external
(gauge)
process.memory.heap.space.size.total
(gauge)
process.memory.heap.space.size.used
(gauge)
process.memory.heap.space.size.available
(gauge)
process.memory.heap.space.size.physical
(gauge)
process.memory.heap.stat.heap.size.total
(gauge)
process.memory.heap.stat.executable.size.total
(gauge)
process.memory.heap.stat.physical.size.total
(gauge)
process.memory.heap.stat.available.size.total
(gauge)
process.memory.heap.stat.used.heap.size
(gauge)
process.memory.heap.stat.heap.size.limit
(gauge)
process.memory.heap.stat.mallocated.memory
(gauge)
process.memory.heap.stat.peak.mallocated.memory
(gauge)
process.memory.heap.stat.zap.garbage
(gauge)
process.uptime
(gauge)
process.internal.active.handles
(gauge)
process.internal.active.requests
(gauge)
process.versions.node
(info)
process.gc.time
(gauge)
process.gc.total.time
(gauge)
process.gc.executed.total
(gauge)
OS metrics
os.memory.free
(gauge)
os.memory.total
(gauge)
os.uptime
(gauge)
os.type
(info)
os.release
(info)
os.hostname
(info)
os.arch
(info)
os.platform
(info)
os.user.uid
(info)
os.user.gid
(info)
os.user.username
(info)
os.user.homedir
(info)
os.network.address
(info)
os.network.mac
(info)
os.datetime.unix
(gauge)
os.datetime.iso
(info)
os.datetime.utc
(info)
os.datetime.tz.offset
(gauge)
os.cpu.load.1
(gauge)
os.cpu.load.5
(gauge)
os.cpu.load.15
(gauge)
os.cpu.utilization
(gauge)
os.cpu.user
(gauge)
os.cpu.system
(gauge)
os.cpu.total
(gauge)
os.cpu.info.model
(info)
os.cpu.info.speed
(gauge)
os.cpu.info.times.user
(gauge)
os.cpu.info.times.sys
(gauge)
Moleculer metrics
moleculer.node.type
(info)
moleculer.node.versions.moleculer
(info)
moleculer.node.versions.protocol
(info)
moleculer.broker.namespace
(info)
moleculer.broker.started
(gauge)
moleculer.broker.local.services.total
(gauge)
moleculer.broker.middlewares.total
(gauge)
moleculer.registry.nodes.total
(gauge)
moleculer.registry.nodes.online.total
(gauge)
moleculer.registry.services.total
(gauge)
moleculer.registry.service.endpoints.total
(gauge)
moleculer.registry.actions.total
(gauge)
moleculer.registry.action.endpoints.total
(gauge)
moleculer.registry.events.total
(gauge)
moleculer.registry.event.endpoints.total
(gauge)
moleculer.request.bulkhead.inflight
(gauge)
moleculer.request.bulkhead.queue.size
(gauge)
moleculer.event.bulkhead.inflight
(gauge)
moleculer.event.bulkhead.queue.size
(gauge)
moleculer.request.timeout.total
(counter)
moleculer.request.retry.attempts.total
(counter)
moleculer.request.fallback.total
(counter)
moleculer.request.total
(counter)
moleculer.request.active
(gauge)
moleculer.request.error.total
(counter)
moleculer.request.time
(histogram)
moleculer.request.levels
(counter)
moleculer.event.emit.total
(counter)
moleculer.event.broadcast.total
(counter)
moleculer.event.broadcast-local.total
(counter)
moleculer.event.received.total
(counter)
moleculer.transit.publish.total
(counter)
moleculer.transit.receive.total
(counter)
moleculer.transit.requests.active
(gauge)
moleculer.transit.streams.send.active
(gauge)
moleculer.transporter.packets.sent.total
(counter)
moleculer.transporter.packets.sent.bytes
(counter)
moleculer.transporter.packets.received.total
(counter)
moleculer.transporter.packets.received.bytes
(counter)
To use the GC & Event loop metrics you should install the gc-stats
and event-loop-stats
packages manually.
Built-in reporters
All reporters have the following options:
{
includes: null,
excludes: null,
metricNamePrefix: null,
metricNameSuffix: null,
metricNameFormatter: null,
labelNameFormatter: null
}
Console reporter
This is a debugging reporter which prints metrics to the console periodically.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "Console",
options: {
interval: 5 * 1000,
logger: null,
colors: true,
onlyChanges: true
}
}
]
}
});
CSV reporter
CSV reporter saves changed to CSV file.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "CSV",
options: {
folder: "./reports/metrics",
delimiter: ",",
rowDelimiter: "\n",
mode: MODE_METRIC,
types: null,
interval: 5 * 1000,
filenameFormatter: null,
rowFormatter: null,
}
}
]
}
});
Datadog reporter
Datadog reporter sends metrics to the Datadog server.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "Datadog",
options: {
host: "my-host",
apiVersion: "v1",
path: "/series",
apiKey: process.env.DATADOG_API_KEY,
defaultLabels: (registry) => ({
namespace: registry.broker.namespace,
nodeID: registry.broker.nodeID
}),
interval: 10 * 1000
}
}
]
}
});
Event reporter
Event reporter sends Moleculer events with metric values.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "Event",
options: {
eventName: "$metrics.snapshot",
broadcast: false,
groups: null,
onlyChanges: false,
interval: 5 * 1000,
}
}
]
}
});
Prometheus reporter
Prometheus reporter publishes metrics in Prometheus format. The Prometheus server can collect them. Default port is 3030
.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "Prometheus",
options: {
port: 3030,
path: "/metrics",
defaultLabels: registry => ({
namespace: registry.broker.namespace,
nodeID: registry.broker.nodeID
})
}
}
]
}
});
StatsD reporter
The StatsD reporter sends metric values to StatsD server via UDP.
const broker = new ServiceBroker({
metrics: {
enabled: true,
reporter: [
{
type: "StatsD",
options: {
protocol: "udp",
host: "localhost",
port: 8125,
maxPayloadSize: 1300,
}
}
]
}
});
New tracing feature
An enhanced tracing middleware has been implemented in version 0.14. It support several exporters and custom tracing spans.
Enable tracing
const broker = new ServiceBroker({
tracing: true
});
Tracing with console exporter
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Console",
options: {
width: 80,
colors: true,
}
}
]
}
});
Tracing with Zipkin exporter
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Zipkin",
options: {
baseURL: "http://zipkin-server:9411",
}
}
]
}
});
Add context values to span tags
In action defintion you can define which Context params or meta values want to add to the span tags.
Example
module.exports = {
name: "posts",
actions: {
get: {
tracing: {
tags: {
params: ["id"],
meta: ["loggedIn.username"],
response: ["id", "title"]
}
},
async handler(ctx) {
}
}
}
});
Example with all properties of params without meta (actually it is the default)
module.exports = {
name: "posts",
actions: {
get: {
tracing: {
tags: {
params: true,
meta: false,
},
async handler(ctx) {
}
}
}
});
Example with custom function
Please note, the tags
function will be called two times in case of success execution. First with ctx
, and second times with ctx
& response
as the response of action call.
module.exports = {
name: "posts",
actions: {
get: {
tracing: {
tags(ctx, response) {
return {
params: ctx.params,
meta: ctx.meta,
custom: {
a: 5
},
response
};
}
},
async handler(ctx) {
}
}
}
});
Example with all properties of params in event definition
module.exports = {
name: "posts",
events: {
"user.created": {
tracing: {
tags: {
params: true,
meta: false,
},
async handler(ctx) {
}
}
}
});
Built-in exporters
Console exporter
This is a debugging exporter which prints the full local trace to the console.
Please note that it can't follow remote calls, only locals.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Console",
options: {
logger: null,
colors: true,
width: 100,
gaugeWidth: 40
}
}
]
}
});
Datadog exporter
Datadog exporter sends tracing data to Datadog server via dd-trace
. It is able to merge tracing spans between instrumented Node.js modules and Moleculer modules.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Datadog",
options: {
agentUrl: process.env.DD_AGENT_URL || "http://localhost:8126",
env: process.env.DD_ENVIRONMENT || null,
samplingPriority: "AUTO_KEEP",
defaultTags: null,
tracerOptions: null,
}
}
]
}
});
To use this exporter, install the dd-trace
module with npm install dd-trace --save
command.
Event exporter
Event exporter sends Moleculer events ($tracing.spans
) with tracing data.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Event",
options: {
eventName: "$tracing.spans",
sendStartSpan: false,
sendFinishSpan: true,
broadcast: false,
groups: null,
interval: 5,
spanConverter: null,
defaultTags: null
}
}
]
}
});
Event (legacy) exporter
This is another event exporter which sends legacy moleculer events (metrics.trace.span.start
& metrics.trace.span.finish
). It is compatible with <= 0.13 Moleculer metrics trace events.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
"EventLegacy"
]
}
});
Jaeger exporter
Jaeger exporter sends tracing spans information to a Jaeger server.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Jaeger",
options: {
endpoint: null,
host: "127.0.0.1",
port: 6832,
sampler: {
type: "Const",
options: {}
},
tracerOptions: {},
defaultTags: null
}
}
]
}
});
To use this exporter, install the jaeger-client
module with npm install jaeger-client --save
command.
Zipkin exporter
Zipkin exporter sends tracing spans information to a Zipkin server.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Zipkin",
options: {
baseURL: process.env.ZIPKIN_URL || "http://localhost:9411",
interval: 5,
payloadOptions: {
debug: false,
shared: false
},
defaultTags: null
}
}
]
}
});
Custom tracing spans
module.exports = {
name: "posts",
actions: {
async find(ctx) {
const span1 = ctx.startSpan("get data from DB", {
tags: {
...ctx.params
}
});
const data = await this.getDataFromDB(ctx.params);
ctx.finishSpan(span1);
const span2 = ctx.startSpan("populating");
const res = await this.populate(data);
ctx.finishSpan(span2);
return res;
}
}
};
Caller action
There is a new caller
property in Context. It contains the service name of the caller when you use ctx.call
in action or event handlers.
broker2.createService({
name: "greeter",
actions: {
hello(ctx) {
this.logger.info(`This action is called from '${ctx.caller}' on '${ctx.nodeID}'`);
}
}
});
If you cann an action with broker.call
, the caller will be null
. In this case, you can set the caller
manually in the calling options.
Bulkhead supports events
Bulkhead feature supports service event handlers, as well.
module.exports = {
name: "my-service",
events: {
"user.created": {
bulkhead: {
enabled: true,
concurrency: 1
},
async handler(ctx) {
}
}
}
}
Use async/await
or return Promise
in event handlers.
NodeID conflict handling
Having remote nodes with same nodeID
in the same namespace
can cause communication problems. In v0.14 ServiceBroker checks the nodeIDs of remote nodes. If some node has the same nodeID, the broker will throw a fatal error and stop the process.
Sharding built-in strategy
There is a new built-in shard invocation strategy. It uses a key value from context params or meta to route the request a specific node. It means the same key value will be route to the same node.
Example with shard key as name
param in context
const broker = new ServiceBroker({
registry: {
strategy: "Shard",
strategyOptions: {
shardKey: "name"
}
}
});
Example with shard key as user.id
meta value in context
const broker = new ServiceBroker({
registry: {
strategy: "Shard",
strategyOptions: {
shardKey: "#user.id"
}
}
});
All available options of Shard strategy
const broker = new ServiceBroker({
registry: {
strategy: "Shard",
strategyOptions: {
shardKey: "#user.id",
vnodes: 10,
ringSize: 1000,
cacheSize: 1000
}
}
});
Different strategy for actions/events
The global invocation strategy can be overwritten in the action/event definitions.
Using 'Shard' strategy for 'hello' service instead of global 'RoundRobin'
module.exports = {
registry: {
strategy: "RoundRobin"
}
});
module.exports = {
name: "greeter",
actions: {
hello: {
params: {
name: "string"
},
strategy: "Shard",
strategyOptions: {
shardKey: "name"
}
handler(ctx) {
return `Hello ${ctx.params.name}`;
}
}
}
};
Extending internal services
Now the internal services can be extended. You can define mixin schema for every internal service under internalServices
broker option.
module.exports = {
nodeID: "node-1",
logger: true,
internalServices: {
$node: {
actions: {
hello(ctx) {
return `Hello Moleculer!`;
}
}
}
}
};
Action hook inside action definition
Sometimes it's better to define action hooks inside action definition instead of service hooks
property.
broker.createService({
name: "greeter",
hooks: {
before: {
"*"(ctx) {
broker.logger.info(chalk.cyan("Before all hook"));
},
hello(ctx) {
broker.logger.info(chalk.magenta(" Before hook"));
}
},
after: {
"*"(ctx, res) {
broker.logger.info(chalk.cyan("After all hook"));
return res;
},
hello(ctx, res) {
broker.logger.info(chalk.magenta(" After hook"));
return res;
}
},
},
actions: {
hello: {
hooks: {
before(ctx) {
broker.logger.info(chalk.yellow.bold(" Before action hook"));
},
after(ctx, res) {
broker.logger.info(chalk.yellow.bold(" After action hook"));
return res;
}
},
handler(ctx) {
broker.logger.info(chalk.green.bold(" Action handler"));
return `Hello ${ctx.params.name}`;
}
}
}
});
Output
INFO - Before all hook
INFO - Before hook
INFO - Before action hook
INFO - Action handler
INFO - After action hook
INFO - After hook
INFO - After all hook
There is a new metadata
property in broker options to store custom values. You can use the metadata
property in your custom middlewares or strategies.
const broker2 = new ServiceBroker({
nodeID: "broker-2",
transporter: "NATS",
metadata: {
region: "eu-west1"
}
});
This information is available in response of $node.list
action.
Enhanced hot-reload feature
In v0.14 the built-in hot-reload feature was entirely rewritten. Now, it can detect dependency-graph between service files and other loaded (with require
) files. This means that the hot-reload mechanism now watches the service files and their dependencies. Every time a file change is detected the hot-reload mechanism will track the affected services and will restart them.
New middleware hooks
There are some new middleware hooks.
registerLocalService
It's called before registering a local service instance.
Signature
module.exports = {
registerLocalService(next) {
return (svc) => {
return next(svc);
};
}
}
serviceCreating
It's called before a local service instance creating. At this point the service mixins are resolved, so the service schema is merged completely.
Signature
module.exports = {
serviceCreating(service, schema) {
schema.myProp = "John";
}
}
transitPublish
It's called before communication packet publishing.
Signature
module.exports = {
transitPublish(next) {
return (packet) => {
return next(packet);
};
},
}
transitMessageHandler
It's called before transit receives & parses an incoming message
Signature
module.exports = {
transitMessageHandler(next) {
return (cmd, packet) => {
return next(cmd, packet);
};
}
}
transporterSend
It's called before transporter send a communication packet (after serialization). Use it to encrypt or compress the packet buffer.
Signature
module.exports = {
transporterSend(next) {
return (topic, data, meta) => {
return next(topic, data, meta);
};
}
}
transporterReceive
It's called after transporter received a communication packet (before serialization). Use it to decrypt or decompress the packet buffer.
Signature
module.exports = {
transporterReceive(next) {
return (cmd, data, s) => {
return next(cmd, data, s);
};
}
}
newLogEntry
It's called when a new logger entry created by the default console logger. It's not called when using external custom logger like (Pino, Winston, Bunyan...etc).
Signature
module.exports = {
newLogEntry(type, args, bindings) {
},
}
Please note: it's not called during broker is starting because logger is created early before middleware initialization.
New built-in middlewares
Encryption
AES encryption middleware protects all inter-services communications that use the transporter module.
This middleware uses built-in Node crypto
library.
const { Middlewares } = require("moleculer");
const broker = new ServiceBroker({
middlewares: [
Middlewares.Transmit.Encryption("secret-password", "aes-256-cbc", initVector)
]
});
Compression
Compression middleware reduces the size of messages that go through the transporter module.
This middleware uses built-in Node zlib
library.
const { Middlewares } = require("moleculer");
const broker = new ServiceBroker({
middlewares: [
Middlewares.Transmit.Compression("deflate")
]
});
Transit Logger
Transit logger middleware allows to easily track the messages that are exchanged between services.
const { Middlewares } = require("moleculer");
const broker = new ServiceBroker({
middlewares: [
Middlewares.Debugging.TransitLogger({
logPacketData: false,
folder: null,
colors: {
send: "magenta",
receive: "blue"
},
packetFilter: ["HEARTBEAT"]
})
]
});
Action Logger
Action Logger middleware tracks "how" service actions were executed.
const { Middlewares } = require("moleculer");
const broker = new ServiceBroker({
middlewares: [
Middlewares.Debugging.ActionLogger({
logParams: true,
logResponse: true,
folder: null,
colors: {
send: "magenta",
receive: "blue"
},
whitelist: ["**"]
})
]
});
Throttle
Throttling is a straightforward reduction of the trigger rate. It will cause the event listener to ignore some portion of the events while still firing the listeners at a constant (but reduced) rate. Same functionality as lodash's _.throttle
.
module.exports = {
name: "my",
events: {
"config.changed": {
throttle: 3000,
handler(ctx) { }
}
}
};
Debounce
Unlike throttling, debouncing is a technique of keeping the trigger rate at exactly 0 until a period of calm, and then triggering the listener exactly once. Same functionality as lodash's _.debounce
.
module.exports = {
name: "my",
events: {
"config.changed": {
debounce: 5000,
handler(ctx) { }
}
}
};
Load middlewares by names
To load built-in middlewares, use its names in middleware
broker option.
const { Middlewares } = require("moleculer");
Middlewares.MyCustom = {
created(broker) {
broker.logger.info("My custom middleware is created!");
}
};
const broker1 = new ServiceBroker({
logger: true,
middlewares: [
"MyCustom"
]
});
Global error handler
There is a new global error handler in ServiceBroker. It can be defined in broker options as errorHandler(err, info)
.
It catches unhandled errors in action & event handlers.
Catch, handle & log the error
const broker = new ServiceBroker({
errorHandler(err, info) {
this.logger.warn("Error handled:", err);
}
});
Catch & throw further the error
const broker = new ServiceBroker({
errorHandler(err, info) {
this.logger.warn("Error handled:", err);
throw err;
}
});
The info
object contains the broker and the service instances, the current context and the action or the event definition.
Timeout setting in action definitions
Timeout can be set in action definition, as well. It overwrites the global broker requestTimeout
option, but not the timeout
in calling options.
module.exports = {
nodeID: "node-1",
requestTimeout: 3000
};
module.exports = {
name: "greeter",
actions: {
normal: {
handler(ctx) {
return "Normal";
}
},
slow: {
timeout: 5000,
handler(ctx) {
return "Slow";
}
}
},
await broker.call("greeter.normal");
await broker.call("greeter.slow");
await broker.call("greeter.slow", null, { timeout: 1000 });
Buffer
handling improved in serializers
In earlier version, if request, response or event data was a Buffer
, the schema-based serializers convert it to JSON string which was not very efficient. In this version all schema-based serializers (ProtoBuf, Avro, Thrift) can detect the type of data & convert it based on the best option and send always as binary data.
Runner support asynchronous configurations
The Moleculer Runner supports asynchronous configuration files. In this case you need to return a Function
in the moleculer.config.js
file which returns a Promise
or use async/await
.
Example to loada remote configuration from the internet
const fetch = require("node-fetch");
module.exports = async function() {
const res = await fetch("https://pastebin.com/raw/SLZRqfHX");
return await res.json();
};
Better service event handler testing
Service class has a new emitLocalEventHandler
method in order to call a service event handler directly. It can be useful in Unit Tests because you don't need to emit an event with broker.emit
.
Example
module.exports = {
name: "posts",
events: {
async "user.created"(ctx) {
this.myMethod(ctx.params);
}
}
};
describe("Test events", () => {
const broker = new ServiceBroker({ logger: false });
const svc = broker.createService(PostService);
beforeAll(() => broker.start());
afterAll(() => broker.stop());
describe("Test 'user.created' event", () => {
beforeAll(() => svc.myMethod = jest.fn());
afterAll(() => svc.myMethod.mockRestore());
it("should call the event handler", async () => {
await svc.emitLocalEventHandler("branch.closed", { a: 5 });
expect(svc.myMethod).toBeCalledTimes(1);
expect(svc.myMethod).toBeCalledWith({ a: 5 });
});
});
});
Stream objectMode
support
Thanks for @artur-krueger, the request & response streams support objectMode
, as well. You can also send Javascript objects via streams.
Example
module.exports = {
name: "posts",
actions: {
list(ctx) {
const pass = new Stream.Readable({
objectMode: true,
read() {}
});
pass.push({ id: 1, title: "First post" });
pass.push({ id: 2, title: "Second post" });
pass.push({ id: 3, title: "Third post" });
return pass;
}
}
};
Other notable changes
- Kafka transporter upgrade to support kafka-node@5.
- rename
ctx.metrics
to ctx.tracing
.
broker.hotReloadService
method has been removed.
- new
hasEventListener
& getEventListeners
broker method.
- new
uidGenerator
broker options to overwrite the default UUID generator code.
- new
ctx.locals
property to store local variables in hooks or actions.
- Context tracking watches local event handlers, as well.
- new
ctx.mcall
method to make multiple calls.
- new
withEvents & grouping
parameters for $node.services
action.
$node
internal service parameters has default value & conversion.
- the Promise chaining improved in event emitting methods.
- the heartbeat logic can be disabled by
heartbeatInterval: 0
broker option.
- in Service instances the original schema (before applying mixins) is available via
this.originalSchema
.
AMQP 1.0 transporter
Thanks for @vladir95, AMQP 1.0 transporter is available.
Please note, it is an experimental transporter. Do not use it in production yet!
module.exports = {
transporter: "amqp10://activemq-server:5672"
};
To use this transporter install the rhea-promise
module with npm install rhea-promise --save
command.
Transporter options
Options can be passed to rhea.connection.open()
method, the topics, the queues, and the messages themselves.
Connect to 'amqp10://guest:guest@localhost:5672'
module.exports = {
transporter: "AMQP10"
};
Connect to a remote server
module.exports = {
transporter: "amqp10://activemq-server:5672"
};
Connect to a remote server with options & credentials
module.exports = {
transporter: {
url: "amqp10://user:pass@activemq-server:5672",
eventTimeToLive: 5000,
heartbeatTimeToLive: 5000,
connectionOptions: {
ca: "",
servername: "",
key: "",
cert: ""
},
queueOptions: {},
topicOptions: {},
messageOptions: {},
topicPrefix: "topic://",
prefetch: 1
}
};
Redis Cluster feature in Redis transporter
Thanks for AAfraitane, use can connect to a Redis Cluster with the Redis transporter.
Connect to Redis cluster
module.exports = {
transporter: {
type: "Redis",
options: {
cluster: {
nodes: [
{ host: "localhost", port: 6379 },
{ host: "localhost", port: 6378 }
]
}
}
}
};
Changes
- fix expire time updating issue in MemoryCacher. #630
- fix action hook calling issue in mixins. #631
- fix NATS transporter "Invalid Subject" issue. #620
- update dependencies.
Changes
- fix retry issue in case of remote calls & disabled preferLocal options. #598
- update dependencies.
New
Customizable serializer for Redis cacher by @shawnmcknight #589
The default serializer is the JSON Serializer but you can change it in Redis cacher options.
You can use any built-in Moleculer serializer or use a custom one.
Example to set the built-in MessagePack serializer:
const broker = new ServiceBroker({
nodeID: "node-123",
cacher: {
type: "Redis",
options: {
ttl: 30,
serializer: "MsgPack",
redis: {
host: "my-redis"
}
}
}
});
Cluster mode of Redis cacher by Gadi-Manor #539
Redis cacher supports cluster mode.
Example
const broker = new ServiceBroker({
cacher: {
type: "Redis",
options: {
ttl: 30,
cluster: {
nodes: [
{ port: 6380, host: "127.0.0.1" },
{ port: 6381, host: "127.0.0.1" },
{ port: 6382, host: "127.0.0.1" }
],
options: { }
}
}
}
});
Changes
- update dependencies.
- update Typescript definitions by @shawnmcknight.
- fix Protocol Buffer definitions by @fugufish.
0.13.9 (2019-04-18)
New
Cache locking feature by @tiaod #490
Example to enable cacher locking:
cacher: {
ttl: 60,
lock: true,
}
cacher: {
ttl: 60,
lock: {
ttl: 15,
staleTime: 10,
}
}
cacher: {
ttl: 60,
lock: {
enable: false,
ttl: 15,
staleTime: 10,
}
}
Example for Redis cacher with redlock
library:
const broker = new ServiceBroker({
cacher: {
type: "Redis",
options: {
prefix: "MOL",
ttl: 30,
monitor: false,
redis: {
host: "redis-server",
port: 6379,
password: "1234",
db: 0
},
lock: {
ttl: 15,
staleTime: 10,
},
redlock: {
clients: [client1, client2, client3],
driftFactor: 0.01,
retryCount: 10,
retryDelay: 200,
retryJitter: 200
}
}
}
});
Changes
- fix event wildcard handling in case of NATS transporter and disabled balancer #517
- update typescript d.ts file. #501 #521
- fix context calling options cloning.
- service modification support for ES6 classes #514
- fix
null
, 0
& false
return value issue in case of ProtoBuf serializer #511
destroyService(name: string | ServiceSearchObj);
getLocalService(name: string | ServiceSearchObj): Service;
0.13.8 (2019-03-21)
Changes
- fix missing field in ProtoBuf & Thrift serializers #496
0.13.7 (2019-02-21)
Changes
- fix ioredis dependency in typescript definition file #476
0.13.6 (2019-02-15)
New
Secure service settings
To protect your tokens & API keys, define a $secureSettings: []
property in service settings and set the protected property keys.
The protected settings won't be published to other nodes and it won't appear in Service Registry. They are only available under this.settings
inside the service functions.
Example
module.exports = {
name: "mailer",
settings: {
$secureSettings: ["transport.auth.user", "transport.auth.pass"],
from: "sender@moleculer.services",
transport: {
service: 'gmail',
auth: {
user: 'gmail.user@gmail.com',
pass: 'yourpass'
}
}
}
};
Changes
- fix
cacher.clean
issue #435
- add
disableVersionCheck
option for broker transit options. It can disable protocol version checking logic in Transit. Default: false
- improve Typescript definition file. #442 #454
- waitForServices accept versioned service names (e.g.:
v2.posts
).
- update dependencies (plus using semver ranges in dependencies)
0.13.5 (2018-12-09)
New
Conditional caching
It's a common issue that you enable caching for an action but sometimes you don't want to get data from cache. To solve it you may set ctx.meta.$cache = false
before calling and the cacher won't send cached responses.
Example
broker.call("greeter.hello", { name: "Moleculer" }, { meta: { $cache: false }}))
Other solution is that you use a custom function which enables or disables caching for every request. The function gets the ctx
Context instance so it has access any params or meta data.
Example
module.exports = {
name: "greeter",
actions: {
hello: {
cache: {
enabled: ctx => ctx.params.noCache !== true,
keys: ["name"]
},
handler(ctx) {
this.logger.debug(chalk.yellow("Execute handler"));
return `Hello ${ctx.params.name}`;
}
}
}
};
broker.call("greeter.hello", { name: "Moleculer", noCache: true }))
LRU memory cacher
An LRU memory cacher has been added to the core modules. It uses the familiar lru-cache library.
Example
let broker = new ServiceBroker({ cacher: "MemoryLRU" });
let broker = new ServiceBroker({
logLevel: "debug",
cacher: {
type: "MemoryLRU",
options: {
max: 100,
ttl: 3
}
}
});
Changes
- throw the error further in
loadService
method so that Runner prints the correct error stack.
- new
packetLogFilter
transit option to filter packets in debug logs (e.g. HEARTBEAT packets) by @faeron
- the Redis cacher
clean
& del
methods handle array parameter by @dkuida
- the Memory cacher
clean
& del
methods handle array parameter by @icebob
- fix to handle
version: 0
as a valid version number by @ngraef
0.13.4 (2018-11-04)
Changes
- catch errors in
getCpuUsage()
method.
- support multiple urls in AMQP transporter by @urossmolnik
- fix AMQP connection recovery by @urossmolnik
- add
transit.disableReconnect
option to disable reconnecting logic at broker starting by @Gadi-Manor
- catch
os.userInfo
errors in health action by @katsanva
- allow specifying 0 as
retries
#404 by @urossmolnik
- fix
GraceFulTimeoutError
bug #400
- fix event return handling to avoid localEvent error handling issue in middleware #403
- update fastest-validator to the 0.6.12 version
- update all dependencies
0.13.3 (2018-09-27)
Changes
- update dependencies
- fix MQTTS connection string protocol from
mqtt+ssl://
to mqtts://
by @AndreMaz
- Moleculer Runner supports typescript configuration file
moleculer.config.ts
- fix to call service start after hot-reloading.
- fix Bluebird warning in service loading #381 by @faeron
- fix
waitForServices
definition in index.d.ts
#358
- fix
cpuUsage
issue #379 by @faeron
0.13.2 (2018-08-16)
Changes
- update dependencies
- add Notepack (other MsgPack) serializer
skipProcessEventRegistration
broker option to disable process.on
shutdown event handlers which stop broker.
- make unique service dependencies
- add
socketOptions
to AMQP transporter options. #330
- fix unhandled promise in AMQP transporter
connect
method.
- add
autoDeleteQueues
option to AMQP transporter. #341
- ES6 support has improved. #348
- add
qos
transporter option to MQTT transporter. Default: 0
- add
topicSeparator
transporter option to MQTT transporter. Default: .
- fix MQTT transporter disconnect logic (waiting for in-flight messages)
- add support for non-defined defaultOptions variables #350
- update ioredis to v4
0.13.1 (2018-07-13)
Changes
- improve
index.d.ts
- support Duplex streams #325
- update dependencies
0.13.0 (2018-07-08)
Migration guide from v0.12.x to v0.13.x is here.
Breaking changes
Streaming support
Built-in streaming support has just been implemented. Node.js streams can be transferred as request params
or as response. You can use it to transfer uploaded file from a gateway or encode/decode or compress/decompress streams.
Why is it a breaking change?
Because the protocol has been extended with a new field and it caused a breaking change in schema-based serializators (ProtoBuf, Avro). Therefore, if you use ProtoBuf or Avro, you won't able to communicate with the previous (<=0.12) brokers. Using JSON or MsgPack serializer, there is nothing extra to do.
Examples
Send a file to a service as a stream
const stream = fs.createReadStream(fileName);
broker.call("storage.save", stream, { meta: { filename: "avatar-123.jpg" }});
Please note, the params
should be a stream, you cannot add any more variables to the request. Use the meta
property to transfer additional data.
Receiving a stream in a service
module.exports = {
name: "storage",
actions: {
save(ctx) {
const s = fs.createWriteStream(`/tmp/${ctx.meta.filename}`);
ctx.params.pipe(s);
}
}
};
Return a stream as response in a service
module.exports = {
name: "storage",
actions: {
get: {
params: {
filename: "string"
},
handler(ctx) {
return fs.createReadStream(`/tmp/${ctx.params.filename}`);
}
}
}
};
Process received stream on the caller side
const filename = "avatar-123.jpg";
broker.call("storage.get", { filename })
.then(stream => {
const s = fs.createWriteStream(`./${filename}`);
stream.pipe(s);
s.on("close", () => broker.logger.info("File has been received"));
})
AES encode/decode example service
const crypto = require("crypto");
const password = "moleculer";
module.exports = {
name: "aes",
actions: {
encrypt(ctx) {
const encrypt = crypto.createCipher("aes-256-ctr", password);
return ctx.params.pipe(encrypt);
},
decrypt(ctx) {
const decrypt = crypto.createDecipher("aes-256-ctr", password);
return ctx.params.pipe(decrypt);
}
}
};
Better Service & Broker lifecycle handling
The ServiceBroker & Service lifecycle handler logic has already been improved. The reason for amendment was a problem occuring during loading more services locally; they could call each others' actions before started
execution. It generally causes errors if database connecting process started in the started
event handler.
This problem has been fixed with a probable side effect: causing errors (mostly in unit tests) if you call the local services without broker.start()
.
It works in the previous version
const { ServiceBroker } = require("moleculer");
const broker = new ServiceBroker();
broker.loadService("./math.service.js");
broker.call("math.add", { a: 5, b: 3 }).then(res => console.log);
From v0.13 it throws a ServiceNotFoundError
exception, because the service is only loaded but not started yet.
Correct logic
const { ServiceBroker } = require("moleculer");
const broker = new ServiceBroker();
broker.loadService("./math.service.js");
broker.start().then(() => {
broker.call("math.add", { a: 5, b: 3 }).then(res => console.log);
});
or with await
broker.loadService("./math.service.js");
await broker.start();
const res = await broker.call("math.add", { a: 5, b: 3 });
console.log(res);
Similar issue has been fixed at broker shutdown. Previously when you stopped a broker, which while started to stop local services, it still acccepted incoming requests from remote nodes.
The shutdown logic has also been changed. When you call broker.stop
, at first broker publishes an empty service list to remote nodes, so they route the requests to other instances.
Default console logger
No longer need to set logger: console
in broker options, because ServiceBroker uses console
as default logger.
const broker = new ServiceBroker();
Disable loggging (e.g. in tests)
const broker = new ServiceBroker({ logger: false });
Changes in internal event sending logic
The $
prefixed internal events will be transferred if they are called by emit
or broadcast
. If you don't want to transfer them, use the broadcastLocal
method.
From v0.13, the $
prefixed events mean built-in core events instead of internal "only-local" events.
Improved Circuit Breaker
Threshold-based circuit-breaker solution has been implemented. It uses a time window to check the failed request rate. Once the threshold
value is reached, it trips the circuit breaker.
const broker = new ServiceBroker({
nodeID: "node-1",
circuitBreaker: {
enabled: true,
threshold: 0.5,
minRequestCount: 20,
windowTime: 60,
halfOpenTime: 5 * 1000,
check: err => err && err.code >= 500
}
});
Instead of failureOnTimeout
and failureOnReject
properties, there is a new check()
function property in the options. It is used by circuit breaker in order to detect which error is considered as a failed request.
You can override these global options in action definition, as well.
module.export = {
name: "users",
actions: {
create: {
circuitBreaker: {
threshold: 0.3,
windowTime: 30
},
handler(ctx) {}
}
}
};
CB metrics events removed
The metrics circuit breaker events have been removed due to internal event logic changes.
Use the $circuit-breaker.*
events instead of metrics.circuit-breaker.*
events.
Improved Retry feature (with exponential backoff)
The old retry feature has been improved. Now it uses exponential backoff for retries. The old solution retries the request immediately in failures.
The retry options have also been changed in the broker options. Every option is under the retryPolicy
property.
const broker = new ServiceBroker({
nodeID: "node-1",
retryPolicy: {
enabled: true,
retries: 5,
delay: 100,
maxDelay: 2000,
factor: 2,
check: err => err && !!err.retryable
}
});
Overwrite the retries
value in calling option
The retryCount
calling options has been renamed to retries
.
broker.call("posts.find", {}, { retries: 3 });
There is a new check()
function property in the options. It is used by the Retry middleware in order to detect which error is a failed request and needs a retry. The default function checks the retryable
property of errors.
These global options can be overridden in action definition, as well.
module.export = {
name: "users",
actions: {
find: {
retryPolicy: {
retries: 3,
delay: 500
},
handler(ctx) {}
},
create: {
retryPolicy: {
enabled: false
},
handler(ctx) {}
}
}
};
Changes in context tracker
There are also some changes in context tracker configuration.
const broker = new ServiceBroker({
nodeID: "node-1",
tracking: {
enabled: true,
shutdownTimeout: 5000
}
});
Disable tracking in calling option at calling
broker.call("posts.find", {}, { tracking: false });
The shutdown timeout can be overwritten by $shutdownTimeout
property in service settings.
Removed internal statistics module
The internal statistics module ($node.stats
) is removed. Yet you need it, download from here, load as a service and call the stat.snapshot
to receive the collected statistics.
Renamed errors
Some errors have been renamed in order to follow name conventions.
ServiceNotAvailable
-> ServiceNotAvailableError
RequestRejected
-> RequestRejectedError
QueueIsFull
-> QueueIsFullError
InvalidPacketData
-> InvalidPacketDataError
Context nodeID changes
The ctx.callerNodeID
has been removed. The ctx.nodeID
contains the target or caller nodeID. If you need the current nodeID, use ctx.broker.nodeID
.
Enhanced ping method
It returns Promise
with results of ping responses. Moreover, the method is renamed to broker.ping
.
Ping a node with 1sec timeout
broker.ping("node-123", 1000).then(res => broker.logger.info(res));
Output:
{
nodeID: 'node-123',
elapsedTime: 16,
timeDiff: -3
}
Ping all known nodes
broker.ping().then(res => broker.logger.info(res));
Output:
{
"node-100": {
nodeID: 'node-100',
elapsedTime: 10,
timeDiff: -2
} ,
"node-101": {
nodeID: 'node-101',
elapsedTime: 18,
timeDiff: 32
},
"node-102": {
nodeID: 'node-102',
elapsedTime: 250,
timeDiff: 850
}
}
Amended cacher key generation logic
When you didn't define keys
at caching, the cacher hashed the whole ctx.params
and used as a key to store the content. This method was too slow and difficult to implement to other platforms. Therefore we have changed it. The new method is simpler, the key generator concatenates all property names & values from ctx.params
.
However, the problem with this new logic is that the key can be very long. It can cause performance issues when you use too long keys to get or save cache entries. To avoid it, there is a maxParamsLength
option to limit the key length. If it is longer than the configured limit, the cacher calculates a hash (SHA256) from the full key and add it to the end of key.
The minimum of maxParamsLength
is 44
(SHA 256 hash length in Base64).
To disable this feature, set it to 0
or null
.
Generate a full key from the whole params
cacher.getCacheKey("posts.find", { id: 2, title: "New post", content: "It can be very very looooooooooooooooooong content. So this key will also be too long" });
Generate a limited key with hash
const broker = new ServiceBroker({
logger: console,
cacher: {
type: "Memory",
options: {
maxParamsLength: 60
}
}
});
cacher.getCacheKey("posts.find", { id: 2, title: "New post", content: "It can be very very looooooooooooooooooong content. So this key will also be too long" });
Of course, you can use your custom solution with keygen
cacher options like earlier.
Cacher matcher changed
The cacher matcher code also changed in cacher.clean
method. The previous (wrong) matcher couldn't handle dots (.) properly in patterns. E.g the posts.*
pattern cleaned the posts.find.something
keys, too. Now it has been fixed, but it means that you should use posts.**
pattern because the params
and meta
values can contain dots.
Changed Moleculer errors signature
The following Moleculer Error classes constructor arguments is changed to constructor(data)
:
ServiceNotFoundError
ServiceNotAvailableError
RequestTimeoutError
RequestSkippedError
RequestRejectedError
QueueIsFullError
MaxCallLevelError
ProtocolVersionMismatchError
InvalidPacketDataError
Before
throw new ServiceNotFoundError("posts.find", "node-123");
Now
throw new ServiceNotFoundError({ action: "posts.find", nodeID: "node-123" });
New
New state-of-the-art middlewares
We have been improved the current middleware handler and enriched it with a lot of useful features. As a result, you can hack more internal flow logic with custom middlewares (e.g. event sending, service creating, service starting...etc)
The new one is an Object
with hooks instead of a simple Function
. However, the new solution is backward compatible, so you don't need to migrate your old middlewares.
A new middleware with all available hooks
const MyCustomMiddleware = {
localAction(next, action) {
},
remoteAction(next, action) {
},
localEvent(next, event) {
}
createService(next) {
}
destroyService(next) {
}
call(next) {
}
mcall(next) {
}
emit(next) {
},
broadcast(next) {
},
broadcastLocal(next) {
},
serviceCreated(service) {
},
serviceStarting(service) {
},
serviceStarted(service) {
},
serviceStopping(service) {
},
serviceStopped(service) {
},
created(broker) {
},
starting(broker) {
},
started(broker) {
},
stopping(broker) {
},
stopped(broker) {
}
}
Use it in broker options
const broker = new ServiceBroker({
middlewares: [
MyCustomMiddleware
]
});
Wrapping handlers
Some hooks are wrappers. It means you need to wrap the original handler and return a new Function.
Wrap hooks where the first parameter is next
.
Wrap local action handler
const MyDoSomethingMiddleware = {
localAction(next, action) {
if (action.myFunc) {
return function(ctx) {
doSomethingBeforeHandler(ctx);
return handler(ctx)
.then(res => {
doSomethingAfterHandler(res);
return res;
})
.catch(err => {
doSomethingAfterHandlerIfFailed(err);
throw err;
});
}
}
return handler;
}
};
Decorate broker (to extend functions)
Other hooks are to help you to decorate new features in ServiceBroker & services.
Decorate broker with a new allCall
method
const broker = new ServiceBroker({
middlewares: [
{
created(broker) {
broker.allCall = function(action, params, opts = {}) {
const nodeIDs = this.registry.getNodeList({ onlyAvailable: true })
.map(node => node.id);
return Promise.all(nodeIDs.map(nodeID => broker.call(action, params, Object.assign({ nodeID }, opts))));
}
}
}
]
});
await broker.start();
const res = await broker.allCall("$node.health");
Decorate services with a new method
const broker = new ServiceBroker({
middlewares: [
{
serviceCreated(service) {
service.customFunc = function() {
}.bind(service);
}
}
]
});
In service schema:
module.export = {
name: "users",
actions: {
find(ctx) {
this.customFunc();
}
}
};
The mixins can do similar things, so we prefer mixins to this decorating.
Many internal features are exposed to internal middlewares
Due to the new advanced middlewares, we could bring out many integrated features to middlewares. They are available under require("moleculer").Middlewares
property, but they load automatically.
New internal middlewares:
- Action hook handling
- Validator
- Bulkhead
- Cacher
- Context tracker
- Circuit Breaker
- Timeout
- Retry
- Fallback
- Error handling
- Metrics
Turn off the automatic loading with internalMiddlewares: false
broker option. In this case you have to add them to middlewares: []
broker option.
The broker.use
method is deprecated. Use middlewares: []
in the broker options instead.
Action hooks
Define action hooks to wrap certain actions coming from mixins.
There are before
, after
and error
hooks. Assign it to a specified action or all actions (*
) in service.
The hook can be a Function
or a String
. The latter must be a local service method name.
Before hooks
const DbService = require("moleculer-db");
module.exports = {
name: "posts",
mixins: [DbService]
hooks: {
before: {
"*": "resolveLoggedUser",
remove: [
function isAuthenticated(ctx) {
if (!ctx.user)
throw new Error("Forbidden");
},
function isOwner(ctx) {
if (!this.checkOwner(ctx.params.id, ctx.user.id))
throw new Error("Only owner can remove it.");
}
]
}
},
methods: {
async resolveLoggedUser(ctx) {
if (ctx.meta.user)
ctx.user = await ctx.call("users.get", { id: ctx.meta.user.id });
}
}
}
After & Error hooks
const DbService = require("moleculer-db");
module.exports = {
name: "users",
mixins: [DbService]
hooks: {
after: {
"*": function(ctx, res) {
delete res.password;
return res;
},
get: [
async function (ctx, res) {
res.friends = await ctx.call("friends.count", { query: { follower: res._id }});
return res;
},
async function (ctx, res) {
if (res.referrer)
res.referrer = await ctx.call("users.get", { id: res._id });
return res;
}
]
},
error: {
"*": function(ctx, err) {
this.logger.error(`Error occurred when '${ctx.action.name}' action was called`, err);
throw err;
}
}
}
};
The recommended use case is to create mixins filling up the service with methods and in hooks
set method names.
Mixin
module.exports = {
methods: {
checkIsAuthenticated(ctx) {
if (!ctx.meta.user)
throw new Error("Unauthenticated");
},
checkUserRole(ctx) {
if (ctx.action.role && ctx.meta.user.role != ctx.action.role)
throw new Error("Forbidden");
},
checkOwner(ctx) {
}
}
}
Use mixin methods in hooks
const MyAuthMixin = require("./my.mixin");
module.exports = {
name: "posts",
mixins: [MyAuthMixin]
hooks: {
before: {
"*": ["checkIsAuthenticated"],
create: ["checkUserRole"],
update: ["checkUserRole", "checkOwner"],
remove: ["checkUserRole", "checkOwner"]
}
},
actions: {
find: {
handler(ctx) {}
},
create: {
role: "admin",
handler(ctx) {}
},
update: {
role: "user",
handler(ctx) {}
}
}
};
New Bulkhead fault-tolerance feature
Bulkhead feature is an internal middleware in Moleculer. Use it to control the concurrent request handling of actions.
Global settings in the broker options. Applied to all registered local actions.
const broker = new ServiceBroker({
bulkhead: {
enabled: true,
concurrency: 3,
maxQueueSize: 10,
}
});
The concurrency
value restricts the concurrent request executions. If maxQueueSize
is bigger than 0, broker queues additional requests, if all slots are taken. If queue size reaches maxQueueSize
limit or it is 0, broker will throw QueueIsFull
error for every addition request.
These global options can be overriden in action definition, as well.
module.export = {
name: "users",
actions: {
find: {
bulkhead: {
enabled: false
},
handler(ctx) {}
},
create: {
bulkhead: {
concurrency: 10
},
handler(ctx) {}
}
}
};
Fallback in action definition
Due to the exposed Fallback middleware, fallback response can be set in the action definition, too.
Please note, this fallback response will only be used if the error occurs within action handler. If the request is called from a remote node and the request is timed out on the remote node, the fallback response is not be used. In this case, use the fallbackResponse
in calling option.
Fallback as function
module.exports = {
name: "recommends",
actions: {
add: {
fallback: (ctx, err) => "Some cached result",
handler(ctx) {
}
}
}
};
Fallback as method name string
module.exports = {
name: "recommends",
actions: {
add: {
fallback: "getCachedResult",
handler(ctx) {
}
}
},
methods: {
getCachedResult(ctx, err) {
return "Some cached result";
}
}
};
Action visibility
The action has a new visibility
property to control the visibility & callability of service actions.
Available values:
published
or null
: public action. It can be called locally, remotely and can be published via API Gateway
public
: public action, can be called locally & remotely but not published via API GW
protected
: can be called only locally (from local services)
private
: can be called only internally (via this.actions.xy()
within service)
module.exports = {
name: "posts",
actions: {
find(ctx) {},
clean: {
visibility: "private",
handler(ctx) {}
}
},
methods: {
cleanEntities() {
return this.actions.clean();
}
}
}
The default value is null
(means published
) due to backward compatibility.
New Thrift serializer
There is a new built-in Thrift serializer.
const broker = new ServiceBroker({
serializer: "Thrift"
});
To use this serializer install the thrift
module with npm install thrift --save
command.
Enhanced log level configuration
A new module-based log level configuration was added. The log level can be set for every Moleculer module. Use of wildcard is allowed.
const broker = new ServiceBroker({
logger: console,
logLevel: {
"MY.**": false,
"TRANS": "warn",
"*.GREETER": "debug",
"**": "debug",
}
});
Please note, it works only with default console logger. In case of external loggers (Pino, Windows, Bunyan, ...etc), these log levels must be applied.
These settings are evaluated from top to bottom, so the **
level must be the last property.
Internal modules: BROKER
, TRANS
, TX
as transporter, CACHER
, REGISTRY
.
For services, the name comes from the service name. E.g. POSTS
.
A version is used as a prefix. E.g. V2.POSTS
The old global log level settings works, as well.
const broker = new ServiceBroker({
logger: console,
logLevel: "warn"
});
A new short
log formatter was also added. It is similar to the default, but doesn't print the date and nodeID
.
const broker = new ServiceBroker({
logFormatter: "short"
});
Output
[19:42:49.055Z] INFO MATH: Service started.
Load services also with glob patterns
Moleculer Runner loads services also from glob patterns. It is useful when loading all services except certain ones.
$ moleculer-runner services !services/others/**/*.service.js services/others/mandatory/main.service.js
Explanations:
services
- legacy mode. Load all services from the services
folder with **/*.service.js
file mask
!services/others/**/*.service.js
- skip all services in the services/others
folder and sub-folders.
services/others/mandatory/main.service.js
- load the exact service
Glob patterns work in the SERVICES
enviroment variables, as well.
MemoryCacher cloning
There is a new clone
property in the MemoryCacher
options. If it's true
, the cacher clones the cached data before returning.
If received value is modified, enable this option. Note: it cuts down the performance.
Enable cloning
const broker = new ServiceBroker({
cacher: {
type: "Memory",
options: {
clone: true
}
}
});
This feature uses the lodash _.cloneDeep
method. To change cloning method set a Function
to the clone
option instead of a Boolean
.
Custom clone function with JSON parse & stringify:
const broker = new ServiceBroker({
cacher: {
type: "Memory",
options: {
clone: data => JSON.parse(JSON.stringify(data))
}
}
});
Changes
- service instances has a new property named
fullName
containing service version & service name.
- the
Action
has a rawName
property containing action name without service name.
- new
$node.options
internal action to get the current broker options.
Context.create
& new Context
signature changed.
- removed Context metrics methods. All metrics feature moved to the
Metrics
middleware.
ctx.timeout
moved to ctx.options.timeout
.
- removed
ctx.callerNodeID
.
ctx.endpoint
is a new property pointing to target Endpoint
. For example you can check with ctx.endpoint.local
flag whether the request is remote or local.
- lazily generated
ctx.id
, i.e. only generated at access. ctx.generateID()
was removed.
- renamed service lifecycle methods in service instances (not in service schema!)
- extended
transit.stat.packets
with byte-based statistics.
utils.deprecate
method was created for deprecation.
- Transporter supports
mqtt+ssl://
, rediss://
& amqps://
protocols in connection URIs.
- fixed circular objects handling in service schema (e.g.: Joi validator problem)
Deprecations
broker.use()
has been deprecated. Use middlewares: [...]
in broker options instead.
0.12.8 (2018-06-14)
Changes
- fix action disabling with mixins #298
- Fix metrics options and add findNextActionEndpoint to index.d.ts
- update dependencies
- set
maxReconnectAttempts
to -1
in NATS client to try reconnecting continuously
0.12.6 (2018-06-07)
Changes
- update dependencies
- The
breakLength
is changed to Infinity
(single-line printing) for better log processing when logger prints objects and arrays.
adds ability to customise console object/array printing #285
const util = require("util");
const broker = new ServiceBroker({
logger: true,
logObjectPrinter: o => util.inspect(o, { depth: 4, colors: false, breakLength: 50 })
});
0.12.5 (2018-05-21)
Changes
- fix AMQP logs. #270
- fix transferred retryable error handling
broker.createService
supports ES6 classes
- fix broken promise chain if trackContext is enabled
0.12.4 (2018-05-10)
New
Graceful shutdown
Thanks for @rmccallum81, ServiceBroker supports graceful shutdown. You can enable it with trackContext
broker option. If you enable it, all services wait for all running contexts before shutdowning. You can also define a timeout value with gracefulStopTimeout
broker option.
const broker = new ServiceBroker({
trackContext: true,
gracefulStopTimeout: 5 * 1000
});
This timeout can be overwrite in service settings with $gracefulStopTimeout
property.
Changes
- fix service registry update after reconnecting. #262
- update index.d.ts
- update dependencies
- fix distributed timeout handling
0.12.3 (2018-04-19)
Changes
- fix empty service mixins issue (
mixins: []
).
- update index.d.ts
0.12.2 (2018-04-11)
New
Latency strategy
This strategy selects a node which has the lowest latency, measured by periodic PING
. Notice that the strategy only ping one of nodes from a single host. Due to the node list can be very long, it gets samples and selects the host with the lowest latency from only samples instead of the whole node list.
Usage
let broker = new ServiceBroker({
registry: {
strategy: "Latency"
}
});
Strategy options
Name |
Type |
Default |
Description |
sampleCount |
Number |
5 |
the number of samples. If you have a lot of hosts/nodes, it's recommended to increase the value. |
lowLatency |
Number |
10 |
the low latency (ms). The node which has lower latency than this value is selected immediately. |
collectCount |
Number |
5 |
the number of measured latency per host to keep in order to calculate the average latency. |
pingInterval |
Number |
10 |
ping interval (s). If you have a lot of host/nodes, it's recommended to increase the value. |
Usage with custom options
let broker = new ServiceBroker({
registry: {
strategy: "Latency",
strategyOptions: {
sampleCount: 15,
lowLatency: 20,
collectCount: 10,
pingInterval: 15
}
}
});
Filemask for Moleculer Runner
There is a new Moleculer Runner option --mask
to define filemask when load all services from folders.
Example
$ moleculer-runner.js -r --mask **/user*.service.js examples
Example to load Typescript services
$ node -r ts-node/register node_modules/moleculer/bin/moleculer-runner --hot --repl --mask **/*.service.ts services
Changes
- fix
d.ts
issues
- fix event
group
handling in mixins (#217)
- move
mergeSchemas
from utils
to Service
static method. It can be overwritten in a custom ServiceFactory
- improve
d.ts
- fix
prefix
option in Redis Cacher (223)
- remove
nanomatch
dependency, use own implementation
- fix ContextFactory issue (235)
- expose utility functions as
require("moleculer").Utils
- overwritable
mergeSchemas
static method in Service
class.
- Moleculer Runner precedence order is changed. The
SERVICES
& SERVICEDIR
env vars overwrites the paths in CLI arguments.
0.12.0 (2018-03-03)
This version contains the most changes in the history of Moleculer! More than 200 commits with 17k additions and a lot of new features.
Breaking changes
Github organization is renamed
The Github organization name (Ice Services) has been renamed to MoleculerJS. Please update your bookmarks.
Mixin merging logic is changed
To support #188, mixin merging logic is changed at actions
. Now it uses defaultsDeep
for merging. It means you can extend the actions definition of mixins, no need to redeclare the handler
.
Add extra action properties but handler
is untouched
module.exports = {
actions: {
create(ctx) {
}
}
};
module.exports = {
mixins: [MixinService]
actions: {
create: {
params: {
name: "string"
}
}
}
};
Wrapper removed from transporter options
If you are using transporter options, you will need to migrate them. The transporter specific wrapper has been removed from options (nats
, redis
, mqtt
, amqp
).
Before
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
nats: {
user: "admin",
pass: "1234"
}
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "Redis",
options: {
redis: {
port: 6379,
db: 0
}
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "MQTT",
options: {
mqtt: {
user: "admin",
pass: "1234"
}
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "AMQP",
options: {
amqp: {
prefetch: 1
}
}
}
});
After
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
user: "admin",
pass: "1234"
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "Redis",
options: {
port: 6379,
db: 0
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "MQTT",
options: {
user: "admin",
pass: "1234"
}
}
});
const broker = new ServiceBroker({
transporter: {
type: "AMQP",
options: {
prefetch: 1
}
}
});
Default nodeID
generator changed
When nodeID
didn't define in broker options, the broker generated it from hostname (os.hostname()
). It could cause problem for new users when they tried to start multiple instances on the same computer. Therefore, the broker generates nodeID
from hostname and process PID. The newly generated nodeID looks like server-6874
where server
is the hostname and 6874
is the PID.
Protocol changed
The transport protocol is changed. The new version is 3
. Check the changes.
It means, the >=0.12.x versions can't communicate with old <=0.11 versions.
Changes:
- the
RESPONSE
packet has a new field meta
.
- the
EVENT
packet has a new field broadcast
.
- the
port
field is removed from INFO
packet.
- the
INFO
packet has a new field hostname
.
New features
New ServiceBroker options
There are some new properties in ServiceBroker option: middlewares
, created
, started
, stopped
.
They can be useful when you use broker config file and start your project with Moleculer Runner.
module.exports = {
logger: true,
middlewares: [myMiddleware()],
created(broker) {
},
started(broker) {
return broker.Promise.resolve();
},
stopped(broker) {
return broker.Promise.resolve();
}
};
Broadcast events with group filter
The broker.broadcast
function has a third groups
argument similar to broker.emit
.
broker.broadcast("user.created", { user }, "mail");
broker.broadcast("user.created", { user }, ["user", "purchase"]);
CPU usage-based strategy
There is a new CpuUsageStrategy
strategy. It selects a node which has the lowest CPU usage.
Due to the node list can be very long, it gets samples and selects the node with the lowest CPU usage from only samples instead of the whole node list.
There are 2 options for the strategy:
sampleCount
: the number of samples. Default: 3
lowCpuUsage
: the low CPU usage percent. The node which has lower CPU usage than this value is selected immediately. Default: 10
Usage:
const broker = new ServiceBroker({
registry: {
strategy: "CpuUsage"
}
});
Usage with custom options
const broker = new ServiceBroker({
registry: {
strategy: "CpuUsage",
strategyOptions: {
sampleCount: 3,
lowCpuUsage: 10
}
}
});
Starting logic is changed
The broker & services starting logic has been changed.
Previous logic: the broker starts transporter connecting. When it's done, it starts all services (calls service started
handlers). It has a disadvantage because other nodes can send requests to these services, while they are still starting and not ready yet.
New logic: the broker starts transporter connecting but it doesn't publish the local service list to remote nodes. When it's done, it starts all services (calls service started
handlers). Once all services start successfully, broker publishes the local service list to remote nodes. Hence other nodes send requests only after all local service started properly.
Please note: you can make dead-locks when two services wait for each other. E.g.: users
service has dependencies: [posts]
and posts
service has dependencies: [users]
. To avoid it remove the concerned service from dependencies
and use waitForServices
method out of started
handler instead.
At requests, ctx.meta
is sent back to the caller service. You can use it to send extra meta information back to the caller.
E.g.: send response headers back to API gateway or set resolved logged in user to metadata.
Export & download a file with API gateway:
export(ctx) {
const rows = this.adapter.find({});
ctx.meta.headers = {
"Content-Type": "application/json; charset=utf-8",
"Content-Disposition": 'attachment; filename=\"book.json\"'
}
return rows;
}
Authenticate:
auth(ctx) {
let user = this.getUserByJWT(ctx.params.token);
if (ctx.meta.user) {
ctx.meta.user = user;
return true;
}
throw new Forbidden();
}
Better ES6 class support
If you like better ES6 classes than Moleculer service schema, you can write your services in ES6 classes.
There are two ways to do it:
Native ES6 classes with schema parsing
Define actions
and events
handlers as class methods. Call the parseServiceSchema
method in constructor with schema definition where the handlers pointed to these class methods.
const Service = require("moleculer").Service;
class GreeterService extends Service {
constructor(broker) {
super(broker);
this.parseServiceSchema({
name: "greeter",
version: "v2",
meta: {
scalable: true
},
dependencies: [
"auth",
"users"
],
settings: {
upperCase: true
},
actions: {
hello: this.hello,
welcome: {
cache: {
keys: ["name"]
},
params: {
name: "string"
},
handler: this.welcome
}
},
events: {
"user.created": this.userCreated
},
created: this.serviceCreated,
started: this.serviceStarted,
stopped: this.serviceStopped,
});
}
hello() {
return "Hello Moleculer";
}
welcome(ctx) {
return this.sayWelcome(ctx.params.name);
}
sayWelcome(name) {
this.logger.info("Say hello to", name);
return `Welcome, ${this.settings.upperCase ? name.toUpperCase() : name}`;
}
userCreated(user) {
this.broker.call("mail.send", { user });
}
serviceCreated() {
this.logger.info("ES6 Service created.");
}
serviceStarted() {
this.logger.info("ES6 Service started.");
}
serviceStopped() {
this.logger.info("ES6 Service stopped.");
}
}
module.exports = GreeterService;
Use decorators
Thanks for @ColonelBundy, you can use ES7/TS decorators as well: moleculer-decorators
Please note, you need to use Typescript or Babel to compile decorators.
Example service
const moleculer = require('moleculer');
const { Service, Action, Event, Method } = require('moleculer-decorators');
const web = require('moleculer-web');
const broker = new moleculer.ServiceBroker({
logger: console,
logLevel: "debug",
});
@Service({
mixins: [web],
settings: {
port: 3000,
routes: [
...
]
}
})
class ServiceName {
@Action()
Login(ctx) {
...
}
@Action({
cache: false,
params: {
a: "number",
b: "number"
}
})
Login2(ctx) {
...
}
@Event
'event.name'(payload, sender, eventName) {
...
}
@Method
authorize(ctx, route, req, res) {
...
}
hello() {
...
}
started() {
...
}
created() {
...
}
stopped() {
...
}
}
broker.createService(ServiceName);
broker.start();
Event group option
The broker groups the event listeners by group name. The group name is the name of the service where your event handler is declared. You can change it in the event definition.
module.export = {
name: "payment",
events: {
"order.created": {
group: "other",
handler(payload) {
}
}
}
}
New experimental TCP transporter with UDP discovery
There is a new built-in zero-config TCP transporter. It uses Gossip protocol to disseminate node info, service list and heartbeats. It has an integrated UDP discovery to detect new nodes on the network. It uses multicast discovery messages.
If the UDP is prohibited on your network, you can use urls
option. It is a list of remote endpoints (host/ip, port, nodeID). It can be a static list in your configuration or a file path which contains the list.
Please note, you don't need to list all remote nodes. It's enough at least one node which is online. For example, you can create a "serviceless" gossiper node, which does nothing, just shares remote nodes addresses by gossip messages. So all nodes need to know only the gossiper node address to be able to detect all other nodes.
Use TCP transporter with default options
const broker = new ServiceBroker({
transporter: "TCP"
});
Use TCP transporter with static node list
const broker = new ServiceBroker({
transporter: "tcp://172.17.0.1:6000/node-1,172.17.0.2:6000/node-2"
});
or
const broker = new ServiceBroker({
nodeID: "node-1",
transporter: {
type: "TCP",
options: {
udpDiscovery: false,
urls: [
"172.17.0.1:6000/node-1",
"172.17.0.2:6000/node-2",
"172.17.0.3:6000/node-3"
]
}
}
});
All TCP transporter options with default values
const broker = new ServiceBroker({
logger: true,
transporter: {
type: "TCP",
options: {
udpDiscovery: true,
udpReuseAddr: true,
udpPort: 4445,
udpBindAddress: null,
udpPeriod: 30,
udpMulticast: "239.0.0.0",
udpMulticastTTL: 1,
udpBroadcast: false,
port: null,
urls: null,
useHostname: true,
gossipPeriod: 2,
maxConnections: 32,
maxPacketSize: 1 * 1024 * 1024
}
}
});
New experimental transporter for Kafka
There is a new transporter for Kafka. It is a very simple implementation. It transfers Moleculer packets to consumers via pub/sub. There are not implemented offset, replay...etc features.
Please note, it is an experimental transporter. Do not use it in production yet!
To use it, install kafka-node
with npm install kafka-node --save
command.
Connect to Zookeeper
const broker = new ServiceBroker({
logger: true,
transporter: "kafka://192.168.51.29:2181"
});
Connect to Zookeeper with custom options
const broker = new ServiceBroker({
logger: true,
transporter: {
type: "kafka",
options: {
host: "192.168.51.29:2181",
client: {
zkOptions: undefined,
noAckBatchOptions: undefined,
sslOptions: undefined
},
producer: {},
customPartitioner: undefined,
consumer: {
},
publish: {
partition: 0,
attributes: 0
}
}
}
});
New experimental transporter for NATS Streaming
There is a new transporter for NATS Streaming. It is a very simple implementation. It transfers Moleculer packets to consumers via pub/sub. There are not implemented offset, replay...etc features.
Please note, it is an experimental transporter. Do not use it in production yet!
To use it, install node-nats-streaming
with npm install node-nats-streaming --save
command.
Connect to NATS Streaming server
const broker = new ServiceBroker({
logger: true,
transporter: "STAN"
});
const broker = new ServiceBroker({
logger: true,
transporter: "stan://192.168.0.120:4222"
});
const broker = new ServiceBroker({
logger: true,
transporter: {
type: "STAN",
options: {
url: "stan://127.0.0.1:4222",
clusterID: "my-cluster"
}
}
});
Define custom REPL commands in broker options
You can define your custom REPL commands in broker options to extend Moleculer REPL commands.
const broker = new ServiceBroker({
logger: true,
replCommands: [
{
command: "hello <name>",
description: "Call the greeter.hello service with name",
alias: "hi",
options: [
{ option: "-u, --uppercase", description: "Uppercase the name" }
],
types: {
string: ["name"],
boolean: ["u", "uppercase"]
},
allowUnknownOptions: true,
action(broker, args) {
const name = args.options.uppercase ? args.name.toUpperCase() : args.name;
return broker.call("greeter.hello", { name }).then(console.log);
}
}
]
});
broker.repl();
Changes
- MemoryCacher clears all cache entries after the transporter connected/reconnected.
broker.loadServices
file mask is changed from *.service.js
to **/*.service.js
in order to load all services from subfolders, too.
ServiceNotFoundError
and ServiceNotAvailableError
errors are retryable errors.
Strategy.select
method gets only available endpoint list.
- old unavailable nodes are removed from registry after 10 minutes.
- CPU usage in
HEARTBEAT
packet is working properly in Windows, too.
- register middlewares before internal service (
$node.*
) loading.
broker.getAction
deprecated method is removed.
PROTOCOL_VERSION
constant is available via broker as ServiceBroker.PROTOCOL_VERSION
or broker.PROTOCOL_VERSION
- serialization functions are moved from transit to transporter codebase.
ctx.broadcast
shortcut method is created to send broadcast events from action handler.
broker.started
property is created to indicate broker starting state.
Fixes
- handles invalid
dependencies
value in service schema #164
- fix event emit error if payload is
null
,
New
Built-in clustering in Moleculer Runner #169
By @tinchoz49 Moleculer Runner has a new built-in clustering function. With it, you can start multiple instances from your broker.
Example to start all services from the services
folder in 4 instances.
$ moleculer-runner --instances 4 services
Please note, the nodeID
will be suffixed with the worker ID.
Context meta & params in metrics events #166
By @dani8art you can set that the broker put some ctx.meta
and ctx.params
fields to the metrics events.
You can define it in the action definition:
module.exports = {
name: "test",
actions: {
import: {
cache: true,
metrics: {
params: false,
meta: true
},
handler(ctx) {
}
}
}
}
If the value is true
, it adds all fields. If Array
, it adds the specified fields. If Function
, it calls with params
or meta
and you need to return an Object
.
0.11.9 (2018-01-08)
New
Strategy resolver
ServiceBroker can resolve the strategy
from a string.
const broker = new ServiceBroker({
registry: {
strategy: "Random"
}
});
You can set it via env variables as well, if you are using the Moleculer Runner:
$ REGISTRY_STRATEGY=random
Load env files in Moleculer Runner #158
Moleculer runner can load .env
file at starting. There are two new cli options to load env file:
-e, --env
- Load envorinment variables from the '.env' file from the current folder.
-E, --envfile <filename>
- Load envorinment variables from the specified file.
Example
$ moleculer-runner --env
$ moleculer-runner --envfile .my-env
Fixes
- fixed hot reloading after broken service files by @askuzminov (#155)
- allow fallbackResponse to be falsy values
0.11.8 (2017-12-15)
Changes
0.11.7 (2017-12-05)
Changes
0.11.6 (2017-11-07)
New
New cacher features
In action cache keys you can use meta keys with #
prefix.
broker.createService({
name: "posts",
actions: {
list: {
cache: {
keys: ["limit", "offset", "#user.id"],
ttl: 5
},
handler(ctx) {...}
}
}
});
You can override the cacher default TTL setting in action definition.
const broker = new ServiceBroker({
cacher: {
type: "memory",
options: {
ttl: 30
}
}
});
broker.createService({
name: "posts",
actions: {
list: {
cache: {
ttl: 5
},
handler(ctx) {...}
}
}
});
You can change the built-in cacher keygen function to your own one.
const broker = new ServiceBroker({
cacher: {
type: "memory",
options: {
keygen(name, params, meta, keys) {
return ...;
}
}
}
});
Others
0.11.5 (2017-10-12)
Changes
strategy
option has been fixed in broker option #121
0.11.4 (2017-10-11)
Changes
- Moleculer Runner arguments have been fixed (
services
arg)
- update AMQP default queue options by @Nathan-Schwartz #119
0.11.3 (2017-10-10)
Changes
- The
ack
handling has been fixed in AMQP transporter.
- AMQP RCP integration tests are added.
0.11.2 (2017-10-06)
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
},
dependencies: [
"likes",
{ name: "users", version: 2 },
{ name: "comments", version: "staging" }
],
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.
const 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 }
};
0.11.1 (2017-09-27)
New
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 with this.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.
const broker = new ServiceBroker({
transporter: "NATS",
disableBalancer: true
});
Changes
- ping nodes with
broker.sendPing
instead of broker.transit.sendPing
.
index.d.ts
updated to v0.11
- AMQP integration tests has been rewritten.
- process exit code changed from
2
to 1
in broker.fatal
. Reason: 2
is reserved by Bash for builtin misuse. More info
0.11.0 (2017-09-12)
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. Use events
in service schema instead.
broker.once
has been removed.
broker.off
has been removed.
broker.getService
has been renamed to broker.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 from 10
to 5
.
heartbeatTimeout
default value is changed from 30
to 15
.
circuitBreaker.maxFailures
default value is changed from 5
to 3
.
logFormatter
accepts string. The simple
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());
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(() => {
});
}
});
Signature:
this.waitForServices(serviceNames: String|Array<String>, timeout: Number, interval: Number): Promise
New error types
We added some new Moleculer error classes.
MoleculerRetryableError
- Common Retryable error. Caller retries the request if retryCount > 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.
0.10.0 (2017-08-20)
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);
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;
const 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];
}
};
const 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
or broker.loadServices
.
Usage
const 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.
const broker = new ServiceBroker({
transporter: "amqp://guest:guest@rabbitmq-server:5672"
});
const broker = new ServiceBroker({
transporter: new AmqpTransporter({
amqp: {
url: "amqp://guest:guest@localhost:5672",
eventTimeToLive: 5000,
prefetch: 1
}
});
});
0.9.0 (2017-08-10)
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"
});
New console output:

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
Pino
const pino = require("pino")({ level: "info" });
const broker = new ServiceBroker({
logger: bindings => pino.child(bindings)
});
Sample output:

Bunyan
const bunyan = require("bunyan");
const logger = bunyan.createLogger({ name: "moleculer", level: "info" });
const broker = new ServiceBroker({
logger: bindings => logger.child(bindings)
});
Sample output:

Winston
const broker = new ServiceBroker({
logger: bindings => new winston.Logger({
transports: [
new (winston.transports.Console)({
timestamp: true,
colorize: true,
prettyPrint: true
})
]
})
});
Winston context
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
- namespace
nodeID
- nodeID
mod
- type of core module: broker
, cacher
, transit
, transporter
svc
- service name
ver
- 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(() => {
broker.createService({
name: "math",
actions: {
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b);
},
}
});
}, 5000);
setTimeout(() => {
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;
})
Fixes
0.8.5 (2017-08-06)
Fixes
- fixed logger method bindings.
- fixed transporter shutdown errors #62
0.8.4 (2017-07-24)
Fixes
- fixed
Calling error! TypeError : Cannot read property 'requestID' of undefined
error when you call a local action from other one directly.
0.8.3 (2017-07-24)
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
0.8.2 (2017-07-06)
Fixes
New
Validator updated
The fastest-validator is updated to v0.5.0. It supports multi rules & custom validators.
0.8.1 (2017-07-03)
New
Improved mixin's merge logic #50
The mixins merge logic is handle better events & lifecycle events. If you have a created
, started
, stopped
lifecycle event or any other service event handler in your services, but your mixin has the same event, Moleculer will call all of them in your service and in mixins.
Read more about mixins
0.8.0 (2017-06-21)
New
Project runner script
There is a new Moleculer project runner script in the bin
folder.
You can use it if you want to create small repos for services. In this case you needn't to create a ServiceBroker with options. Just create a moleculer.config.js
or moleculer.config.json
file in the root of repo fill it with your options and call the moleculer-runner
within the NPM scripts.
As an other solution you can put it to the environment variables instead of putting options to file.
Read more about runner
Shorthand for transporters, cachers and serializers in broker options
Some new resolvers are implemented in broker options to support shorthand configurations. This feature is enabled to load broker options easily from a JSON file or load from environment variables.
Usage for transporters
const broker = new ServiceBroker({
transporter: "NATS"
});
const broker = new ServiceBroker({
transporter: "nats://nats-server:4222"
});
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
prefix: "TEST",
nats: {
host: "nats-server",
user: "admin",
pass: "nats-pass"
}
}
}
});
Usage for cachers
const broker = new ServiceBroker({
cacher: true
});
const broker = new ServiceBroker({
cacher: "Redis"
});
const broker = new ServiceBroker({
cacher: {
type: "Redis",
options: {
ttl: 100
}
}
});
Usage for serializers
const broker = new ServiceBroker({
serializers: "Avro"
});
const broker = new ServiceBroker({
serializers: {
type: "ProtoBuf"
}
});
Built-in circuit breaker #22
A better circuit breaker solution has recently been implemented. As a result of this improvement every call (local and remote) is protected by the built-in circuit breaker.
You only need to enable it in broker options.
Usage
const broker = new ServiceBroker({
circuitBreaker: {
enabled: true,
maxFailures: 5,
halfOpenTime: 10 * 1000
failureOnTimeout: true
failureOnReject: true
}
});
nodeUnavailable
method is dropped.
Service Registry module
A built-in Service Registry module was created. It handles actions of services on nodes, circuit breaker logic...etc. It would be pluggable in the future.
You can change the load balancing strategies of Service Registry via broker options.
Example
const { STRATEGY_ROUND_ROBIN, STRATEGY_RANDOM } = require("moleculer");
const broker = new ServiceBroker({
registry: {
strategy: STRATEGY_ROUND_ROBIN,
preferLocal: true
}
});
REPL mode #30
Broker module has an interactive REPL mode. You can call actions, load services, also emit events, subscribe to & unsubscribe from events from your console. You can list registered nodes & actions.
To use REPL mode please install the moleculer-repl module with npm install moleculer-repl --save
command.
Start REPL mode
const broker = new ServiceBroker({ logger: console });
broker.repl();
Commands
Commands:
help [command...] Provides help for a given command.
exit Exits application.
q Exit application
call <actionName> [params] Call an action
dcall <nodeID> <actionName> [params] Call a direct action
emit <eventName> [payload] Emit an event
load <servicePath> Load a service from file
loadFolder <serviceFolder> [fileMask] Load all service from folder
subscribe <eventName> Subscribe to an event
unsubscribe <eventName> Unsubscribe from an event
actions [options] List of actions
nodes List of nodes
info Information from broker
REPL Commands
List nodes
mol $ nodes

List services
mol $ services
List actions
mol $ actions

Show common informations
mol $ info

Call an action
mol $ call "test.hello"
Call an action with params
mol $ call "math.add" '{"a": 5, "b": 4}'
Direct call
mol $ dcall server-2 "$node.health"
Emit an event
mol $ emit "user.created"
Subscribe to an event
mol $ subscribe "user.created"
Unsubscribe from an event
mol $ unsubscribe "user.created"
Load a service
mol $ load "./math.service.js"
Load services from folder
mol $ load "./services"
Direct call
It is available to call an action directly on a specified node. To use it set nodeID
in options of call.
Example
broker.call("user.create", {}, { timeout: 5000, nodeID: "server-12" });
Mergeable schemas in createService
Now there is a second parameter of broker.createService
. With it you can override the schema properties. You can use it to use a built-in service & override some props.
Example
broker.createService(apiGwService, {
settings: {
port: 8080
},
actions: {
myAction() {
}
},
created() {
}
});
Or you can merge it manually with mergeSchemas
method.
let mergedSchema = broker.mergeSchemas(origSchema, modifications);
broker.createService(mergedSchema);
Service mixins
Like mergeable schemas, the service may include any mixin schemas. The constructor of Service merges these mixins with the schema of Service. It is to reuse an other Service in your service or extend an other Service.
Examples
const ApiGwService = require("moleculer-web");
module.exports = {
name: "api",
mixins: [ApiGwService]
settings: {
port: 8080
},
actions: {
myAction() {
}
}
}
New option to protect calling loop
You can protect your app against calling loop with the new maxCallLevel
option. If the ctx.level
value reaches this limit, it throwns a MaxCallLevelError
error.
const broker = new ServiceBroker({
maxCallLevel: 100
});
New Service setting
There is a new useVersionPrefix
option in Service settings. If it is false
, Moleculer can't use the version number of service as prefix for action names. The name of service will be users.find
instead of v2.users.find
. The default is true
.
Changes
Removed the node.reconnected
and node.broken
events (breaking)
We merged the node.connected
and node.reconnected
events. The payload is changed:
{
node: {...},
reconnected: false
}
We merged also the node.disconnected
and node.broken
events. The payload is changed:
{
node: {...},
unexpected: true
}
Remove Transporter, Cacher and Serializers dependencies (breaking)
Moleculer doesn't contain dependencies for NATS, Redis, MQTT, MsgPack, Avro and Protobuf. So it need install manually in your project.
If you want to create a Moleculer project which communicates via NATS and your Redis cacher, you have to install npm install moleculer nats redis --save
Changed code of ServiceNotFoundError
The code of ServiceNotFoundError
is changed from 501
to 404
. More info
Using Nanomatch instead of micromatch
Memory cacher is using nanomatch instead of micromatch. The nanomatch
is ~10x faster.
Removed metricsSendInterval
option #24
The metricsSendInterval
option is removed from broker options. If you want to access statistics & health info, call the $node.health
and $node.stats
actions.
Metrics & Statistics separated #24
The metrics & statistics features separated. You can use just metrics or just statistics.
Metrics nodeID
Metrics events contains two nodeID properties.
nodeID
: the "caller" nodeID
targetNodeID
: in case of remote call this is the remote nodeID
Response error with stack trace
If an action responses an error on a remote node, the transporter will send back the error to the caller with the stack traces.
broker.call("account.deposit").catch(err => console.log(err.stack));
Type property in custom error
The CustomError
class renamed to MoleculerError
. also it has a type
new property. You can store here a custom error type. For example, if you have a ValidationError
, in some cases the name
& code
is not enough. By type
error causes are to be stored.
Example
const ERR_MISSING_ID = "ERR_MISSING_ID";
const ERR_ENTITY_NOT_FOUND = "ERR_ENTITY_NOT_FOUND";
broker.createService({
actions: {
get(ctx) {
if (ctx.params.id) {
const entity = this.searchEntity(ctx.params.id);
if (entity)
return entity;
else
return Promise.reject(new ValidationError("Not found entity!", ERR_ENTITY_NOT_FOUND));
} else
return Promise.reject(new ValidationError("Please set the ID field!", ERR_MISSING_ID));
}
}
});
Renamed appendServiceName
settings to serviceNamePrefix
in Service schema
Fatal crash
The ServiceBroker
has a new fatal
method. If you call it, broker will log the message with fatal
level and exit the process with code 2
.
broker.fatal(message, err, needExit = true)
If you are running your app in containers and it has restart policy, you can use it to restart your app.
Usage
try {
} catch(err) {
broker.fatal("Dangerous thing is happened!", err, true);
}
Low-level changes
- new output of
$node.actions
and $node.services
- In packet
INFO
& DISCOVER
changed the actions
property to services
and now it contains all services with actions of node
- splitted
broker.registerService
to registerLocalService
and registerRemoteService
- new
broker.unregisterServicesByNode
. It will be called when a node disconnected
0.7.0 (2017-04-24)
New
Serializers for transporters #10
Implemented pluggable serializers.
Built-in serializers:
- <input checked="" disabled="" type="checkbox"> JSON (default)
- <input checked="" disabled="" type="checkbox"> Avro
- <input checked="" disabled="" type="checkbox"> MsgPack
- <input checked="" disabled="" type="checkbox"> ProtoBuf
Usage
let JSONSerializer = require("moleculer").Serializers.JSON;
const broker = new ServiceBroker({
serializer: new JSONSerializer(),
transporter: new Transporter(),
nodeID: "node-1"
});
Typescript definition file #5
Created an index.d.ts file. I'm not familiar in Typescript, so if you found error please help me and open a PR with fix. Thank you!
Metrics rate option
Added metricsRate
options to broker. This property sets the rate of sampled calls.
1
means to metric all calls
0.5
means to metric 50% of calls
0.1
means to metric 10% of calls
Usage
const broker = new ServiceBroker({
metrics: true,
metricsRate: 0.1
});
Context meta data (#16)
Added meta
prop to Context
. The meta
will be merged if has parent context.
In case of remote calls the metadata will be transfered to the target service.
Usage
Set meta in broker.call
:
broker.call("user.create", { name: "Adam", status: true}, {
timeout: 1000,
meta: {
loggedInUser: {
userID: 45,
roles: [ "admin" ]
}
}
})
Access meta in action:
broker.createService({
name: "user",
actions: {
create(ctx) {
const meta = ctx.meta;
if (meta.loggedInUser && meta.loggedInUser.roles.indexOf("admin") !== -1)
return Promise.resolve(...);
else
throw new MoleculerError("Access denied!");
}
}
});
Changes
Update benchmarkify
Benchmarkify updated & created continuous benchmarking with bench-bot.
Bench-bot is a benchmark runner. If a new Pull Request opened, bench-bot will run benchmarks against the master
branch and it will post the results to the PR conversation.
Timeout & fallback response handling in local calls too
- Can be use timeout & fallback response in local calls.
- Timeout handling move from
Transit
to ServiceBroker
- Remove
wrapContentAction
- In case of calling error, Node will be unavailable only if the error code >=
500
Context changes
- Removed
createSubContext
- Removed
ctx.parent
and added ctx.parentID
- Removed options in constructor. New constructor syntax:
let ctx = new Context(broker, action);
ctx.setParams({ a: 5 });
ctx.generateID();
ctx.requestID = requestID;
- Add Context reference to returned Promise
const p = broker.call("user.create");
console.log("Context:", p.ctx);
Sender in event handlers
If an event triggered remotely on an other node, broker passes the nodeID of sender to the event handler as 2nd parameter.
broker.on("**", (payload, sender) => console.log(`Event from ${sender || "local"}:`, payload));
broker.createService({
...
events: {
something(payload, sender) {
console.log(`Something happened on '${sender}':`, payload);
}
}
});
Distributed timeout handling
Moleculer uses distributed timeouts.In the chained calls the ctx.call
decrement the original timeout value with the elapsed time. If the new calculated timeout is less or equal than 0, it'll skip the next calls because the first call is rejected with RequestTimeoutError
error.
0.6.0 (2017-03-31)
New
Validator library changed
The previous validatorjs
validator removed and added own very fast fastest-validator library. It can 3M validations/sec. Hereafter validation is not the bottle-neck. Only -7% slower with validation.
Here is the new benchmark result:
Suite: Call with param validator
√ No validator x 588,463 ops/sec ±1.11% (84 runs sampled)
√ With validator passes x 541,903 ops/sec ±1.41% (84 runs sampled)
√ With validator fail x 25,648 ops/sec ±1.62% (85 runs sampled)
No validator 0.00% (588,463 ops/sec)
With validator passes -7.91% (541,903 ops/sec)
With validator fail -95.64% (25,648 ops/sec)
Example params definition:
mult: {
params: {
a: { type: "number" },
b: { type: "number" }
},
handler(ctx) {
return Number(ctx.params.a) * Number(ctx.params.b);
}
}
Validation error object:
[ {
type: 'number',
field: 'b',
message: 'The \'b\' field must be a number!'
} ]
Changes
Added & removed log levels
- Added 2 new log levels (
fatal
and trace
);
- Removed unused
log
level. Use info
level instead.
Available levels:
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
Logger fallback levels:
trace
-> debug
-> info
debug
-> info
info
: main level, no fallback
warn
-> error
-> info
error
-> info
fatal
-> error
-> info
0.5.0 (2017-02-26)
First release.