feat: make client features endpoint memoizable (#734)

This commit is contained in:
Christopher Kolstad
2021-02-23 13:50:22 +01:00
committed by Ivar Conradi Østhus
parent 5f83fbc43d
commit 44af41547e
7 changed files with 157 additions and 15 deletions
+6
View File
@@ -90,6 +90,12 @@ function defaultOptions() {
version,
secureHeaders: process.env.SECURE_HEADERS || false,
enableOAS: process.env.ENABLE_OAS || false,
experimental: {
clientFeatureMemoize: {
enabled: process.env.CLIENT_FEATURE_MEMOIZE || false,
maxAge: process.env.CLIENT_FEATURE_MAXAGE || 1000,
},
},
};
}
+30 -5
View File
@@ -2,6 +2,8 @@
import { handleErrors } from '../admin-api/util';
const memoizee = require('memoizee');
const Controller = require('../controller');
const version = 1;
@@ -16,20 +18,43 @@ const FEATURE_COLUMNS_CLIENT = [
];
class FeatureController extends Controller {
constructor({ featureToggleService }, getLogger) {
constructor({ featureToggleService }, { getLogger, experimental }) {
super();
this.toggleService = featureToggleService;
this.logger = getLogger('client-api/feature.js');
this.get('/', this.getAll);
this.get('/:featureName', this.getFeatureToggle);
if (experimental && experimental.clientFeatureMemoize) {
this.cache = experimental.clientFeatureMemoize.enabled;
this.cachedFeatures = memoizee(
(query, fields) =>
this.toggleService.getFeatures(query, fields),
{
promise: true,
maxAge: experimental.clientFeatureMemoize.maxAge,
normalizer(args) {
// args is arguments object as accessible in memoized function
return JSON.stringify(args[0]);
},
},
);
}
}
async getAll(req, res) {
try {
const features = await this.toggleService.getFeatures(
req.query,
FEATURE_COLUMNS_CLIENT,
);
let features;
if (this.cache) {
features = await this.cachedFeatures(
req.query,
FEATURE_COLUMNS_CLIENT,
);
} else {
features = await this.toggleService.getFeatures(
req.query,
FEATURE_COLUMNS_CLIENT,
);
}
res.json({ version, features });
} catch (e) {
handleErrors(res, this.logger, e);
+48
View File
@@ -3,10 +3,12 @@
const test = require('ava');
const supertest = require('supertest');
const { EventEmitter } = require('events');
const sinon = require('sinon');
const store = require('../../../test/fixtures/store');
const getLogger = require('../../../test/fixtures/no-logger');
const getApp = require('../../app');
const { createServices } = require('../../services');
const FeatureController = require('./feature');
const eventBus = new EventEmitter();
@@ -40,6 +42,52 @@ test('should get empty getFeatures via client', t => {
});
});
test('if caching is enabled should memoize', t => {
const getFeatures = sinon.fake.returns([]);
const featureToggleService = {
getFeatures,
};
const controller = new FeatureController(
{ featureToggleService },
{
getLogger,
experimental: {
clientFeatureMemoize: {
enabled: true,
maxAge: 10000,
},
},
},
);
controller.getAll({ query: {} }, { json: () => {} });
controller.getAll({ query: {} }, { json: () => {} });
t.is(getFeatures.callCount, 1);
});
test('if caching is not enabled all calls goes to service', t => {
const getFeatures = sinon.fake.returns([]);
const featureToggleService = {
getFeatures,
};
const controller = new FeatureController(
{ featureToggleService },
{
getLogger,
experimental: {
clientFeatureMemoize: {
enabled: false,
maxAge: 10000,
},
},
},
);
controller.getAll({ query: {} }, { json: () => {} });
controller.getAll({ query: {} }, { json: () => {} });
t.is(getFeatures.callCount, 2);
});
test('fetch single feature', t => {
t.plan(1);
const { request, featureToggleStore, base } = getSetup();
+1 -4
View File
@@ -13,10 +13,7 @@ class ClientApi extends Controller {
const { getLogger } = config;
this.get('/', this.index);
this.use(
'/features',
new FeatureController(services, getLogger).router,
);
this.use('/features', new FeatureController(services, config).router);
this.use('/metrics', new MetricsController(services, getLogger).router);
this.use(
'/register',
+1 -4
View File
@@ -22,10 +22,7 @@ class IndexRouter extends Controller {
// legacy support (remove in 4.x)
if (config.enableLegacyRoutes) {
const featureController = new FeatureController(
services,
config.getLogger,
);
const featureController = new FeatureController(services, config);
this.use('/api/features', featureController.router);
}
}