Skip to content

Commit

Permalink
Merge pull request #766 from softwaremill/use-autowire
Browse files Browse the repository at this point in the history
Use autowire
  • Loading branch information
adamw authored Feb 16, 2022
2 parents dc3910c + b8ee708 commit 06807e8
Show file tree
Hide file tree
Showing 22 changed files with 210 additions and 320 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.softwaremill.bootzooka

import cats.data.NonEmptyList
import cats.effect.{IO, Resource}
import com.softwaremill.bootzooka.config.Config
import com.softwaremill.bootzooka.email.EmailService
import com.softwaremill.bootzooka.email.sender.EmailSender
import com.softwaremill.bootzooka.http.{Http, HttpApi, HttpConfig}
import com.softwaremill.bootzooka.metrics.{MetricsApi, VersionApi}
import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken}
import com.softwaremill.bootzooka.security.ApiKeyAuthToken
import com.softwaremill.bootzooka.user.UserApi
import com.softwaremill.bootzooka.util.{Clock, DefaultIdGenerator}
import com.softwaremill.macwire.autocats.autowire
import doobie.util.transactor.Transactor
import io.prometheus.client.CollectorRegistry
import sttp.client3.SttpBackend

case class Dependencies(api: HttpApi, emailService: EmailService)

object Dependencies {
def wire(config: Config, sttpBackend: Resource[IO, SttpBackend[IO, Any]], xa: Resource[IO, Transactor[IO]], clock: Clock): Resource[IO, Dependencies] = {
def buildHttpApi(http: Http, userApi: UserApi, passwordResetApi: PasswordResetApi, metricsApi: MetricsApi, versionApi: VersionApi, collectorRegistry: CollectorRegistry, cfg: HttpConfig) =
new HttpApi(
http,
userApi.endpoints concatNel passwordResetApi.endpoints,
NonEmptyList.of(metricsApi.metricsEndpoint, versionApi.versionEndpoint),
collectorRegistry,
cfg)

autowire[Dependencies](
config.api,
config.user,
config.passwordReset,
config.email,
DefaultIdGenerator,
clock,
CollectorRegistry.defaultRegistry,
sttpBackend,
xa,
buildHttpApi _,
new ApiKeyAuthToken(_),
new EmailService(_, _, _, _, _),
EmailSender.create _,
new PasswordResetAuthToken(_),
)
}
}
17 changes: 0 additions & 17 deletions backend/src/main/scala/com/softwaremill/bootzooka/InitModule.scala

This file was deleted.

35 changes: 21 additions & 14 deletions backend/src/main/scala/com/softwaremill/bootzooka/Main.scala
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
package com.softwaremill.bootzooka

import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.effect.{IO, Resource}
import com.softwaremill.bootzooka.config.Config
import com.softwaremill.bootzooka.infrastructure.DB
import com.softwaremill.bootzooka.metrics.Metrics
import com.softwaremill.bootzooka.util.DefaultClock
import com.typesafe.scalalogging.StrictLogging
import doobie.util.transactor
import sttp.capabilities.WebSockets
import sttp.capabilities.fs2.Fs2Streams
import sttp.client3.SttpBackend
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
import sttp.client3.logging.slf4j.Slf4jLoggingBackend
import sttp.client3.prometheus.PrometheusBackend

object Main extends StrictLogging {
def main(args: Array[String]): Unit = {
Metrics.init()
Thread.setDefaultUncaughtExceptionHandler((t, e) => logger.error("Uncaught exception in thread: " + t, e))

val initModule = new InitModule {}
initModule.logConfig()
val config = Config.read
Config.log(config)

val mainTask = initModule.db.transactorResource.use { _xa =>
initModule.baseSttpBackend.use { _baseSttpBackend =>
val modules = new MainModule {
override def xa: transactor.Transactor[IO] = _xa
override def baseSttpBackend: SttpBackend[IO, Any] = _baseSttpBackend
override def config: Config = initModule.config
}
lazy val sttpBackend: Resource[IO, SttpBackend[IO, Fs2Streams[IO] with WebSockets]] =
AsyncHttpClientFs2Backend
.resource[IO]()
.map(baseSttpBackend => Slf4jLoggingBackend(PrometheusBackend(baseSttpBackend), includeTiming = true))

val xa = new DB(config.db).transactorResource

Dependencies
.wire(config, sttpBackend, xa, DefaultClock)
.use { case Dependencies(httpApi, emailService) =>
/*
Sequencing two tasks using the >> operator:
- the first starts the background processes (such as an email sender)
- the second allocates the http api resource, and never releases it (so that the http server is available
as long as our application runs)
*/
modules.startBackgroundProcesses >> modules.httpApi.resource.use(_ => IO.never)
emailService.startProcesses().void >> httpApi.resource.use(_ => IO.never)
}
}
mainTask.unsafeRunSync()
.unsafeRunSync()
}
}
35 changes: 0 additions & 35 deletions backend/src/main/scala/com/softwaremill/bootzooka/MainModule.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,37 @@ import com.softwaremill.bootzooka.http.HttpConfig
import com.softwaremill.bootzooka.infrastructure.DBConfig
import com.softwaremill.bootzooka.passwordreset.PasswordResetConfig
import com.softwaremill.bootzooka.user.UserConfig
import com.softwaremill.bootzooka.version.BuildInfo
import com.typesafe.scalalogging.StrictLogging
import pureconfig.ConfigSource
import pureconfig.generic.auto._

/** Maps to the `application.conf` file. Configuration for all modules of the application.
*/
import scala.collection.immutable.TreeMap

/** Maps to the `application.conf` file. Configuration for all modules of the application. */
case class Config(db: DBConfig, api: HttpConfig, email: EmailConfig, passwordReset: PasswordResetConfig, user: UserConfig)

object Config extends StrictLogging {
def log(config: Config): Unit = {
val baseInfo = s"""
|Bootzooka configuration:
|-----------------------
|DB: ${config.db}
|API: ${config.api}
|Email: ${config.email}
|Password reset: ${config.passwordReset}
|User: ${config.user}
|
|Build & env info:
|-----------------
|""".stripMargin

val info = TreeMap(BuildInfo.toMap.toSeq: _*).foldLeft(baseInfo) { case (str, (k, v)) =>
str + s"$k: $v\n"
}

logger.info(info)
}

def read: Config = ConfigSource.default.loadOrThrow[Config]
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package com.softwaremill.bootzooka.email.sender

import cats.effect.IO
import com.softwaremill.bootzooka.email.EmailData
import com.softwaremill.bootzooka.email.{EmailConfig, EmailData}
import sttp.client3.SttpBackend

trait EmailSender {
def apply(email: EmailData): IO[Unit]
}

object EmailSender {
def create(sttpBackend: SttpBackend[IO, Any], config: EmailConfig): EmailSender = if (config.mailgun.enabled) {
new MailgunEmailSender(config.mailgun, sttpBackend)
} else if (config.smtp.enabled) {
new SmtpEmailSender(config.smtp)
} else {
DummyEmailSender
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.typesafe.scalalogging.StrictLogging
import io.prometheus.client.CollectorRegistry
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.metrics.prometheus.Prometheus
import org.http4s.server.Router
import org.http4s.server.{Router, Server}
import org.http4s.server.middleware.{CORS, Metrics}
import org.http4s.server.staticcontent._
import org.http4s.{HttpApp, HttpRoutes, Request, Response}
Expand Down Expand Up @@ -40,29 +40,36 @@ class HttpApi(
/** The resource describing the HTTP server; binds when the resource is allocated.
*/
lazy val resource: Resource[IO, org.http4s.server.Server] = {
val prometheusHttp4sMetrics = Prometheus.metricsOps[IO](collectorRegistry)
prometheusHttp4sMetrics
.map(m => Metrics[IO](m)(mainRoutes))
.flatMap { monitoredRoutes =>
val app: HttpApp[IO] = Router(
// for /api/v1 requests, first trying the API; then the docs; then, returning 404
s"/${apiContextPath.mkString("/")}" -> {
CORS.policy.withAllowOriginAll
.withAllowCredentials(false)
.apply(monitoredRoutes <+> docsRoutes <+> respondWithNotFound)
},
"/admin" -> adminRoutes,
// for all other requests, first trying getting existing webapp resource;
// otherwise, returning index.html; this is needed to support paths in the frontend apps (e.g. /login)
// the frontend app will handle displaying appropriate error messages
"" -> (webappRoutes <+> indexResponse())
).orNotFound
import com.softwaremill.macwire.autocats._

BlazeServerBuilder[IO]
.bindHttp(config.port, config.host)
.withHttpApp(app)
.resource
}
val monitoredRoues =
Prometheus.metricsOps[IO](collectorRegistry).map(m => Metrics[IO](m)(mainRoutes))

def buildApp(monitoredRoutes: HttpRoutes[IO]): HttpApp[IO] = Router(
// for /api/v1 requests, first trying the API; then the docs; then, returning 404
s"/${apiContextPath.mkString("/")}" -> {
CORS.policy
.withAllowOriginAll
.withAllowCredentials(false)
.apply(monitoredRoutes <+> docsRoutes <+> respondWithNotFound)
},
"/admin" -> adminRoutes,
// for all other requests, first trying getting existing webapp resource;
// otherwise, returning index.html; this is needed to support paths in the frontend apps (e.g. /login)
// the frontend app will handle displaying appropriate error messages
"" -> (webappRoutes <+> indexResponse())
).orNotFound

def buildServer(app: HttpApp[IO]): Resource[IO, Server] = BlazeServerBuilder[IO]
.bindHttp(config.port, config.host)
.withHttpApp(app)
.resource

autowire[Server](
monitoredRoues,
buildApp _,
buildServer _
)
}

private def indexResponse(): HttpRoutes[IO] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import org.flywaydb.core.Flyway
import scala.concurrent.duration._
import Doobie._
import com.softwaremill.bootzooka.config.Sensitive
import com.softwaremill.macwire.autocats.autowire

/** Configures the database, setting up the connection pool and performing migrations.
*/
import scala.concurrent.ExecutionContext

/** Configures the database, setting up the connection pool and performing migrations. */
class DB(_config: DBConfig) extends StrictLogging {

private val config: DBConfig = {
Expand All @@ -36,17 +38,17 @@ class DB(_config: DBConfig) extends StrictLogging {
*
* See also: https://tpolecat.github.io/doobie/docs/14-Managing-Connections.html#about-threading
*/
for {
connectEC <- doobie.util.ExecutionContexts.fixedThreadPool[IO](config.connectThreadPoolSize)
xa <- HikariTransactor.newHikariTransactor[IO](
config.driver,
config.url,
config.username,
config.password.value,
connectEC
)
_ <- Resource.eval(connectAndMigrate(xa))
} yield xa
def buildTransactor(ec: ExecutionContext) = HikariTransactor.newHikariTransactor[IO](
config.driver,
config.url,
config.username,
config.password.value,
ec
)
autowire[Transactor[IO]](
doobie.util.ExecutionContexts.fixedThreadPool[IO](config.connectThreadPoolSize),
buildTransactor _
).evalTap(connectAndMigrate)
}

private def connectAndMigrate(xa: Transactor[IO]): IO[Unit] = {
Expand Down
Loading

0 comments on commit 06807e8

Please sign in to comment.