Last active
June 6, 2022 20:57
-
-
Save alterationx10/b7a2c664d957efcfe82685a75c679f81 to your computer and use it in GitHub Desktop.
Just playing around with the concept of Feature Flagging w/ZIO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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