Skip to content

Instantly share code, notes, and snippets.

@manjufy
Forked from icebob/saga-sample.js
Last active September 26, 2020 07:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manjufy/2c512b969ee1bc43af410bc4777ddef1 to your computer and use it in GitHub Desktop.
Save manjufy/2c512b969ee1bc43af410bc4777ddef1 to your computer and use it in GitHub Desktop.
Saga middleware PoC for Moleculer
"use strict";
const _ = require("lodash");
const chalk = require("chalk");
const Promise = require("bluebird");
const ServiceBroker = require("../src/service-broker");
const { MoleculerError } = require("../src/errors");
// --- SAGA MIDDLEWARE ---
const SagaMiddleware = function() {
return {
localAction(handler, action) {
if (action.saga) {
const opts = action.saga;
return function sagaHandler(ctx) {
return handler(ctx)
.then(res => {
if (opts.compensation) {
if (!ctx.meta.$saga) {
ctx.meta.$saga = {
compensations: []
};
}
const comp = {
action: opts.compensation.action
};
if (opts.compensation.params) {
comp.params = opts.compensation.params.reduce((a, b) => {
_.set(a, b, _.get(res, b));
return a;
}, {});
}
ctx.meta.$saga.compensations.unshift(comp);
}
return res;
})
.catch(err => {
if (ctx.meta.$saga && ctx.meta.$saga.compensations) {
// Start compensating
ctx.service.logger.warn(chalk.red.bold("Some error occured. Start compensating..."));
ctx.service.logger.info(ctx.meta.$saga.compensations);
if (ctx.meta.$saga && Array.isArray(ctx.meta.$saga.compensations)) {
return Promise.map(ctx.meta.$saga.compensations, item => {
return ctx.call(item.action, item.params);
}).then(() => {
throw err;
});
}
}
throw err;
});
};
}
return handler;
}
};
};
// --- BROKER ---
const broker = new ServiceBroker({
logFormatter: "short",
middlewares: [
SagaMiddleware()
]
});
// --- CARS SERVICE ---
broker.createService({
name: "cars",
actions: {
reserve: {
saga: {
compensation: {
action: "cars.cancel",
params: ["id"]
}
},
handler(ctx) {
this.logger.info(chalk.cyan.bold("Car is reserved."));
return {
id: 5,
name: "Honda Civic"
};
}
},
cancel: {
handler(ctx) {
this.logger.info(chalk.yellow.bold(`Cancel car reservation of ID: ${ctx.params.id}`));
}
}
}
});
// --- HOTELS SERVICE ---
broker.createService({
name: "hotels",
actions: {
book: {
saga: {
compensation: {
action: "hotels.cancel",
params: ["id"]
}
},
handler(ctx) {
this.logger.info(chalk.cyan.bold("Hotel is booked."));
return {
id: 8,
name: "Holiday Inn",
from: "2019-08-10",
to: "2019-08-18"
};
}
},
cancel: {
handler(ctx) {
this.logger.info(chalk.yellow.bold(`Cancel hotel reservation of ID: ${ctx.params.id}`));
}
}
}
});
// --- FLIGHTS SERVICE ---
broker.createService({
name: "flights",
actions: {
book: {
saga: {
compensation: {
action: "flights.cancel",
params: ["id"]
}
},
handler(ctx) {
return this.Promise.reject(new MoleculerError("Unable to book flight!"));
this.logger.info(chalk.cyan.bold("Flight is booked."));
return {
id: 2,
number: "SQ318",
from: "SIN",
to: "LHR"
};
}
},
cancel: {
handler(ctx) {
this.logger.info(chalk.yellow.bold(`Cancel flight ticket of ID: ${ctx.params.id}`));
}
}
}
});
// --- TRIP SAGA SERVICE ---
broker.createService({
name: "trip-saga",
actions: {
createTrip: {
saga: true,
async handler(ctx) {
try {
const car = await ctx.call("cars.reserve");
const hotel = await ctx.call("hotels.book");
const flight = await ctx.call("flights.book");
this.logger.info(chalk.green.bold("Trip is created successfully: "), { car, flight, hotel });
} catch(err) {
this.logger.error(chalk.red.bold("Trip couldn't be created. Reason: "), err.message);
}
}
}
}
});
// --- START ---
async function start() {
await broker.start();
//broker.repl();
await broker.call("trip-saga.createTrip");
}
start();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment