Skip to content

Commit

Permalink
make the eventing provider specific instead of being singletone
Browse files Browse the repository at this point in the history
Signed-off-by: vahid torkaman <[email protected]>
  • Loading branch information
vahidlazio committed Dec 14, 2023
1 parent 4d19a0a commit 2d3d26c
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dev.openfeature.sdk

interface FeatureProvider {
import dev.openfeature.sdk.events.EventObserver
import dev.openfeature.sdk.events.ProviderStatus

interface FeatureProvider : EventObserver, ProviderStatus {
val hooks: List<Hook<*>>
val metadata: ProviderMetadata

Expand Down
8 changes: 8 additions & 0 deletions OpenFeature/src/main/java/dev/openfeature/sdk/NoOpProvider.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvider {
override val metadata: ProviderMetadata = NoOpProviderMetadata("No-op provider")
override fun initialize(initialContext: EvaluationContext?) {
Expand Down Expand Up @@ -57,5 +61,9 @@ class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvid
return ProviderEvaluation(defaultValue, "Passed in default", Reason.DEFAULT.toString())
}

override fun observe(): Flow<OpenFeatureEvents> = flowOf()

override fun isProviderReady(): Boolean = true

data class NoOpProviderMetadata(override val name: String?) : ProviderMetadata
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher

@Suppress("TooManyFunctions")
object OpenFeatureAPI {
private var provider: FeatureProvider? = null
Expand All @@ -23,10 +18,6 @@ object OpenFeatureAPI {
return provider
}

inline fun <reified T : OpenFeatureEvents> observeEvents(dispatcher: CoroutineDispatcher) =
EventHandler.eventsObserver(dispatcher)
.observe<T>()

fun clearProvider() {
provider = null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dev.openfeature.sdk.async

import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.Client
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.Value
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
Expand All @@ -16,10 +16,11 @@ interface AsyncClient {
}

internal class AsyncClientImpl(
private val client: OpenFeatureClient,
private val dispatcher: CoroutineDispatcher
private val client: Client,
private val provider: FeatureProvider
) : AsyncClient {
private fun <T> observeEvents(callback: () -> T) = observeProviderReady(dispatcher)
private fun <T> observeEvents(callback: () -> T) = provider
.observeProviderReady()
.map { callback() }
.distinctUntilChanged()

Expand Down
38 changes: 27 additions & 11 deletions OpenFeature/src/main/java/dev/openfeature/sdk/async/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
package dev.openfeature.sdk.async

import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

fun OpenFeatureClient.toAsync(dispatcher: CoroutineDispatcher = Dispatchers.IO): AsyncClient {
return AsyncClientImpl(this, dispatcher)
fun OpenFeatureClient.toAsync(): AsyncClient? {
val provider = OpenFeatureAPI.getProvider()
return provider?.let {
AsyncClientImpl(
this,
it
)
}
}

internal fun observeProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = EventHandler.eventsObserver(dispatcher)
.observe<OpenFeatureEvents.ProviderReady>()
internal fun FeatureProvider.observeProviderReady() = observe<OpenFeatureEvents.ProviderReady>()
.onStart {
if (EventHandler.providerStatus().isProviderReady()) {
if (isProviderReady()) {
this.emit(OpenFeatureEvents.ProviderReady)
}
}

suspend fun awaitProviderReady(
suspend fun OpenFeatureAPI.awaitProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
val provider = getProvider()
requireNotNull(provider)
return provider.awaitProviderReady(dispatcher)
}

fun OpenFeatureAPI.observeEvents(): Flow<OpenFeatureEvents>? {
return getProvider()?.observe()
}

suspend fun FeatureProvider.awaitProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = suspendCancellableCoroutine { continuation ->
val coroutineScope = CoroutineScope(dispatcher)
Expand All @@ -40,8 +57,7 @@ suspend fun awaitProviderReady(
}

coroutineScope.launch {
EventHandler.eventsObserver()
.observe<OpenFeatureEvents.ProviderError>()
observe<OpenFeatureEvents.ProviderError>()
.take(1)
.collect {
continuation.resumeWith(Result.failure(it.error))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,28 @@ package dev.openfeature.sdk.events

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

interface ProviderStatus {
fun isProviderReady(): Boolean
interface EventObserver {
fun observe(): Flow<OpenFeatureEvents>
}

interface EventObserver {
fun <T : OpenFeatureEvents> observe(kClass: KClass<T>): Flow<T>
interface ProviderStatus {
fun isProviderReady(): Boolean
}

interface EventsPublisher {
fun publish(event: OpenFeatureEvents)
}

inline fun <reified T : OpenFeatureEvents> EventObserver.observe() = observe(T::class)
inline fun <reified T : OpenFeatureEvents> EventObserver.observe() = observe()
.filterIsInstance<T>()

class EventHandler(dispatcher: CoroutineDispatcher) : EventObserver, EventsPublisher, ProviderStatus {
private val sharedFlow: MutableSharedFlow<OpenFeatureEvents> = MutableSharedFlow()
Expand Down Expand Up @@ -56,29 +55,9 @@ class EventHandler(dispatcher: CoroutineDispatcher) : EventObserver, EventsPubli
}
}

override fun <T : OpenFeatureEvents> observe(kClass: KClass<T>): Flow<T> = sharedFlow
.filterIsInstance(kClass)
override fun observe(): Flow<OpenFeatureEvents> = sharedFlow

override fun isProviderReady(): Boolean {
return isProviderReady.value
}

companion object {
@Volatile
private var instance: EventHandler? = null

private fun getInstance(dispatcher: CoroutineDispatcher) =
instance ?: synchronized(this) {
instance ?: create(dispatcher).also { instance = it }
}

fun eventsObserver(dispatcher: CoroutineDispatcher = Dispatchers.IO): EventObserver =
getInstance(dispatcher)
internal fun providerStatus(dispatcher: CoroutineDispatcher = Dispatchers.IO): ProviderStatus =
getInstance(dispatcher)
fun eventsPublisher(dispatcher: CoroutineDispatcher = Dispatchers.IO): EventsPublisher =
getInstance(dispatcher)

private fun create(dispatcher: CoroutineDispatcher) = EventHandler(dispatcher)
}
}
Loading

0 comments on commit 2d3d26c

Please sign in to comment.