Skip to content

Instantly share code, notes, and snippets.

@anatomic
Created May 24, 2018 00:13
Show Gist options
  • Save anatomic/600886f69e700cb1bfc6133c483ad1bf to your computer and use it in GitHub Desktop.
Save anatomic/600886f69e700cb1bfc6133c483ad1bf to your computer and use it in GitHub Desktop.
Circuit Breaking Async
// This little workaround allows us to have a fallback value if our remote service fails us
let lastKnownGood = {
count: 0,
fixtures: [],
};
const fetchJson = (url, options) => fetch(url, options).then(res => res.json());
const fetchm = nAry(2, Async.fromPromise(fetchJson));
const toDate = d => new Date(d);
const parseFixture = compose(
mapProps({ date: toDate }),
pick(['date', 'status', 'matchday', 'homeTeamName', 'awayTeamName', 'result'])
);
const parseFixtureResponse = compose(
mapProps({ fixtures: map(parseFixture) }),
pick(['count', 'fixtures'])
);
const getFixtures = () =>
fetchm(`${API_BASE}/competitions/${COMPETITION_ID}/fixtures`, {
headers: { 'X-Auth-Token': API_KEY },
})
.map(parseFixtureResponse)
const cacheValidResponse = compose(
assoc('fromCache', false),
tap(f => (lastKnownGood = f))
);
const flow = () =>
getFixtures()
.map(cacheValidResponse)
.toPromise();
// Needs to be stateful to enable Circuit Breaking features
const getFixturesBrake = new Brakes(flow, { timeout: 120 });
module.exports = async (req, res) => {
return getFixturesBrake
.exec()
.catch(_ => ({ ...lastKnownGood, fromCache: true }));
};
// This little workaround allows us to have a fallback value if our remote service fails us
let lastKnownGood = {
count: 0,
fixtures: [],
};
const cacheValidResponse = compose(
assoc('fromCache', false),
tap(f => (lastKnownGood = f))
);
const fetchJson = (url, options) => fetch(url, options).then(res => res.json());
const fetchBreaker = new Brakes(fetchJson, { timeout: 120});
const fetchm = (url, options) => Async((_, res) => fetchBreaker
.exec(url.options)
.then(json => res(cacheValidResponse(json)))
.catch(e => {
debug("breaker-failure")(e);
res({...lastKnownGood, fromCache: true});
}));
const toDate = d => new Date(d);
const parseFixture = compose(
mapProps({ date: toDate }),
pick(['date', 'status', 'matchday', 'homeTeamName', 'awayTeamName', 'result'])
);
const parseFixtureResponse = compose(
mapProps({ fixtures: map(parseFixture) }),
pick(['count', 'fixtures'])
);
const getFixtures = () =>
fetchm(`${API_BASE}/competitions/${COMPETITION_ID}/fixtures`, {
headers: { 'X-Auth-Token': API_KEY },
})
.map(parseFixtureResponse)
module.exports = async (req, res) => {
return getFixtures().toPromise()
};
@anatomic
Copy link
Author

anatomic commented May 24, 2018

There's quite a bit of nuance in how both these approaches will affect the running service. Wrapping the underlying fetch call offers the finest grain resilience, especially if the app makes calls to many different services or endpoints to build up a response.

Wrapping the overall flow ignores where individual issues arise and instead takes the view that if anything fails or takes too long a cached response should be sent back.

I'm not sure I really like the backwards and forwards to Promises involved in working with a library like Brakes, however, making something suitable using Async or State or a combination feels like a reasonably big undertaking!

The http microservice library being used is Micro, by Zeit (https://github.com/zeit/micro)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment