Custom Feature Store¶
FeatureStore has five suspend functions:
interface FeatureStore {
suspend fun get(id: String): Feature?
suspend fun getAll(): List<Feature>
suspend fun put(feature: Feature) // insert or replace
suspend fun update(id: String, transform: (Feature) -> Feature): Feature
suspend fun delete(id: String) // no-op when missing
}
Rules every implementation must follow (the contract tests check them):
getreturns null for unknown ids.updateis atomic against concurrent updates, throwsFeatureNotFoundExceptionfor unknown ids, and rejects a transform that changes the id withIllegalArgumentException.enable,disable,group,enableGroupanddisableGroupare extension functions built onupdateandgetAll; you do not implement them.
Serialize a Feature with ff4kJson (or a Json whose module extends ff4kSerializersModule) when your backend stores text:
class RedisFeatureStore(private val redis: RedisCommands) : FeatureStore {
override suspend fun get(id: String): Feature? =
redis.get("ff4k:feature:$id")?.let { ff4kJson.decodeFromString(it) }
override suspend fun getAll(): List<Feature> =
redis.keys("ff4k:feature:*").mapNotNull { redis.get(it) }.map { ff4kJson.decodeFromString(it) }
override suspend fun put(feature: Feature) {
redis.set("ff4k:feature:${feature.id}", ff4kJson.encodeToString(feature))
}
override suspend fun update(id: String, transform: (Feature) -> Feature): Feature {
// use WATCH/MULTI or a version field so concurrent updates do not lose writes
val updated = transform(get(id) ?: throw FeatureNotFoundException(id))
require(updated.id == id)
put(updated)
return updated
}
override suspend fun delete(id: String) {
redis.del("ff4k:feature:$id")
}
}