WIP store-agnostic cache manager.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-05-01 21:23:50 +02:00
parent d2eed74d35
commit 93ff8275dd
8 changed files with 202 additions and 9 deletions
+22
View File
@@ -68,6 +68,28 @@ export interface Config {
connection: string | object;
};
/** Cache connection configuration, select cache type using the `store` field */
cache?:
| {
store: 'memory';
}
| {
store: 'memcache';
connection: {
/**
* An array of memcache hosts, optionally including a port number.
* e.g. 127.0.0.1:11211
*/
hosts: string[];
/**
* Number of milliseconds to wait before assuming there is a
* network timeout. Defaults to 500ms.
*/
netTimeout?: number;
};
};
cors?: {
origin?: string | string[];
methods?: string | string[];
+4
View File
@@ -36,10 +36,13 @@
"@backstage/integration": "^0.5.2",
"@google-cloud/storage": "^5.8.0",
"@octokit/rest": "^18.5.3",
"@types/cache-manager": "^3.4.0",
"@types/cors": "^2.8.6",
"@types/dockerode": "^3.2.1",
"@types/express": "^4.17.6",
"archiver": "^5.0.2",
"cache-manager": "^3.4.3",
"cache-manager-memcached-store": "^4.0.0",
"compression": "^1.7.4",
"concat-stream": "^2.0.0",
"cors": "^2.8.5",
@@ -54,6 +57,7 @@
"knex": "^0.95.1",
"lodash": "^4.17.15",
"logform": "^2.1.1",
"memcache-pp": "^0.3.3",
"minimatch": "^3.0.4",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Config } from '@backstage/config';
import cacheManager from 'cache-manager';
// @ts-expect-error
import Memcache from 'memcache-pp';
// @ts-expect-error
import memcachedStore from 'cache-manager-memcached-store';
/**
* A Cache Manager, which is able to retrieve a `node-cache-manager`-compliant
* cache client, configured according to app configuration. If no cache is
* configured, or an unknown store is provided, a no-op cache instance will be
* returned.
*/
export class CacheManager {
private readonly storeGetterMap = {
memcache: this.getMemcacheClient,
memory: this.getMemoryClient,
none: this.getNoneClient,
};
/**
* Creates a new CacheManager instance by reading from the `backend` config
* section, specifically the `.cache` key.
*
* @param config The loaded application configuration.
*/
static fromConfig(config: Config): CacheManager {
return new CacheManager(config.getConfig('backend.cache'));
}
private constructor(private readonly config: Config) {}
getClientWithTtl(ttl: number): cacheManager.Cache {
const store = this.config.getOptionalString(
'store',
) as keyof CacheManager['storeGetterMap'];
if (this.storeGetterMap.hasOwnProperty(store)) {
return this.storeGetterMap[store].call(this, ttl);
}
return this.storeGetterMap.none.call(this, ttl);
}
private getMemcacheClient(ttl: number): cacheManager.Cache {
const hosts = this.config.getStringArray('connection.hosts');
const netTimeout = this.config.getOptionalNumber('connection.netTimeout');
return cacheManager.caching({
store: memcachedStore,
driver: Memcache,
options: {
hosts,
...(netTimeout && { netTimeout }),
},
ttl,
});
}
private getMemoryClient(ttl: number): cacheManager.Cache {
return cacheManager.caching({
store: 'memory',
ttl,
});
}
private getNoneClient(ttl: number): cacheManager.Cache {
return cacheManager.caching({
store: 'none',
ttl,
});
}
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './CacheManager';
+1
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
export * from './cache';
export * from './config';
export * from './database';
export * from './discovery';
+3 -1
View File
@@ -24,6 +24,7 @@
import Router from 'express-promise-router';
import {
CacheManager,
createServiceBuilder,
getRootLogger,
loadBackendConfig,
@@ -59,11 +60,12 @@ function makeCreateEnv(config: Config) {
root.info(`Created UrlReader ${reader}`);
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
const cache = CacheManager.fromConfig(config);
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const database = databaseManager.forPlugin(plugin);
return { logger, database, config, reader, discovery };
return { logger, cache, database, config, reader, discovery };
};
}
+2
View File
@@ -17,6 +17,7 @@
import { Logger } from 'winston';
import { Config } from '@backstage/config';
import {
CacheManager,
PluginDatabaseManager,
PluginEndpointDiscovery,
UrlReader,
@@ -24,6 +25,7 @@ import {
export type PluginEnvironment = {
logger: Logger;
cache: CacheManager;
database: PluginDatabaseManager;
config: Config;
reader: UrlReader;