Skip to content

Commit

Permalink
contexual api and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
vahidlazio committed Mar 19, 2024
1 parent 760ff6f commit 8fac731
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class MainVm(app: Application) : AndroidViewModel(app) {
OpenFeatureAPI.setEvaluationContext(ctx)
}

eventSender.emit("eventDefinitions/navigate")
eventSender.send("eventDefinitions/navigate")

Log.d(TAG, "client secret is $clientSecret")
Log.d(TAG, "init took ${System.currentTimeMillis() - start} ms")
Expand All @@ -96,7 +96,7 @@ class MainVm(app: Application) : AndroidViewModel(app) {
}.toComposeColor()
_message.postValue(messageValue)
_color.postValue(colorFlag)
eventSender.emit("eventDefinitions/navigate", mapOf("screen" to ConfidenceValue.String("Hello")))
eventSender.send("eventDefinitions/navigate", mapOf("screen" to ConfidenceValue.String("Hello")))
}

fun updateContext() {
Expand Down
147 changes: 147 additions & 0 deletions Provider/src/main/java/com/spotify/confidence/Confidence.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.spotify.confidence

import android.content.Context
import com.spotify.confidence.client.ConfidenceRegion
import com.spotify.confidence.client.SdkMetadata
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import java.io.File

interface FlagEvaluator: Contextual {
suspend fun <T> getValue(flag: String, defaultValue: T): T
}

data class Evaluation<T>(
val reason: String,
val value: T,
)

interface FlagResolution {
val context: Map<String, ConfidenceValue>
fun <T> getEvaluation(flag: String, defaultValue: T): Evaluation<T>
fun <T> getValue(flag: String, defaultValue: T): T {
return getEvaluation(flag, defaultValue).value
}
}

class Confidence private constructor(
private val clientSecret: String,
private val region: ConfidenceRegion = ConfidenceRegion.GLOBAL,
private val dispatcher: CoroutineDispatcher,
private val eventSenderEngine: EventSenderEngine,
private val root: ConfidenceContextProvider
) : Contextual, EventSender, FlagEvaluator {
private val removedKeys = mutableListOf<String>()
private val coroutineScope = CoroutineScope(dispatcher)
private var contextMap: MutableMap<String, ConfidenceValue> = mutableMapOf()
internal val flagResolver by lazy {
RemoteFlagResolver(
clientSecret,
region,
OkHttpClient(),
dispatcher,
SdkMetadata(SDK_ID, BuildConfig.SDK_VERSION)
)
}

internal suspend fun resolveFlags(flags: List<String>): FlagResolution {
return flagResolver.resolve(flags, getContext())
}

override fun putContext(key: String, value: ConfidenceValue) {
contextMap[key] = value
}

override fun putContext(context: ConfidenceContext) {
putContext(context.name, context.value)
}

override fun setContext(context: Map<String, ConfidenceValue>) {
contextMap = context.toMutableMap()
}

override fun removeContext(key: String) {
removedKeys.add(key)
contextMap.remove(key)
}

override fun getContext(): Map<String, ConfidenceValue> =
this.root.getContext().filterKeys { removedKeys.contains(it) } + contextMap

override suspend fun <T> getValue(flag: String, defaultValue: T): T {
val response = resolveFlags(listOf(flag))
return response.getValue(flag, defaultValue)
}

override fun withContext(context: ConfidenceContext) = Confidence(
clientSecret,
region,
dispatcher,
eventSenderEngine,
this
).also {
it.putContext(context)
}
override fun send(
definition: String,
payload: ConfidenceFieldsType
) {
eventSenderEngine.emit(definition, payload, getContext())
}

override fun onLowMemory(body: (List<File>) -> Unit): EventSender {
coroutineScope.launch {
eventSenderEngine
.onLowMemoryChannel()
.consumeEach {
body(it)
}
}
return this
}

override fun stop() {
eventSenderEngine.stop()
}

companion object {
fun create(
context: Context,
clientSecret: String,
region: ConfidenceRegion = ConfidenceRegion.GLOBAL,
dispatcher: CoroutineDispatcher = Dispatchers.IO
): EventSender {
val engine = EventSenderEngine.instance(
context,
clientSecret,
flushPolicies = listOf(confidenceFlushPolicy),
dispatcher = dispatcher
)
val confidenceContext = object : ConfidenceContextProvider {
override fun getContext(): Map<String, ConfidenceValue> {
return emptyMap()
}
}
return Confidence(clientSecret, region, dispatcher, engine, confidenceContext)
}
}
}

private val confidenceFlushPolicy = object : FlushPolicy {
private var size = 0
override fun reset() {
size = 0
}

override fun hit(event: Event) {
size++
}

override fun shouldFlush(): Boolean {
return size > 4
}
}
44 changes: 44 additions & 0 deletions Provider/src/main/java/com/spotify/confidence/ConfidenceContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.spotify.confidence

import dev.openfeature.sdk.EvaluationContext

interface ConfidenceContextProvider {
fun getContext(): Map<String, ConfidenceValue>
}

typealias ConfidenceFieldsType = Map<String, ConfidenceValue>

interface Contextual : ConfidenceContextProvider {
fun withContext(context: ConfidenceContext): Contextual

fun putContext(context: ConfidenceContext)
fun setContext(context: Map<String, ConfidenceValue>)
fun putContext(key: String, value: ConfidenceValue)
fun removeContext(key: String)
}

interface ConfidenceContext {
val name: String
val value: ConfidenceValue
}

class PageContext(private val page: String) : ConfidenceContext {
override val value: ConfidenceValue
get() = ConfidenceValue.String(page)
override val name: String
get() = "page"
}

class CommonContext : ConfidenceContextProvider {
override fun getContext(): Map<String, ConfidenceValue> = mapOf()
}

fun EvaluationContext.toConfidenceContext() = object : ConfidenceContext {
override val name: String = "open_feature"
override val value: ConfidenceValue
get() = ConfidenceValue.Struct(
asMap()
.map { it.key to ConfidenceValue.String(it.value.toString()) }
.toMap() + ("targeting_key" to ConfidenceValue.String(getTargetingKey()))
)
}
112 changes: 2 additions & 110 deletions Provider/src/main/java/com/spotify/confidence/ConfidenceExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,115 +1,7 @@
package com.spotify.confidence

import android.content.Context
import com.spotify.confidence.client.ResolveResponse
import dev.openfeature.sdk.EvaluationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

interface ConfidenceContextProvider {
fun confidenceContext(): ConfidenceValue.Struct
}

typealias ConfidenceFieldsType = Map<String, ConfidenceValue>

interface ConfidenceAPI : ConfidenceContextProvider {
fun fork(context: ConfidenceContext): ConfidenceAPI
fun putContext(context: ConfidenceContext)
}

interface ConfidenceContext {
val name: String
val value: ConfidenceValue
}

class PageContext(private val page: String) : ConfidenceContext {
override val value: ConfidenceValue
get() = ConfidenceValue.String(page)
override val name: String
get() = "page"
}

class CommonContext : ConfidenceContextProvider {
override fun confidenceContext(): ConfidenceValue.Struct = ConfidenceValue.Struct(mapOf())
}

fun EvaluationContext.toConfidenceContext() = object : ConfidenceContext {
override val name: String = "open_feature"
override val value: ConfidenceValue
get() = ConfidenceValue.Struct(
asMap()
.map { it.key to ConfidenceValue.String(it.value.toString()) }
.toMap() + ("targeting_key" to ConfidenceValue.String(getTargetingKey()))
)
}

class Confidence(
val clientSecret: String,
private val root: ConfidenceContextProvider = CommonContext()
) : ConfidenceAPI {
private var contextMap: ConfidenceValue.Struct = ConfidenceValue.Struct(mapOf())
override fun putContext(context: ConfidenceContext) {
val map = contextMap.value.toMutableMap()
map[context.name] = context.value
contextMap = ConfidenceValue.Struct(map)
}

override fun confidenceContext(): ConfidenceValue.Struct {
return ConfidenceValue.Struct(root.confidenceContext().value + contextMap.value)
}

override fun fork(context: ConfidenceContext) = Confidence(clientSecret, this).also {
it.putContext(context)
}
}

internal fun ConfidenceAPI.resolve(flags: List<String>): ResolveResponse {
TODO()
}

fun Confidence.openFeatureProvider(
context: Context,
initialisationStrategy: InitialisationStrategy
): ConfidenceFeatureProvider = ConfidenceFeatureProvider.create(
context,
confidenceAPI = this,
clientSecret = clientSecret,
initialisationStrategy = initialisationStrategy
)

fun Confidence.eventSender(
context: Context,
dispatcher: CoroutineDispatcher = Dispatchers.IO
): EventSender = EventSenderImpl.create(
clientSecret = clientSecret,
dispatcher = dispatcher,
flushPolicies = listOf(confidenceFlushPolicy),
context = context,
confidenceContext = this
).onLowMemory { files ->
val sortedFiles = files.sortedBy { it.lastModified() }
sortedFiles.take(10).forEach { it.delete() }
}

val confidenceFlushPolicy = object : FlushPolicy {
private var size = 0
override fun reset() {
size = 0
}

override fun hit(event: Event) {
size++
}

override fun shouldFlush(): Boolean {
return size > 4
}
}

sealed class ConfidenceValue {
data class String(val value: kotlin.String) : ConfidenceValue()
data class Double(val value: kotlin.Double) : ConfidenceValue()
data class Boolean(val value: kotlin.Boolean) : ConfidenceValue()
data class Int(val value: kotlin.Int) : ConfidenceValue()
data class Struct(val value: Map<kotlin.String, ConfidenceValue>) : ConfidenceValue()
internal suspend fun Confidence.resolveFlags(flags: List<String>): ResolveResponse {
return flagResolver.resolve(flags)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import com.spotify.confidence.cache.ProviderCache
import com.spotify.confidence.cache.ProviderCache.CacheResolveResult
import com.spotify.confidence.cache.StorageFileCache
import com.spotify.confidence.client.ConfidenceClient
import com.spotify.confidence.client.ConfidenceRegion
import com.spotify.confidence.client.ConfidenceRemoteClient
import com.spotify.confidence.client.ResolveResponse
import com.spotify.confidence.client.SdkMetadata
import dev.openfeature.sdk.EvaluationContext
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.Hook
Expand Down Expand Up @@ -83,7 +80,7 @@ class ConfidenceFeatureProvider private constructor(

coroutineScope.launch(networkExceptionHandler) {
confidenceAPI.putContext(initialContext.toConfidenceContext())
val resolveResponse = confidenceAPI.resolve(listOf())
val resolveResponse = confidenceAPI.resolveFlags(listOf())
if (resolveResponse is ResolveResponse.Resolved) {
val (flags, resolveToken) = resolveResponse.flags

Expand Down Expand Up @@ -252,9 +249,8 @@ class ConfidenceFeatureProvider private constructor(

@Suppress("LongParameterList")
fun create(
confidence: Confidence,
context: Context,
clientSecret: String,
region: ConfidenceRegion = ConfidenceRegion.GLOBAL,
initialisationStrategy: InitialisationStrategy = InitialisationStrategy.FetchAndActivate,
hooks: List<Hook<*>> = listOf(),
client: ConfidenceClient? = null,
Expand All @@ -263,19 +259,11 @@ class ConfidenceFeatureProvider private constructor(
storage: DiskStorage? = null,
flagApplier: FlagApplier? = null,
eventHandler: EventHandler = EventHandler(Dispatchers.IO),
dispatcher: CoroutineDispatcher = Dispatchers.IO,
confidenceAPI: Confidence? = null
dispatcher: CoroutineDispatcher = Dispatchers.IO
): ConfidenceFeatureProvider {
val confidence = confidenceAPI ?: Confidence(clientSecret)
val configuredClient = client ?: ConfidenceRemoteClient(
clientSecret = clientSecret,
sdkMetadata = SdkMetadata(SDK_ID, BuildConfig.SDK_VERSION),
region = region,
dispatcher = dispatcher
)
val diskStorage = storage ?: StorageFileCache.create(context)
val flagApplierWithRetries = flagApplier ?: FlagApplierWithRetries(
client = configuredClient,
client = TODO(),
dispatcher = dispatcher,
diskStorage = diskStorage
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.spotify.confidence

sealed class ConfidenceValue {
data class String(val value: kotlin.String) : ConfidenceValue()
data class Double(val value: kotlin.Double) : ConfidenceValue()
data class Boolean(val value: kotlin.Boolean) : ConfidenceValue()
data class Int(val value: kotlin.Int) : ConfidenceValue()
data class Struct(val value: Map<kotlin.String, ConfidenceValue>) : ConfidenceValue()
}
Loading

0 comments on commit 8fac731

Please sign in to comment.