Skip to content

Commit 43285e1

Browse files
committed
v1.1.0
1 parent 3ceb201 commit 43285e1

File tree

14 files changed

+203
-131
lines changed

14 files changed

+203
-131
lines changed

README.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# Fast Feature-Flags Provider
2+
23
An [open feature](https://openfeature.dev/) provider to operate with MongoDB
34

45
---
56

67
# Pre-requisites
8+
79
- MongoDB running (see for an example in `examples/docker-compose.yml`) )
810
- import the dependency in your `pom.xml` and add the GitHub repository. You need a PAT token with read packages scope.
11+
912
``` xml
1013
<dependency>
1114
<groupId>com.github.jacopocarlini</groupId>
@@ -24,7 +27,13 @@ An [open feature](https://openfeature.dev/) provider to operate with MongoDB
2427

2528
# How To Use
2629

30+
There are two versions of this provider: the `MongoDBFeatureFlagProvider`, which implements only the basic openFeature
31+
methods to evaluate flags,
32+
and the `MongoDBFeatureFlagProviderExtended`, which includes additional methods for retrieving, creating, deleting, and
33+
updating flags.
34+
2735
In your Java class:
36+
2837
``` java
2938
import it.jacopocarlini.fffp.providers.MongoDBFeatureFlagProvider;
3039
...
@@ -33,8 +42,8 @@ In your Java class:
3342
OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
3443

3544
// set the connection string
36-
MongoDBFeatureFlagProvider provider =
37-
new MongoDBFeatureFlagProvider(
45+
MongoDBFeatureFlagProviderExtended provider =
46+
new MongoDBFeatureFlagProviderExtended(
3847
"mongodb://user:adminpassword@localhost:27017/openfeature?authSource=admin");
3948
openFeatureAPI.setProvider(provider);
4049

@@ -65,11 +74,23 @@ In your Java class:
6574
}
6675
```
6776

68-
If the flag is not enabled the value will be the `defaultValue` passed in `client.getStringValue("font", "10")`.
77+
If the flag is not enabled the value will be the `defaultValue` passed in `client.getStringValue("font", "10")`.
78+
79+
| Parameter | Required | Description |
80+
|----------------|----------|------------------------|
81+
| flagKey | yes | the flag key |
82+
| enabled | yes | if the flag is enabled |
83+
| variants | yes | a map of variants |
84+
| defaultVariant | yes | the default variant |
85+
| timeWindow | no | the time window |
86+
| target | no | the target |
87+
| rollout | no | the rollout percentage |
6988

7089
## Set a Time Window
71-
You can set a time window to evaluate the flag.
72-
If the current time is outside the time window the flag will always be disabled.
90+
91+
You can set a time window to evaluate the flag.
92+
If the current time is outside the time window the flag will always be disabled.
93+
7394
``` java
7495
Flag.builder()
7596
...
@@ -101,6 +122,7 @@ Flag.builder()
101122
```
102123

103124
## Set a Rollout Percentage
125+
104126
You can set a rollout percentage foreach variant.
105127

106128
``` java
@@ -117,6 +139,6 @@ Flag.builder()
117139
.build();
118140
```
119141

120-
> **_NOTE:_** When a target key is provided in the context,
142+
> **_NOTE:_** When a target key is provided in the context,
121143
> the random assigned variant is persisted and used on subsequent evaluations, overriding the rollout percentage.
122144
> When the rollout percentage is modified or deleted, the assigned variant is also removed.

pom.xml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.github.jacopocarlini</groupId>
88
<artifactId>fast-feature-flags-provider</artifactId>
9-
<version>1.0.7</version>
9+
<version>1.1.0</version>
1010
<packaging>jar</packaging>
1111

1212

@@ -33,12 +33,7 @@
3333
<dependency>
3434
<groupId>org.springframework.boot</groupId>
3535
<artifactId>spring-boot-starter-data-mongodb</artifactId>
36-
</dependency>
37-
38-
<dependency>
39-
<groupId>org.projectlombok</groupId>
40-
<artifactId>lombok</artifactId>
41-
<optional>true</optional>
36+
<version>3.4.2</version>
4237
</dependency>
4338
<dependency>
4439
<groupId>org.springframework.boot</groupId>
@@ -50,6 +45,22 @@
5045
<artifactId>sdk</artifactId>
5146
<version>1.14.0</version>
5247
</dependency>
48+
<dependency>
49+
<groupId>org.projectlombok</groupId>
50+
<artifactId>lombok</artifactId>
51+
<version>1.18.36</version>
52+
<scope>provided</scope>
53+
</dependency>
54+
<dependency>
55+
<groupId>com.fasterxml.jackson.core</groupId>
56+
<artifactId>jackson-databind</artifactId>
57+
<version>2.17.2</version>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.springframework.boot</groupId>
61+
<artifactId>spring-boot-starter-validation</artifactId>
62+
</dependency>
63+
5364
</dependencies>
5465
<repositories>
5566
<repository>

src/main/java/com/github/jacopocarlini/fffp/config/MongoClientManager.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@
99
import org.springframework.data.mongodb.core.MongoTemplate;
1010

1111
/**
12-
* Manages the MongoDB client and provides a MongoTemplate for database interactions.
13-
* Ensures thread-safe operations when updating the connection settings.
12+
* Manages the MongoDB client and provides a MongoTemplate for database interactions. Ensures
13+
* thread-safe operations when updating the connection settings.
1414
*/
1515
public class MongoClientManager {
1616
private final Lock lock = new ReentrantLock();
1717
private MongoClient mongoClient;
1818
private MongoTemplate mongoTemplate;
1919
private String currentConnectionString = null;
2020

21-
2221
/**
23-
* Updates the MongoDB connection. Closes the current connection if it exists and establishes a new one
24-
* with the provided connection string.
22+
* Updates the MongoDB connection. Closes the current connection if it exists and establishes a
23+
* new one with the provided connection string.
2524
*
2625
* @param newConnectionString the new MongoDB connection string
27-
* @throws IllegalStateException if the MongoDB database name is not specified in the connection string
26+
* @throws IllegalStateException if the MongoDB database name is not specified in the connection
27+
* string
2828
*/
2929
public void updateConnection(String newConnectionString) {
3030
lock.lock();
@@ -53,9 +53,7 @@ public void updateConnection(String newConnectionString) {
5353
}
5454
}
5555

56-
/**
57-
* Shuts down the MongoClient, closing any open connections.
58-
*/
56+
/** Shuts down the MongoClient, closing any open connections. */
5957
public void shutdown() {
6058
if (mongoClient != null) {
6159
mongoClient.close();

src/main/java/com/github/jacopocarlini/fffp/entity/AssignedTarget.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package com.github.jacopocarlini.fffp.entity;
22

3-
import lombok.AllArgsConstructor;
4-
import lombok.Builder;
5-
import lombok.Data;
6-
import lombok.ToString;
3+
import lombok.*;
74
import org.springframework.data.mongodb.core.index.Indexed;
85
import org.springframework.data.mongodb.core.mapping.Document;
96

10-
@Builder
7+
@Builder(toBuilder = true)
118
@Data
12-
@ToString
13-
@AllArgsConstructor
9+
@AllArgsConstructor(access = AccessLevel.PUBLIC)
10+
@NoArgsConstructor(access = AccessLevel.PUBLIC)
1411
@Document(collection = "assignedTarget")
1512
public class AssignedTarget {
1613

src/main/java/com/github/jacopocarlini/fffp/entity/Flag.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,34 @@
33
import java.time.LocalDateTime;
44
import java.util.List;
55
import java.util.Map;
6-
import lombok.AllArgsConstructor;
7-
import lombok.Builder;
8-
import lombok.Data;
9-
import lombok.ToString;
6+
7+
import jakarta.validation.constraints.NotBlank;
8+
import jakarta.validation.constraints.NotNull;
9+
import jakarta.validation.constraints.Size;
10+
import lombok.*;
1011
import org.springframework.data.annotation.Id;
1112
import org.springframework.data.mongodb.core.index.Indexed;
1213
import org.springframework.data.mongodb.core.mapping.Document;
1314

1415
@Builder(toBuilder = true)
1516
@Data
16-
@AllArgsConstructor
17-
@ToString
17+
@AllArgsConstructor(access = AccessLevel.PUBLIC)
18+
@NoArgsConstructor(access = AccessLevel.PUBLIC)
1819
@Document(collection = "flags")
1920
public class Flag {
2021

21-
@Id
22-
private String id;
22+
@Id private String id;
2323

2424
@Indexed(unique = true)
25+
@NotBlank
2526
private String flagKey;
2627

28+
@NotNull
2729
private Boolean enabled;
2830

31+
@Size(min = 1)
2932
private Map<String, Object> variants;
33+
@NotBlank
3034
private String defaultVariant;
3135

3236
private List<Target> target;
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package com.github.jacopocarlini.fffp.entity;
22

3-
import lombok.AllArgsConstructor;
4-
import lombok.Builder;
5-
import lombok.Data;
6-
import lombok.ToString;
3+
import jakarta.validation.constraints.NotBlank;
4+
import lombok.*;
75

6+
@Builder(toBuilder = true)
87
@Data
9-
@Builder
10-
@ToString
11-
@AllArgsConstructor
8+
@AllArgsConstructor(access = AccessLevel.PUBLIC)
9+
@NoArgsConstructor(access = AccessLevel.PUBLIC)
1210
public class Target {
1311

12+
@NotBlank
1413
private String filter;
14+
@NotBlank
1515
private String variant;
1616
}

src/main/java/com/github/jacopocarlini/fffp/exceptions/FeatureFlagEvaluationException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package it.jacopocarlini.fffp.exceptions;
1+
package com.github.jacopocarlini.fffp.exceptions;
22

3-
public class FeatureFlagEvaluationException extends RuntimeException {
3+
public class FeatureFlagEvaluationException extends Exception {
44
public FeatureFlagEvaluationException(String message, Throwable cause) {
55
super(message, cause);
66
}

src/main/java/com/github/jacopocarlini/fffp/exceptions/InvalidFeatureFlagException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package it.jacopocarlini.fffp.exceptions;
1+
package com.github.jacopocarlini.fffp.exceptions;
22

3-
public class InvalidFeatureFlagException extends RuntimeException {
3+
public class InvalidFeatureFlagException extends Exception {
44
public InvalidFeatureFlagException(String message) {
55
super(message);
66
}

src/main/java/com/github/jacopocarlini/fffp/providers/MongoDBFeatureFlagProvider.java

Lines changed: 7 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
package com.github.jacopocarlini.fffp.providers;
22

3+
import static com.github.jacopocarlini.fffp.util.ProviderUtility.*;
4+
import static com.github.jacopocarlini.fffp.util.Reason.*;
35

46
import com.github.jacopocarlini.fffp.config.MongoClientManager;
57
import com.github.jacopocarlini.fffp.entity.AssignedTarget;
68
import com.github.jacopocarlini.fffp.entity.Flag;
9+
import com.github.jacopocarlini.fffp.exceptions.InvalidFeatureFlagException;
710
import com.github.jacopocarlini.fffp.repository.AssignedTargetRepository;
811
import com.github.jacopocarlini.fffp.repository.FlagRepository;
912
import dev.openfeature.sdk.*;
10-
import java.util.List;
1113
import java.util.Optional;
1214

13-
import static com.github.jacopocarlini.fffp.util.ProviderUtility.*;
14-
import static com.github.jacopocarlini.fffp.util.Reason.*;
15-
1615
public class MongoDBFeatureFlagProvider extends EventProvider {
1716

18-
private final FlagRepository flagRepository;
17+
protected final FlagRepository flagRepository;
1918

20-
private final AssignedTargetRepository assignedTargetRepository;
19+
protected final AssignedTargetRepository assignedTargetRepository;
2120

2221
private final MongoClientManager mongoClientManager;
2322

@@ -69,51 +68,6 @@ public ProviderEvaluation<Value> getObjectEvaluation(
6968
return evaluation(flagKey, defaultValue, ctx, Value.class);
7069
}
7170

72-
public List<Flag> getFlags() {
73-
return flagRepository.findAll();
74-
}
75-
76-
public Flag getFlag(String flagKey) {
77-
return getFlagIfIsPresent(flagKey);
78-
}
79-
80-
public void crateFlag(Flag flag) {
81-
var isPresent = flagRepository.findFirstByFlagKey(flag.getFlagKey()).isPresent();
82-
if (isPresent) {
83-
throw new it.jacopocarlini.fffp.exceptions.InvalidFeatureFlagException("Conflict. Flag already present");
84-
}
85-
86-
checkRolloutPercentage(flag);
87-
checkVariant(flag);
88-
89-
flagRepository.save(flag);
90-
}
91-
92-
public void updateFlag(String flagKey, Flag newFlag) {
93-
var flag = getFlagIfIsPresent(flagKey);
94-
95-
checkRolloutPercentage(newFlag);
96-
checkVariant(newFlag);
97-
98-
newFlag.setId(flag.getId());
99-
100-
if (newFlag.getRolloutPercentage() == null
101-
|| newFlag.getRolloutPercentage().equals(flag.getRolloutPercentage())) {
102-
assignedTargetRepository.deleteAllByFlagKey(flagKey);
103-
}
104-
105-
flagRepository.save(newFlag);
106-
}
107-
108-
public void deleteFlag(String flagKey) {
109-
var flag = flagRepository.findFirstByFlagKey(flagKey);
110-
if (flag.isEmpty()) {
111-
throw new it.jacopocarlini.fffp.exceptions.InvalidFeatureFlagException("Flag not found");
112-
}
113-
assignedTargetRepository.deleteAllByFlagKey(flagKey);
114-
flagRepository.deleteByFlagKey(flagKey);
115-
}
116-
11771
private <T> ProviderEvaluation<T> evaluation(
11872
String flagKey, T defaultValue, EvaluationContext ctx, Class<T> valueType) {
11973
try {
@@ -153,10 +107,8 @@ private <T> ProviderEvaluation<T> evaluation(
153107
.reason("default variant")
154108
.build();
155109

156-
} catch (ClassCastException e) {
157-
throw new it.jacopocarlini.fffp.exceptions.FeatureFlagEvaluationException("Value type mismatch for flag: " + flagKey, e);
158110
} catch (Exception e) {
159-
throw new it.jacopocarlini.fffp.exceptions.FeatureFlagEvaluationException("Error evaluating flag: " + flagKey, e);
111+
return createDefaultEvaluation(defaultValue, INVALID_FLAG_DATA.name());
160112
}
161113
}
162114

@@ -169,7 +121,7 @@ private <T> ProviderEvaluation<T> createDefaultEvaluation(T defaultValue, String
169121
}
170122

171123
private <T> ProviderEvaluation<T> handleRollout(
172-
Flag flag, EvaluationContext ctx, Class<T> valueType) {
124+
Flag flag, EvaluationContext ctx, Class<T> valueType) throws InvalidFeatureFlagException {
173125
String targetKey = ctx.getTargetingKey();
174126
String variant = determineVariantForRollout(flag);
175127
if (targetKey != null) {
@@ -195,12 +147,4 @@ private <T> ProviderEvaluation<T> handleRollout(
195147
.variant(variant)
196148
.build();
197149
}
198-
199-
private Flag getFlagIfIsPresent(String flagKey) {
200-
var flag = flagRepository.findFirstByFlagKey(flagKey);
201-
if (flag.isEmpty()) {
202-
throw new it.jacopocarlini.fffp.exceptions.InvalidFeatureFlagException("Flag not found");
203-
}
204-
return flag.get();
205-
}
206150
}

0 commit comments

Comments
 (0)