Skip to content

Commit b951205

Browse files
committed
new cache'ing service
1 parent 0deb141 commit b951205

File tree

6 files changed

+271
-2
lines changed

6 files changed

+271
-2
lines changed

src/apigateway/resolver/cache.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
class ResolverCache {
2+
constructor(max = 128, mode = 'all') {
3+
this.__max = max;
4+
this.__mode = mode;
5+
this.__cache = new Map();
6+
}
7+
8+
put(routePath, endpointModule, isDynamic = false) {
9+
if (!this.__max) {
10+
return;
11+
}
12+
if (this.__mode === 'static' && isDynamic) {
13+
return;
14+
}
15+
if (this.__mode === 'dynamic' && !isDynamic) {
16+
return;
17+
}
18+
if (!this.__cache.has(routePath) && this.__cache.size === this.__max) {
19+
const delKey = this.__cache.keys().next().value;
20+
this.__cache.delete(delKey);
21+
}
22+
const entry = {endpointModule, isDynamic};
23+
this.__cache.set(routePath, entry);
24+
}
25+
26+
get(routePath) {
27+
const entry = this.__cache.get(routePath);
28+
if (entry) {
29+
this.__cache.delete(routePath);
30+
this.__cache.set(routePath, entry);
31+
return entry;
32+
}
33+
return null;
34+
}
35+
36+
delete(routePath) {
37+
this.__cache.delete(routePath);
38+
}
39+
40+
clear() {
41+
this.__cache.clear();
42+
}
43+
}
44+
45+
module.exports = ResolverCache;

src/apigateway/resolver/index.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ const ImportManager = require('../import-manager');
33
const DirectoryResolver = require('./directory-resolver');
44
const ListResolver = require('./list-resolver');
55
const PatternResolver = require('./pattern-resolver');
6+
const ResolverCache = require('./cache');
67

78
class RouteResolver {
89
constructor(params) {
910
this.__params = params;
1011
this.__importer = new ImportManager();
12+
this.__cacher = new ResolverCache(params.cacheSize, params.cacheMode);
13+
this.__cacheMisses = 0;
1114
this.__resolver = null;
1215
this.__resolvers = {
1316
pattern: PatternResolver,
@@ -16,6 +19,10 @@ class RouteResolver {
1619
};
1720
}
1821

22+
get cacheMisses() {
23+
return this.__cacheMisses;
24+
}
25+
1926
autoLoad() {
2027
this.__setResolverMode();
2128
this.__resolver.autoLoad();
@@ -30,7 +37,7 @@ class RouteResolver {
3037
try {
3138
this.__validateConfigs();
3239
this.__setResolverMode();
33-
const endpointModule = this.__resolver.resolve(request);
40+
const endpointModule = this.__getEndpointModule(request);
3441
if (this.__resolver.hasPathParams) {
3542
this.__configurePathParams(endpointModule, request);
3643
}
@@ -45,6 +52,18 @@ class RouteResolver {
4552
}
4653
}
4754

55+
__getEndpointModule(request) {
56+
const cached = this.__cacher.get(request.path);
57+
if (cached) {
58+
this.__resolver.hasPathParams = cached.isDynamic;
59+
return cached.endpointModule;
60+
}
61+
this.__cacheMisses++;
62+
const endpointModule = this.__resolver.resolve(request);
63+
this.__cacher.put(request.path, endpointModule, this.__resolver.hasPathParams);
64+
return endpointModule;
65+
}
66+
4867
__setResolverMode() {
4968
if (!this.__resolver) {
5069
const mode = this.__params.routingMode;
@@ -53,7 +72,13 @@ class RouteResolver {
5372
}
5473

5574
__validateConfigs() {
56-
const {routingMode, handlerPath, handlerPattern, handlerList} = this.__params;
75+
const {routingMode, handlerPath, handlerPattern, handlerList, cacheSize, cacheMode} = this.__params;
76+
if (!Number.isInteger(cacheSize) && cacheSize !== undefined) {
77+
this.__importer.raiseRouterConfigError('cacheSize must be an integer');
78+
}
79+
if (cacheMode !== 'all' && cacheMode !== 'all' && cacheMode !== 'dynamic' && cacheMode !== undefined) {
80+
this.__importer.raiseRouterConfigError('cacheMode must be either: all, dynamic, static');
81+
}
5782
if (routingMode !== 'pattern' && routingMode !== 'directory' && routingMode !== 'list') {
5883
this.__importer.raiseRouterConfigError('routingMode must be either directory, pattern or list');
5984
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
exports.requirements = {
2+
post: {
3+
requiredBody: 'v1-test-request'
4+
}
5+
};
6+
7+
exports.post = async (request, response) => {
8+
response.body = {test: true};
9+
return response;
10+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
exports.requirements = {
2+
get: {
3+
requiredPath: 'multi-trailing/{key}/{value}'
4+
}
5+
};
6+
7+
exports.get = async (request, response) => {
8+
response.body = {test: true};
9+
return response;
10+
};
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const {assert} = require('chai');
2+
const ResolverCache = require('../../../../src/apigateway/resolver/cache');
3+
4+
describe('Test Cache: src/apigateway/resolver/cache.js', () => {
5+
describe('test simple cache operations', () => {
6+
const cacher = new ResolverCache();
7+
it('should cache the module sent', () => {
8+
const mockModule = {module: true};
9+
const dynamic = false;
10+
const routePath = '/unit-test/v1/cache-test';
11+
cacher.put(routePath, mockModule, dynamic);
12+
const results = cacher.get(routePath);
13+
assert.deepEqual(results.endpointModule, mockModule);
14+
assert.isFalse(results.isDynamic);
15+
});
16+
it('should delete the module sent', () => {
17+
const mockModule = {module: true};
18+
const dynamic = false;
19+
const routePath = '/unit-test/v1/cache-delete-test';
20+
cacher.put(routePath, mockModule, dynamic);
21+
cacher.delete(routePath);
22+
const result = cacher.get(routePath);
23+
assert.equal(result, null);
24+
});
25+
it('should clear all modules sent', () => {
26+
const cacher = new ResolverCache();
27+
const mockModule = {module: true};
28+
const dynamic = false;
29+
const routePath1 = '/unit-test/v1/cache-clear1-test';
30+
const routePath2 = '/unit-test/v1/cache-clear2-test';
31+
const routePath3 = '/unit-test/v1/cache-clear3-test';
32+
cacher.put(routePath1, mockModule, dynamic);
33+
cacher.put(routePath2, mockModule, dynamic);
34+
cacher.put(routePath3, mockModule, dynamic);
35+
cacher.clear();
36+
const result1 = cacher.get(routePath1);
37+
assert.equal(result1, null);
38+
const result2 = cacher.get(routePath2);
39+
assert.equal(result2, null);
40+
const result3 = cacher.get(routePath3);
41+
assert.equal(result3, null);
42+
});
43+
});
44+
describe('test conditional max size', () => {
45+
it('should cache nothing', () => {
46+
const cacher = new ResolverCache(0);
47+
const mockModule = {module: true};
48+
const dynamic = false;
49+
const routePath = '/unit-test/v1/cache-nothing-test';
50+
cacher.put(routePath, mockModule, dynamic);
51+
const result = cacher.get(routePath);
52+
assert.equal(result, null);
53+
});
54+
it('should only cache last 2 routes', () => {
55+
const cacher = new ResolverCache(2);
56+
const mockModule = {module: true};
57+
const dynamic = false;
58+
const routePath1 = '/unit-test/v1/cache-max1-test';
59+
const routePath2 = '/unit-test/v1/cache-max2-test';
60+
const routePath3 = '/unit-test/v1/cache-max3-test';
61+
cacher.put(routePath1, mockModule, dynamic);
62+
cacher.put(routePath2, mockModule, dynamic);
63+
cacher.put(routePath3, mockModule, dynamic);
64+
const result1 = cacher.get(routePath1);
65+
assert.equal(result1, null);
66+
const result2 = cacher.get(routePath2);
67+
assert.deepEqual(result2.endpointModule, mockModule);
68+
const result3 = cacher.get(routePath3);
69+
assert.deepEqual(result3.endpointModule, mockModule);
70+
});
71+
});
72+
describe('test conditional modes', () => {
73+
it('should cache only static routes', () => {
74+
const cacher = new ResolverCache(128, 'static');
75+
const mockModule1 = {module: true};
76+
const dynamic1 = false;
77+
const routePath1 = '/unit-test/v1/cache-static-test';
78+
cacher.put(routePath1, mockModule1, dynamic1);
79+
const result1 = cacher.get(routePath1);
80+
assert.deepEqual(result1.endpointModule, mockModule1);
81+
const mockModule2 = {module: true};
82+
const dynamic2 = true;
83+
const routePath2 = '/unit-test/v1/cache-dynamic-test';
84+
cacher.put(routePath2, mockModule2, dynamic2);
85+
const result2 = cacher.get(routePath2);
86+
assert.equal(result2, null);
87+
});
88+
it('should cache only dynamic routes', () => {
89+
const cacher = new ResolverCache(128, 'dynamic');
90+
const mockModule1 = {module: true};
91+
const dynamic1 = true;
92+
const routePath1 = '/unit-test/v1/cache-dynamic-test';
93+
cacher.put(routePath1, mockModule1, dynamic1);
94+
const result1 = cacher.get(routePath1);
95+
assert.deepEqual(result1.endpointModule, mockModule1);
96+
const mockModule2 = {module: true};
97+
const dynamic2 = false;
98+
const routePath2 = '/unit-test/v1/cache-staic-test';
99+
cacher.put(routePath2, mockModule2, dynamic2);
100+
const result2 = cacher.get(routePath2);
101+
assert.equal(result2, null);
102+
});
103+
});
104+
});

test/src/apigateway/resolver/index.test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,19 @@ describe('Test Resolver: src/apigateway/resolver/index.js', () => {
191191
assert.deepEqual(request.pathParams, {id: '1', org: 'syngenta'});
192192
assert.equal(request.route, '/unit-test/v1/nested-1/{org}/path-parameters/{id}');
193193
});
194+
it('should find file with multiple trailing path parameters', () => {
195+
const basePath = 'unit-test/v1';
196+
const routingMode = 'directory';
197+
const handlerPath = 'test/mocks/apigateway/mock-directory-handlers';
198+
const importer = new ImportManager();
199+
const resolver = new RouteResolver({basePath, routingMode, handlerPath}, importer);
200+
const mock = mockData.getApiGateWayCustomRouteWithParams('multi-trailing/some-key/some-value', 'get');
201+
const request = new Request(mock);
202+
const response = new Response();
203+
resolver.getEndpoint(request, response);
204+
assert.deepEqual(request.pathParams, {key: 'some-key', value: 'some-value'});
205+
assert.equal(request.route, '/unit-test/v1/multi-trailing/{key}/{value}');
206+
});
194207
});
195208
describe('test suffix pattern resolver with path parameters', () => {
196209
it('should resolve endpoint with trailing path parameter and have path params', () => {
@@ -395,4 +408,66 @@ describe('Test Resolver: src/apigateway/resolver/index.js', () => {
395408
assert.equal(request.route, '/unit-test/v1/n1/n2/{nested}/basic/{id}');
396409
});
397410
});
411+
describe('test resolver cache', () => {
412+
it('should raise an error when resolver cacheSize is incorrect', () => {
413+
const basePath = 'unit-test/v1';
414+
const routingMode = 'directory';
415+
const handlerPath = 'test/mocks/apigateway/mock-directory-handlers';
416+
const cacheSize = 'must-be-string';
417+
const mock = mockData.getData();
418+
const request = new Request(mock);
419+
const response = new Response();
420+
const importer = new ImportManager();
421+
const resolver = new RouteResolver({basePath, routingMode, handlerPath, cacheSize}, importer);
422+
resolver.getEndpoint(request, response);
423+
assert.equal(response.body, '{"errors":[{"key_path":"router-config","message":"cacheSize must be an integer"}]}');
424+
});
425+
it('should raise an error when resolver cacheMode is incorrect', () => {
426+
const basePath = 'unit-test/v1';
427+
const routingMode = 'directory';
428+
const handlerPath = 'test/mocks/apigateway/mock-directory-handlers';
429+
const cacheMode = 'must-be-one-of-all-dynamic-static-';
430+
const mock = mockData.getData();
431+
const request = new Request(mock);
432+
const response = new Response();
433+
const importer = new ImportManager();
434+
const resolver = new RouteResolver({basePath, routingMode, handlerPath, cacheMode}, importer);
435+
resolver.getEndpoint(request, response);
436+
assert.equal(
437+
response.body,
438+
'{"errors":[{"key_path":"router-config","message":"cacheMode must be either: all, dynamic, static"}]}'
439+
);
440+
});
441+
it('should not have any cache misses', () => {
442+
const basePath = 'unit-test/v1';
443+
const routingMode = 'directory';
444+
const handlerPath = 'test/mocks/apigateway/mock-directory-handlers';
445+
const mock = mockData.getData();
446+
const request = new Request(mock);
447+
const response = new Response();
448+
const importer = new ImportManager();
449+
const resolver = new RouteResolver({basePath, routingMode, handlerPath}, importer);
450+
resolver.getEndpoint(request, response);
451+
resolver.getEndpoint(request, response);
452+
resolver.getEndpoint(request, response);
453+
resolver.getEndpoint(request, response);
454+
assert.equal(resolver.cacheMisses, 1);
455+
});
456+
it('should have any 4 cache misses', () => {
457+
const basePath = 'unit-test/v1';
458+
const routingMode = 'directory';
459+
const handlerPath = 'test/mocks/apigateway/mock-directory-handlers';
460+
const mock = mockData.getData();
461+
const cacheSize = 0;
462+
const request = new Request(mock);
463+
const response = new Response();
464+
const importer = new ImportManager();
465+
const resolver = new RouteResolver({basePath, routingMode, handlerPath, cacheSize}, importer);
466+
resolver.getEndpoint(request, response);
467+
resolver.getEndpoint(request, response);
468+
resolver.getEndpoint(request, response);
469+
resolver.getEndpoint(request, response);
470+
assert.equal(resolver.cacheMisses, 4);
471+
});
472+
});
398473
});

0 commit comments

Comments
 (0)