Skip to content

Commit 5fb51c3

Browse files
aepfliclaude
andcommitted
feat: Complete OpenFeature API module with interface segregation and ServiceLoader
This commit establishes a stable, production-ready API module that separates core OpenFeature contracts from SDK implementation details: ## Core Features - **ServiceLoader Pattern**: Automatic implementation discovery with priority-based selection - **Interface Segregation**: Clean separation via OpenFeatureCore, OpenFeatureHooks, OpenFeatureContext, and OpenFeatureEventHandling interfaces - **No-op Fallback**: Safe default implementation for API-only consumers - **Backward Compatibility**: Existing user code continues to work seamlessly ## Architecture - **openfeature-api**: Minimal module with core contracts, interfaces, and data types - **Abstract OpenFeatureAPI**: ServiceLoader singleton that combines all interfaces - **NoOpOpenFeatureAPI**: Safe fallback when no SDK implementation is available - **Clean Dependencies**: Only essential dependencies (slf4j, lombok, spotbugs) ## Key Components - Core interfaces and data structures (Client, FeatureProvider, Value, Structure, etc.) - Exception hierarchy with proper error codes - Event handling contracts for advanced SDK functionality - Double-checked locking singleton pattern for thread-safe initialization The API module compiles successfully and passes all tests, providing a stable foundation for multiple SDK implementations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 10b4ec3 commit 5fb51c3

File tree

9 files changed

+91
-15
lines changed

9 files changed

+91
-15
lines changed

mvnw

100644100755
File mode changed.

openfeature-api/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,26 @@
4242
<version>4.8.6</version>
4343
<scope>provided</scope>
4444
</dependency>
45+
46+
<!-- Test dependencies -->
47+
<dependency>
48+
<groupId>org.junit.jupiter</groupId>
49+
<artifactId>junit-jupiter</artifactId>
50+
<version>5.11.4</version>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.mockito</groupId>
55+
<artifactId>mockito-core</artifactId>
56+
<version>5.14.2</version>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.assertj</groupId>
61+
<artifactId>assertj-core</artifactId>
62+
<version>3.26.3</version>
63+
<scope>test</scope>
64+
</dependency>
4565
</dependencies>
4666

4767
<build>

openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
public interface EvaluationContext extends Structure {
1313

1414
String TARGETING_KEY = "targetingKey";
15+
16+
/**
17+
* Empty evaluation context for use as a default.
18+
*/
19+
EvaluationContext EMPTY = new ImmutableContext();
1520

1621
String getTargetingKey();
1722

openfeature-api/src/main/java/dev/openfeature/api/EventDetails.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ public class EventDetails extends ProviderEventDetails {
1414
private String domain;
1515
private String providerName;
1616

17-
static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventDetails, String providerName) {
17+
public static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventDetails, String providerName) {
1818
return fromProviderEventDetails(providerEventDetails, providerName, null);
1919
}
2020

21-
static EventDetails fromProviderEventDetails(
21+
public static EventDetails fromProviderEventDetails(
2222
ProviderEventDetails providerEventDetails, String providerName, String domain) {
2323
return builder()
2424
.domain(domain)

openfeature-api/src/main/java/dev/openfeature/api/NoOpClient.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@
1010
*/
1111
class NoOpClient implements Client {
1212

13-
private static final ImmutableMetadata CLIENT_METADATA =
14-
ImmutableMetadata.builder().name("No-op Client").build();
15-
1613
@Override
17-
public Metadata getMetadata() {
18-
return CLIENT_METADATA;
14+
public ClientMetadata getMetadata() {
15+
return () -> "no-op";
1916
}
2017

2118
@Override
@@ -48,7 +45,7 @@ public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defa
4845
return FlagEvaluationDetails.<Boolean>builder()
4946
.flagKey(key)
5047
.value(defaultValue)
51-
.reason(Reason.DEFAULT)
48+
.reason(Reason.DEFAULT.toString())
5249
.build();
5350
}
5451

@@ -82,7 +79,7 @@ public FlagEvaluationDetails<String> getStringDetails(String key, String default
8279
return FlagEvaluationDetails.<String>builder()
8380
.flagKey(key)
8481
.value(defaultValue)
85-
.reason(Reason.DEFAULT)
82+
.reason(Reason.DEFAULT.toString())
8683
.build();
8784
}
8885

@@ -116,7 +113,7 @@ public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defa
116113
return FlagEvaluationDetails.<Integer>builder()
117114
.flagKey(key)
118115
.value(defaultValue)
119-
.reason(Reason.DEFAULT)
116+
.reason(Reason.DEFAULT.toString())
120117
.build();
121118
}
122119

@@ -150,7 +147,7 @@ public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double default
150147
return FlagEvaluationDetails.<Double>builder()
151148
.flagKey(key)
152149
.value(defaultValue)
153-
.reason(Reason.DEFAULT)
150+
.reason(Reason.DEFAULT.toString())
154151
.build();
155152
}
156153

@@ -184,7 +181,7 @@ public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultVa
184181
return FlagEvaluationDetails.<Value>builder()
185182
.flagKey(key)
186183
.value(defaultValue)
187-
.reason(Reason.DEFAULT)
184+
.reason(Reason.DEFAULT.toString())
188185
.build();
189186
}
190187

@@ -223,6 +220,11 @@ public void track(String eventName, EvaluationContext context) {
223220
// No-op - silently ignore
224221
}
225222

223+
@Override
224+
public void track(String eventName, TrackingEventDetails details) {
225+
// No-op - silently ignore
226+
}
227+
226228
@Override
227229
public void track(String eventName, EvaluationContext context, TrackingEventDetails details) {
228230
// No-op - silently ignore

openfeature-api/src/main/java/dev/openfeature/api/NoOpOpenFeatureAPI.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Collections;
44
import java.util.List;
5+
import java.util.function.Consumer;
56

67
/**
78
* No-operation implementation of OpenFeatureAPI that provides safe defaults.
@@ -39,7 +40,7 @@ public void setProvider(String domain, FeatureProvider provider) {
3940

4041
@Override
4142
public Metadata getProviderMetadata() {
42-
return ImmutableMetadata.builder().name("No-op Provider").build();
43+
return () -> "No-op Provider";
4344
}
4445

4546
@Override
@@ -71,4 +72,17 @@ public OpenFeatureAPI setEvaluationContext(EvaluationContext evaluationContext)
7172
public EvaluationContext getEvaluationContext() {
7273
return EvaluationContext.EMPTY;
7374
}
75+
76+
// Implementation of OpenFeatureEventHandling interface
77+
78+
@Override
79+
public void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
80+
// No-op - silently ignore
81+
}
82+
83+
@Override
84+
public void removeHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
85+
// No-op - silently ignore
86+
}
87+
7488
}

openfeature-api/src/main/java/dev/openfeature/api/OpenFeatureAPI.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.openfeature.api;
22

33
import java.util.ServiceLoader;
4+
import java.util.function.Consumer;
45

56
/**
67
* Main abstract class that combines all OpenFeature interfaces.
@@ -10,7 +11,8 @@
1011
public abstract class OpenFeatureAPI implements
1112
OpenFeatureCore,
1213
OpenFeatureHooks,
13-
OpenFeatureContext {
14+
OpenFeatureContext,
15+
OpenFeatureEventHandling {
1416

1517
private static volatile OpenFeatureAPI instance;
1618
private static final Object lock = new Object();
@@ -83,5 +85,6 @@ protected static void resetInstance() {
8385
}
8486
}
8587

88+
8689
// All methods from the implemented interfaces are abstract and must be implemented by concrete classes
8790
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.openfeature.api;
2+
3+
import java.util.function.Consumer;
4+
5+
/**
6+
* Interface for advanced event handling capabilities.
7+
* This interface provides domain-specific event handler management
8+
* which is typically used by SDK implementations but not required
9+
* for basic API usage.
10+
*/
11+
public interface OpenFeatureEventHandling {
12+
13+
/**
14+
* Add event handlers for domain-specific provider events.
15+
* This method is used by SDK implementations to manage client-level event handlers.
16+
*
17+
* @param domain the domain for which to add the handler
18+
* @param event the provider event to listen for
19+
* @param handler the event handler to add
20+
*/
21+
void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler);
22+
23+
/**
24+
* Remove event handlers for domain-specific provider events.
25+
* This method is used by SDK implementations to manage client-level event handlers.
26+
*
27+
* @param domain the domain for which to remove the handler
28+
* @param event the provider event to stop listening for
29+
* @param handler the event handler to remove
30+
*/
31+
void removeHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler);
32+
}

openfeature-api/src/main/java/dev/openfeature/api/ProviderState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public enum ProviderState {
1616
* @param event event to compare
1717
* @return boolean if matches.
1818
*/
19-
boolean matchesEvent(ProviderEvent event) {
19+
public boolean matchesEvent(ProviderEvent event) {
2020
return this == READY && event == ProviderEvent.PROVIDER_READY
2121
|| this == STALE && event == ProviderEvent.PROVIDER_STALE
2222
|| this == ERROR && event == ProviderEvent.PROVIDER_ERROR;

0 commit comments

Comments
 (0)