Created
May 24, 2018 00:13
-
-
Save anatomic/600886f69e700cb1bfc6133c483ad1bf to your computer and use it in GitHub Desktop.
Circuit Breaking Async
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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() | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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)