From cea2ed8a79a7ecc6d95aa55229e572b9f043556c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Lemaitre?= Date: Sat, 30 Nov 2024 19:14:42 +0100 Subject: [PATCH] refactor: #188 Refactor modules management --- .../core/src/main/scala/pillars/Pillars.scala | 8 +- modules/core/src/main/scala/pillars/app.scala | 39 ++++---- .../core/src/main/scala/pillars/modules.scala | 10 +-- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../src/main/scala/pillars/db_doobie/db.scala | 9 +- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../pillars/db/migrations/migrations.scala | 11 +-- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../src/main/scala/pillars/db/db.scala | 9 +- .../example/src/main/scala/example/app.scala | 48 +++++----- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../{FlagManager.scala => FeatureFlags.scala} | 32 ++++--- .../scala/pillars/flags/FlagController.scala | 2 +- .../main/scala/pillars/flags/package.scala | 6 +- ...gerTests.scala => FeatureFlagsTests.scala} | 20 ++--- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../scala/pillars/httpclient/httpclient.scala | 89 ++++++++++--------- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../scala/pillars/rabbitmq/fs2/rabbitmq.scala | 12 ++- .../META-INF/services/pillars.ModuleDef | 1 - .../META-INF/services/pillars.ModuleSupport | 1 + .../pillars/redis_rediculous/redis.scala | 9 +- 28 files changed, 151 insertions(+), 167 deletions(-) delete mode 100644 modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleSupport delete mode 100644 modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleSupport delete mode 100644 modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleSupport delete mode 100644 modules/flags/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/flags/src/main/resources/META-INF/services/pillars.ModuleSupport rename modules/flags/src/main/scala/pillars/flags/{FlagManager.scala => FeatureFlags.scala} (82%) rename modules/flags/src/test/scala/pillars/flags/{FlagManagerTests.scala => FeatureFlagsTests.scala} (82%) delete mode 100644 modules/http-client/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/http-client/src/main/resources/META-INF/services/pillars.ModuleSupport delete mode 100644 modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleSupport delete mode 100644 modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleDef create mode 100644 modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleSupport diff --git a/modules/core/src/main/scala/pillars/Pillars.scala b/modules/core/src/main/scala/pillars/Pillars.scala index 5c1c3ae7e..b44d1beff 100644 --- a/modules/core/src/main/scala/pillars/Pillars.scala +++ b/modules/core/src/main/scala/pillars/Pillars.scala @@ -74,7 +74,7 @@ object Pillars: */ def apply[F[_]: LiftIO: Async: Console: Network: Parallel]( infos: AppInfo, - modules: Seq[ModuleDef], + modules: Seq[ModuleSupport], path: Path ): Resource[F, Pillars[F]] = val configReader = Reader[F](path) @@ -84,7 +84,7 @@ object Pillars: given Tracer[F] = obs.tracer _ <- Resource.eval(Logging.init(_config.log)) _logger = ScribeImpl[F](Sync[F]) - context = ModuleDef.Context(obs, configReader, _logger) + context = ModuleSupport.Context(obs, configReader, _logger) _ <- Resource.eval(_logger.info("Loading modules...")) _modules <- loadModules(modules, context) _ <- Resource.eval(_logger.debug(s"Loaded ${_modules.size} modules")) @@ -114,8 +114,8 @@ object Pillars: * @return a resource that will instantiate the modules. */ private def loadModules[F[_]: Async: Network: Tracer: Console]( - modules: Seq[ModuleDef], - context: ModuleDef.Context[F] + modules: Seq[ModuleSupport], + context: ModuleSupport.Context[F] ): Resource[F, Modules[F]] = val loaders = modules .groupBy(_.key) diff --git a/modules/core/src/main/scala/pillars/app.scala b/modules/core/src/main/scala/pillars/app.scala index cde2f136b..23b7e6a76 100644 --- a/modules/core/src/main/scala/pillars/app.scala +++ b/modules/core/src/main/scala/pillars/app.scala @@ -1,10 +1,13 @@ package pillars -import cats.effect.* +import cats.Parallel +import cats.effect.{IOApp as CEIOApp, *} import cats.effect.std.Console +import cats.syntax.all.* import com.monovore.decline.Command import com.monovore.decline.Opts import fs2.io.file.Path +import fs2.io.net.Network import io.github.iltotore.iron.* import io.github.iltotore.iron.constraint.all.* import pillars.App.Description @@ -12,14 +15,28 @@ import pillars.App.Name import pillars.App.Version import pillars.probes.Probe -trait App[F[_]]: +abstract class App[F[_]: LiftIO: Async: Console: Network: Parallel](val modules: ModuleSupport*): def infos: AppInfo - def modules: List[ModuleDef] = Nil def probes: List[Probe[F]] = Nil def adminControllers: List[Controller[F]] = Nil def run: Run[F, F[Unit]] + + import pillars.given + def run(args: List[String]): F[ExitCode] = + val command = Command(infos.name, infos.description): + Opts.option[Path]("config", "Path to the configuration file").map: configPath => + Pillars(infos, modules, configPath).use: pillars => + given Pillars[F] = pillars + run.as(ExitCode.Success) + + command.parse(args, sys.env) match + case Left(help) => Console[F].errorln(help).as(ExitCode.Error) + case Right(prog) => prog + end run end App +abstract class IOApp(override val modules: ModuleSupport*) extends App[IO](modules*), CEIOApp + object App: private type NameConstraint = Not[Blank] opaque type Name <: String = String :| NameConstraint @@ -44,19 +61,3 @@ trait BuildInfo: def description: String def toAppInfo: AppInfo = AppInfo(Name(name.assume), Version(version.assume), Description(description.assume)) end BuildInfo - -trait EntryPoint extends IOApp: - import pillars.given - def app: App[IO] - override final def run(args: List[String]): IO[ExitCode] = - val command = Command(app.infos.name, app.infos.description): - Opts.option[Path]("config", "Path to the configuration file").map: configPath => - Pillars(app.infos, app.modules, configPath).use: pillars => - given Pillars[IO] = pillars - app.run.as(ExitCode.Success) - - command.parse(args, sys.env) match - case Left(help) => Console[IO].errorln(help).as(ExitCode.Error) - case Right(prog) => prog - end run -end EntryPoint diff --git a/modules/core/src/main/scala/pillars/modules.scala b/modules/core/src/main/scala/pillars/modules.scala index fc19ea81b..4db30bb5a 100644 --- a/modules/core/src/main/scala/pillars/modules.scala +++ b/modules/core/src/main/scala/pillars/modules.scala @@ -38,22 +38,22 @@ end Modules object Modules: def empty[F[_]]: Modules[F] = Modules(Map.empty) -trait ModuleDef: +trait ModuleSupport: type M[F[_]] <: Module[F] def key: Module.Key def dependsOn: Set[Module.Key] = Set.empty def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] = Modules.empty ): Resource[F, M[F]] -end ModuleDef +end ModuleSupport -object ModuleDef: +object ModuleSupport: final case class Context[F[_]: Async: Network: Tracer: Console]( observability: Observability[F], reader: Reader[F], logger: Scribe[F] ) -end ModuleDef +end ModuleSupport diff --git a/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index 499c00ea7..000000000 --- a/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.db_doobie.DBDoobieModule diff --git a/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..5ef07bcbf --- /dev/null +++ b/modules/db-doobie/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.db_doobie.DBDoobieSupport diff --git a/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala b/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala index cc1be2689..501dfdea6 100644 --- a/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala +++ b/modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala @@ -20,8 +20,8 @@ import java.util.Properties import org.typelevel.otel4s.trace.Tracer import pillars.Config.* import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.probes.* @@ -38,16 +38,15 @@ end DB def db[F[_]](using p: Pillars[F]): DB[F] = p.module[DB[F]](DB.Key) -object DB: +object DB extends ModuleSupport: case object Key extends Module.Key: override val name: String = "db-doobie" -object DBDoobieModule extends ModuleDef: override type M[F[_]] = DB[F] override val key: Module.Key = DB.Key def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] ): Resource[F, DB[F]] = import context.* @@ -60,7 +59,7 @@ object DBDoobieModule extends ModuleDef: yield DB(config, xa) end for end load -end DBDoobieModule +end DB final case class DatabaseConfig( driverClassName: DriverClassName, diff --git a/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index 06dfe88ec..000000000 --- a/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.db.migrations.DBMigrationModule diff --git a/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..4fbd08e55 --- /dev/null +++ b/modules/db-migration/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.db.migrations.DBMigrationSupport diff --git a/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala b/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala index d063b61ad..d399b1154 100644 --- a/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala +++ b/modules/db-migration/src/main/scala/pillars/db/migrations/migrations.scala @@ -15,8 +15,8 @@ import org.flywaydb.core.Flyway import org.typelevel.otel4s.trace.Tracer import pillars.Config.Secret import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.Run import pillars.db.DB @@ -69,19 +69,17 @@ end DBMigration def dbMigration[F[_]](using p: Pillars[F]): DBMigration[F] = p.module[DBMigration[F]](DBMigration.Key) -object DBMigration: +object DBMigration extends ModuleSupport: case object Key extends Module.Key: override val name: String = "db-migration" -end DBMigration -object DBMigrationModule extends ModuleDef: override type M[F[_]] = DBMigration[F] override val key: Module.Key = DBMigration.Key override def dependsOn: Set[Module.Key] = Set(DB.Key) def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] ): Resource[F, DBMigration[F]] = given Files[F] = Files.forAsync[F] @@ -93,8 +91,7 @@ object DBMigrationModule extends ModuleDef: yield DBMigration(config) end for end load - -end DBMigrationModule +end DBMigration final case class MigrationConfig( url: JdbcUrl, diff --git a/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index 746c2c640..000000000 --- a/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.db.DBSkunkModule diff --git a/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..539dd9f83 --- /dev/null +++ b/modules/db-skunk/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.db.DBSkunkSupport diff --git a/modules/db-skunk/src/main/scala/pillars/db/db.scala b/modules/db-skunk/src/main/scala/pillars/db/db.scala index e1ff48fd4..ebbf18bef 100644 --- a/modules/db-skunk/src/main/scala/pillars/db/db.scala +++ b/modules/db-skunk/src/main/scala/pillars/db/db.scala @@ -16,8 +16,8 @@ import io.github.iltotore.iron.constraint.all.* import org.typelevel.otel4s.trace.Tracer import pillars.Config.* import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.codec.given import pillars.probes.* @@ -44,16 +44,15 @@ final case class DB[F[_]: Async: Network: Tracer: Console](config: DatabaseConfi end probes end DB -object DB: +object DB extends ModuleSupport: case object Key extends Module.Key: override val name: String = "db" -object DBSkunkModule extends ModuleDef: override type M[F[_]] = DB[F] override val key: Module.Key = DB.Key def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] ): Resource[F, DB[F]] = import context.* @@ -82,7 +81,7 @@ object DBSkunkModule extends ModuleDef: yield DB(config, poolRes) end for end load -end DBSkunkModule +end DB final case class DatabaseConfig( host: Host = host"localhost", diff --git a/modules/example/src/main/scala/example/app.scala b/modules/example/src/main/scala/example/app.scala index b2f212618..c1f6a2b9b 100644 --- a/modules/example/src/main/scala/example/app.scala +++ b/modules/example/src/main/scala/example/app.scala @@ -14,36 +14,28 @@ import skunk.codec.all.* import skunk.implicits.* // tag::quick-start[] -object app extends pillars.EntryPoint: // // <1> - def app: pillars.App[IO] = new: // // <2> - def infos: AppInfo = BuildInfo.toAppInfo // // <3> +object app extends pillars.IOApp(DB, DBMigration, FeatureFlags, HttpClient): // // <1> + def infos: AppInfo = BuildInfo.toAppInfo // // <3> - override def modules: List[ModuleDef] = List( // // <4> - DBSkunkModule, - DBMigrationModule, - FeatureFlagsModule, - HttpClientModule - ) - - def run: Run[IO, IO[Unit]] = // // <4> - for - _ <- logger.info(s"📚 Welcome to ${config.name}!") - _ <- dbMigration.migrate("classpath:db-migrations") // // <5> - _ <- flag"feature-1".whenEnabled: - sessions.use: session => - for - date <- session.unique(sql"select now()".query(timestamptz)) - _ <- logger.info(s"The current date is $date.") - yield () - _ <- http.get("https://swapi.dev/api/people/1"): response => + def run: Run[IO, IO[Unit]] = // // <4> + for + _ <- logger.info(s"📚 Welcome to ${config.name}!") + _ <- dbMigration.migrate("classpath:db-migrations") // // <5> + _ <- flag"feature-1".whenEnabled: + sessions.use: session => for - _ <- logger.info(s"Response: ${response.status}") - size <- response.body.compile.count - _ <- logger.info(s"Body: $size bytes") + date <- session.unique(sql"select now()".query(timestamptz)) + _ <- logger.info(s"The current date is $date.") yield () - _ <- server.start(homeController, userController) // // <6> - yield () - end for - end run + _ <- http.get("https://swapi.dev/api/people/1"): response => + for + _ <- logger.info(s"Response: ${response.status}") + size <- response.body.compile.count + _ <- logger.info(s"Body: $size bytes") + yield () + _ <- server.start(homeController, userController) // // <6> + yield () + end for + end run end app // end::quick-start[] diff --git a/modules/flags/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/flags/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index cc5ab1827..000000000 --- a/modules/flags/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.flags.FeatureFlagsModule diff --git a/modules/flags/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/flags/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..f41c71d75 --- /dev/null +++ b/modules/flags/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.flags.FeatureFlagsSupport diff --git a/modules/flags/src/main/scala/pillars/flags/FlagManager.scala b/modules/flags/src/main/scala/pillars/flags/FeatureFlags.scala similarity index 82% rename from modules/flags/src/main/scala/pillars/flags/FlagManager.scala rename to modules/flags/src/main/scala/pillars/flags/FeatureFlags.scala index f7a918283..b58b7dd1f 100644 --- a/modules/flags/src/main/scala/pillars/flags/FlagManager.scala +++ b/modules/flags/src/main/scala/pillars/flags/FeatureFlags.scala @@ -11,11 +11,11 @@ import fs2.io.net.Network import org.typelevel.otel4s.trace.Tracer import pillars.Controller import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars -trait FlagManager[F[_]: Sync] extends Module[F]: +trait FeatureFlags[F[_]: Sync] extends Module[F]: override type ModuleConfig = FlagsConfig def isEnabled(flag: Flag): F[Boolean] def config: FlagsConfig @@ -29,33 +29,31 @@ trait FlagManager[F[_]: Sync] extends Module[F]: case false => Sync[F].unit extension (pillars: Pillars[F]) - def flags: FlagManager[F] = this + def flags: FeatureFlags[F] = this def when(flag: Flag)(thunk: => F[Unit]): F[Unit] = this.when(flag)(thunk) end extension -end FlagManager +end FeatureFlags -object FlagManager: +object FeatureFlags extends ModuleSupport: case object Key extends Module.Key: def name: String = "feature-flags" end Key - def noop[F[_]: Sync](conf: FlagsConfig): FlagManager[F] = - new FlagManager[F]: + def noop[F[_]: Sync](conf: FlagsConfig): FeatureFlags[F] = + new FeatureFlags[F]: def isEnabled(flag: Flag): F[Boolean] = false.pure[F] override def config: FlagsConfig = conf def getFlag(name: Flag): F[Option[FeatureFlag]] = None.pure[F] def flags: F[List[FeatureFlag]] = List.empty.pure[F] private[flags] def setStatus(flag: Flag, status: Status) = None.pure[F] -end FlagManager -object FeatureFlagsModule extends ModuleDef: - override type M[F[_]] = FlagManager[F] + override type M[F[_]] = FeatureFlags[F] - override def key: Module.Key = FlagManager.Key + override def key: Module.Key = FeatureFlags.Key def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] - ): Resource[F, FlagManager[F]] = + ): Resource[F, FeatureFlags[F]] = import context.* given Files[F] = Files.forAsync[F] Resource.eval: @@ -67,14 +65,14 @@ object FeatureFlagsModule extends ModuleDef: yield manager end load - private[flags] def createManager[F[_]: Async: Network: Tracer: Console](conf: FlagsConfig): F[FlagManager[F]] = - if !conf.enabled then Sync[F].pure(FlagManager.noop[F](conf)) + private[flags] def createManager[F[_]: Async: Network: Tracer: Console](conf: FlagsConfig): F[FeatureFlags[F]] = + if !conf.enabled then Sync[F].pure(FeatureFlags.noop[F](conf)) else val flags = conf.flags.groupBy(_.name).map((name, flags) => name -> flags.head) Ref .of[F, Map[Flag, FeatureFlag]](flags) .map: ref => - new FlagManager[F]: + new FeatureFlags[F]: def flags: F[List[FeatureFlag]] = ref.get.map(_.values.toList) override def config: FlagsConfig = conf @@ -96,4 +94,4 @@ object FeatureFlagsModule extends ModuleDef: override def adminControllers: List[Controller[F]] = flagController(this).pure[List] end if end createManager -end FeatureFlagsModule +end FeatureFlags diff --git a/modules/flags/src/main/scala/pillars/flags/FlagController.scala b/modules/flags/src/main/scala/pillars/flags/FlagController.scala index 83eb37829..2bff3d378 100644 --- a/modules/flags/src/main/scala/pillars/flags/FlagController.scala +++ b/modules/flags/src/main/scala/pillars/flags/FlagController.scala @@ -16,7 +16,7 @@ import sttp.tapir.* import sttp.tapir.codec.iron.given import sttp.tapir.json.circe.jsonBody -def flagController[F[_]: Functor](manager: FlagManager[F]): Controller[F] = +def flagController[F[_]: Functor](manager: FeatureFlags[F]): Controller[F] = val listAll = FlagEndpoints.list.serverLogicSuccess(_ => manager.flags) val getOne = FlagEndpoints.get.serverLogic: name => diff --git a/modules/flags/src/main/scala/pillars/flags/package.scala b/modules/flags/src/main/scala/pillars/flags/package.scala index 6d8a7b771..c3dd2f0cf 100644 --- a/modules/flags/src/main/scala/pillars/flags/package.scala +++ b/modules/flags/src/main/scala/pillars/flags/package.scala @@ -26,10 +26,10 @@ package object flags: given Schema[FeatureFlag] = Schema.derived extension [F[_]](p: Pillars[F]) - def flags: FlagManager[F] = p.module(FlagManager.Key) + def flags: FeatureFlags[F] = p.module(FeatureFlags.Key) def whenEnabled[A](flag: Flag)(thunk: => F[A]): F[Unit] = - p.module[FlagManager[F]](FlagManager.Key).when(flag)(thunk) + p.module[FeatureFlags[F]](FeatureFlags.Key).when(flag)(thunk) end extension extension (inline ctx: StringContext) @@ -41,5 +41,5 @@ package object flags: extension (flag: Flag) def whenEnabled[F[_], A](using p: Pillars[F])(thunk: => F[A]): F[Unit] = - p.module[FlagManager[F]](FlagManager.Key).when(flag)(thunk) + p.module[FeatureFlags[F]](FeatureFlags.Key).when(flag)(thunk) end flags diff --git a/modules/flags/src/test/scala/pillars/flags/FlagManagerTests.scala b/modules/flags/src/test/scala/pillars/flags/FeatureFlagsTests.scala similarity index 82% rename from modules/flags/src/test/scala/pillars/flags/FlagManagerTests.scala rename to modules/flags/src/test/scala/pillars/flags/FeatureFlagsTests.scala index 866f52259..893e25158 100644 --- a/modules/flags/src/test/scala/pillars/flags/FlagManagerTests.scala +++ b/modules/flags/src/test/scala/pillars/flags/FeatureFlagsTests.scala @@ -6,7 +6,7 @@ import munit.CatsEffectSuite import org.typelevel.otel4s.trace.Tracer import pillars.flags.* -class FlagManagerTests extends CatsEffectSuite: +class FeatureFlagsTests extends CatsEffectSuite: val flag1 = FeatureFlag(flag"flag1", Status.Enabled) val flag2 = FeatureFlag(flag"flag2", Status.Disabled) @@ -16,7 +16,7 @@ class FlagManagerTests extends CatsEffectSuite: given Tracer[IO] = Tracer.noop[IO] val flag = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) flag <- manager.getFlag(flag"flag1") yield flag assertIO(flag, flag1.some) @@ -25,7 +25,7 @@ class FlagManagerTests extends CatsEffectSuite: given Tracer[IO] = Tracer.noop[IO] val flag = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) flag <- manager.getFlag(flag"undefined") yield flag assertIO(flag, none) @@ -34,7 +34,7 @@ class FlagManagerTests extends CatsEffectSuite: given Tracer[IO] = Tracer.noop[IO] def isEnabled(flag: Flag) = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) enabled <- manager.isEnabled(flag) yield enabled for @@ -49,7 +49,7 @@ class FlagManagerTests extends CatsEffectSuite: var called = false val performed = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) _ <- manager.when(flag"flag1")(IO { called = true }) yield called assertIO(performed, true) @@ -59,7 +59,7 @@ class FlagManagerTests extends CatsEffectSuite: var called = false val performed = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) _ <- manager.when(flag"flag2")(IO { called = true }) yield called assertIO(performed, false) @@ -69,7 +69,7 @@ class FlagManagerTests extends CatsEffectSuite: var called = false val performed = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) _ <- manager.when(flag"undefined")(IO { called = true }) yield called assertIO(performed, false) @@ -78,7 +78,7 @@ class FlagManagerTests extends CatsEffectSuite: given Tracer[IO] = Tracer.noop[IO] val modified = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) _ <- manager.setStatus(flag"flag1", Status.Disabled) flag <- manager.getFlag(flag"flag1") yield flag @@ -88,10 +88,10 @@ class FlagManagerTests extends CatsEffectSuite: given Tracer[IO] = Tracer.noop[IO] val modified = for - manager <- FeatureFlagsModule.createManager[IO](config) + manager <- FeatureFlags.createManager[IO](config) _ <- manager.setStatus(flag"undefined", Status.Disabled) flag <- manager.getFlag(flag"undefined") yield flag assertIO(modified, none) -end FlagManagerTests +end FeatureFlagsTests diff --git a/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index 7311a6835..000000000 --- a/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.httpclient.HttpClientModule diff --git a/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..839e5b3d1 --- /dev/null +++ b/modules/http-client/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.httpclient.HttpClientSupport diff --git a/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala b/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala index 3d8191bec..75c4c473e 100644 --- a/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala +++ b/modules/http-client/src/main/scala/pillars/httpclient/httpclient.scala @@ -27,6 +27,7 @@ import org.typelevel.otel4s.trace.Tracer import pillars.Logging import pillars.Module import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.PillarsError import pillars.PillarsError.* @@ -40,47 +41,7 @@ import sttp.tapir.ValidationError import sttp.tapir.client.http4s.Http4sClientInterpreter import sttp.tapir.client.http4s.Http4sClientOptions -object HttpClientModule extends pillars.ModuleDef: - override type M[F[_]] = HttpClient[F] - - override def key: Module.Key = HttpClient.Key - - override def load[F[_]: Async: Network: Tracer: Console]( - context: pillars.ModuleDef.Context[F], - modules: Modules[F] - ): Resource[F, HttpClient[F]] = - import context.* - given Files[F] = Files.forAsync[F] - for - _ <- Resource.eval(logger.info("Loading HTTP client module")) - conf <- Resource.eval(reader.read[HttpClient.Config]("http-client")) - metrics <- ClientMetrics(observability).toResource - client <- NettyClientBuilder[F] - .withHttp2 - .withNioTransport - .withUserAgent(conf.userAgent) - .resource - .map: client => - val logging = - if conf.logging.enabled then - Logger[F]( - logHeaders = conf.logging.headers, - logBody = conf.logging.body, - logAction = conf.logging.logAction - ) - else identity[Client[F]] - val followRedirect = - if conf.followRedirect then FollowRedirect[F](10) else identity[Client[F]] - client - |> metrics.middleware - |> logging - |> followRedirect - |> HttpClient(conf) - _ <- Resource.eval(logger.info("HTTP client module loaded")) - yield client - end for - end load -end HttpClientModule +def http[F[_]](using p: Pillars[F]): HttpClient[F] = p.module[HttpClient[F]](HttpClient.Key) final case class HttpClient[F[_]: Async](config: HttpClient.Config)(client: org.http4s.client.Client[F]) extends pillars.Module[F], Client[F]: @@ -121,12 +82,51 @@ final case class HttpClient[F[_]: Async](config: HttpClient.Config)(client: org. end HttpClient -def http[F[_]](using p: Pillars[F]): HttpClient[F] = p.module[HttpClient[F]](HttpClient.Key) - -object HttpClient: +object HttpClient extends ModuleSupport: case object Key extends Module.Key: override def name: String = "http-client" + override type M[F[_]] = HttpClient[F] + + override def key: Module.Key = HttpClient.Key + + override def load[F[_]: Async: Network: Tracer: Console]( + context: pillars.ModuleSupport.Context[F], + modules: Modules[F] + ): Resource[F, HttpClient[F]] = + import context.* + given Files[F] = Files.forAsync[F] + + for + _ <- Resource.eval(logger.info("Loading HTTP client module")) + conf <- Resource.eval(reader.read[HttpClient.Config]("http-client")) + metrics <- ClientMetrics(observability).toResource + client <- NettyClientBuilder[F] + .withHttp2 + .withNioTransport + .withUserAgent(conf.userAgent) + .resource + .map: client => + val logging = + if conf.logging.enabled then + Logger[F]( + logHeaders = conf.logging.headers, + logBody = conf.logging.body, + logAction = conf.logging.logAction + ) + else identity[Client[F]] + val followRedirect = + if conf.followRedirect then FollowRedirect[F](10) else identity[Client[F]] + client + |> metrics.middleware + |> logging + |> followRedirect + |> HttpClient(conf) + _ <- Resource.eval(logger.info("HTTP client module loaded")) + yield client + end for + end load + final case class Config( followRedirect: Boolean = true, userAgent: `User-Agent` = Config.defaultUserAgent, @@ -179,6 +179,7 @@ object HttpClient: |endpoint: $endpoint |""") end Error + end HttpClient trait FailureHandler[F[_], EO, O]: diff --git a/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index c19e3d225..000000000 --- a/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.rabbitmq.fs2.RabbitMQModule diff --git a/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..431850991 --- /dev/null +++ b/modules/rabbitmq-fs2/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.rabbitmq.fs2.RabbitMQSupport diff --git a/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala b/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala index 1c7b2175d..df1b4cc5c 100644 --- a/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala +++ b/modules/rabbitmq-fs2/src/main/scala/pillars/rabbitmq/fs2/rabbitmq.scala @@ -22,8 +22,8 @@ import io.github.iltotore.iron.constraint.all.* import org.typelevel.otel4s.trace.Tracer import pillars.Config.Secret import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.codec.given import pillars.probes.Component @@ -46,7 +46,7 @@ final case class RabbitMQ[F[_]: Async](config: RabbitMQConfig, client: RabbitCli end probes end RabbitMQ -object RabbitMQ: +object RabbitMQ extends ModuleSupport: case object Key extends Module.Key: override val name: String = "rabbitmq" @@ -55,14 +55,11 @@ object RabbitMQ: def apply[F[_]: Async](config: RabbitMQConfig): Resource[F, RabbitMQ[F]] = RabbitClient.default[F](config.convert).resource.map(apply(config, _)) -end RabbitMQ - -object RabbitMQModule extends ModuleDef: override type M[F[_]] = RabbitMQ[F] override val key: Module.Key = RabbitMQ.Key override def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] ): Resource[F, RabbitMQ[F]] = import context.* @@ -75,7 +72,8 @@ object RabbitMQModule extends ModuleDef: yield client end for end load -end RabbitMQModule + +end RabbitMQ case class RabbitMQConfig( nodes: NonEmptyList[RabbitMQConfig.Node] = NonEmptyList.one(RabbitMQConfig.Node(host"localhost", port"5672")), diff --git a/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleDef b/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleDef deleted file mode 100644 index a323c7750..000000000 --- a/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleDef +++ /dev/null @@ -1 +0,0 @@ -pillars.redis_rediculous.RedisModule diff --git a/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleSupport b/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleSupport new file mode 100644 index 000000000..2eab86d35 --- /dev/null +++ b/modules/redis-rediculous/src/main/resources/META-INF/services/pillars.ModuleSupport @@ -0,0 +1 @@ +pillars.redis_rediculous.RedisSupport diff --git a/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala b/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala index 7e50ac0d1..73db8aa90 100644 --- a/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala +++ b/modules/redis-rediculous/src/main/scala/pillars/redis_rediculous/redis.scala @@ -18,8 +18,8 @@ import io.github.iltotore.iron.circe.given import io.github.iltotore.iron.constraint.all.* import org.typelevel.otel4s.trace.Tracer import pillars.Module -import pillars.ModuleDef import pillars.Modules +import pillars.ModuleSupport import pillars.Pillars import pillars.codec.given import pillars.probes.* @@ -46,17 +46,16 @@ final case class Redis[F[_]: MonadCancelThrow](config: RedisConfig, connection: end probes end Redis -object Redis: +object Redis extends ModuleSupport: case object Key extends Module.Key: override val name: String = "redis-rediculous" def apply[F[_]](using p: Pillars[F]): Redis[F] = p.module[Redis[F]](Redis.Key) -object RedisModule extends ModuleDef: override type M[F[_]] = Redis[F] override val key: Module.Key = Redis.Key def load[F[_]: Async: Network: Tracer: Console]( - context: ModuleDef.Context[F], + context: ModuleSupport.Context[F], modules: Modules[F] ): Resource[F, Redis[F]] = import context.* @@ -79,7 +78,7 @@ object RedisModule extends ModuleDef: yield connection end for end load -end RedisModule +end Redis final case class RedisConfig( host: Host = host"localhost",