Skip to content

Commit

Permalink
refactor: #188 Refactor modules management
Browse files Browse the repository at this point in the history
  • Loading branch information
rlemaitre committed Nov 30, 2024
1 parent 79d0bef commit cea2ed8
Show file tree
Hide file tree
Showing 28 changed files with 151 additions and 167 deletions.
8 changes: 4 additions & 4 deletions modules/core/src/main/scala/pillars/Pillars.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"))
Expand Down Expand Up @@ -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)
Expand Down
39 changes: 20 additions & 19 deletions modules/core/src/main/scala/pillars/app.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
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
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
Expand All @@ -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
10 changes: 5 additions & 5 deletions modules/core/src/main/scala/pillars/modules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillars.db_doobie.DBDoobieSupport
9 changes: 4 additions & 5 deletions modules/db-doobie/src/main/scala/pillars/db_doobie/db.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand All @@ -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.*
Expand All @@ -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,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillars.db.migrations.DBMigrationSupport
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillars.db.DBSkunkSupport
9 changes: 4 additions & 5 deletions modules/db-skunk/src/main/scala/pillars/db/db.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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.*
Expand Down Expand Up @@ -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",
Expand Down
48 changes: 20 additions & 28 deletions modules/example/src/main/scala/example/app.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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[]

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillars.flags.FeatureFlagsSupport
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Loading

0 comments on commit cea2ed8

Please sign in to comment.