Skip to content

Releases: moleculerjs/moleculer

v0.11.5

12 Oct 19:50
Compare
Choose a tag to compare

Changes

  • strategy option has been fixed in broker option #121

v0.11.4

11 Oct 20:04
Compare
Choose a tag to compare

Changes

  • Moleculer Runner arguments have been fixed (services arg)
  • update AMQP default queue options by @Nathan-Schwartz #119

v0.11.3

10 Oct 18:24
Compare
Choose a tag to compare

Changes

  • The ack handling has been fixed in AMQP transporter.
  • AMQP RCP integration tests are added.

v0.11.2

06 Oct 08:50
Compare
Choose a tag to compare

New

Service dependencies #102

The Service schema has a new dependencies property. The serice can wait for other dependening ones when it starts. This way you don't need to call waitForServices in started any longer.

module.exports = {
  name: "posts",
  settings: {
      $dependencyTimeout: 30000 // Default: 0 - no timeout
  },
  dependencies: [
      "likes", // shorthand w/o version
      { name: "users", version: 2 }, // with numeric version
      { name: "comments", version: "staging" } // with string version
  ],
  started() {
      this.logger.info("Service started after the dependent services available.");
  }
  ....
}

The started service handler is called once the likes, users and comments services are registered (on the local or remote nodes).

Pending request queue size limit #111

The ServiceBroker has a new maxQueueSize option under transit key. The broker protects the process to avoid crash during a high load with it. The maxQueueSize default value is 50,000. If pending request queue size reaches it, broker rejects the request with a QueueIsFull (retryable) error.

let broker = new ServiceBroker({
	transporter: "NATS",
	transit: {
		maxQueueSize: 10 * 1000
	}
}

Changes

The waitForServices method supports service versions #112

By @imatefx, the waitForServices broker & service methods support service versions. Use the following formats to define version in a dependency:

module.exports = {
    name: "test",
    dependencies: { name: "users", version: 2 }
};

v0.11.1

27 Sep 16:54
Compare
Choose a tag to compare

New

Service metadata #91

The Service schema has a new metadata property. The Moleculer modules doesn't use it, so you can use it whatever you want.

broker.createService({
    name: "posts",
    settings: {},
    metadata: {
        scalable: true,
        priority: 5
    },

    actions: { ... }
});

The metadata is transferred between nodes, you can access it via $node.services. Or inside service with this.metadata like settings.

NATS transporter supports to use the built-in balancer

The NATS transporter has been changed. It supports to use the NATS built-in balancer instead of Moleculer balancer. In this case every call & emit will be transferred through NATS message broker.

let broker = new ServiceBroker({
    transporter: "NATS",
    disableBalancer: true
});

Changes

  • ping nodes with broker.sendPing instead of broker.transit.sendPing.
  • index.d.ts updated to v0.11
  • AMQP integration tests has been rewritten.
  • process exit code changed from 2 to 1 in broker.fatal. Reason: 2 is reserved by Bash for builtin misuse. More info

v0.11.0

12 Sep 11:51
Compare
Choose a tag to compare

Breaking changes

Protocol changed #86

The Moleculer transportation protocol has been changed. It means, the new (>= v0.11) versions can't communicate with the old (<= v0.10.x) ones.
You can find more information about changes in #86 issue.

Balanced events

The whole event handling has been rewritten. By now Moleculer supports event driven architecture. It means that event emits are balanced like action calls are.

For example, you have 2 main services: users & payments. Both subscribe to the user.created event. You start 3 instances from users service and 2 instances from payments service. If you emit the event with broker.emit('user.created'), broker groups & balances the event, so only one users and one payments service receive the event.
You can also send broadcast events with the broker.broadcast('user.created) command. This way every service instance on every node receives the event.
The broker.broadcastLocal('user.created') command sends events only to the local services.

Renamed & new internal events

Every internal event name starts with '$'. These events are not transferred to remote nodes.

Renamed events:

  • node.connected -> $node.connected
  • node.updated -> $node.updated
  • node.disconnected -> $node.disconnected
  • services.changed -> $services.changed. It is called if local or remote service list is changed.
  • circuit-breaker.closed -> $circuit-breaker.closed
  • circuit-breaker.opened -> $circuit-breaker.opened
  • circuit-breaker.half-opened -> $circuit-breaker.half-opened

New events:

  • global circuit breaker events for metrics: metrics.circuit-breaker.closed, metrics.circuit-breaker.opened, metrics.circuit-breaker.half-opened

Switchable built-in load balancer

The built-in Moleculer load balancer is switchable. You can turn it off, if the transporter has internal balancer (currently AMQP has it).

const broker = new ServiceBroker({
    disableBalancer: false
});

Please note! If built-in balancer is disabled, every call & emit (including local ones too) are transferred via transporter.

Removed broker methods

Some internal broker methods have been removed or renamed.

  • broker.bus has been removed.
  • broker.on has been removed. Use events in service schema instead.
  • broker.once has been removed.
  • broker.off has been removed.
  • broker.getService has been renamed to broker.getLocalService
  • broker.hasService has been removed.
  • broker.hasAction has been removed.
  • broker.getAction has been deprecated.
  • broker.isActionAvailable has been removed.

Changed local service responses

Internal action ($node.list, $node.services, $node.actions, $node.health) responses are changed. New internal action ($node.events) to list event subscriptiion is added.

Broker option changes

  • heartbeatInterval default value is changed from 10 to 5.
  • heartbeatTimeout default value is changed from 30 to 15.
  • circuitBreaker.maxFailures default value is changed from 5 to 3.
  • logFormatter accepts string. The simple value is a new formatter to show only log level & log messages.

New

Ping command

New PING & PONG feature has been implemented. Ping remite nodes to measure the network latency and system time differences.

broker.createService({
    name: "test",
    events: {
        "$node.pong"({ nodeID, elapsedTime, timeDiff }) {
            this.logger.info(`Pong received from '${nodeID}' - Time: ${elapsedTime}ms, System time difference: ${timeDiff}ms`);
        }
    }
});

broker.start().then(() => broker.transit.sendPing(/*nodeID*/));

Pluggable validator

The Validator in ServiceBroker is plugable. So you can change the built-in fastest-validator to a slower one :) Example Joi validator

Waiting for other services feature

If your services depend on other ones, use the waitForService method to make services wait until dependencies start.

let svc = broker.createService({
    name: "seed",
    started() {
        return this.waitForServices(["posts", "users"]).then(() => {
            // Do work...
        });
    }
});

Signature:

this.waitForServices(serviceNames: String|Array<String>, timeout: Number/*milliseconds*/, interval: Number/*milliseconds*/): Promise

New error types

We added some new Moleculer error classes.

  • MoleculerRetryableError - Common Retryable error. Caller retries the request if retryCount > 0.
  • MoleculerServerError - Common server error (5xx).
  • MoleculerClientError - Common client/request error (4xx).
  • ServiceNotAvailable - Raises if the service is registered but isn't available (no live nodes or CB disabled them).
  • ProtocolVersionMismatchError - Raises if connect a node with an older client (<= v0.10.0)).

Other changes

  • The cachers don't listen "cache.clean" event.

v0.10.0

20 Aug 08:24
Compare
Choose a tag to compare

Breaking changes

No more nodeID == null in local stuff

In all core modules removed the nullable nodeID. Every places (context, events, $node.* results) the nodeID contains a valid (local or remote) nodeID. On local nodes it equals with broker.nodeID.

Migration guide

Before:

if (ctx.nodeID == null) { ... }
// ---------
events: {
    "users.created"(payload, sender) {
        if (sender == null) { ... }
    }
}

After:

if (ctx.nodeID == ctx.broker.nodeID) { ... }
// ---------
events: {
    "users.created"(payload, sender) {
        if (sender == this.broker.nodeID) { ... }
    }
}

internalActions is renamed to internalServices

The internalActions broker option is renamed to internalServices.

Removed broker.createNewContext method

The createNewContext broker method is moved to Contextclass as a static method.

Migration guide:

Before:

let ctx = broker.createNewContext(action, nodeID, params, opts);

After:

let ctx = Context.create(broker, action, nodeID, params, opts);
// or better
let ctx = broker.ContextFactory.create(broker, action, nodeID, params, opts);

Removed LOCAL_NODE_ID constant

The recently added LOCAL_NODE_ID constant is removed. If you want to check the nodeID is local, please use the if (nodeID == broker.nodeID) syntax.

Class based pluggable Service registry strategies #75

By @WoLfulus, the service registry balancer strategy is now pluggable.

New syntax:

let Strategies = require("moleculer").Strategies;

let broker = new ServiceBroker({
    registry: {        
        strategy: new Strategies.RoundRobin()
    }
});

Custom strategy

You can create you custom strategy.

let BaseStrategy = require("moleculer").Strategies.Base;

class CustomStrategy extends BaseStrategy {
    select(list) {
        return list[0];
    }
};

let broker = new ServiceBroker({
    registry: {        
        strategy: new CustomStrategy()
    }
});

Metrics event payloads are changed

The metrics payload contains remoteCall and callerNodeID properties. The remoteCall is true if the request is called from a remote node. In this case the callerNodeID contains the caller nodeID.

metrics.trace.span.start:

{
    "action": {
        "name": "users.get"
    },
    "id": "123123123",
    "level": 1,
    "parent": 123,
    "remoteCall": true,
    "requestID": "abcdef",
    "startTime": 123456789,
    "nodeID": "node-1",
    "callerNodeID": "node-2"
}

metrics.trace.span.start:

{
    "action": {
        "name": "users.get"
    },
    "duration": 45,
    "id": "123123123",
    "parent": 123,
    "requestID": "abcdef",
    "startTime": 123456789,
    "endTime": 123456795,
    "fromCache": false,
    "level": 1,
    "remoteCall": true,
    "nodeID": "node-1",
    "callerNodeID": "node-2"
}

New

Hot reload services #82

The ServiceBroker supports hot reloading services. If you enable it broker will watch file changes. If you modify service file, broker will reload it on-the-fly.
Demo video

Note: Hot reloading is only working with Moleculer Runner or if you load your services with broker.loadService or broker.loadServices.

Usage

let broker = new ServiceBroker({
    logger: console,
    hotReload: true
});

broker.loadService("./services/test.service.js");

Usage with Moleculer Runner

Turn it on with --hot or -H flags.

$ moleculer-runner --hot ./services/test.service.js

Protocol documentation

Moleculer protocol documentation is available in docs/PROTOCOL.md file.

AMQP transporter #72

By @Nathan-Schwartz, AMQP (for RabbitMQ) transporter added to Moleculer project.

let broker = new ServiceBroker({
    transporter: "amqp://guest:guest@rabbitmq-server:5672"
});

let broker = new ServiceBroker({
    transporter: new AmqpTransporter({
        amqp: {
            url: "amqp://guest:guest@localhost:5672",
            eventTimeToLive: 5000,
            prefetch: 1 
        }
    });
});

v0.9.0

10 Aug 13:14
Compare
Choose a tag to compare

Breaking changes

Namespace support, removed prefix options #57

The broker has a new namespace option to segment your services. For example, you are running development & production services (or more production services) on the same transporter. If you are using different namespace you can avoid collisions between different environments.

You can reach it in your services as this.broker.namespace.

Thereupon the prefix option in transporters & cachers is removed.

Example

const broker = new ServiceBroker({
    logger: console,
    namespace: "DEV",
    transporter: "NATS",
    cacher: "Redis"
});

In this case the transporter & cacher prefix will be MOL-DEV.

Renamed internal service settings

The useVersionPrefix is renamed to $noVersionPrefix. The serviceNamePrefix is renamed to $noServiceNamePrefix. Both settings logical state is changed.
The cache setting is renamed to $cache.

Migration guide

Before

broker.createService({
    name: "test",
    settings: {
        useVersionPrefix: false,
        serviceNamePrefix: false,
        cache: true
    }
});

After

broker.createService({
    name: "test",
    settings: {
        $noVersionPrefix: true,
        $noServiceNamePrefix: true,
        $cache: true
    }
});

Changed versioned action names #58

Based on #58 if service version is a String, the version in action names won't be prefixed with v, expect if it is a Number.

Example

broker.createService({
    name: "test",
    version: 3,
    actions: {
        hello(ctx) {}
    }
});
broker.call("v3.test.hello");

broker.createService({
    name: "test",
    version: "staging",
    actions: {
        hello(ctx) {}
    }
});
broker.call("staging.test.hello");

Module log level configuration is removed

The module log level is not supported. The logLevel option can be only String. It is used if the logger is the console. In case of external loggers you have to handle log levels.

New

Better logging #61

The whole Moleculer logger is rewritten. It supports better the external loggers. The built-in log message format is also changed.

Built-in console logger

const broker = createBroker({ 
    logger: console, 
    logLevel: "info"
});

New console output:
image

With custom logFormatter

const broker = new ServiceBroker({ 
    logger: console, 
    logFormatter(level, args, bindings) {
        return level.toUpperCase() + " " + bindings.nodeID + ": " + args.join(" ");
    }
});
broker.logger.warn("Warn message");
broker.logger.error("Error message");

Output:

WARN dev-pc: Warn message
ERROR dev-pc: Error message

External loggers

Pino

const pino = require("pino")({ level: "info" });
const broker = new ServiceBroker({ 
    logger: bindings => pino.child(bindings)
});

Sample output:
image

Bunyan

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

Sample output:
image

Winston

const broker = new ServiceBroker({ 
    logger: bindings => new winston.Logger({
        transports: [
            new (winston.transports.Console)({
                timestamp: true,
                colorize: true,
                prettyPrint: true
            })
        ]
    })
});

Winston context

const WinstonContext = require("winston-context");
const winston = require("winston");
const broker = createBroker({ 
    logger: bindings => new WinstonContext(winston, "", bindings)
});

Please note! Some external loggers have not trace & fatal log methods (e.g.: winston). In this case you have to extend your logger.

const WinstonContext = require("winston-context");
const winston = require("winston");
const { extend } = require("moleculer").Logger;
const broker = createBroker({ 
    logger: bindings => extend(new WinstonContext(winston, "", bindings))
});

The bindings contains the following properties:

  • ns - namespace
  • nodeID - nodeID
  • mod - type of core module: broker, cacher, transit, transporter
  • svc - service name
  • ver - service version

Please avoid to use these property names when you log an Object. For example: the broker.logger.error({ mod: "peanut" }) overrides the original mod value!

Dynamic service load & destroy

Available to load & destroy services after the broker started. For example you can hot-reload your services in runtime. The remote nodes will be notified about changes and the broker will emit a services.changed event locally.

Example

broker.start().then(() => {

    setTimeout(() => {
        // Create a new service after 5s
        broker.createService({
            name: "math",
            actions: {
                add(ctx) {
                    return Number(ctx.params.a) + Number(ctx.params.b);
                },
            }
        });

    }, 5000);

    setTimeout(() => {
        // Destroy a created service after 10s
        let svc = broker.getService("math");
        broker.destroyService(svc);

    }, 10000);

});

Multiple service calls #31

With broker.mcall method you can call multiple actions (in parallel).

Example with Array

broker.mcall([
    { action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
    { action: "users.find", params: {limit: 5, sort: "username"} }
]).then(results => {
    let posts = results[0];
    let users = results[1];
})

Example with Object

broker.mcall({
    posts: { action: "posts.find", params: {limit: 5, offset: 0}, options: { timeout: 500 } },
    users: { action: "users.find", params: {limit: 5, sort: "username"} }
}).then(results => {
    let posts = results.posts;
    let users = results.users;
})

v0.8.4

24 Jul 09:58
Compare
Choose a tag to compare

Fixes

  • fixed Calling error! TypeError : Cannot read property 'requestID' of undefined error when you call a local action from other one directly.

v0.8.3

24 Jul 09:43
Compare
Choose a tag to compare

New

Removable actions in mixins

You can remove an existing action when mixing a service.

broker.createService({
    name: "test",
    mixins: [OtherService],
    actions: {
        dangerAction: false
    }
});

In the test service the dangerAction action won't be registered.

Support NPM modules in moleculer-runner

You can load services from NPM module in moleculer-runner.

With CLI arguments

$ moleculer-runner -r npm:moleculer-fake npm:moleculer-twilio

With env

$ SERVICES=posts,users,npm:moleculer-fale,npm:moleculer-twilio

$ moleculer-runner