0.13.10 (2019-08-26)
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,
// Using MessagePack serializer to store data.
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: { /* More information: https://github.com/luin/ioredis#cluster */ }
}
}
}
});
- update dependencies.
- update Typescript definitions by @shawnmcknight.
- fix Protocol Buffer definitions by @fugufish.
0.13.9 (2019-04-18)
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
}
}
}
});
- 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
0.13.8 (2019-03-21)
- fix missing field in ProtoBuf & Thrift serializers #496
0.13.7 (2019-02-21)
- fix ioredis dependency in typescript definition file #476
0.13.6 (2019-02-15)
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: "sender@moleculer.services",
transport: {
service: 'gmail',
auth: {
user: 'gmail.user@gmail.com',
pass: 'yourpass'
}
}
}
// ...
};
- 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)
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
}
}
});
- 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)
- 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)
- update dependencies
- fix MQTTS connection string protocol from
mqtt+ssl://
tomqtts://
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 inindex.d.ts
#358 - fix
cpuUsage
issue #379 by @faeron
0.13.2 (2018-08-16)
- update dependencies
- add Notepack (other MsgPack) serializer
skipProcessEventRegistration
broker option to disableprocess.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)
- 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.
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:
- 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 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
ornull
: 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 (viathis.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"
});
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.
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 theservices
folder with**/*.service.js
file mask!services/others/**/*.service.js
- skip all services in theservices/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.
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))
}
}
});
- service instances has a new property named
fullName
containing service version & service name. - the
Action
has arawName
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 toctx.options.timeout
.- removed
ctx.callerNodeID
. ctx.endpoint
is a new property pointing to targetEndpoint
. For example you can check withctx.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)
broker.use()
has been deprecated. Usemiddlewares: [...]
in broker options instead.
0.12.8 (2018-06-14)
- 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)
- update dependencies
- The
breakLength
is changed toInfinity
(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 }) // `breakLength: 50` activates multi-line object });
0.12.5 (2018-05-21)
- 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)
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 // waiting max 5 sec
});
This timeout can be overwrite in service settings with $gracefulStopTimeout
property.
- fix service registry update after reconnecting. #262
- update index.d.ts
- update dependencies
- fix distributed timeout handling
0.12.3 (2018-04-19)
- fix empty service mixins issue (
mixins: []
). - update index.d.ts
0.12.2 (2018-04-11)
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
}
}
});
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
- fix
d.ts
issues - fix event
group
handling in mixins (#217) - move
mergeSchemas
fromutils
toService
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 inService
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.
The Github organization name (Ice Services) has been renamed to MoleculerJS. Please update your bookmarks.
- Github organization: https://github.com/moleculerjs
- Website: https://moleculer.services or http://moleculerjs.com/
- Gitter chat: https://gitter.im/moleculerjs/moleculer
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
// mixin.service.js
module.exports = {
actions: {
create(ctx) {
// Action handler without `params`
}
}
};
// my.service.js
module.exports = {
mixins: [MixinService]
actions: {
create: {
// Add only `params` property to the `create` action
// The handler is merged from mixin
params: {
name: "string"
}
}
}
};
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
// NATS transporter
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
nats: {
user: "admin",
pass: "1234"
}
}
}
});
// Redis transporter
const broker = new ServiceBroker({
transporter: {
type: "Redis",
options: {
redis: {
port: 6379,
db: 0
}
}
}
});
// MQTT transporter
const broker = new ServiceBroker({
transporter: {
type: "MQTT",
options: {
mqtt: {
user: "admin",
pass: "1234"
}
}
}
});
// AMQP transporter
const broker = new ServiceBroker({
transporter: {
type: "AMQP",
options: {
amqp: {
prefetch: 1
}
}
}
});
After
// NATS transporter
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
user: "admin",
pass: "1234"
}
}
});
// Redis transporter
const broker = new ServiceBroker({
transporter: {
type: "Redis",
options: {
port: 6379,
db: 0
}
}
});
// MQTT transporter
const broker = new ServiceBroker({
transporter: {
type: "MQTT",
options: {
user: "admin",
pass: "1234"
}
}
});
// AMQP transporter
const broker = new ServiceBroker({
transporter: {
type: "AMQP",
options: {
prefetch: 1
}
}
});
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.
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 fieldmeta
. - the
EVENT
packet has a new fieldbroadcast
. - the
port
field is removed fromINFO
packet. - the
INFO
packet has a new fieldhostname
.
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.
// moleculer.config.js
module.exports = {
logger: true,
// Add middlewares
middlewares: [myMiddleware()],
// Fired when the broker created
created(broker) {
},
// Fired when the broker started
started(broker) {
// You can return Promise
return broker.Promise.resolve();
},
// Fired when the broker stopped
stopped(broker) {
// You can return Promise
return broker.Promise.resolve();
}
};
The broker.broadcast
function has a third groups
argument similar to broker.emit
.
// Send to all "mail" service instances
broker.broadcast("user.created", { user }, "mail");
// Send to all "user" & "purchase" service instances.
broker.broadcast("user.created", { user }, ["user", "purchase"]);
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
}
}
});
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 hasdependencies: [posts]
andposts
service hasdependencies: [users]
. To avoid it remove the concerned service fromdependencies
and usewaitForServices
method out ofstarted
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 data
export(ctx) {
const rows = this.adapter.find({});
// Set response headers to download it as a file
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();
}
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
andevents
handlers as class methods. Call theparseServiceSchema
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, }); } // Action handler hello() { return "Hello Moleculer"; } // Action handler welcome(ctx) { return this.sayWelcome(ctx.params.name); } // Private method sayWelcome(name) { this.logger.info("Say hello to", name); return `Welcome, ${this.settings.upperCase ? name.toUpperCase() : name}`; } // Event handler 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) { ... } // With options @Action({ cache: false, params: { a: "number", b: "number" } }) Login2(ctx) { ... } @Event 'event.name'(payload, sender, eventName) { ... } @Method authorize(ctx, route, req, res) { ... } hello() { // Private ... } started() { // Reserved for moleculer, fired when started ... } created() { // Reserved for moleculer, fired when created ... } stopped() { // Reserved for moleculer, fired when stopped ... } } broker.createService(ServiceName); broker.start();
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": {
// Register handler to "other" group instead of "payment" group.
group: "other",
handler(payload) {
// ...
}
}
}
}
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: {
// Enable UDP discovery
udpDiscovery: true,
// Reusing UDP server socket
udpReuseAddr: true,
// UDP port
udpPort: 4445,
// UDP bind address (if null, bind on all interfaces)
udpBindAddress: null,
// UDP sending period (seconds)
udpPeriod: 30,
// Multicast address.
udpMulticast: "239.0.0.0",
// Multicast TTL setting
udpMulticastTTL: 1,
// Send broadcast (Boolean, String, Array<String>)
udpBroadcast: false,
// TCP server port. Null or 0 means random port
port: null,
// Static remote nodes address list (when UDP discovery is not available)
urls: null,
// Use hostname as preffered connection address
useHostname: true,
// Gossip sending period in seconds
gossipPeriod: 2,
// Maximum enabled outgoing connections. If reach, close the old connections
maxConnections: 32,
// Maximum TCP packet size
maxPacketSize: 1 * 1024 * 1024
}
}
});
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
withnpm 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",
// KafkaClient options. More info: https://github.com/SOHU-Co/kafka-node#clientconnectionstring-clientid-zkoptions-noackbatchoptions-ssloptions
client: {
zkOptions: undefined,
noAckBatchOptions: undefined,
sslOptions: undefined
},
// KafkaProducer options. More info: https://github.com/SOHU-Co/kafka-node#producerclient-options-custompartitioner
producer: {},
customPartitioner: undefined,
// ConsumerGroup options. More info: https://github.com/SOHU-Co/kafka-node#consumergroupoptions-topics
consumer: {
},
// Advanced options for `send`. More info: https://github.com/SOHU-Co/kafka-node#sendpayloads-cb
publish: {
partition: 0,
attributes: 0
}
}
}
});
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
withnpm install node-nats-streaming --save
command.
Connect to NATS Streaming server
// Shorthand to local server
const broker = new ServiceBroker({
logger: true,
transporter: "STAN"
});
// Shorthand
const broker = new ServiceBroker({
logger: true,
transporter: "stan://192.168.0.120:4222"
});
// Shorthand with options
const broker = new ServiceBroker({
logger: true,
transporter: {
type: "STAN",
options: {
url: "stan://127.0.0.1:4222",
clusterID: "my-cluster"
}
}
});
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"]
},
//parse(command, args) {},
//validate(args) {},
//help(args) {},
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();
- 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
andServiceNotAvailableError
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 asServiceBroker.PROTOCOL_VERSION
orbroker.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.
- handles invalid
dependencies
value in service schema #164 - fix event emit error if payload is
null
,
0.11.10 (2018-01-19)
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: {
// Disable to add `ctx.params` to metrics payload. Default: false
params: false,
// Enable to add `ctx.meta` to metrics payload. Default: true
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)
ServiceBroker can resolve the strategy
from a string.
const broker = new ServiceBroker({
registry: {
strategy: "Random"
// strategy: "RoundRobin"
}
});
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
# Load the default .env file from current directory
$ moleculer-runner --env
# Load the specified .my-env file
$ moleculer-runner --envfile .my-env
- fixed hot reloading after broken service files by @askuzminov (#155)
- allow fallbackResponse to be falsy values
0.11.8 (2017-12-15)
d.ts
has been improved.
0.11.7 (2017-12-05)
d.ts
has been improved.
0.11.6 (2017-11-07)
In action cache keys you can use meta keys with #
prefix.
broker.createService({
name: "posts",
actions: {
list: {
cache: {
// Cache key: "limit" & "offset" from ctx.params, "user.id" from ctx.meta
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 // 30 seconds
}
}
});
broker.createService({
name: "posts",
actions: {
list: {
cache: {
// This cache entries will be expired after 5 seconds instead of 30.
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) {
// Generate a cache key
return ...;
}
}
}
});
d.ts
has been improved by @rmccallum81
0.11.5 (2017-10-12)
strategy
option has been fixed in broker option #121
0.11.4 (2017-10-11)
- Moleculer Runner arguments have been fixed (
services
arg) - update AMQP default queue options by @Nathan-Schwartz #119
0.11.3 (2017-10-10)
- The
ack
handling has been fixed in AMQP transporter. - AMQP RCP integration tests are added.
0.11.2 (2017-10-06)
Service dependencies #102
The Service
schema has a new dependencies
property. The serice can wait for other dependening ones when it starts. This way you don't need to call waitForServices
in started
any longer.
module.exports = {
name: "posts",
settings: {
$dependencyTimeout: 30000 // Default: 0 - no timeout
},
dependencies: [
"likes", // shorthand w/o version
{ name: "users", version: 2 }, // with numeric version
{ name: "comments", version: "staging" } // with string version
],
started() {
this.logger.info("Service started after the dependent services available.");
}
....
}
The started
service handler is called once the likes
, users
and comments
services are registered (on the local or remote nodes).
Pending request queue size limit #111
The ServiceBroker
has a new maxQueueSize
option under transit
key. The broker protects the process to avoid crash during a high load with it. The maxQueueSize
default value is 50,000. If pending request queue size reaches it, broker rejects the request with a QueueIsFull
(retryable) error.
const broker = new ServiceBroker({
transporter: "NATS",
transit: {
maxQueueSize: 10 * 1000
}
}
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)
Service metadata #91
The Service
schema has a new metadata
property. The Moleculer modules doesn't use it, so you can use it whatever you want.
broker.createService({
name: "posts",
settings: {},
metadata: {
scalable: true,
priority: 5
},
actions: { ... }
});
The
metadata
is transferred between nodes, you can access it via$node.services
. Or inside service withthis.metadata
like settings.
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
});
- ping nodes with
broker.sendPing
instead ofbroker.transit.sendPing
. index.d.ts
updated to v0.11- AMQP integration tests has been rewritten.
- process exit code changed from
2
to1
inbroker.fatal
. Reason:2
is reserved by Bash for builtin misuse. More info
0.11.0 (2017-09-12)
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.
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.
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
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.
Some internal broker methods have been removed or renamed.
broker.bus
has been removed.broker.on
has been removed. Useevents
in service schema instead.broker.once
has been removed.broker.off
has been removed.broker.getService
has been renamed tobroker.getLocalService
broker.hasService
has been removed.broker.hasAction
has been removed.broker.getAction
has been deprecated.broker.isActionAvailable
has been removed.
Internal action ($node.list
, $node.services
, $node.actions
, $node.health
) responses are changed. New internal action ($node.events
) to list event subscriptiion is added.
heartbeatInterval
default value is changed from10
to5
.heartbeatTimeout
default value is changed from30
to15
.circuitBreaker.maxFailures
default value is changed from5
to3
.logFormatter
accepts string. Thesimple
value is a new formatter to show only log level & log messages.
New PING & PONG feature has been implemented. Ping remite nodes to measure the network latency and system time differences.
broker.createService({
name: "test",
events: {
"$node.pong"({ nodeID, elapsedTime, timeDiff }) {
this.logger.info(`Pong received from '${nodeID}' - Time: ${elapsedTime}ms, System time difference: ${timeDiff}ms`);
}
}
});
broker.start().then(() => broker.transit.sendPing(/*nodeID*/));
The Validator in ServiceBroker is plugable. So you can change the built-in fastest-validator
to a slower one :) Example Joi validator
If your services depend on other ones, use the waitForService
method to make services wait until dependencies start.
let svc = broker.createService({
name: "seed",
started() {
return this.waitForServices(["posts", "users"]).then(() => {
// Do work...
});
}
});
Signature:
this.waitForServices(serviceNames: String|Array<String>, timeout: Number/*milliseconds*/, interval: Number/*milliseconds*/): Promise
We added some new Moleculer error classes.
MoleculerRetryableError
- Common Retryable error. Caller retries the request ifretryCount > 0
.MoleculerServerError
- Common server error (5xx).MoleculerClientError
- Common client/request error (4xx).ServiceNotAvailable
- Raises if the service is registered but isn't available (no live nodes or CB disabled them).ProtocolVersionMismatchError
- Raises if connect a node with an older client (<= v0.10.0)).
- The cachers don't listen "cache.clean" event.
0.10.0 (2017-08-20)
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) { ... }
}
}
The internalActions
broker option is renamed to internalServices
.
The createNewContext
broker method is moved to Context
class as a static method.
Migration guide:
Before:
let ctx = broker.createNewContext(action, nodeID, params, opts);
After:
let ctx = Context.create(broker, action, nodeID, params, opts);
// or better
let ctx = broker.ContextFactory.create(broker, action, nodeID, params, opts);
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()
}
});
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"
}
Hot reload services #82
The ServiceBroker supports hot reloading services. If you enable it broker will watch file changes. If you modify service file, broker will reload it on-the-fly. Demo video
Note: Hot reloading is only working with Moleculer Runner or if you load your services with
broker.loadService
orbroker.loadServices
.
Usage
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
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)
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
.
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
.
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");
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.
Better logging #61
The whole Moleculer logger is rewritten. It supports better the external loggers. The built-in log message format is also changed.
const broker = createBroker({
logger: console,
logLevel: "info"
});
With custom logFormatter
const broker = new ServiceBroker({
logger: console,
logFormatter(level, args, bindings) {
return level.toUpperCase() + " " + bindings.nodeID + ": " + args.join(" ");
}
});
broker.logger.warn("Warn message");
broker.logger.error("Error message");
Output:
WARN dev-pc: Warn message
ERROR dev-pc: Error message
const pino = require("pino")({ level: "info" });
const broker = new ServiceBroker({
logger: bindings => pino.child(bindings)
});
const bunyan = require("bunyan");
const logger = bunyan.createLogger({ name: "moleculer", level: "info" });
const broker = new ServiceBroker({
logger: bindings => logger.child(bindings)
});
const broker = new ServiceBroker({
logger: bindings => new winston.Logger({
transports: [
new (winston.transports.Console)({
timestamp: true,
colorize: true,
prettyPrint: true
})
]
})
});
const WinstonContext = require("winston-context");
const winston = require("winston");
const broker = createBroker({
logger: bindings => new WinstonContext(winston, "", bindings)
});
Please note! Some external loggers have not
trace
&fatal
log methods (e.g.: winston). In this case you have to extend your logger.
const WinstonContext = require("winston-context");
const winston = require("winston");
const { extend } = require("moleculer").Logger;
const broker = createBroker({
logger: bindings => extend(new WinstonContext(winston, "", bindings))
});
The bindings
contains the following properties:
ns
- namespacenodeID
- nodeIDmod
- type of core module:broker
,cacher
,transit
,transporter
svc
- service namever
- service version
Please avoid to use these property names when you log an Object
. For example: the broker.logger.error({ mod: "peanut" })
overrides the original mod
value!
Available to load & destroy services after the broker started. For example you can hot-reload your services in runtime. The remote nodes will be notified about changes and the broker will emit a services.changed
event locally.
Example
broker.start().then(() => {
setTimeout(() => {
// Create a new service after 5s
broker.createService({
name: "math",
actions: {
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b);
},
}
});
}, 5000);
setTimeout(() => {
// Destroy a created service after 10s
let svc = broker.getService("math");
broker.destroyService(svc);
}, 10000);
});
Multiple service calls #31
With broker.mcall
method you can call multiple actions (in parallel).
Example with Array
broker.mcall([
{ action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
{ action: "users.find", params: {limit: 5, sort: "username"} }
]).then(results => {
let posts = results[0];
let users = results[1];
})
Example with Object
broker.mcall({
posts: { action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
users: { action: "users.find", params: {limit: 5, sort: "username"} }
}).then(results => {
let posts = results.posts;
let users = results.users;
})
0.8.5 (2017-08-06)
- fixed logger method bindings.
- fixed transporter shutdown errors #62
0.8.4 (2017-07-24)
- 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)
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.
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)
-
fixed Redis cacher option resolver in ServiceBroker. Now it accepts connection string.
const broker = new ServiceBroker({ cacher: "redis://localhost" });
The fastest-validator is updated to v0.5.0. It supports multi rules & custom validators.
0.8.1 (2017-07-03)
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.
0.8.0 (2017-06-21)
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.
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
// Connect to the NATS default (localhost) server
const broker = new ServiceBroker({
transporter: "NATS"
});
// Connect to a NATS server with connection string
const broker = new ServiceBroker({
transporter: "nats://nats-server:4222"
});
// Connect to a NATS server with transporter options
const broker = new ServiceBroker({
transporter: {
type: "NATS",
options: {
prefix: "TEST",
nats: {
host: "nats-server",
user: "admin",
pass: "nats-pass"
}
}
}
});
Usage for cachers
// Use a memory cacher
const broker = new ServiceBroker({
cacher: true
// or
// cacher: "Memory"
});
// Use a Redis cacher with default options
const broker = new ServiceBroker({
cacher: "Redis"
});
// Use a Redis cacher with options
const broker = new ServiceBroker({
cacher: {
type: "Redis",
options: {
ttl: 100
}
}
});
Usage for serializers
// Use the Avro serializer
const broker = new ServiceBroker({
serializers: "Avro"
});
// Use the Protocol Buffer serializer
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, // Enable this feature
maxFailures: 5, // Trip breaker on 5 failures
halfOpenTime: 10 * 1000 // 10 sec to switch to `half-open` state
failureOnTimeout: true // Failure if request timed out
failureOnReject: true // Failure if request rejected with error code >= 500
}
});
nodeUnavailable
method is dropped.
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, // Load balancing strategy
preferLocal: true // First call local service if available
}
});
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 });
// Start REPL
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
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"
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" });
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: {
// Change port setting
port: 8080
},
actions: {
myAction() {
// Add a new action to apiGwService service
}
},
created() {
// Overwrite apiGwService.created handler
}
});
Or you can merge it manually with mergeSchemas
method.
let mergedSchema = broker.mergeSchemas(origSchema, modifications);
broker.createService(mergedSchema);
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: {
// Change port setting
port: 8080
},
actions: {
myAction() {
// Add a new action to apiGwService service
}
}
}
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
});
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
.
We merged the node.connected
and node.reconnected
events. The payload is changed:
{
node: {...},
reconnected: false // it indicates the node is connected or reconnected
}
We merged also the node.disconnected
and node.broken
events. The payload is changed:
{
node: {...},
unexpected: true // True: broken, not coming heart-beat, False: received "DISCONNECT" packet
}
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
The code of ServiceNotFoundError
is changed from 501
to 404
. More info
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 events contains two nodeID properties.
nodeID
: the "caller" nodeIDtargetNodeID
: in case of remote call this is the remote nodeID
If an action responses an error on a remote node, the transporter will send back the error to the caller with the stack traces.
// It will print the original error stack trace.
broker.call("account.deposit").catch(err => console.log(err.stack));
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));
}
}
});
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 {
// Do something dangerous
} catch(err) {
broker.fatal("Dangerous thing is happened!", err, true);
}
- new output of
$node.actions
and$node.services
- In packet
INFO
&DISCOVER
changed theactions
property toservices
and now it contains all services with actions of node - splitted
broker.registerService
toregisterLocalService
andregisterRemoteService
- new
broker.unregisterServicesByNode
. It will be called when a node disconnected
Serializers for transporters #10
Implemented pluggable serializers. Built-in serializers:
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!
Added metricsRate
options to broker. This property sets the rate of sampled calls.
1
means to metric all calls0.5
means to metric 50% of calls0.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 with meta data
broker.call("user.create", { name: "Adam", status: true}, {
timeout: 1000,
meta: {
// Send logged in user data with request to the service
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!");
}
}
});
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.
- Can be use timeout & fallback response in local calls.
- Timeout handling move from
Transit
toServiceBroker
- Remove
wrapContentAction
- In case of calling error, Node will be unavailable only if the error code >=
500
- Removed
createSubContext
- Removed
ctx.parent
and addedctx.parentID
- Removed options in constructor. New constructor syntax:
let ctx = new Context(broker, action); ctx.setParams({ a: 5 }); ctx.generateID(); // for metrics ctx.requestID = requestID;
- Add Context reference to returned Promise
const p = broker.call("user.create"); console.log("Context:", p.ctx);
If an event triggered remotely on an other node, broker passes the nodeID of sender to the event handler as 2nd parameter.
// Usage in subscription
broker.on("**", (payload, sender) => console.log(`Event from ${sender || "local"}:`, payload));
// Usage in Service schema
broker.createService({
...
events: {
something(payload, sender) {
console.log(`Something happened on '${sender}':`, payload);
}
}
});
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.
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!'
} ]
- Added 2 new log levels (
fatal
andtrace
); - Removed unused
log
level. Useinfo
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 fallbackwarn
->error
->info
error
->info
fatal
->error
->info
First release.