Skip to content

Instantly share code, notes, and snippets.

@alterationx10
Last active June 6, 2022 20:57
Show Gist options
  • Save alterationx10/b7a2c664d957efcfe82685a75c679f81 to your computer and use it in GitHub Desktop.
Save alterationx10/b7a2c664d957efcfe82685a75c679f81 to your computer and use it in GitHub Desktop.
Just playing around with the concept of Feature Flagging w/ZIO
package com.alterationx10
import com.alterationx10.ZFlag.ZIOFeatureFlag
import zio._
// PSA, just a POC - bad code ahead :-)
// ZIO 2.0.0-RC6 for this...
// Feature flagging service, mainly driven by Ref[Set[String]]...
trait FeatureFlag {
def enabledFeatures: UIO[Set[String]]
def isEnabled(feat: String): UIO[Boolean]
def disableFeature(feat: String): UIO[Set[String]]
def enableFeature(feat: String): UIO[Set[String]]
def loadFeatures(feats: Set[String]): UIO[Unit]
def disableAll(feats: Set[String]): UIO[Unit]
}
case class FeatureFlagLive(private val featRef: Ref[Set[String]]) extends FeatureFlag {
override def enabledFeatures: UIO[Set[String]] =
featRef.get
override def isEnabled(feat: String): UIO[Boolean] =
featRef.get.map(_.contains(feat))
override def disableFeature(feat: String): UIO[Set[String]] =
featRef.getAndUpdate(_.filterNot(_ == feat))
override def enableFeature(feat: String): UIO[Set[String]] =
featRef.getAndUpdate(_ + feat)
override def loadFeatures(feats: Set[String]): UIO[Unit] =
featRef.set(feats)
override def disableAll(feats: Set[String]): UIO[Unit] =
featRef.set(Set.empty[String])
}
object FeatureFlag {
val live: ZLayer[Any, Nothing, FeatureFlag] = ZLayer.fromZIO(for {
ref <- Ref.make(Set.empty[String])
} yield FeatureFlagLive(ref))
def disableFeature(feat: String): ZIO[FeatureFlag, Nothing, Set[String]] =
ZIO.serviceWithZIO[FeatureFlag](_.disableFeature(feat))
def enableFeature(feat: String): ZIO[FeatureFlag, Nothing, Set[String]] =
ZIO.serviceWithZIO[FeatureFlag](_.enableFeature(feat))
def isEnabled(feat: String): ZIO[FeatureFlag, Nothing, Boolean] =
ZIO.serviceWithZIO[FeatureFlag](_.isEnabled(feat))
}
object ZFlag {
// TODO need politely indicate that R need FeatureFlag, vs the `with FeatureFlag` that's hacked in.
// Extension to work on our ZIO
implicit final class ZIOFeatureFlag[R, E, A](private val io: ZIO[R, E, A]) extends AnyVal {
def whenFlag(feat: String, alt: ZIO[R with FeatureFlag, E, A]): ZIO[R with FeatureFlag, E, A] = {
new Flagged[R, E](feat).apply(default = io, onEnabled = alt)
}
}
final class Flagged[R, E](private val f: String) extends AnyVal {
def apply[R1 <: R with FeatureFlag, E1 <: E, A](default: => ZIO[R1, E1, A], onEnabled: => ZIO[R1, E1, A])
: ZIO[R1, E1, A] = {
ZIO.ifZIO(FeatureFlag.isEnabled(f))(
onTrue = onEnabled,
onFalse = default
)
}
}
}
object FlaggedApp extends zio.ZIOAppDefault {
val impl1: ZIO[Console, Throwable, Unit] = Console.printLine("defaultImpl")
val impl2: ZIO[Console, Throwable, Unit] = Console.printLine("flaggedImpl")
val program: ZIO[Console with FeatureFlag, Throwable, ExitCode] = for {
// no features enabled
_ <- impl1.whenFlag("feat1", impl2)
_ <- FeatureFlag.enableFeature("feat1")
_ <- impl1.whenFlag("feat1", impl2)
_ <- FeatureFlag.disableFeature("feat1")
_ <- impl1.whenFlag("feat1", impl2)
} yield ExitCode.success
override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
program.provideSome(Console.live ++ FeatureFlag.live)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment