Progressive microservices framework for Node.js
MIT License
Bot releases are visible (Hide)
Published by icebob almost 5 years ago
The Node version 8 LTS lifecycle has been ended on December 31, 2019, so the minimum required Node version is 10.
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.
// mailer.service.js
module.exports = {
name: "mailer",
events: {
"send.mail": {
// Validation schema
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.
Published by icebob almost 5 years ago
fastest-validator, the default validation has been upgraded to the 1.0.0 version. It means breaking changes but the new version faster and contains many sanitization functions.
If you use custom rules, you should upgrade your codes. Check the changes here.
The gc-stats
and event-loop-stats
optional dependencies removed and moved to devDependencies. If you want to use them, you should install manually.
Published by icebob about 5 years ago
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,
// Using MessagePack serializer to store data.
serializer: "MsgPack",
redis: {
host: "my-redis"
}
}
}
});
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: { /* More information: https://github.com/luin/ioredis#cluster */ }
}
}
}
});
Published by icebob over 5 years ago
Migration guide from 0.13 to 0.14
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.
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()
});
broker.use
removedThe 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.
$node.health
response changedThe $node.health
action's response has been changed. The transit
property is removed. To get transit metrics, use the new $node.metrics
internal action.
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) {
// Create a custom named logger
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]
});
localEvent
middleware hook signature changedOld signature
// my-middleware.js
module.exports = {
// Wrap local event handlers
localEvent(next, event) {
return (payload, sender, event) => {
return next(payload, sender, event);
};
},
};
New context-based signature
// my-middleware.js
module.exports = {
// Wrap local event handlers
localEvent(next, event) {
return (ctx) => {
return next(ctx);
};
},
};
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 if 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 });
}
}
};
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
// posts.service.js
module.exports = {
name: "posts",
actions: {
get(ctx) {
// Update metrics
this.broker.metrics.increment("posts.get.total");
return posts;
}
},
created() {
// Register new custom metrics
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.**"]
}
}
]
}
});
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.
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.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)All reporters have the following options:
{
includes: null,
excludes: null,
metricNamePrefix: null,
metricNameSuffix: null,
metricNameFormatter: null,
labelNameFormatter: null
}
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 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, // MODE_METRIC, MODE_LABEL
types: null,
interval: 5 * 1000,
filenameFormatter: null,
rowFormatter: null,
}
}
]
}
});
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 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 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
})
}
}
]
}
});
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,
}
}
]
}
});
An enhanced tracing middleware has been implemented in version 0.14. It support several exporters, custom tracing spans and integration with instrumentation libraries (like dd-trace
).
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",
}
}
]
}
});
In action defintion you can define which Context params or meta values want to add to the span tags.
Example
// posts.service.js
module.exports = {
name: "posts",
actions: {
get: {
tracing: {
// Add `ctx.params.id` and `ctx.meta.loggedIn.username` values
// to tracing span tags.
tags: {
params: ["id"],
meta: ["loggedIn.username"],
response: ["id", "title"] // add data to tags from the action response.
},
async handler(ctx) {
// ...
}
}
}
});
Example with all properties of params without meta (actually it is the default)
// posts.service.js
module.exports = {
name: "posts",
actions: {
get: {
tracing: {
// Add all params without meta
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.
// posts.service.js
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
// posts.service.js
module.exports = {
name: "posts",
events: {
"user.created": {
tracing: {
// Add all params without meta
tags: {
params: true,
meta: false,
},
async handler(ctx) {
// ...
}
}
}
});
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 sends tracing data to Datadog server via dd-trace
. It is able to merge tracing spans between instrumented Node.js modules and Moleculer modules.
TODO screenshot
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 withnpm install dd-trace --save
command.
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,
/** @type {Number} Batch send time interval. */
interval: 5,
spanConverter: null,
/** @type {Object?} Default span tags */
defaultTags: null
}
}
]
}
});
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 sends tracing spans information to a Jaeger server.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Jaeger",
options: {
/** @type {String?} HTTP Reporter endpoint. If set, HTTP Reporter will be used. */
endpoint: null,
/** @type {String} UDP Sender host option. */
host: "127.0.0.1",
/** @type {Number?} UDP Sender port option. */
port: 6832,
/** @type {Object?} Sampler configuration. */
sampler: {
/** @type {String?} Sampler type */
type: "Const",
/** @type: {Object?} Sampler specific options. */
options: {}
},
/** @type {Object?} Additional options for `Jaeger.Tracer` */
tracerOptions: {},
/** @type {Object?} Default span tags */
defaultTags: null
}
}
]
}
});
To use this exporter, install the
jaeger-client
module withnpm install jaeger-client --save
command.
Zipkin exporter sends tracing spans information to a Zipkin server.
const broker = new ServiceBroker({
tracing: {
enabled: true,
exporter: [
{
type: "Zipkin",
options: {
/** @type {String} Base URL for Zipkin server. */
baseURL: process.env.ZIPKIN_URL || "http://localhost:9411",
/** @type {Number} Batch send time interval. */
interval: 5,
/** @type {Object} Additional payload options. */
payloadOptions: {
/** @type {Boolean} Set `debug` property in v2 payload. */
debug: false,
/** @type {Boolean} Set `shared` property in v2 payload. */
shared: false
},
/** @type {Object?} Default span tags */
defaultTags: null
}
}
]
}
});
// posts.service.js
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);
span1.finish();
const span2 = ctx.startSpan("populating");
const res = await this.populate(data);
span2.finish();
return res;
}
}
};
There is a new caller
property in Context. It contains the action name of the caller when you use ctx.call
in action handlers.
broker2.createService({
name: "greeter",
actions: {
hello(ctx) {
this.logger.info(`This action is called from '${ctx.caller}' on '${ctx.nodeID}'`);
}
}
});
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.
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
}
}
});
Now the internal services can be extended. You can define mixin schema for every internal service under internalServices
broker option.
// moleculer.config.js
module.exports = {
nodeID: "node-1",
logger: true,
internalServices: {
$node: {
actions: {
// Call as `$node.hello`
hello(ctx) {
return `Hello Moleculer!`;
}
}
}
}
};
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.
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.
There are some new middleware hooks.
registerLocalService
It's called before registering a local service instance.
Signature
// my-middleware.js
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
// my-middleware.js
module.exports = {
serviceCreating(service, schema) {
// Modify schema
schema.myProp = "John";
}
}
transitPublish
It's called before communication packet publishing.
Signature
// my-middleware.js
module.exports = {
transitPublish(next) {
return (packet) => {
return next(packet);
};
},
}
transitMessageHandler
It's called before transit receives & parses an incoming message
Signature
// my-middleware.js
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
// my-middleware.js
module.exports = {
transporterSend(next) {
return (topic, data, meta) => {
// Do something with data
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
// my-middleware.js
module.exports = {
transporterReceive(next) {
return (cmd, data, s) => {
// Do something with data
return next(cmd, data, s);
};
}
}
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");
// Create broker
const broker = new ServiceBroker({
middlewares: [
Middlewares.Transmit.Encryption("secret-password", "aes-256-cbc", initVector) // "aes-256-cbc" is the default
]
});
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");
// Create broker
const broker = new ServiceBroker({
middlewares: [
Middlewares.Transmit.Compression("deflate") // or "deflateRaw" or "gzip"
]
});
Transit logger middleware allows to easily track the messages that are exchanged between services.
const { Middlewares } = require("moleculer");
// Create broker
const broker = new ServiceBroker({
middlewares: [
Middlewares.Debugging.TransitLogger({
logPacketData: false,
folder: null,
colors: {
send: "magenta",
receive: "blue"
},
packetFilter: ["HEARTBEAT"]
})
]
});
Action Logger middleware tracks "how" service actions were executed.
const { Middlewares } = require("moleculer");
// Create broker
const broker = new ServiceBroker({
middlewares: [
Middlewares.Debugging.ActionLogger({
logParams: true,
logResponse: true,
folder: null,
colors: {
send: "magenta",
receive: "blue"
},
whitelist: ["**"]
})
]
});
To load built-in middlewares, use its names in middleware
broker option.
const { Middlewares } = require("moleculer");
// Extend with custom middlewares
Middlewares.MyCustom = {
created(broker) {
broker.logger.info("My custom middleware is created!");
}
};
const broker1 = new ServiceBroker({
logger: true,
middlewares: [
// Load by middleware name
"MyCustom"
]
});
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; // Throw further
}
});
The
info
object contains the broker and the service instances, the current context and the action or the event definition.
ServiceBroker has a continuous local storage in order to store the current context. It means you don't need to always pass the ctx
from actions to service methods. You can get it with this.currentContext
.
// greeter.service.js
module.exports = {
name: "greeter",
actions: {
hello(ctx) {
return this.Promise.resolve()
.then(() => this.doSomething());
}
},
methods: {
doSomething() {
const ctx = this.currentContext;
return ctx.call("other.service");
}
}
});
Buffer
supporting improved in serializersIn 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.
ctx.metrics
to ctx.tracing
.broker.hotReloadService
method has been removed.hasEventListener
& getEventListeners
broker method.uidGenerator
broker options to overwrite the default UUID generator code.Published by icebob over 5 years ago
Example to enable cacher locking:
cacher: {
ttl: 60,
lock: true, // Set to true to enable cache locks. Default is disabled.
}
// Or
cacher: {
ttl: 60,
lock: {
ttl: 15, //the maximum amount of time you want the resource locked in seconds
staleTime: 10, // If the ttl is less than this number, means that the resources are staled
}
}
// Disable the lock
cacher: {
ttl: 60,
lock: {
enable: false, // Set to false to disable.
ttl: 15, //the maximum amount of time you want the resource locked in seconds
staleTime: 10, // If the ttl is less than this number, means that the resources are staled
}
}
Example for Redis cacher with redlock
library:
const broker = new ServiceBroker({
cacher: {
type: "Redis",
options: {
// Prefix for keys
prefix: "MOL",
// set Time-to-live to 30sec.
ttl: 30,
// Turns Redis client monitoring on.
monitor: false,
// Redis settings
redis: {
host: "redis-server",
port: 6379,
password: "1234",
db: 0
},
lock: {
ttl: 15, //the maximum amount of time you want the resource locked in seconds
staleTime: 10, // If the ttl is less than this number, means that the resources are staled
},
// Redlock settings
redlock: {
// Redis clients. Support node-redis or ioredis. By default will use the local client.
clients: [client1, client2, client3],
// the expected clock drift; for more details
// see http://redis.io/topics/distlock
driftFactor: 0.01, // time in ms
// the max number of times Redlock will attempt
// to lock a resource before erroring
retryCount: 10,
// the time in ms between attempts
retryDelay: 200, // time in ms
// the max time in ms randomly added to retries
// to improve performance under high contention
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
retryJitter: 200 // time in ms
}
}
}
});
Published by icebob over 5 years ago
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
// mail.service.js
module.exports = {
name: "mailer",
settings: {
$secureSettings: ["transport.auth.user", "transport.auth.pass"],
from: "[email protected]",
transport: {
service: 'gmail',
auth: {
user: '[email protected]',
pass: 'yourpass'
}
}
}
// ...
};
cacher.clean
issue #435
disableVersionCheck
option for broker transit options. It can disable protocol version checking logic in Transit. Default: false
v2.posts
).Published by icebob almost 6 years ago
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
// Turn off caching for this request
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
// greeter.service.js
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}`;
}
}
}
};
// Use custom `enabled` function to turn off caching for this request
broker.call("greeter.hello", { name: "Moleculer", noCache: true }))
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: {
// Maximum items
max: 100,
// Time-to-Live
ttl: 3
}
}
});
loadService
method so that Runner prints the correct error stack.packetLogFilter
transit option to filter packets in debug logs (e.g. HEARTBEAT packets) by @faeron
clean
& del
methods handle array parameter by @dkuida
clean
& del
methods handle array parameter by @icebob
version: 0
as a valid version number by @ngraef
Published by icebob almost 6 years ago
getCpuUsage()
method.transit.disableReconnect
option to disable reconnecting logic at broker starting by @Gadi-Manor
os.userInfo
errors in health action by @katsanva
retries
#404 by @urossmolnik
GraceFulTimeoutError
bug #400
Published by icebob about 6 years ago
mqtt+ssl://
to mqtts://
by @AndreMaz
moleculer.config.ts
waitForServices
definition in index.d.ts
#358
cpuUsage
issue #379 by @faeron
Published by icebob about 6 years ago
skipProcessEventRegistration
broker option to disable process.on
shutdown event handlers which stop broker.socketOptions
to AMQP transporter options. #330
connect
method.autoDeleteQueues
option to AMQP transporter. #341
qos
transporter option to MQTT transporter. Default: 0
topicSeparator
transporter option to MQTT transporter. Default: .
Published by icebob over 6 years ago
Migration guide from v0.12.x to v0.13.x is here.
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.
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);
}
}
};
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);
// Prints: 8
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);
// Prints: 8
});
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);
// Prints: 8
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.
No longer need to set logger: console
in broker options, because ServiceBroker uses console
as default logger.
const broker = new ServiceBroker();
// It will print log messages to the console
Disable loggging (e.g. in tests)
const broker = new ServiceBroker({ logger: false });
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.
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, // in seconds
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: {
// All CB options can be overwritten from broker options.
threshold: 0.3,
windowTime: 30
},
handler(ctx) {}
}
}
};
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.
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: {
// All Retry policy options can be overwritten from broker options.
retries: 3,
delay: 500
},
handler(ctx) {}
},
create: {
retryPolicy: {
// Disable retries for this action
enabled: false
},
handler(ctx) {}
}
}
};
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.
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.
Some errors have been renamed in order to follow name conventions.
ServiceNotAvailable
-> ServiceNotAvailableError
RequestRejected
-> RequestRejectedError
QueueIsFull
-> QueueIsFullError
InvalidPacketData
-> InvalidPacketDataError
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
.
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
}
}
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
is44
(SHA 256 hash length in Base64).To disable this feature, set it to
0
ornull
.
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" });
// Key: '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" });
// Key: 'posts.find:id|2|title|New pL4ozUU24FATnNpDt1B0t1T5KP/T5/Y+JTIznKDspjT0='
Of course, you can use your custom solution with keygen
cacher options like earlier.
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.
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" });
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 = {
// Wrap local action handlers (legacy middleware handler)
localAction(next, action) {
},
// Wrap remote action handlers
remoteAction(next, action) {
},
// Wrap local event handlers
localEvent(next, event) {
}
// Wrap broker.createService method
createService(next) {
}
// Wrap broker.destroyService method
destroyService(next) {
}
// Wrap broker.call method
call(next) {
}
// Wrap broker.mcall method
mcall(next) {
}
// Wrap broker.emit method
emit(next) {
},
// Wrap broker.broadcast method
broadcast(next) {
},
// Wrap broker.broadcastLocal method
broadcastLocal(next) {
},
// After a new local service created (sync)
serviceCreated(service) {
},
// Before a local service started (async)
serviceStarting(service) {
},
// After a local service started (async)
serviceStarted(service) {
},
// Before a local service stopping (async)
serviceStopping(service) {
},
// After a local service stopped (async)
serviceStopped(service) {
},
// After broker is created (async)
created(broker) {
},
// Before broker starting (async)
starting(broker) {
},
// After broker started (async)
started(broker) {
},
// Before broker stopping (async)
stopping(broker) {
},
// After broker stopped (async)
stopped(broker) {
}
}
Use it in broker options
const broker = new ServiceBroker({
middlewares: [
MyCustomMiddleware
]
});
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) {
// Wrap the handler
return function(ctx) {
doSomethingBeforeHandler(ctx);
return handler(ctx)
.then(res => {
doSomethingAfterHandler(res);
// Return the original result
return res;
})
.catch(err => {
doSomethingAfterHandlerIfFailed(err);
// Throw further the error
throw err;
});
}
}
// If the feature is disabled we don't wrap it, return the original handler
// So it won't cut down the performance for actions where the feature is disabled.
return handler;
}
};
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: [
{
// After broker is created
created(broker) {
// Call action on all available nodes
broker.allCall = function(action, params, opts = {}) {
const nodeIDs = this.registry.getNodeList({ onlyAvailable: true })
.map(node => node.id);
// Make direct call to the given Node ID
return Promise.all(nodeIDs.map(nodeID => broker.call(action, params, Object.assign({ nodeID }, opts))));
}
}
}
]
});
await broker.start();
// Call `$node.health` on every nodes & collect results
const res = await broker.allCall("$node.health");
Decorate services with a new method
const broker = new ServiceBroker({
middlewares: [
{
// After a new local service created
serviceCreated(service) {
// Call action on all available nodes
service.customFunc = function() {
// Do something
}.bind(service);
}
}
]
});
In service schema:
module.export = {
name: "users",
actions: {
find(ctx) {
// Call the new custom function
this.customFunc();
}
}
};
The mixins can do similar things, so we prefer mixins to this decorating.
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:
Turn off the automatic loading with
internalMiddlewares: false
broker option. In this case you have to add them tomiddlewares: []
broker option.
The
broker.use
method is deprecated. Usemiddlewares: []
in the broker options instead.
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: {
// Define a global hook for all actions
// The hook will call the `resolveLoggedUser` method.
"*": "resolveLoggedUser",
// Define multiple hooks
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: {
// Define a global hook for all actions to remove sensitive data
"*": function(ctx, res) {
// Remove password
delete res.password;
// Please note, must return result (either the original or a new)
return res;
},
get: [
// Add a new virtual field to the entity
async function (ctx, res) {
res.friends = await ctx.call("friends.count", { query: { follower: res._id }});
return res;
},
// Populate the `referrer` field
async function (ctx, res) {
if (res.referrer)
res.referrer = await ctx.call("users.get", { id: res._id });
return res;
}
]
},
error: {
// Global error handler
"*": function(ctx, err) {
this.logger.error(`Error occurred when '${ctx.action.name}' action was called`, err);
// Throw further the error
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) {
// Check the owner of entity
}
}
}
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: {
// No required role
handler(ctx) {}
},
create: {
role: "admin",
handler(ctx) {}
},
update: {
role: "user",
handler(ctx) {}
}
}
};
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: {
// Increment the concurrency value
// for this action
concurrency: 10
},
handler(ctx) {}
}
}
};
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",
//fallback: "fakeResult",
handler(ctx) {
// Do something
}
}
}
};
Fallback as method name string
module.exports = {
name: "recommends",
actions: {
add: {
// Call the 'getCachedResult' method when error occurred
fallback: "getCachedResult",
handler(ctx) {
// Do something
}
}
},
methods: {
getCachedResult(ctx, err) {
return "Some cached result";
}
}
};
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 Gatewaypublic
: public action, can be called locally & remotely but not published via API GWprotected
: 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: {
// It's published by default
find(ctx) {},
clean: {
// Callable only via `this.actions.clean`
visibility: "private",
handler(ctx) {}
}
},
methods: {
cleanEntities() {
// Call the action directly
return this.actions.clean();
}
}
}
The default value is
null
(meanspublished
) due to backward compatibility.
There is a new built-in Thrift serializer.
const broker = new ServiceBroker({
serializer: "Thrift"
});
To use this serializer install the
thrift
module withnpm install thrift --save
command.
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, // Disable logs
"TRANS": "warn", // Only 'warn ' and 'error' log entries
"*.GREETER": "debug", // All log entries
"**": "debug", // All other modules use this level
}
});
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"
});
short
log formatterA 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.
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 serviceGlob patterns work in the
SERVICES
enviroment variables, as well.
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))
}
}
});
fullName
containing service version & service name.Action
has a rawName
property containing action name without service name.$node.options
internal action to get the current broker options.Context.create
& new Context
signature changed.Metrics
middleware.ctx.timeout
moved to ctx.options.timeout
.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.ctx.id
, i.e. only generated at access. ctx.generateID()
was removed.transit.stat.packets
with byte-based statistics.utils.deprecate
method was created for deprecation.mqtt+ssl://
, rediss://
& amqps://
protocols in connection URIs.broker.use()
has been deprecated. Use middlewares: [...]
in broker options instead.Published by icebob over 6 years ago
Fix typescript def.
Published by icebob over 6 years ago
breakLength
is changed to Infinity
(single-line printing) for better log processing when logger prints objects and arrays.const util = require("util");
const broker = new ServiceBroker({
logger: true,
logObjectPrinter: o => util.inspect(o, { depth: 4, colors: false, breakLength: 50 }) // `breakLength: 50` activates multi-line object
});