Skip to content

Commit

Permalink
Feat: init main services (#10)
Browse files Browse the repository at this point in the history
* feat(services): add damaged `DistributedCacheService` (Scala 2.13 powered)

* fix(fmt): use newlines & clean

* feat(sbt): try Akka DD & try Akka Cluster

* fix(app): move finalization log message

* fix(resource): use wide columns

* feat(core): add servives & add app errors & fix repositories (+ types)

* feat(doc): add docs for an additional module
  • Loading branch information
sobakavosne authored Nov 7, 2024
1 parent 336111d commit 85d87c3
Show file tree
Hide file tree
Showing 28 changed files with 911 additions and 133 deletions.
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

0 comments on commit 85d87c3

Please sign in to comment.