feat(cache): allow a defaultTTL to be set through the app config
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Allow a default cache TTL to be set through the app config
|
||||
Vendored
+6
@@ -149,6 +149,8 @@ export interface Config {
|
||||
cache?:
|
||||
| {
|
||||
store: 'memory';
|
||||
/** An optional default TTL (in milliseconds). */
|
||||
defaultTtl?: number;
|
||||
}
|
||||
| {
|
||||
store: 'redis';
|
||||
@@ -157,6 +159,8 @@ export interface Config {
|
||||
* @visibility secret
|
||||
*/
|
||||
connection: string;
|
||||
/** An optional default TTL (in milliseconds). */
|
||||
defaultTtl?: number;
|
||||
}
|
||||
| {
|
||||
store: 'memcache';
|
||||
@@ -165,6 +169,8 @@ export interface Config {
|
||||
* @visibility secret
|
||||
*/
|
||||
connection: string;
|
||||
/** An optional default TTL (in milliseconds). */
|
||||
defaultTtl?: number;
|
||||
};
|
||||
|
||||
cors?: {
|
||||
|
||||
@@ -33,11 +33,13 @@ jest.mock('./CacheClient', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const globalDefaultTtl = 1234;
|
||||
describe('CacheManager', () => {
|
||||
const defaultConfigOptions = {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
defaultTtl: globalDefaultTtl,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -49,9 +51,11 @@ describe('CacheManager', () => {
|
||||
it('accesses the backend.cache key', () => {
|
||||
const getOptionalString = jest.fn();
|
||||
const getOptionalBoolean = jest.fn();
|
||||
const getOptionalNumber = jest.fn();
|
||||
const config = defaultConfig();
|
||||
config.getOptionalString = getOptionalString;
|
||||
config.getOptionalBoolean = getOptionalBoolean;
|
||||
config.getOptionalNumber = getOptionalNumber;
|
||||
|
||||
CacheManager.fromConfig(config);
|
||||
|
||||
@@ -62,6 +66,9 @@ describe('CacheManager', () => {
|
||||
expect(getOptionalBoolean.mock.calls[0][0]).toEqual(
|
||||
'backend.cache.useRedisSets',
|
||||
);
|
||||
expect(getOptionalNumber.mock.calls[0][0]).toEqual(
|
||||
'backend.cache.defaultTtl',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not require the backend.cache key', () => {
|
||||
@@ -159,6 +166,20 @@ describe('CacheManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns memory client with a global defaultTtl when explicitly configured', () => {
|
||||
const manager = CacheManager.fromConfig(defaultConfig());
|
||||
const expectedNamespace = 'test-plugin';
|
||||
manager.forPlugin(expectedNamespace).getClient();
|
||||
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCalls = cache.mock.calls.splice(-1);
|
||||
const callArgs = mockCalls[0];
|
||||
expect(callArgs[0]).toMatchObject({
|
||||
ttl: globalDefaultTtl,
|
||||
namespace: expectedNamespace,
|
||||
});
|
||||
});
|
||||
|
||||
it('shares memory across multiple instances of the memory client', () => {
|
||||
const manager = CacheManager.fromConfig(defaultConfig());
|
||||
const plugin = 'test-plugin';
|
||||
@@ -201,6 +222,32 @@ describe('CacheManager', () => {
|
||||
expect(mockMemcacheCalls[0][0]).toEqual(expectedHost);
|
||||
});
|
||||
|
||||
it('returns a memcache client with a global defaultTtl when configured', () => {
|
||||
const expectedHost = '127.0.0.1:11211';
|
||||
const manager = CacheManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memcache',
|
||||
connection: expectedHost,
|
||||
defaultTtl: globalDefaultTtl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
manager.forPlugin('test').getClient();
|
||||
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCacheCalls = cache.mock.calls.splice(-1);
|
||||
expect(mockCacheCalls[0][0]).toMatchObject({
|
||||
ttl: globalDefaultTtl,
|
||||
});
|
||||
expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache);
|
||||
const memcache = KeyvMemcache as unknown as jest.Mock;
|
||||
const mockMemcacheCalls = memcache.mock.calls.splice(-1);
|
||||
expect(mockMemcacheCalls[0][0]).toEqual(expectedHost);
|
||||
});
|
||||
|
||||
it('returns a Redis client when configured', () => {
|
||||
const redisConnection = 'redis://127.0.0.1:6379';
|
||||
const manager = CacheManager.fromConfig(
|
||||
@@ -227,6 +274,32 @@ describe('CacheManager', () => {
|
||||
expect(mockRedisCalls[0][0]).toEqual(redisConnection);
|
||||
});
|
||||
|
||||
it('returns a Redis client with a global defaultTtl when configured', () => {
|
||||
const redisConnection = 'redis://127.0.0.1:6379';
|
||||
const manager = CacheManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'redis',
|
||||
connection: redisConnection,
|
||||
defaultTtl: globalDefaultTtl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
manager.forPlugin('test').getClient();
|
||||
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCacheCalls = cache.mock.calls.splice(-1);
|
||||
expect(mockCacheCalls[0][0]).toMatchObject({
|
||||
ttl: globalDefaultTtl,
|
||||
});
|
||||
expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis);
|
||||
const redis = KeyvRedis as unknown as jest.Mock;
|
||||
const mockRedisCalls = redis.mock.calls.splice(-1);
|
||||
expect(mockRedisCalls[0][0]).toEqual(redisConnection);
|
||||
});
|
||||
|
||||
it('returns a Redis client when configured with useRedisSets flag', () => {
|
||||
const redisConnection = 'redis://127.0.0.1:6379';
|
||||
const useRedisSets = false;
|
||||
|
||||
+6
-1
@@ -57,6 +57,7 @@ export class CacheManager {
|
||||
private readonly connection: string;
|
||||
private readonly useRedisSets: boolean;
|
||||
private readonly errorHandler: CacheManagerOptions['onError'];
|
||||
private readonly defaultTtl?: number;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CacheManager} instance by reading from the `backend`
|
||||
@@ -71,6 +72,7 @@ export class CacheManager {
|
||||
// If no `backend.cache` config is provided, instantiate the CacheManager
|
||||
// with an in-memory cache client.
|
||||
const store = config.getOptionalString('backend.cache.store') || 'memory';
|
||||
const defaultTtl = config.getOptionalNumber('backend.cache.defaultTtl');
|
||||
const connectionString =
|
||||
config.getOptionalString('backend.cache.connection') || '';
|
||||
const useRedisSets =
|
||||
@@ -84,6 +86,7 @@ export class CacheManager {
|
||||
useRedisSets,
|
||||
logger,
|
||||
options.onError,
|
||||
defaultTtl,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,6 +96,7 @@ export class CacheManager {
|
||||
useRedisSets: boolean,
|
||||
logger: LoggerService,
|
||||
errorHandler: CacheManagerOptions['onError'],
|
||||
defaultTtl?: number,
|
||||
) {
|
||||
if (!this.storeFactories.hasOwnProperty(store)) {
|
||||
throw new Error(`Unknown cache store: ${store}`);
|
||||
@@ -102,6 +106,7 @@ export class CacheManager {
|
||||
this.connection = connectionString;
|
||||
this.useRedisSets = useRedisSets;
|
||||
this.errorHandler = errorHandler;
|
||||
this.defaultTtl = defaultTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +121,7 @@ export class CacheManager {
|
||||
const clientFactory = (options: CacheServiceOptions) => {
|
||||
const concreteClient = this.getClientWithTtl(
|
||||
pluginId,
|
||||
options.defaultTtl,
|
||||
options.defaultTtl ?? this.defaultTtl,
|
||||
);
|
||||
|
||||
// Always provide an error handler to avoid stopping the process.
|
||||
|
||||
Reference in New Issue
Block a user