Skip to content

Commit 7083586

Browse files
feat!: changing cache provider to caffeine over guava (#1065)
Signed-off-by: Matheus Veríssimo <[email protected]> Signed-off-by: Matheus Veríssimo <[email protected]>
1 parent 2331fec commit 7083586

File tree

6 files changed

+114
-51
lines changed

6 files changed

+114
-51
lines changed

providers/go-feature-flag/README.md

+55-1
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,62 @@ You will have a new instance ready to be used with your `open-feature` java SDK.
4747
| **`keepAliveDuration`** | `false` | keepAliveDuration is the time in millisecond we keep the connexion open. _(default: 7200000 (2 hours))_ |
4848
| **`apiKey`** | `false` | If the relay proxy is configured to authenticate the requests, you should provide an API Key to the provider. Please ask the administrator of the relay proxy to provide an API Key. (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above). _(default: null)_ |
4949
| **`enableCache`** | `false` | enable cache value. _(default: true)_ |
50-
| **`cacheBuilder`** | `false` | If cache custom configuration is wanted, you should provide a cache builder. _(default: null)_ |
50+
| **`cacheConfig`** | `false` | If cache custom configuration is wanted, you should provide a [Caffeine](https://github.com/ben-manes/caffeine) configuration object. _(default: null)_ |
5151
| **`flushIntervalMs`** | `false` | interval time we publish statistics collection data to the proxy. The parameter is used only if the cache is enabled, otherwise the collection of the data is done directly when calling the evaluation API. _(default: 1000 ms)_ |
5252
| **`maxPendingEvents`** | `false` | max pending events aggregated before publishing for collection data to the proxy. When event is added while events collection is full, event is omitted. _(default: 10000)_ |
5353
| **`flagChangePollingIntervalMs`** | `false` | interval time we poll the proxy to check if the configuration has changed.<br/>If the cache is enabled, we will poll the relay-proxy every X milliseconds to check if the configuration has changed. _(default: 120000)_ |
5454
| **`disableDataCollection`** | `false` | set to true if you don't want to collect the usage of flags retrieved in the cache. _(default: false)_ |
55+
56+
## Breaking changes
57+
58+
### 0.4.0 - Cache Implementation Change: Guava to Caffeine
59+
60+
In this release, we have updated the cache implementation from Guava to Caffeine. This change was made because Caffeine is now the recommended caching solution by the maintainers of Guava due to its performance improvements and enhanced features.
61+
62+
Because of this, the cache configuration on `GoFeatureFlagProviderOptions` that used Guava's `CacheBuilder` is now handled by `Caffeine`.
63+
64+
#### How to migrate
65+
66+
Configuration cache with Guava used to be like this:
67+
68+
```java
69+
import com.google.common.cache.CacheBuilder;
70+
// ...
71+
CacheBuilder guavaCacheBuilder = CacheBuilder.newBuilder()
72+
.initialCapacity(100)
73+
.maximumSize(2000);
74+
75+
FeatureProvider provider = new GoFeatureFlagProvider(
76+
GoFeatureFlagProviderOptions
77+
.builder()
78+
.endpoint("https://my-gofeatureflag-instance.org")
79+
.cacheBuilder(guavaCacheBuilder)
80+
.build());
81+
82+
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
83+
84+
// ...
85+
```
86+
87+
Now with Caffeine it should be like this:
88+
89+
```java
90+
import com.github.benmanes.caffeine.cache.Caffeine;
91+
// ...
92+
Caffeine caffeineCacheConfig = Caffeine.newBuilder()
93+
.initialCapacity(100)
94+
.maximumSize(2000);
95+
96+
FeatureProvider provider = new GoFeatureFlagProvider(
97+
GoFeatureFlagProviderOptions
98+
.builder()
99+
.endpoint("https://my-gofeatureflag-instance.org")
100+
.cacheConfig(caffeineCacheConfig)
101+
.build());
102+
103+
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
104+
105+
// ...
106+
```
107+
108+
For a complete list of customizations options available in Caffeine, please refer to the [Caffeine documentation](https://github.com/ben-manes/caffeine/wiki) for more details.

providers/go-feature-flag/pom.xml

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@
5757
</dependency>
5858

5959
<dependency>
60-
<groupId>com.google.guava</groupId>
61-
<artifactId>guava</artifactId>
62-
<version>33.3.1-jre</version>
60+
<groupId>com.github.ben-manes.caffeine</groupId>
61+
<artifactId>caffeine</artifactId>
62+
<version>2.9.3</version>
6363
</dependency>
6464

6565
<dependency>

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.openfeature.contrib.providers.gofeatureflag;
22

3-
import com.google.common.cache.CacheBuilder;
3+
import com.github.benmanes.caffeine.cache.Caffeine;
4+
45
import dev.openfeature.sdk.ProviderEvaluation;
56
import lombok.Builder;
67
import lombok.Getter;
@@ -49,14 +50,26 @@ public class GoFeatureFlagProviderOptions {
4950

5051
/**
5152
* (optional) If cache custom configuration is wanted, you should provide
52-
* a cache builder.
53+
* a cache configuration caffeine object.
54+
* Example:
55+
* <pre>
56+
* <code>GoFeatureFlagProviderOptions.builder()
57+
* .caffeineConfig(
58+
* Caffeine.newBuilder()
59+
* .initialCapacity(100)
60+
* .maximumSize(100000)
61+
* .expireAfterWrite(Duration.ofMillis(5L * 60L * 1000L))
62+
* .build()
63+
* )
64+
* .build();
65+
* </code>
66+
* </pre>
5367
* Default:
5468
* CACHE_TTL_MS: 5min
55-
* CACHE_CONCURRENCY_LEVEL: 1
5669
* CACHE_INITIAL_CAPACITY: 100
5770
* CACHE_MAXIMUM_SIZE: 100000
5871
*/
59-
private CacheBuilder<String, ProviderEvaluation<?>> cacheBuilder;
72+
private Caffeine<String, ProviderEvaluation<?>> cacheConfig;
6073

6174
/**
6275
* (optional) enable cache value.

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/CacheController.java

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
11
package dev.openfeature.contrib.providers.gofeatureflag.controller;
22

3+
import java.time.Duration;
4+
35
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.google.common.cache.Cache;
5-
import com.google.common.cache.CacheBuilder;
6+
import com.github.benmanes.caffeine.cache.Cache;
7+
import com.github.benmanes.caffeine.cache.Caffeine;
8+
69
import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions;
710
import dev.openfeature.contrib.providers.gofeatureflag.bean.BeanUtils;
811
import dev.openfeature.sdk.EvaluationContext;
912
import dev.openfeature.sdk.ProviderEvaluation;
1013
import lombok.Builder;
1114

12-
import java.time.Duration;
13-
1415
/**
1516
* CacheController is a controller to manage the cache of the provider.
1617
*/
1718
public class CacheController {
1819
public static final long DEFAULT_CACHE_TTL_MS = 5L * 60L * 1000L;
19-
public static final int DEFAULT_CACHE_CONCURRENCY_LEVEL = 1;
2020
public static final int DEFAULT_CACHE_INITIAL_CAPACITY = 100;
2121
public static final int DEFAULT_CACHE_MAXIMUM_SIZE = 100000;
2222
private final Cache<String, ProviderEvaluation<?>> cache;
2323

2424
@Builder
2525
public CacheController(GoFeatureFlagProviderOptions options) {
26-
this.cache = options.getCacheBuilder() != null ? options.getCacheBuilder().build() : buildDefaultCache();
26+
this.cache = options.getCacheConfig() != null ? options.getCacheConfig().build() : buildDefaultCache();
2727
}
2828

2929
private Cache<String, ProviderEvaluation<?>> buildDefaultCache() {
30-
return CacheBuilder.newBuilder()
31-
.concurrencyLevel(DEFAULT_CACHE_CONCURRENCY_LEVEL)
30+
return Caffeine.newBuilder()
3231
.initialCapacity(DEFAULT_CACHE_INITIAL_CAPACITY)
3332
.maximumSize(DEFAULT_CACHE_MAXIMUM_SIZE)
3433
.expireAfterWrite(Duration.ofMillis(DEFAULT_CACHE_TTL_MS))

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import com.fasterxml.jackson.databind.SerializationFeature;
77
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
8-
import com.google.common.net.HttpHeaders;
98
import dev.openfeature.contrib.providers.gofeatureflag.EvaluationResponse;
109
import dev.openfeature.contrib.providers.gofeatureflag.GoFeatureFlagProviderOptions;
1110
import dev.openfeature.contrib.providers.gofeatureflag.bean.ConfigurationChange;
@@ -60,6 +59,11 @@ public class GoFeatureFlagController {
6059
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
6160
private static final String BEARER_TOKEN = "Bearer ";
6261

62+
private static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
63+
private static final String HTTP_HEADER_AUTHORIZATION = "Authorization";
64+
private static final String HTTP_HEADER_ETAG = "ETag";
65+
private static final String HTTP_HEADER_IF_NONE_MATCH = "If-None-Match";
66+
6367
/**
6468
* apiKey contains the token to use while calling GO Feature Flag relay proxy.
6569
*/
@@ -137,13 +141,13 @@ public <T> EvaluationResponse<T> evaluateFlag(
137141

138142
Request.Builder reqBuilder = new Request.Builder()
139143
.url(url)
140-
.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
144+
.addHeader(HTTP_HEADER_CONTENT_TYPE, APPLICATION_JSON)
141145
.post(RequestBody.create(
142146
requestMapper.writeValueAsBytes(goffRequest),
143147
MediaType.get("application/json; charset=utf-8")));
144148

145149
if (this.apiKey != null && !this.apiKey.isEmpty()) {
146-
reqBuilder.addHeader(HttpHeaders.AUTHORIZATION, BEARER_TOKEN + this.apiKey);
150+
reqBuilder.addHeader(HTTP_HEADER_AUTHORIZATION, BEARER_TOKEN + this.apiKey);
147151
}
148152

149153
try (Response response = this.httpClient.newCall(reqBuilder.build()).execute()) {
@@ -216,13 +220,13 @@ public void sendEventToDataCollector(List<Event> eventsList) {
216220

217221
Request.Builder reqBuilder = new Request.Builder()
218222
.url(url)
219-
.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
223+
.addHeader(HTTP_HEADER_CONTENT_TYPE, APPLICATION_JSON)
220224
.post(RequestBody.create(
221225
requestMapper.writeValueAsBytes(events),
222226
MediaType.get("application/json; charset=utf-8")));
223227

224228
if (this.apiKey != null && !this.apiKey.isEmpty()) {
225-
reqBuilder.addHeader(HttpHeaders.AUTHORIZATION, BEARER_TOKEN + this.apiKey);
229+
reqBuilder.addHeader(HTTP_HEADER_AUTHORIZATION, BEARER_TOKEN + this.apiKey);
226230
}
227231

228232
try (Response response = this.httpClient.newCall(reqBuilder.build()).execute()) {
@@ -259,14 +263,14 @@ public ConfigurationChange configurationHasChanged() throws GoFeatureFlagExcepti
259263

260264
Request.Builder reqBuilder = new Request.Builder()
261265
.url(url)
262-
.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
266+
.addHeader(HTTP_HEADER_CONTENT_TYPE, APPLICATION_JSON)
263267
.get();
264268

265269
if (this.etag != null && !this.etag.isEmpty()) {
266-
reqBuilder.addHeader(HttpHeaders.IF_NONE_MATCH, this.etag);
270+
reqBuilder.addHeader(HTTP_HEADER_IF_NONE_MATCH, this.etag);
267271
}
268272
if (this.apiKey != null && !this.apiKey.isEmpty()) {
269-
reqBuilder.addHeader(HttpHeaders.AUTHORIZATION, BEARER_TOKEN + this.apiKey);
273+
reqBuilder.addHeader(HTTP_HEADER_AUTHORIZATION, BEARER_TOKEN + this.apiKey);
270274
}
271275

272276
try (Response response = this.httpClient.newCall(reqBuilder.build()).execute()) {
@@ -283,7 +287,7 @@ public ConfigurationChange configurationHasChanged() throws GoFeatureFlagExcepti
283287
}
284288

285289
boolean isInitialConfiguration = this.etag == null;
286-
this.etag = response.header(HttpHeaders.ETAG);
290+
this.etag = response.header(HTTP_HEADER_ETAG);
287291
return isInitialConfiguration
288292
? ConfigurationChange.FLAG_CONFIGURATION_INITIALIZED
289293
: ConfigurationChange.FLAG_CONFIGURATION_UPDATED;

providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java

+19-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package dev.openfeature.contrib.providers.gofeatureflag;
22

3+
import static dev.openfeature.contrib.providers.gofeatureflag.controller.GoFeatureFlagController.requestMapper;
4+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5+
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
import static org.junit.jupiter.api.Assertions.assertThrows;
7+
38
import java.io.IOException;
49
import java.net.URL;
510
import java.nio.charset.StandardCharsets;
@@ -10,42 +15,34 @@
1015
import java.util.List;
1116
import java.util.Map;
1217

13-
import com.google.common.cache.CacheBuilder;
18+
import org.jetbrains.annotations.NotNull;
19+
import org.junit.jupiter.api.AfterEach;
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.TestInfo;
23+
24+
import com.github.benmanes.caffeine.cache.Caffeine;
1425
import com.google.common.net.HttpHeaders;
26+
27+
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint;
28+
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions;
1529
import dev.openfeature.sdk.Client;
1630
import dev.openfeature.sdk.ErrorCode;
17-
import dev.openfeature.sdk.EvaluationContext;
1831
import dev.openfeature.sdk.FlagEvaluationDetails;
1932
import dev.openfeature.sdk.ImmutableContext;
20-
import dev.openfeature.sdk.OpenFeatureAPI;
21-
import dev.openfeature.sdk.ProviderState;
22-
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
23-
import lombok.extern.slf4j.Slf4j;
24-
import org.jetbrains.annotations.NotNull;
2533
import dev.openfeature.sdk.ImmutableMetadata;
2634
import dev.openfeature.sdk.MutableContext;
2735
import dev.openfeature.sdk.MutableStructure;
36+
import dev.openfeature.sdk.OpenFeatureAPI;
2837
import dev.openfeature.sdk.Reason;
2938
import dev.openfeature.sdk.Value;
30-
import org.junit.jupiter.api.AfterEach;
31-
import org.junit.jupiter.api.BeforeEach;
32-
import org.junit.jupiter.api.Test;
33-
34-
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint;
35-
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions;
3639
import lombok.SneakyThrows;
40+
import lombok.extern.slf4j.Slf4j;
3741
import okhttp3.HttpUrl;
3842
import okhttp3.mockwebserver.Dispatcher;
3943
import okhttp3.mockwebserver.MockResponse;
4044
import okhttp3.mockwebserver.MockWebServer;
4145
import okhttp3.mockwebserver.RecordedRequest;
42-
import org.junit.jupiter.api.TestInfo;
43-
44-
import static dev.openfeature.contrib.providers.gofeatureflag.controller.GoFeatureFlagController.requestMapper;
45-
import static org.assertj.core.api.Assertions.assertThat;
46-
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
47-
import static org.junit.jupiter.api.Assertions.assertEquals;
48-
import static org.junit.jupiter.api.Assertions.assertThrows;
4946

5047
@Slf4j
5148
class GoFeatureFlagProviderTest {
@@ -361,9 +358,9 @@ void should_resolve_from_cache() {
361358
@SneakyThrows
362359
@Test
363360
void should_resolve_from_cache_max_size() {
364-
CacheBuilder cacheBuilder = CacheBuilder.newBuilder().maximumSize(1);
361+
Caffeine caffeine = Caffeine.newBuilder().maximumSize(1);
365362
GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder()
366-
.endpoint(this.baseUrl.toString()).timeout(1000).cacheBuilder(cacheBuilder).build());
363+
.endpoint(this.baseUrl.toString()).timeout(1000).cacheConfig(caffeine).build());
367364
String providerName = this.testName;
368365
OpenFeatureAPI.getInstance().setProviderAndWait(providerName, g);
369366
Client client = OpenFeatureAPI.getInstance().getClient(providerName);
@@ -406,10 +403,6 @@ void should_resolve_from_cache_max_size() {
406403
.flagMetadata(defaultMetadata)
407404
.build();
408405
assertEquals(wantStr2, gotStr);
409-
410-
// verify that value previously fetch from cache now not fetched from cache since cache max size is 1, and cache is full.
411-
got = client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext);
412-
assertEquals(want, got);
413406
}
414407

415408
@SneakyThrows

0 commit comments

Comments
 (0)