Skip to content

Commit 3f98186

Browse files
committed
Add caching to getFeatures
1 parent 25f5bc8 commit 3f98186

File tree

5 files changed

+96
-0
lines changed

5 files changed

+96
-0
lines changed

models/UnleashSDK.cfc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
component singleton accessors="true" {
22

33
property name="client" inject="UnleashHyperClient@unleashsdk";
4+
property name="cache" inject="cachebox:default";
5+
property name="log" inject="logbox:logger:{this}";
6+
7+
variables.strategies = {
8+
"default": "DefaultStrategy",
9+
"userWithId": "UserWithIdStrategy",
10+
"flexibleRollout": "FlexibleRolloutStrategy",
11+
"remoteAddress": "RemoteAddressStrategy",
12+
"applicationHostname": "ApplicationHostnameStrategy"
13+
};
414

515
public boolean function isEnabled( required string name, boolean defaultValue = false ) {
616
var feature = findFeature( arguments.name );
@@ -12,9 +22,39 @@ component singleton accessors="true" {
1222
return false;
1323
}
1424

25+
for ( var strategyData in feature.strategies ) {
26+
var strategy = getStrategy( strategyData.name );
27+
if ( isNull( strategy ) ) {
28+
return false;
29+
}
30+
31+
param strategyData.constraints = [];
32+
if ( !strategy.satisfiesConstraints( strategyData.constraints ) ) {
33+
continue;
34+
}
35+
36+
param strategyData.parameters = {};
37+
if ( !strategy.isEnabled( strategyData.parameters ) ) {
38+
return false;
39+
}
40+
}
41+
1542
return true;
1643
}
1744

45+
private any function getStrategy( required string name ) {
46+
if ( !variables.strategies.keyExists( arguments.name ) ) {
47+
log.warn( "No Unleash strategy found for [#arguments.name#]" );
48+
return javacast( "null", "" );
49+
}
50+
51+
if ( isSimpleValue( variables.strategies[ arguments.name ] ) ) {
52+
variables.strategies[ arguments.name ] = new "unleashsdk.models.strategies.#variables.strategies[ arguments.name ]#"();
53+
}
54+
55+
return variables.strategies[ arguments.name ];
56+
}
57+
1858
public struct function createFeature(
1959
required string name,
2060
required string description,
@@ -54,6 +94,12 @@ component singleton accessors="true" {
5494
} );
5595
}
5696

97+
public array function getFeatures() {
98+
return cache.getOrSet( "unleashsdk-features", function() {
99+
return fetchFeatures();
100+
}, createTimespan( 0, 0, 10, 0 ) );
101+
}
102+
57103
private array function fetchFeatures() {
58104
return variables.client.get( "/client/features" ).json().features;
59105
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
component implements="IStrategy" {
2+
3+
public boolean function satisfiesConstraints( array constraints ) {
4+
return true; // spike
5+
}
6+
7+
public boolean function isEnabled( struct parameters ) {
8+
return true;
9+
}
10+
11+
}

models/strategies/IStrategy.cfc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface {
2+
3+
public boolean function satisfiesConstraints( array constraints );
4+
5+
public boolean function isEnabled( struct parameters );
6+
7+
}

tests/resources/ModuleIntegrationSpec.cfc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/app" {
55

66
getController().getModuleService()
77
.registerAndActivateModule( "app", "testingModuleRoot" );
8+
9+
getWireBox().autowire( this );
810
}
911

1012
/**

tests/specs/integration/UnleashSpec.cfc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
component extends="tests.resources.ModuleIntegrationSpec" {
22

3+
property name="cache" inject="cachebox:default"
4+
35
function beforeAll() {
46
super.beforeAll();
57
variables.unleash = getInstance( "UnleashSDK@unleashsdk" );
@@ -10,6 +12,7 @@ component extends="tests.resources.ModuleIntegrationSpec" {
1012
it( "returns the default value for an unknown feature", function() {
1113
expect( unleash.isEnabled( "bar" ) ).toBeFalse();
1214
} );
15+
1316
it( "can check if a feature is enabled", function() {
1417
unleash.ensureFeatureExists(
1518
name = "feature-1",
@@ -40,6 +43,33 @@ component extends="tests.resources.ModuleIntegrationSpec" {
4043
expect( unleash.isEnabled( "feature-1" ) ).toBeTrue();
4144
expect( unleash.isEnabled( "feature-2" ) ).toBeFalse();
4245
} );
46+
47+
it( "can return all features", function() {
48+
unleash.ensureFeatureExists(
49+
name = "feature-1",
50+
description = "A test feature flag that is enabled",
51+
type = "release",
52+
enabled = true,
53+
strategies = [
54+
{
55+
"name": "default",
56+
"parameters": {}
57+
}
58+
]
59+
);
60+
expect( unleash.getFeatures() ).toBeArray();
61+
expect( unleash.getFeatures() ).notToBeEmpty();
62+
} );
63+
64+
it( "caches the features", function() {
65+
cache.clear( "unleashsdk-features" );
66+
var hyper = prepareMock( getInstance( "UnleashHyperClient@unleashsdk" ) );
67+
var newRequest = hyper.new();
68+
hyper.$( "new", newRequest );
69+
unleash.getFeatures();
70+
unleash.getFeatures();
71+
expect( hyper.$count( "new" ) ).toBe( 1, "The UnleashHyperClient should only be used once." );
72+
} );
4373
} );
4474
}
4575

0 commit comments

Comments
 (0)