npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

scatter-gather-circuit-breaker

v2.3.0

Published

Circuit Breaker module for the scatter-gather library.

Downloads

4

Readme

scatter-gather-circuit-breaker

Circuit Breaker module for the scatter-gather library.

This Circuit Breaker package provides a module that can be plugged into the scatter-gather package. It allows you to specify a sequential list of function calls that will each be wrapped in a circuit breaker.

You can also optionally configure a cache that will be used to cache the responses from your function outputs.

Usage

Basic usage

You can instantiate a new CircuitBreaker in the following manner:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    phases: [{
        name: 'myApiCall',
        impl: async (message, prevResults) => {
            return 'I made some API call and here is the results'
        }
    }]
})

You can specify more than one function to be invoked:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    phases: [
        {
            name: 'myApiCall',
            impl: async (message, prevResults) => {
                return 'I made some API call and here is the results'
            }
        },
        {
            name: 'myApiCall2',
            impl: async (message, prevResults) => {
                const firstCallResult = prevResults.myApiCall
                // Do something with the firstCallResult
                return 'Here are some more results'
            }
        }
    ]
})

The prevResults object contains the results from the phases that were sequentially executed before the current phase.

Once you have instantiated the circuit breaker object, you can call the processMessage function to execute the process of calling each phase:

const requestMessage = {
    id: 'FakeId',
    type: 'SomeMessageType',
    key: 'ByuId',
    body: {
        some: 'param'
    }
}
cb.processMessage(requestMessage)
    .then(responseMessage => {
        // Do something with the response
    })

The returned object from processMessage looks like the following:

{
    id: 'FakeId',
    type: 'SomeMessageType',
    key: 'ByuId',
    response: {
        item: 'Whatever was returned from the last phase of your circuit breaker'
        cachedLastUpdated: new Date() // If the returned result came from the cache, this tells when the result was cached. If it did not come from the cache, it will be undefined.
    },
    error: new CircuitBreakerError // If there were any errors during the process, this will be set, otherwise it will be undefined
}

The CircuitBreakerError object wraps the possible errors that could occur in both the cache and the api calls. You can access the original errors like this:

error.apiError  // The error returned by the API call (if any)
error.cacheError // The error returned by the cache (if any)

Using a cache

You can optionally specify a cache that the module should use to cache your responses. You can currently use one of two cache types:

  • Redis
  • DynamoDB

Redis

Here is an example of using a Redis cache:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    cache: {
        redis: {
            host: 'localhost',
            port: 6379
        }
    },
    phases: [
        {
            name: 'myApiCall',
            cacheTtl: 0, // Cache forever
            impl: async (message, prevResults) => {
                return 'I made some API call and here is the results'
            }
        },
        {
            name: 'myApiCall2',
            cacheTtl: 60, // Cache for 60 secons
            impl: async (message, prevResults) => {
                const firstCallResult = prevResults.myApiCall
                // Do something with the firstCallResult
                return 'Here are some more results'
            }
        }
    ]
})

Note that the cache property is where you specify the Redis configuration. This will be used as a look-aside cache by default.

Note that the output of each phase will be cached separately, so each phase allows you to specify a cacheTtl parameter that specifies how long you want to cache the output for that phase.

DynamoDB

You can also use DynamoDB as a cache backend:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    cache: {
        dynamo: {
            tableName: 'dsw88-test-cache-table',
            partitionKey: 'byuid',
            sortKey: 'phase',
            region: 'us-west-2'
        }  
    },
    phases: [
        {
            name: 'myApiCall',
            cacheTtl: 0, // Cache forever
            impl: async (message, prevResults) => {
                return 'I made some API call and here is the results'
            }
        },
        {
            name: 'myApiCall2',
            cacheTtl: 60, // Cache for 60 secons
            impl: async (message, prevResults) => {
                const firstCallResult = prevResults.myApiCall
                // Do something with the firstCallResult
                return 'Here are some more results'
            }
        }
    ]
})

Note that when using DyanmoDB as a cache, you must provide the table name and region where the table is located, as well as the paritition and range key attributes configured on the table.

Optional parameters

  • cachingStrategy: choose between 'look-aside', and 'read-through'. 'look-aside' is the default.
    • 'look-aside' calls both the cache and the api simultaneously and returns whichever returns first.
    • 'read-through' calls the cache first and then the api if necessary.
  • passThrough: if you want to only cache some responses and not others, specify a parameter that if found in the response, the response will not be cached.
const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    cache: {
        cachingStrategy: 'read-through', // prefer the cache over the api
        passThrough: 'validation_response',
        dynamo: {
            tableName: 'dsw88-test-cache-table',
            partitionKey: 'byuid',
            sortKey: 'phase',
            region: 'us-west-2'
        }
    },
    phases: [
        {
            name: 'myApiCall',
            cacheTtl: 0, // Cache forever
            impl: async (message, prevResults) => {
                if (message.type === 'someMessageType') {
                  return 'This response will be cached'
                }
                return {'validation_response': 'This response will not be cached'}
            }
        },
        {
            name: 'myApiCall2',
            cacheTtl: 60, // Cache for 60 secons
            impl: async (message, prevResults) => {
                const firstCallResult = prevResults.myApiCall
                // Check the firstCallResult for the passThrough variable
                if (firstCallResult.validation_response) {
                  return "validation_response found"
                }
                return "Here are some more results"
            }
        }
    ]
})

Configuring the circuit breaker

This module takes care of wrapping your functions in a circuit breaker for you. You can change the behavior of this circuit breaker when creating your instance:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    circuitBreaker: {
        timeout: 10000, // The time in milliseconds to wait for responses before throwing a timeout error
        errorThresholdPercentage: 50, // The percent of responses that need to fail before the circuit breaker fails open.
        resetTimeout: 30000 // The time in milliseconds to wait when a circuit breaker fails open before trying again to talk to the API.
    },
    phases: [{
        name: 'myApiCall',
        impl: async (message, prevResults) => {
            return 'I made some API call and here is the results'
        }
    }]
})

Only handling certain messages

By default the CircuitBreaker will process all messages sent to it by the scatter-gather framework. You can configure a set of message types that you only want to handle, and all others will be ignored by the CircuitBreaker:

const CircuitBreaker = require('scatter-gather-circuit-breaker').CircuitBreaker;
const cb = new CircuitBreaker({
    processedMessageTypes: [
        'someMessageType1',
        'someMessageType2'
    ],
    phases: [{
        name: 'myApiCall',
        impl: async (message, prevResults) => {
            return 'I made some API call and here is the results'
        }
    }]
})