Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement domain scoping #934

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ See [here](https://javadoc.io/doc/dev.openfeature/sdk/latest/) for the Javadocs.
| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
| ✅ | [Logging](#logging) | Integrate with popular logging packages. |
| ✅ | [Named clients](#named-clients) | Utilize multiple providers in a single application. |
| ✅ | [Domains](#domains) | Logically bind clients with providers. |
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
| ✅ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread). |
Expand Down Expand Up @@ -160,7 +160,7 @@ To register a provider in a non-blocking manner, you can use the `setProvider` m
```

In some situations, it may be beneficial to register multiple providers in the same application.
This is possible using [named clients](#named-clients), which is covered in more details below.
This is possible using [domains](#domains), which is covered in more detail below.

### Targeting

Expand Down Expand Up @@ -219,27 +219,27 @@ Once you've added a hook as a dependency, it can be registered at the global, cl

The Java SDK uses SLF4J. See the [SLF4J manual](https://slf4j.org/manual.html) for complete documentation.

### Named clients
### Domains

Clients can be given a name.
A name is a logical identifier which can be used to associate clients with a particular provider.
If a name has no associated provider, the global provider is used.
Clients can be assigned to a domain.
A domain is a logical identifier which can be used to associate clients with a particular provider.
If a domain has no associated provider, the global provider is used.

```java
FeatureProvider scopedProvider = new MyProvider();

// registering the default provider
OpenFeatureAPI.getInstance().setProvider(LocalProvider());
// registering a named provider
OpenFeatureAPI.getInstance().setProvider("clientForCache", new CachedProvider());
// registering a provider to a domain
OpenFeatureAPI.getInstance().setProvider("my-domain", new CachedProvider());

// a client backed by default provider
// A client bound to the default provider
Client clientDefault = OpenFeatureAPI.getInstance().getClient();
// a client backed by CachedProvider
Client clientNamed = OpenFeatureAPI.getInstance().getClient("clientForCache");
// A client bound to the CachedProvider provider
Client domainScopedClient = OpenFeatureAPI.getInstance().getClient("my-domain");
```

Named providers can be set in a blocking or non-blocking way.
Providers for domains can be set in a blocking or non-blocking way.
For more details, please refer to the [providers](#providers) section.

### Eventing
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/dev/openfeature/sdk/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Interface used to resolve flags of varying types.
*/
public interface Client extends Features, EventBus<Client> {
Metadata getMetadata();
ClientMetadata getMetadata();
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved

/**
* Return an optional client-level evaluation context.
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/dev/openfeature/sdk/ClientMetadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.openfeature.sdk;

/**
* Metadata specific to an OpenFeature {@code Client}.
*/
public interface ClientMetadata {
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
String getDomain();

@Deprecated
// this is here for compatibility with getName() exposed from {@link Metadata}
default String getName() {
return getDomain();
}
}
6 changes: 3 additions & 3 deletions src/main/java/dev/openfeature/sdk/EventDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@Data
@SuperBuilder(toBuilder = true)
public class EventDetails extends ProviderEventDetails {
private String clientName;
private String domain;
private String providerName;

static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventDetails, String providerName) {
Expand All @@ -19,9 +19,9 @@ static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventD
static EventDetails fromProviderEventDetails(
ProviderEventDetails providerEventDetails,
String providerName,
String clientName) {
String domain) {
return builder()
.clientName(clientName)
.domain(domain)
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
.providerName(providerName)
.flagsChanged(providerEventDetails.getFlagsChanged())
.eventMetadata(providerEventDetails.getEventMetadata())
Expand Down
35 changes: 17 additions & 18 deletions src/main/java/dev/openfeature/sdk/EventSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ class EventSupport {
});

/**
* Run all the event handlers associated with this client name.
* If the client name is null, handlers attached to unnamed clients will run.
* Run all the event handlers associated with this domain.
* If the domain is null, handlers attached to unnamed clients will run.
*
* @param clientName the client name to run event handlers for, or null
* @param domain the domain to run event handlers for, or null
* @param event the event type
* @param eventDetails the event details
*/
public void runClientHandlers(String clientName, ProviderEvent event, EventDetails eventDetails) {
clientName = Optional.ofNullable(clientName)
public void runClientHandlers(String domain, ProviderEvent event, EventDetails eventDetails) {
domain = Optional.ofNullable(domain)
.orElse(defaultClientUuid);

// run handlers if they exist
Optional.ofNullable(handlerStores.get(clientName))
Optional.ofNullable(handlerStores.get(domain))
.filter(store -> Optional.of(store).isPresent())
.map(store -> store.handlerMap.get(event))
.ifPresent(handlers -> handlers
Expand All @@ -66,15 +66,14 @@ public void runGlobalHandlers(ProviderEvent event, EventDetails eventDetails) {
}

/**
* Add a handler for the specified client name, or all unnamed clients.
* Add a handler for the specified domain, or all unnamed clients.
*
* @param clientName the client name to add handlers for, or else the unnamed
* client
* @param domain the domain to add handlers for, or else unnamed
* @param event the event type
* @param handler the handler function to run
*/
public void addClientHandler(String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
final String name = Optional.ofNullable(clientName)
public void addClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
final String name = Optional.ofNullable(domain)
.orElse(defaultClientUuid);

// lazily create and cache a HandlerStore if it doesn't exist
Expand All @@ -90,15 +89,15 @@ public void addClientHandler(String clientName, ProviderEvent event, Consumer<Ev
/**
* Remove a client event handler for the specified event type.
*
* @param clientName the name of the client handler to remove, or null to remove
* @param domain the domain of the client handler to remove, or null to remove
* from unnamed clients
* @param event the event type
* @param handler the handler ref to be removed
*/
public void removeClientHandler(String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
clientName = Optional.ofNullable(clientName)
public void removeClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
domain = Optional.ofNullable(domain)
.orElse(defaultClientUuid);
this.handlerStores.get(clientName).removeHandler(event, handler);
this.handlerStores.get(domain).removeHandler(event, handler);
}

/**
Expand All @@ -122,11 +121,11 @@ public void removeGlobalHandler(ProviderEvent event, Consumer<EventDetails> hand
}

/**
* Get all client names for which we have event handlers registered.
* Get all domain names for which we have event handlers registered.
*
* @return set of client names
* @return set of domain names
*/
public Set<String> getAllClientNames() {
public Set<String> getAllDomainNames() {
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
return this.handlerStores.keySet();
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/dev/openfeature/sdk/HookContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class HookContext<T> {
@NonNull FlagValueType type;
@NonNull T defaultValue;
@NonNull EvaluationContext ctx;
Metadata clientMetadata;
ClientMetadata clientMetadata;
Metadata providerMetadata;

/**
Expand All @@ -30,7 +30,7 @@ public class HookContext<T> {
* @param <T> type that the flag is evaluating against
* @return resulting context for hook
*/
public static <T> HookContext<T> from(String key, FlagValueType type, Metadata clientMetadata,
public static <T> HookContext<T> from(String key, FlagValueType type, ClientMetadata clientMetadata,
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
Metadata providerMetadata, EvaluationContext ctx, T defaultValue) {
return HookContext.<T>builder()
.flagKey(key)
Expand Down
82 changes: 41 additions & 41 deletions src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.internal.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.internal.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import lombok.extern.slf4j.Slf4j;

/**
* A global singleton which holds base configuration for the OpenFeature
* library.
Expand Down Expand Up @@ -52,8 +52,8 @@ public Metadata getProviderMetadata() {
return getProvider().getMetadata();
}

public Metadata getProviderMetadata(String clientName) {
return getProvider(clientName).getMetadata();
public Metadata getProviderMetadata(String domain) {
return getProvider(domain).getMetadata();
}

/**
Expand All @@ -66,16 +66,16 @@ public Client getClient() {
/**
* {@inheritDoc}
*/
public Client getClient(String name) {
return getClient(name, null);
public Client getClient(String domain) {
return getClient(domain, null);
}

/**
* {@inheritDoc}
*/
public Client getClient(String name, String version) {
public Client getClient(String domain, String version) {
return new OpenFeatureClient(this,
name,
domain,
version);
}

Expand Down Expand Up @@ -154,14 +154,14 @@ public void setProvider(FeatureProvider provider) {
}

/**
* Add a provider for a named client.
* Add a provider for a domain.
*
* @param clientName The name of the client.
* @param domain The domain to bind the provider to.
* @param provider The provider to set.
*/
public void setProvider(String clientName, FeatureProvider provider) {
public void setProvider(String domain, FeatureProvider provider) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(clientName,
providerRepository.setProvider(domain,
provider,
this::attachEventProvider,
this::emitReady,
Expand All @@ -187,14 +187,14 @@ public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError
}

/**
* Add a provider for a named client and wait for initialization to finish.
* Add a provider for a domain and wait for initialization to finish.
*
* @param clientName The name of the client.
* @param domain The domain to bind the provider to.
* @param provider The provider to set.
*/
public void setProviderAndWait(String clientName, FeatureProvider provider) throws OpenFeatureError {
public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
providerRepository.setProvider(clientName,
providerRepository.setProvider(domain,
provider,
this::attachEventProvider,
this::emitReady,
Expand Down Expand Up @@ -240,13 +240,13 @@ public FeatureProvider getProvider() {
}

/**
* Fetch a provider for a named client. If not found, return the default.
* Fetch a provider for a domain. If not found, return the default.
*
* @param name The client name to look for.
* @param domain The domain to look for.
* @return A named {@link FeatureProvider}
*/
public FeatureProvider getProvider(String name) {
return providerRepository.getProvider(name);
public FeatureProvider getProvider(String domain) {
return providerRepository.getProvider(domain);
}

/**
Expand Down Expand Up @@ -344,20 +344,20 @@ public OpenFeatureAPI removeHandler(ProviderEvent event, Consumer<EventDetails>
return this;
}

void removeHandler(String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
void removeHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
eventSupport.removeClientHandler(clientName, event, handler);
eventSupport.removeClientHandler(domain, event, handler);
}
}

void addHandler(String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
// if the provider is in the state associated with event, run immediately
if (Optional.ofNullable(this.providerRepository.getProvider(clientName).getState())
if (Optional.ofNullable(this.providerRepository.getProvider(domain).getState())
.orElse(ProviderState.READY).matchesEvent(event)) {
eventSupport.runHandler(handler, EventDetails.builder().clientName(clientName).build());
eventSupport.runHandler(handler, EventDetails.builder().domain(domain).build());
}
eventSupport.addClientHandler(clientName, event, handler);
eventSupport.addClientHandler(domain, event, handler);
}
}

Expand All @@ -371,8 +371,8 @@ void addHandler(String clientName, ProviderEvent event, Consumer<EventDetails> h
private void runHandlersForProvider(FeatureProvider provider, ProviderEvent event, ProviderEventDetails details) {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {

List<String> clientNamesForProvider = providerRepository
.getClientNamesForProvider(provider);
List<String> domainsForProvider = providerRepository
.getDomainsForProvider(provider);

final String providerName = Optional.ofNullable(provider.getMetadata())
.map(metadata -> metadata.getName())
Expand All @@ -381,20 +381,20 @@ private void runHandlersForProvider(FeatureProvider provider, ProviderEvent even
// run the global handlers
eventSupport.runGlobalHandlers(event, EventDetails.fromProviderEventDetails(details, providerName));

// run the handlers associated with named clients for this provider
clientNamesForProvider.forEach(name -> {
eventSupport.runClientHandlers(name, event,
EventDetails.fromProviderEventDetails(details, providerName, name));
// run the handlers associated with domains for this provider
domainsForProvider.forEach(domain -> {
eventSupport.runClientHandlers(domain, event,
EventDetails.fromProviderEventDetails(details, providerName, domain));
});

if (providerRepository.isDefaultProvider(provider)) {
// run handlers for clients that have no bound providers (since this is the default)
Set<String> allClientNames = eventSupport.getAllClientNames();
Set<String> boundClientNames = providerRepository.getAllBoundClientNames();
allClientNames.removeAll(boundClientNames);
allClientNames.forEach(name -> {
eventSupport.runClientHandlers(name, event,
EventDetails.fromProviderEventDetails(details, providerName, name));
Set<String> allDomainNames = eventSupport.getAllDomainNames();
Set<String> boundDomains = providerRepository.getAllBoundDomains();
allDomainNames.removeAll(boundDomains);
allDomainNames.forEach(domain -> {
eventSupport.runClientHandlers(domain, event,
EventDetails.fromProviderEventDetails(details, providerName, domain));
});
}
}
Expand Down
Loading
Loading