opossum
Opossum is a Node.js circuit breaker that executes asynchronous functions
and monitors their execution status. When things start failing, opossum
plays dead and fails fast. If you want, you can provide a fallback function
to be executed when in the failure state.
For more about the circuit breaker pattern, there are lots of resources
on the web - search it! Fowler's blog post is one place to
start reading.
Usage
Let's say you've got an API that depends on something that might fail -
a network operation, or disk read, for example. Wrap those functions up in a
CircuitBreaker
and you have control over your destiny.
const circuitBreaker = require('opossum');
function asyncFunctionThatCouldFail (x, y) {
return new Promise((resolve, reject) => {
});
}
const options = {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
};
const breaker = circuitBreaker(asyncFunctionThatCouldFail, options);
breaker.fire(params)
.then(console.log)
.catch(console.error);
Fallback
You can also provide a fallback function that will be executed in the
event of failure. To take some action when the fallback is performed,
listen for the fallback
event.
const breaker = circuitBreaker(asyncFunctionThatCouldFail, options);
breaker.fallback(() => 'Sorry, out of service right now');
breaker.on('fallback', (result) => reportFallbackEvent(result));
Once the circuit has opened, a timeout is set based on options.resetTimeout
.
When the resetTimeout
expires, opossum
will enter the halfOpen
state.
Once in the halfOpen
state, the next time the circuit is fired, the circuit's
action will be executed again. If successful, the circuit will close and emit
the close
event. If the action fails or times out, it immediately re-enters
the open
state.
When a fallback function is triggered, it's considered a failure, and the
fallback function will continue to be executed until the breaker is closed.
Browser
Opossum really shines in a browser. You can use it to guard against network
failures in your AJAX calls.
We recommend using webpack to bundle your applications,
since it does not have the effect of polluting the window
object with a global.
However, if you need it, you can access a circuitBreaker
function in the global
namespace by doing something similar to what is shown in the below example.
Here is an example using hapi.js. See the
examples
folder for more detail.
Include opossum.js
in your HTML file.
<html>
<head>
<title>My Super App</title>
<script type='text/javascript' src="/jquery.js"></script>
<script type='text/javascript' src="/opossum.js"></script>
<script type='text/javascript' src="/app.js"></script>
<body>
...
</body>
</head>
</html>
In your application, set a route to the file, pointing to
node_modules/opossum/dist/opossum-min.js
.
const server = new Hapi.Server();
server.register(require('inert', (err) => possibleError(err)));
server.route({
method: 'GET',
path: '/opossum.js',
handler: {
file: {
path: path.join(__dirname, 'node_modules', 'opossum', 'dist', 'opossum-min.js'),
}
}
});
In the browser's global scope will be a circuitBreaker
function. Use it
to create circuit breakers, guarding against network failures in your REST
API calls.
const route = 'https://example-service.com/rest/route';
const circuitBreakerOptions = {
timeout: 500,
maxFailures: 3,
resetTimeout: 5000
};
const circuit = circuitBreaker(() => $.get(route), circuitBreakerOptions);
circuit.fallback(() => `${route} unavailable right now. Try later.`));
circuit.on('success', (result) => $(element).append(JSON.stringify(result)}));
$(() => {
$('#serviceButton').click(() => circuit.fire().catch((e) => console.error(e)));
});
Events
A CircuitBreaker
will emit events for important things that occur.
Here are the events you can listen for.
fire
- emitted when the breaker is fired.reject
- emitted when the breaker is open (or halfOpen).timeout
- emitted when the breaker action times out.success
- emitted when the breaker action completes successfullyfailure
- emitted when the breaker action fails, called with the erroropen
- emitted when the breaker state changes to open
close
- emitted when the breaker state changes to closed
halfOpen
- emitted when the breaker state changes to halfOpen
fallback
- emitted when the breaker has a fallback function and executes itsemaphore-locked
- emitted when the breaker is at capacity and cannot execute the requesthealth-check-failed
- emitted when a user-supplied health check function returns a rejected promise
Handling events gives a greater level of control over your application behavior.
const circuit = circuitBreaker(() => $.get(route), circuitBreakerOptions);
circuit.fallback(() => ({ body: `${route} unavailable right now. Try later.` }));
circuit.on('success',
(result) => $(element).append(
makeNode(`SUCCESS: ${JSON.stringify(result)}`)));
circuit.on('timeout',
() => $(element).append(
makeNode(`TIMEOUT: ${route} is taking too long to respond.`)));
circuit.on('reject',
() => $(element).append(
makeNode(`REJECTED: The breaker for ${route} is open. Failing fast.`)));
circuit.on('open',
() => $(element).append(
makeNode(`OPEN: The breaker for ${route} just opened.`)));
circuit.on('halfOpen',
() => $(element).append(
makeNode(`HALF_OPEN: The breaker for ${route} is half open.`)));
circuit.on('close',
() => $(element).append(
makeNode(`CLOSE: The breaker for ${route} has closed. Service OK.`)));
circuit.on('fallback',
(data) => $(element).append(
makeNode(`FALLBACK: ${JSON.stringify(data)}`)));
Promises vs. Callbacks
The opossum
API returns a Promise
from CircuitBreaker.fire()
.
But your circuit action - the async function that might fail -
doesn't have to return a promise. You can easily turn Node.js style
callback functions into something opossum
understands by using
circuitBreaker.promisify()
.
const fs = require('fs');
const circuitBreaker = require('opossum');
const readFile = circuitBreaker.promisify(fs.readFile);
const breaker = circuitBreaker(readFile, options);
breaker.fire('./package.json', 'utf-8')
.then(console.log)
.catch(console.error);
And just for fun, your circuit doesn't even really have to be a function.
Not sure when you'd use this - but you could if you wanted to.
const breaker = circuitBreaker('foo', options);
breaker.fire()
.then(console.log)
.catch(console.error);
Typings
Typings are available here.
If you'd like to add them, run npm install @types/opossum
in your project.
Hystrix Metrics
A Hystrix Stream is available for use with a Hystrix Dashboard using the circuitBreaker.hystrixStats.getHystrixStream
method.
This method returns a Node.js Stream, which makes it easy to create an SSE stream that will be compliant with a Hystrix Dashboard.
Additional Reading: Hystrix Metrics Event Stream, Turbine, Hystrix Dashboard