Skip to content
This repository was archived by the owner on Sep 8, 2023. It is now read-only.

Commit b8683d0

Browse files
cmoodytuckbick
authored andcommitted
Feature: Add internal cache of Service Client instances (#8)
* Update changelog * Add client instances object to global config * Check for cached instance of service * Add destroy method to remove service from cache * Remove return * Fix tests * Change destroy to remove for service cache * Update logic for overrides * Gramar fix * Updated tests to provide a check against cached instances * Update README, CHANGELOG, redundant overrides check
1 parent dff9964 commit b8683d0

File tree

8 files changed

+69
-4
lines changed

8 files changed

+69
-4
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
## [1.2.1](https://github.com/expediagroup/service-client/compare/v1.2.0...v1.2.1) (2019-08-22)
1+
## [1.3.0](https://github.com/expediagroup/service-client/compare/v1.2.1...v1.3.0) (2019-12-03)
2+
3+
#### Feature
4+
5+
* Caches a client if one does not already exist
6+
* Returns a cached client if already generated
27

38

9+
## [1.2.1](https://github.com/expediagroup/service-client/compare/v1.2.0...v1.2.1) (2019-08-22)
10+
411
#### Bug Fixes
512

613
* add hook debug statements ([#5](https://github.com/expediagroup/service-client/issues/5)) ([647534c](https://github.com/expediagroup/service-client/commit/647534c))

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ For a more thorough collection of examples see the [examples directory](https://
9494

9595
Returns a new service client instance for `servicename` with optional `overrides` to the global defaults listed above:
9696

97+
*Note: If no `overrides` are provided, when a service client instance is created for a `servicename` it will be stored in a cache. That instance will be returned instead of creating a new instance.*
98+
9799
- **protocol** - The protocol to use for the request. Defaults to `"http:"`.
98100
- **hostname** - The hostname to use for the request. Accepts a `string` or a `function(serviceName, serviceConfig)` that returns a string.
99101
- **port** - The port number to use for the request.

lib/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const globalConfig = individual('__serviceclientconfig', {
3636
keepAliveMsecs: 30000
3737
}
3838
},
39+
// cached client instances
40+
clientInstances: {},
3941
overrides: {
4042
// service specific overrides
4143
// consulkey: { protocol, host, port }

lib/index.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,27 @@ const create = function (servicename, overrides = {}) {
3838
config.agent = Agent.create(config.protocol, config)
3939
}
4040

41-
const client = new Client(servicename, config)
41+
// If overrides are passed do not cache the client
42+
if (Object.keys(overrides).length) {
43+
const client = new Client(servicename, config)
4244

43-
debug('create(): created client id %s for service %s', client.id, servicename)
45+
debug('create(): created client id %s for service %s', client.id, servicename)
4446

45-
return client
47+
return client
48+
}
49+
50+
// If the client is not already cached create an instance and cache
51+
if (typeof GlobalConfig.clientInstances[servicename] === 'undefined') {
52+
GlobalConfig.clientInstances[servicename] = new Client(servicename, config)
53+
}
54+
55+
debug('create(): created client id %s for service %s', GlobalConfig.clientInstances[servicename].id, servicename)
56+
57+
return GlobalConfig.clientInstances[servicename]
58+
}
59+
60+
const remove = function (servicename) {
61+
delete GlobalConfig.clientInstances[servicename]
4662
}
4763

4864
const use = function (plugins = []) {
@@ -77,6 +93,7 @@ const mergeConfig = function (externalConfig = {}) {
7793
// Exported API
7894
const ServiceClient = {
7995
create,
96+
remove,
8097
read: Read,
8198
use,
8299
mergeConfig

test/unit/client.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ describe('client', function () {
8989
afterEach(() => {
9090
Object.assign(__serviceclientconfig, suite.originalConfig) // eslint-disable-line no-undef
9191
process.env = suite.originalEnvVars
92+
ServiceClient.remove('myservice')
9293
})
9394

9495
it('should throw an error if creating an instance without a service name', async () => {
@@ -194,9 +195,26 @@ describe('client', function () {
194195
const SC = require('../../lib/client')
195196
assert.throws(() => new SC(), 'A service name is required')
196197
})
198+
199+
it('should use a cached instance when no overrides provided', () => {
200+
__serviceclientconfig.overrides.cachedservice = { hostname: 'cachedservice.service.local' } // eslint-disable-line no-undef
201+
202+
const client1 = ServiceClient.create('cachedservice')
203+
const client2 = ServiceClient.create('cachedservice', {})
204+
const client3 = ServiceClient.create('cachedservice', {
205+
basePath: '/v1/test'
206+
})
207+
208+
assert.equal(client1, client2, 'client2 is equal to client1 because its pulled from the cache')
209+
assert.notEqual(client1, client3, 'client3 provides an override and is a new instance of service client')
210+
})
197211
})
198212

199213
describe('service-client request', () => {
214+
afterEach(() => {
215+
ServiceClient.remove('myservice')
216+
})
217+
200218
it('should throw an error if requesting without an `operation`', async function () {
201219
const client = ServiceClient.create('myservice', { hostname: 'vrbo.com' })
202220

@@ -330,6 +348,10 @@ describe('client', function () {
330348
})
331349

332350
describe('service-client read', () => {
351+
afterEach(() => {
352+
ServiceClient.remove('myservice')
353+
})
354+
333355
it('should read the response payload', async function () {
334356
Nock('http://myservice.service.local:80')
335357
.get('/v1/test/stuff')
@@ -346,6 +368,10 @@ describe('client', function () {
346368
})
347369

348370
describe('connectTimeout and circuit breaker', () => {
371+
afterEach(() => {
372+
ServiceClient.remove('myservice')
373+
})
374+
349375
it('should fail the request because `connectTimeout` is short', async function () {
350376
const client = ServiceClient.create('myservice', { hostname: 'myservice.service.local', connectTimeout: 1, maxFailures: 1 })
351377

@@ -409,6 +435,10 @@ describe('client', function () {
409435
})
410436

411437
describe('timeout and circuit breaker', () => {
438+
afterEach(() => {
439+
ServiceClient.remove('myservice')
440+
})
441+
412442
it('should fail the request because `timeout` is short and the circuit breaker should trip', async function () {
413443
Nock('http://myservice.service.local:80')
414444
.get('/v1/test/stuff')
@@ -438,6 +468,10 @@ describe('client', function () {
438468
})
439469

440470
describe('circuit breaker and hooks', () => {
471+
afterEach(() => {
472+
ServiceClient.remove('myservice')
473+
})
474+
441475
it('should execute `error`, `stats`, and `end` hooks if the circuit breaker is open', async function () {
442476
Nock('http://myservice.service.local:80')
443477
.get('/v1/test/stuff')

test/unit/hooks/request-context.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('Using ServiceClient in the context of a Hapi request', () => {
4343
GlobalConfig.plugins = []
4444
suite.sandbox.restore()
4545
suite = null
46+
ServiceClient.remove('myservice')
4647
})
4748

4849
it('should process requests that may be in flight simultaneously', async function () {

test/unit/hooks/server-context.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('Using ServiceClient in the context of a Hapi server', () => {
3131
GlobalConfig.plugins = []
3232
suite.sandbox.restore()
3333
suite = null
34+
ServiceClient.remove('myservice')
3435
})
3536

3637
it('should pass the server via `context` to the `request` hook', async function () {

test/unit/hooks/standalone.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('Using ServiceClient in a standalone context', () => {
2929
GlobalConfig.plugins = []
3030
suite.sandbox.restore()
3131
suite = null
32+
ServiceClient.remove('myservice')
3233
})
3334

3435
it('should call hooks (successful request)', async function () {

0 commit comments

Comments
 (0)