openfeature-scala
Features
- Cross-platform, cross-version Scala feature flagging
- OpenFeature SDK
- Flipt client
- Flipt OpenFeature Provider
- In-Memory OpenFeature Provider
Installing
libraryDependencies += "io.cardell" %%% "flipt-sdk-server" % "0.3.0-RC1"
// or
libraryDependencies ++= Seq(
"io.cardell" %%% "openfeature-sdk" % "0.3.0-RC1",
// for circe json variant types
"io.cardell" %%% "openfeature-sdk-circe" % "0.3.0-RC1",
// to use flipt as a backend
"io.cardell" %%% "openfeature-provider-flipt" % "0.3.0-RC1"
)
OpenFeature Compatibility
Features | Status |
---|---|
Providers | ✅ |
Targeting | ✅ |
Logging | 🚧 |
Domains | 🚧 |
Eventing | 🚧 |
Shutdown | 🚧 |
Transaction Context Propagation | 🚧 |
OpenFeature Usage
The OpenFeature SDK adds features like handling default values in case of errors. Eventually the SDK will cover the full range of the openfeature specification, like hooks, events, static vs dynamic context.
See Flipt usage
on how to set up the FliptApi
. Once done, set up a provider:
import cats.effect.IO
import io.circe.Decoder
import io.cardell.flipt.FliptApi
import io.cardell.openfeature.OpenFeature
import io.cardell.openfeature.provider.flipt.FliptProvider
import io.cardell.openfeature.circe._
case class SomeVariant(field: String, field2: Int)
def provider(flipt: FliptApi[IO])(implicit d: Decoder[SomeVariant]) = {
val featureSdk = OpenFeature[IO](new FliptProvider[IO](flipt, "some-namespace"))
featureSdk.client.flatMap { featureClient =>
for {
eval <- featureClient.getBooleanValue("boolean-flag", false)
_ <- IO.println(s"${eval}")
eval2 <- featureClient.getStructureValue[SomeVariant](
"structure-flag",
SomeVariant("a", 1)
)
_ <- IO.println(s"${eval2}")
} yield ()
}
}
Hooks
Hooks are work-in-progress. All four OpenFeature hook types
are supported but only on the FeatureClient
and Provider
interfaces.
Implementing A New EvaluationProvider
EvaluationProvider
does not need to handle any errors that aren't deemed recoverable, or need
to implement any hook logic. Running hooks, and handling default evaluations on error is handled
in the library
Implement the call, response decoding, and handle any recoverable errors that make sense.
Flipt Usage
The Flipt client is bare-bones, using it is not recommended, unless as OpenFeature SDK Provider.
import cats.effect.IO
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.Uri
import io.cardell.flipt.FliptApi
import io.cardell.flipt.EvaluationRequest
import io.cardell.flipt.auth.AuthenticationStrategy
val url: Uri = Uri.unsafeFromString("https://flipt.example.com")
// url: Uri = Uri(
// scheme = Some(value = Scheme(https)),
// authority = Some(
// value = Authority(
// userInfo = None,
// host = RegName(host = flipt.example.com),
// port = None
// )
// ),
// path = ,
// query = ,
// fragment = None
// )
val token: String = "token"
// token: String = "token"
val resource = EmberClientBuilder
.default[IO]
.build
.map(client =>
FliptApi[IO](client, url, AuthenticationStrategy.ClientToken("token"))
)
// resource: cats.effect.kernel.Resource[IO, FliptApi[IO]] = Bind(
// source = Bind(
// source = Eval(fa = Pure(value = ())),
// fs = org.http4s.ember.client.EmberClientBuilder$$Lambda$15870/0x00000008041e6840@1710e3fc
// ),
// fs = cats.effect.kernel.Resource$$Lambda$15871/0x00000008041f8040@1ac50675
// )
resource.use { flipt =>
for {
res <- flipt.evaluateBoolean(
EvaluationRequest(
namespaceKey = "default",
flagKey = "my-flag-1",
entityId = None,
context = Map.empty,
reference = None
)
)
} yield res.enabled
}
// res0: IO[Boolean] = FlatMap(
// ioe = Pure(value = ()),
// f = cats.effect.kernel.Resource$$Lambda$15873/0x00000008041ff840@47ac4d2f,
// event = cats.effect.tracing.TracingEvent$StackTrace
// )
Future Work
- Java OpenFeature Provider wrapper, to unlock more SDKs
- LaunchDarkly Provider using
typelevel/catapult