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 6292faa
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 89 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
9 changes: 9 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,10 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.reflect.KClass

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 +62,9 @@ class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvid
return ProviderEvaluation(defaultValue, "Passed in default", Reason.DEFAULT.toString())
}

override fun <T : OpenFeatureEvents> observe(kClass: KClass<T>): Flow<T> = flow { }

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,6 +1,7 @@
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
Expand All @@ -16,10 +17,12 @@ interface AsyncClient {
}

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

Expand Down
41 changes: 30 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,53 @@
package dev.openfeature.sdk.async

import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.EventObserver
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(
dispatcher: CoroutineDispatcher = Dispatchers.IO
): AsyncClient? {
val provider = OpenFeatureAPI.getProvider()
return provider?.let {
AsyncClientImpl(
this,
it,
dispatcher
)
}
}

internal fun observeProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = EventHandler.eventsObserver(dispatcher)
.observe<OpenFeatureEvents.ProviderReady>()
internal fun EventObserver.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)
}

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

suspend fun EventObserver.awaitProviderReady(
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = suspendCancellableCoroutine { continuation ->
val coroutineScope = CoroutineScope(dispatcher)
Expand All @@ -40,8 +60,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,7 +2,6 @@ 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
Expand All @@ -12,14 +11,14 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

interface ProviderStatus {
fun isProviderReady(): Boolean
}

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

interface ProviderStatus {
fun isProviderReady(): Boolean
}

interface EventsPublisher {
fun publish(event: OpenFeatureEvents)
}
Expand Down Expand Up @@ -62,23 +61,4 @@ class EventHandler(dispatcher: CoroutineDispatcher) : EventObserver, EventsPubli
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 6292faa

Please sign in to comment.