UNPKG

4.56 kBJavaScriptView Raw
1const isObject = require('lodash.isobject')
2const precond = require('precond')
3
4const dependencyLevel = require('./dependency-level')
5const HealthFactoryProvider = require('./health-strategies').HealthFactoryProvider
6const ExponentialBackoffStrategy = require('./backoff-strategies').ExponentialBackoffStrategy
7
8/**
9 * @class
10 * @classdesc Holds information about the health of a dependency, and executes strategies for
11 * checking a dependency's health
12 */
13class Dependency {
14 /**
15 * @param {Function} [backoffStrategy=ExponentialBackoffStrategy] The behaviour of the delay
16 * when re-checking a dependencys
17 * @param {Function} [healthStrategy=HttpStrategy] The strategy used to check a dependency's
18 * health
19 * @param {Enum} [level] A description of the reliance on this dependency
20 * @param {String} name The identifier to be used for a dependency
21 * @param {String} [url] The location where the dependency can be reached
22 */
23 constructor ({ backoffStrategy, healthStrategy, level, name, url } = {}) {
24 precond.checkArgument(
25 isObject(backoffStrategy) && backoffStrategy.execute,
26 'backoffStrategy must be of type object and have a "execute" method'
27 )
28 precond.checkArgument(
29 isObject(healthStrategy) && healthStrategy.check,
30 'healthStrategy must be of type object and have a "check" method'
31 )
32 precond.checkArgument(
33 level === 'HARD' || level === 'SOFT',
34 'a dependency must either have a dependency level of HARD or soft'
35 )
36 precond.checkIsString(name, 'The dependency name must be a string')
37
38 if (url) {
39 precond.checkIsString(url, 'The dependency url must be a string')
40 }
41
42 this.backoffStrategy = backoffStrategy
43 this.healthStrategy = healthStrategy
44 this.level = level
45 this.healthy = true
46 this.name = name
47 this.url = url
48 }
49
50 /**
51 * @return {Object} A description of the dependency's health
52 */
53 get healthSummary () {
54 /**
55 * Health information about a dependency
56 * Strategies may alter this summary to provide additional information
57 * @typedef {Object} Dependency~HealthInfo
58 * @property {String} name
59 * @property {Boolean} healthy
60 * @property {String} level
61 * @property {Date} lastChecked
62 */
63 const summary = {
64 name: this.name,
65 healthy: this.healthy,
66 level: this.level,
67 lastChecked: this.lastChecked
68 }
69 if (this.healthStrategy.improveSummary) {
70 this.healthStrategy.improveSummary(summary, this.lastCheckResult)
71 }
72 return summary
73 }
74
75 /**
76 * Sets a dependency's status to healthy
77 */
78 onHealthy () {
79 this.healthy = true
80 this.lastChecked = new Date()
81 }
82
83 /**
84 * Sets a dependency's status to unhealthy
85 */
86 onUnhealthy () {
87 this.healthy = false
88 this.lastChecked = new Date()
89 }
90
91 /**
92 * Performs any cleanup needed
93 */
94 cleanup () {
95 this.healthStrategy.cleanup()
96 }
97
98 /**
99 * @param {Integer} failAfter The number of times a dependency's health
100 * will be checked, before giving up
101 * @param {Integer} maxDelay The maximum interval of time to wait when retrying
102 * @return {Promise}
103 */
104 check (retries = 1, maxDelay = 30000) {
105 // console.log('here', retries, maxDelay)
106 return this
107 .backoffStrategy
108 .execute({
109 func: () => this.healthStrategy.check(this.url),
110 retries,
111 maxDelay
112 })
113 .catch((e) => {
114 this.onUnhealthy()
115 this.lastCheckResult = undefined
116
117 return Promise.reject(this.healthSummary)
118 })
119 .then((result) => {
120 this.lastCheckResult = result
121 if (this.healthStrategy.areYouOk) {
122 if (!this.healthStrategy.areYouOk(result)) {
123 this.onUnhealthy()
124
125 return Promise.reject(this.healthSummary)
126 }
127 }
128 this.onHealthy()
129
130 return Promise.resolve(this.healthSummary)
131 })
132 }
133}
134
135module.exports = (injector) => {
136 const HealthStrategy = HealthFactoryProvider(injector)
137
138 /**
139 * @constructs Dependency
140 * @param {Object} opts Args for creating an instance of Dependency
141 */
142 return ({
143 backoffStrategy = ExponentialBackoffStrategy({
144 retries: 3,
145 maxDelay: 0,
146 multiplier: 1000,
147 onSuccessEvt: 'healthy',
148 onFailureEvt: 'unhealthy'
149 }),
150 strategy = { type: 'http' },
151 level = dependencyLevel.SOFT,
152 name,
153 url
154 } = {}) => {
155 return new Dependency({
156 backoffStrategy,
157 healthStrategy: HealthStrategy(strategy),
158 level,
159 name,
160 url
161 })
162 }
163}