Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: init main services #10

Merged
merged 12 commits into from
Nov 7, 2024
39 changes: 31 additions & 8 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
version = "3.7.15"
runner.dialect = scala3
align.preset = more

style = defaultWithAlign

assumeStandardLibraryStripMargin = false
align.stripMargin = true

style = defaultWithAlign
docstrings.style = Asterisk
docstrings.oneline = unfold

align = more
align.stripMargin = true
align.arrowEnumeratorGenerator = true
align.multiline = true
align.inInterpolation = true
align.openParenCallSite = false
align.openParenDefnSite = false
# align.tokens = [{code = "->"}, {code = "<-"}, {code = "=>", owner = "Case"}]
align.tokens = [
{code = "->"},
{code = "<-"},
{code = "=>", owner = "Case"},
{code = ")"},
{code = "="},
{code = "%"},
{code = "%%"},
{code = ":", owners = [{regex = "Term\\.Param", parents = [ "Ctor\\.Primary" ]}]}
]

continuationIndent.callSite = 2
continuationIndent.defnSite = 2

danglingParentheses.preset = true
# verticalMultiline.atDefnSite = true
# newlines.implicitParamListModifierForce = [before]

verticalMultiline.arityThreshold = 1

newlines.inInterpolation = oneline
newlines.source = keep
newlines.topLevelStatements = [before, after]

indentOperator.preset = spray

maxColumn = 120

project.excludeFilters = [".*\\.sbt"]

rewrite.rules = [RedundantParens, SortModifiers, AsciiSortImports]
spaces.inImportCurlyBraces = false
# newlines.topLevelStatements = [before]

spaces.inImportCurlyBraces = false
7 changes: 5 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ lazy val root = (project in file("."))
http4sDSL,
pureconfig.cross(CrossVersion.for3Use2_13),
akkaStream.cross(CrossVersion.for3Use2_13),
// akkaCluster.cross(CrossVersion.for3Use2_13)
// akkaActor.cross(CrossVersion.for3Use2_13),
akkaCluster.cross(CrossVersion.for3Use2_13),
akkaDistributedData.cross(CrossVersion.for3Use2_13),
akkaActor.cross(CrossVersion.for3Use2_13),
// akkaTest.cross(CrossVersion.for3Use2_13),
// docker
)
)

resolvers += "Akka library repository".at("https://repo.akka.io/maven")
40 changes: 20 additions & 20 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import sbt._

object Dependencies {
lazy val akkaVersion = "2.7.0"
lazy val akkaHttpVersion = "10.4.0"
lazy val akkaVersion = "2.10.0"
lazy val scalaTestVersion = "3.2.15"
lazy val scalaLogVersion = "1.2.11"
lazy val scalaLogVersion = "1.4.6"
lazy val dockerVersion = "8.9.0"
lazy val catsEffectVersion = "3.3.11"
lazy val circeVersion = "0.14.5"
lazy val pureconfigVersion = "0.17.1"
lazy val http4sVersion = "0.23.29"
lazy val log4catsVersion = "2.7.0"

lazy val akkaActor = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % akkaVersion
lazy val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion
lazy val scalaLogging = "ch.qos.logback" % "logback-classic" % scalaLogVersion
lazy val log4cats = "org.typelevel" %% "log4cats-core" % log4catsVersion
lazy val docker = "com.spotify" % "docker-client" % dockerVersion
lazy val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion
lazy val circeCore = "io.circe" %% "circe-core" % circeVersion
lazy val circeGeneric = "io.circe" %% "circe-generic" % circeVersion
lazy val circeParser = "io.circe" %% "circe-parser" % circeVersion
lazy val pureconfig = "com.github.pureconfig" %% "pureconfig" % pureconfigVersion
lazy val http4sEmberClient = "org.http4s" %% "http4s-ember-client" % http4sVersion
lazy val http4sEmberServer = "org.http4s" %% "http4s-ember-server" % http4sVersion
lazy val http4sCirce = "org.http4s" %% "http4s-circe" % http4sVersion
lazy val http4sDSL = "org.http4s" %% "http4s-dsl" % http4sVersion
lazy val akkaTest = "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test
lazy val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion % Test
lazy val akkaActor = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream-typed" % akkaVersion
lazy val akkaCluster = "com.typesafe.akka" %% "akka-cluster-typed" % akkaVersion
lazy val akkaDistributedData = "com.typesafe.akka" %% "akka-distributed-data" % akkaVersion
lazy val scalaLogging = "ch.qos.logback" % "logback-classic" % scalaLogVersion
lazy val log4cats = "org.typelevel" %% "log4cats-core" % log4catsVersion
lazy val docker = "com.spotify" % "docker-client" % dockerVersion
lazy val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion
lazy val circeCore = "io.circe" %% "circe-core" % circeVersion
lazy val circeGeneric = "io.circe" %% "circe-generic" % circeVersion
lazy val circeParser = "io.circe" %% "circe-parser" % circeVersion
lazy val pureconfig = "com.github.pureconfig" %% "pureconfig" % pureconfigVersion
lazy val http4sEmberClient = "org.http4s" %% "http4s-ember-client" % http4sVersion
lazy val http4sEmberServer = "org.http4s" %% "http4s-ember-server" % http4sVersion
lazy val http4sCirce = "org.http4s" %% "http4s-circe" % http4sVersion
lazy val http4sDSL = "org.http4s" %% "http4s-dsl" % http4sVersion
lazy val akkaTest = "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test
lazy val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion % Test
}
2 changes: 2 additions & 0 deletions src/main/scala/api/Endpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.circe.syntax.EncoderOps
import io.circe.generic.auto._

class Endpoints {

private val healthRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "health" =>
Ok("Health check response")
Expand Down Expand Up @@ -44,4 +45,5 @@ class Endpoints {
"/api" -> (healthRoute <+> getReactionRoute <+> postReactionRoute <+> deleteReactionRoute)
)
)

}
2 changes: 2 additions & 0 deletions src/main/scala/api/ErrorHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.http4s.circe.CirceEntityEncoder._
final case class ErrorResponse(error: String, message: String)

object ErrorHandler {

def apply(routes: HttpRoutes[IO]): HttpRoutes[IO] = HttpRoutes.of[IO] { request =>
routes(request).getOrElseF(
IO(Response(Status.NotFound).withEntity(ErrorResponse(
Expand All @@ -35,4 +36,5 @@ object ErrorHandler {
).asJson))
}
}

}
9 changes: 3 additions & 6 deletions src/main/scala/api/ServerBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package api

import cats.effect.{IO, Resource}
import com.comcast.ip4s.{Host, Port}
import cats.syntax.flatMap.toFlatMapOps
import org.http4s.server.Server
import org.http4s.ember.server.EmberServerBuilder
import org.typelevel.log4cats.Logger

class ServerBuilder(
implicit
endpoints: Endpoints,
logger: Logger[IO]
implicit endpoints: Endpoints
) {

def startServer(
host: Host,
port: Port
Expand All @@ -22,6 +19,6 @@ class ServerBuilder(
.withPort(port)
.withHttpApp(ErrorHandler(endpoints.routes).orNotFound)
.build
.flatTap { server => Resource.eval(logger.info("Press ENTER to terminate...")) }
}

}
3 changes: 3 additions & 0 deletions src/main/scala/app/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.typelevel.log4cats.Logger
import scala.concurrent.ExecutionContext

object Main extends IOApp {

def actorSystemResource(
implicit
ec: ExecutionContext,
Expand Down Expand Up @@ -50,6 +51,7 @@ object Main extends IOApp {
_ <- Resource.eval(logger.info("Creating ServerBuilder resource"))
serverBuilder <- serverBuilderResource
_ <- serverBuilder.startServer(host, port)
_ <- Resource.eval(logger.info("Press ENTER to terminate..."))
_ <- Resource.eval(IO(scala.io.StdIn.readLine))
} yield ()

Expand All @@ -66,4 +68,5 @@ object Main extends IOApp {

runApp(httpConfig.host, httpConfig.port).use(_ => IO.unit).as(ExitCode.Success)
}

}
112 changes: 80 additions & 32 deletions src/main/scala/config/ConfigLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,108 @@ import com.typesafe.config.{Config, ConfigFactory}
import pureconfig.{ConfigReader, ConfigSource}
import pureconfig.error.CannotConvert
import java.io.File
import scala.concurrent.duration.FiniteDuration

case class KafkaTopics(
reactions: String,
reactions: String,
mechanisms: String
)

object KafkaTopics
implicit val kafkaTopicsReader: ConfigReader[KafkaTopics] =
ConfigReader.forProduct2("reactions", "mechanisms")(KafkaTopics.apply)
object KafkaTopics {

implicit val kafkaTopicsReader: ConfigReader[KafkaTopics] =
ConfigReader.forProduct2("reactions", "mechanisms")(KafkaTopics.apply)

}

case class KafkaConfig(
bootstrapServers: String,
topic: KafkaTopics
topic: KafkaTopics
)

object KafkaConfig
implicit val kafkaConfigReader: ConfigReader[KafkaConfig] =
ConfigReader.forProduct2("bootstrapServers", "topic")(KafkaConfig.apply)
object KafkaConfig {

implicit val kafkaConfigReader: ConfigReader[KafkaConfig] =
ConfigReader.forProduct2("bootstrapServers", "topic")(KafkaConfig.apply)

}

case class HttpConfig(
host: Host,
port: Port
)

implicit val hostReader: ConfigReader[Host] = ConfigReader.fromString { str =>
Host.fromString(str).toRight(CannotConvert(str, "Host", "Invalid host format"))
}
object HttpConfig {

implicit val portReader: ConfigReader[Port] = ConfigReader.fromString { str =>
Port.fromString(str).toRight(CannotConvert(str, "Port", "Invalid port format"))
}
implicit val hostReader: ConfigReader[Host] = ConfigReader.fromString { str =>
Host.fromString(str).toRight(CannotConvert(str, "Host", "Invalid host format"))
}

object HttpConfig
implicit val httpConfigReader: ConfigReader[HttpConfig] =
ConfigReader.forProduct2("host", "port")(HttpConfig.apply)
implicit val portReader: ConfigReader[Port] = ConfigReader.fromString { str =>
Port.fromString(str).toRight(CannotConvert(str, "Port", "Invalid port format"))
}

implicit val httpConfigReader: ConfigReader[HttpConfig] =
ConfigReader.forProduct2("host", "port")(HttpConfig.apply)

}

case class DatabaseConfig(
url: String,
user: String,
url: String,
user: String,
password: String
)

object DatabaseConfig
implicit val databaseConfigReader: ConfigReader[DatabaseConfig] =
ConfigReader.forProduct3("url", "user", "password")(DatabaseConfig.apply)
object DatabaseConfig {

implicit val databaseConfigReader: ConfigReader[DatabaseConfig] =
ConfigReader.forProduct3("url", "user", "password")(DatabaseConfig.apply)

}

case class HttpClientConfig(
baseUri: String,
timeout: HttpClientTimeout,
retries: Int,
pool: HttpClientPool
)

case class HttpClientTimeout(
connect: FiniteDuration,
request: FiniteDuration
)

case class HttpClientPool(
maxConnections: Int,
maxIdleTime: FiniteDuration
)

object HttpClientConfig {

implicit val httpClientTimeoutReader: ConfigReader[HttpClientTimeout] =
ConfigReader.forProduct2("connect", "request")(HttpClientTimeout.apply)

implicit val httpClientPoolReader: ConfigReader[HttpClientPool] =
ConfigReader.forProduct2("max-connections", "max-idle-time")(HttpClientPool.apply)

implicit val httpClientConfigReader: ConfigReader[HttpClientConfig] =
ConfigReader.forProduct4("baseUri", "timeout", "retries", "pool")(HttpClientConfig.apply)

}

case class AppConfig(
kafka: KafkaConfig,
http: HttpConfig,
database: DatabaseConfig
kafka: KafkaConfig,
http: HttpConfig,
database: DatabaseConfig,
httpClient: HttpClientConfig
)

object AppConfig
implicit val appConfigReader: ConfigReader[AppConfig] =
ConfigReader.forProduct3("kafka", "http", "database")(AppConfig.apply)
object AppConfig {

implicit val appConfigReader: ConfigReader[AppConfig] =
ConfigReader.forProduct4("kafka", "http", "database", "httpClient")(AppConfig.apply)

}

object ConfigLoader {
System.setProperty("logback.configurationFile", "src/main/scala/resource/logback.xml")
Expand All @@ -73,8 +120,9 @@ object ConfigLoader {
private val config: Config =
appConf.withFallback(refConf).resolve()

val appConfig: AppConfig = ConfigSource.fromConfig(config).loadOrThrow[AppConfig]
val kafkaConfig: KafkaConfig = appConfig.kafka
val httpConfig: HttpConfig = appConfig.http
val databaseConfig: DatabaseConfig = appConfig.database
val appConfig: AppConfig = ConfigSource.fromConfig(config).loadOrThrow[AppConfig]
val kafkaConfig: KafkaConfig = appConfig.kafka
val httpConfig: HttpConfig = appConfig.http
val databaseConfig: DatabaseConfig = appConfig.database
val httpClientConfig: HttpClientConfig = appConfig.httpClient
}
Loading
Loading