From af667970e49f27761bd0e3a8dcdf17d6d02f4897 Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Wed, 18 Sep 2024 00:07:12 -0400 Subject: [PATCH] Feature flag impl --- .../java/cc/pulseapp/api/model/Feature.java | 46 ++++++++++++++ .../cc/pulseapp/api/service/FlagsService.java | 61 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/main/java/cc/pulseapp/api/model/Feature.java create mode 100644 src/main/java/cc/pulseapp/api/service/FlagsService.java diff --git a/src/main/java/cc/pulseapp/api/model/Feature.java b/src/main/java/cc/pulseapp/api/model/Feature.java new file mode 100644 index 0000000..ea91a1a --- /dev/null +++ b/src/main/java/cc/pulseapp/api/model/Feature.java @@ -0,0 +1,46 @@ +package cc.pulseapp.api.model; + +import lombok.*; + +/** + * A feature flag. + * + * @author Braydon + */ +@RequiredArgsConstructor @Getter @ToString +public enum Feature { + USER_REGISTRATION_ENABLED("user-registration"), + ORG_CREATION_ENABLED("org-creation"); + + public static final Feature[] VALUES = values(); + + /** + * The name of this feature. + */ + @NonNull private final String id; + + /** + * Whether this feature is enabled. + */ + @Setter private boolean enabled; + + /** + * The value of this feature, if any. + */ + @Setter private Object value; + + /** + * Get a feature by its id. + * + * @param id the feature id + * @return the feature, null if none + */ + public static Feature getById(@NonNull String id) { + for (Feature feature : VALUES) { + if (feature.getId().equals(id)) { + return feature; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/service/FlagsService.java b/src/main/java/cc/pulseapp/api/service/FlagsService.java new file mode 100644 index 0000000..00e21aa --- /dev/null +++ b/src/main/java/cc/pulseapp/api/service/FlagsService.java @@ -0,0 +1,61 @@ +package cc.pulseapp.api.service; + +import cc.pulseapp.api.model.Feature; +import com.flagsmith.FlagsmithClient; +import com.flagsmith.models.BaseFlag; +import jakarta.annotation.PostConstruct; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +/** + * This service is responsible for + * fetching remote feature flags. + * + * @author Braydon + */ +@Service @Log4j2(topic = "Flags") +public final class FlagsService { + private static final long FETCH_INTERVAL = TimeUnit.SECONDS.toMillis(30L); + + @Value("${flagsmith.api-url}") + private String apiUrl; + + @Value("${flagsmith.api-key}") + private String apiKey; + + /** + * The Flagsmith client. + */ + private FlagsmithClient client; + + @PostConstruct + public void onInitialize() { + client = FlagsmithClient.newBuilder() + .withApiUrl(apiUrl) + .setApiKey(apiKey) + .build(); + + // Schedule a task to fetch all flags + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override @SneakyThrows + public void run() { + for (BaseFlag flag : client.getEnvironmentFlags().getAllFlags()) { + Feature feature = Feature.getById(flag.getFeatureName()); + if (feature == null) { + continue; + } + Object value = flag.getValue(); + feature.setEnabled(flag.getEnabled()); + feature.setValue(value instanceof String stringedValue && (stringedValue.isBlank()) ? null : value); + } + log.info("Fetched new flags (:"); + } + }, 0L, FETCH_INTERVAL); + } +} \ No newline at end of file