diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..303ac23f1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: Build +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Check/Compile/Test + run: sbt checkAll diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml new file mode 100644 index 000000000..f95aa847b --- /dev/null +++ b/.github/workflows/pull.yml @@ -0,0 +1,14 @@ +name: PR Build +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Check/Compile/Test + run: sbt checkAll diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..1dc7c4a40 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +name: Release +on: + push: + tags: ["v*"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - uses: actions/setup-ruby@v1 + with: + ruby-version: '2.6' + - name: Install jekyll + run: gem install jekyll -v 4.0.0 + - name: Install GPG + uses: olafurpg/setup-gpg@v2 + - name: Check/Compile/Test + run: sbt checkAll + - name: Release + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + run: sbt ci-release + - name: Publish Microsite + env: + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no" + run: | + eval "$(ssh-agent -s)" + ssh-add - <<< "${DEPLOY_KEY}" + git config --global user.email "janecek@avast.com" + git config --global user.name "scala-server-toolkit bot" + sbt site/publishMicrosite diff --git a/.mergify.yml b/.mergify.yml index 99ffe7ccc..b41281444 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -11,7 +11,7 @@ pull_request_rules: conditions: - base=master - author=scala-steward - - status-success=Travis CI - Pull Request + - status-success=PR Build actions: merge: method: merge @@ -21,7 +21,7 @@ pull_request_rules: - "#review-requested=0" - "#changes-requested-reviews-by=0" - "#approved-reviews-by>=1" - - status-success=Travis CI - Pull Request + - status-success=PR Build actions: merge: method: squash diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8da896465..000000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -sudo: false -language: scala -jdk: openjdk8 -scala: - - 2.12.10 - -before_install: - - git fetch --tags - -stages: - - name: test - - name: release - if: ((branch = master AND type = push) OR (tag IS present)) AND NOT fork - -jobs: - include: - - - name: checkAll - script: sbt checkAll - # run ci-release only if previous stages passed - - stage: release - script: sbt ci-release - -# These directories are cached to S3 at the end of the build -cache: - directories: - - $HOME/.cache/coursier - - $HOME/.ivy2/cache - - $HOME/.sbt/boot/ - - $HOME/.sbt/launchers - -before_cache: - # Tricks to avoid unnecessary cache updates - - find $HOME/.sbt -name "*.lock" | xargs rm - - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm - - rm -f $HOME/.ivy2/.sbt.ivy.lock diff --git a/README.md b/README.md index e19cb3048..0d3bdd607 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Scala Server Toolkit -[![Build Status](https://travis-ci.org/avast/scala-server-toolkit.svg?branch=master)](https://travis-ci.org/avast/scala-server-toolkit) +![Build Status](https://github.com/avast/scala-server-toolkit/workflows/Build/badge.svg) [![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-bundle-zio-http4s-blaze_2.12)](https://repo1.maven.org/maven2/com/avast/sst-bundle-zio-http4s-blaze_2.12/) [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) [![Mergify badge](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/avast/scala-server-toolkit)](https://mergify.io) @@ -19,7 +19,7 @@ sbt new avast/sst-seed.g8 ## Documentation -**[Read it here](./docs/index.md)** or you can [deep dive into example code](example/src/main/scala/com/avast/sst/example/Main.scala). +**[Read it here](https://avast.github.io/scala-server-toolkit)** or you can [deep dive into example code](example/src/main/scala/com/avast/sst/example/Main.scala). ### Articles diff --git a/build.sbt b/build.sbt index d743b0875..e349708fe 100644 --- a/build.sbt +++ b/build.sbt @@ -1,19 +1,15 @@ ThisBuild / organization := "com.avast" +ThisBuild / organizationName := "Avast" +ThisBuild / organizationHomepage := Some(url("https://avast.com")) ThisBuild / homepage := Some(url("https://github.com/avast/scala-server-toolkit")) ThisBuild / description := "Functional programming toolkit for building server applications in Scala." ThisBuild / licenses := Seq("MIT" -> url("https://raw.githubusercontent.com/avast/scala-server-toolkit/master/LICENSE")) ThisBuild / developers := List(Developer("jakubjanecek", "Jakub Janecek", "janecek@avast.com", url("https://www.avast.com"))) +ThisBuild / scalaVersion := "2.12.10" ThisBuild / turbo := true - -lazy val commonSettings = BuildSettings.common ++ Seq( - libraryDependencies ++= Seq( - Dependencies.catsEffect, - Dependencies.logbackClassic % Test, - Dependencies.scalaTest % Test - ), - Test / publishArtifact := false -) +Global / onChangedBuildSource := ReloadOnSourceChanges +Global / cancelable := true lazy val root = project .in(file(".")) @@ -65,7 +61,7 @@ lazy val bundleMonixHttp4sBlaze = project jvmPureConfig, pureConfig ) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-bundle-monix-http4s-blaze", libraryDependencies += Dependencies.monixEval @@ -83,7 +79,7 @@ lazy val bundleZioHttp4sBlaze = project jvmPureConfig, pureConfig ) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-bundle-zio-http4s-blaze", libraryDependencies ++= Seq( @@ -94,7 +90,7 @@ lazy val bundleZioHttp4sBlaze = project lazy val cassandraDatastaxDriver = project .in(file("cassandra-datastax-driver")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-cassandra-datastax-driver", libraryDependencies += Dependencies.datastaxJavaDriverCore @@ -103,7 +99,7 @@ lazy val cassandraDatastaxDriver = project lazy val cassandraDatastaxDriverPureConfig = project .in(file("cassandra-datastax-driver-pureconfig")) .dependsOn(cassandraDatastaxDriver) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-cassandra-datastax-driver-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -111,7 +107,7 @@ lazy val cassandraDatastaxDriverPureConfig = project lazy val doobieHikari = project .in(file("doobie-hikari")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-doobie-hikari", libraryDependencies ++= Seq( @@ -123,7 +119,7 @@ lazy val doobieHikari = project lazy val doobieHikariPureConfig = project .in(file("doobie-hikari-pureconfig")) .dependsOn(doobieHikari) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-doobie-hikari-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -145,25 +141,19 @@ lazy val example = project micrometerJmxPureConfig, sslConfig ) - .enablePlugins(MdocPlugin) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-example", publish / skip := true, - run / fork := true, - Global / cancelable := true, - mdocIn := baseDirectory.value / "mdoc", - mdocOut := baseDirectory.value / ".." / "docs", libraryDependencies ++= Seq( Dependencies.logbackClassic, Dependencies.postgresql - ), - scalacOptions := scalacOptions.value.filterNot(_ == "-Xfatal-warnings") + ) ) lazy val flyway = project .in(file("flyway")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-flyway", libraryDependencies += Dependencies.flywayCore @@ -172,7 +162,7 @@ lazy val flyway = project lazy val flywayPureConfig = project .in(file("flyway-pureconfig")) .dependsOn(flyway) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-flyway-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -180,7 +170,7 @@ lazy val flywayPureConfig = project lazy val grpcServer = project .in(file("grpc-server")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-grpc-server", libraryDependencies ++= Seq( @@ -193,7 +183,7 @@ lazy val grpcServer = project lazy val grpcServerPureConfig = project .in(file("grpc-server-pureconfig")) .dependsOn(grpcServer) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-grpc-server-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -201,7 +191,7 @@ lazy val grpcServerPureConfig = project lazy val http4sClientBlaze = project .in(file("http4s-client-blaze")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-client-blaze", libraryDependencies += Dependencies.http4sBlazeClient @@ -210,13 +200,13 @@ lazy val http4sClientBlaze = project lazy val http4sClientBlazePureConfig = project .in(file("http4s-client-blaze-pureconfig")) .dependsOn(http4sClientBlaze, jvmPureConfig) - .settings(commonSettings) + .settings(BuildSettings.common) .settings(name := "sst-http4s-client-blaze-pureconfig") lazy val http4sClientMonixCatnap = project .in(file("http4s-client-monix-catnap")) .dependsOn(monixCatnapMicrometer) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-client-monix-catnap", libraryDependencies += Dependencies.http4sClient @@ -224,7 +214,7 @@ lazy val http4sClientMonixCatnap = project lazy val http4sServer = project .in(file("http4s-server")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-server", libraryDependencies ++= Seq( @@ -238,7 +228,7 @@ lazy val http4sServer = project lazy val http4sServerBlaze = project .in(file("http4s-server-blaze")) .dependsOn(http4sServer, http4sClientBlaze % Test) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-server-blaze", libraryDependencies ++= Seq( @@ -251,7 +241,7 @@ lazy val http4sServerBlaze = project lazy val http4sServerBlazePureConfig = project .in(file("http4s-server-blaze-pureconfig")) .dependsOn(http4sServerBlaze) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-server-blaze-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -260,7 +250,7 @@ lazy val http4sServerBlazePureConfig = project lazy val http4sServerMicrometer = project .in(file("http4s-server-micrometer")) .dependsOn(http4sServer) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-http4s-server-micrometer", libraryDependencies += Dependencies.micrometerCore @@ -268,7 +258,7 @@ lazy val http4sServerMicrometer = project lazy val jvm = project .in(file("jvm")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-jvm", libraryDependencies += Dependencies.slf4jApi @@ -277,7 +267,7 @@ lazy val jvm = project lazy val jvmMicrometer = project .in(file("jvm-micrometer")) .dependsOn(jvm) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-jvm-micrometer", libraryDependencies += Dependencies.micrometerCore @@ -286,7 +276,7 @@ lazy val jvmMicrometer = project lazy val jvmPureConfig = project .in(file("jvm-pureconfig")) .dependsOn(jvm) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-jvm-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -294,7 +284,7 @@ lazy val jvmPureConfig = project lazy val micrometerJmx = project .in(file("micrometer-jmx")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-micrometer-jmx", libraryDependencies ++= Seq( @@ -306,7 +296,7 @@ lazy val micrometerJmx = project lazy val micrometerJmxPureConfig = project .in(file("micrometer-jmx-pureconfig")) .dependsOn(micrometerJmx) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-micrometer-jmx-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -314,7 +304,7 @@ lazy val micrometerJmxPureConfig = project lazy val micrometerStatsD = project .in(file("micrometer-statsd")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-micrometer-statsd", libraryDependencies ++= Seq( @@ -326,7 +316,7 @@ lazy val micrometerStatsD = project lazy val micrometerStatsDPureConfig = project .in(file("micrometer-statsd-pureconfig")) .dependsOn(micrometerStatsD) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-micrometer-statsd-pureconfig", libraryDependencies += Dependencies.pureConfig @@ -334,7 +324,7 @@ lazy val micrometerStatsDPureConfig = project lazy val monixCatnap = project .in(file("monix-catnap")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-monix-catnap", libraryDependencies ++= Seq( @@ -346,7 +336,7 @@ lazy val monixCatnap = project lazy val monixCatnapMicrometer = project .in(file("monix-catnap-micrometer")) .dependsOn(monixCatnap) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-monix-catnap-micrometer", libraryDependencies ++= Seq( @@ -358,23 +348,50 @@ lazy val monixCatnapMicrometer = project lazy val monixCatnapPureConfig = project .in(file("monix-catnap-pureconfig")) .dependsOn(monixCatnap) - .settings(commonSettings) - .settings( - name := "sst-monix-catnap-pureconfig", - libraryDependencies += Dependencies.pureConfig - ) + .settings(BuildSettings.common) + .settings(run / fork := true, name := "sst-monix-catnap-pureconfig", libraryDependencies += Dependencies.pureConfig) lazy val pureConfig = project .in(file("pureconfig")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-pureconfig", libraryDependencies += Dependencies.pureConfig ) +lazy val site = project + .in(file("site")) + .enablePlugins( + MicrositesPlugin, + MdocPlugin, + SiteScaladocPlugin, + ScalaUnidocPlugin + ) + .dependsOn( + bundleZioHttp4sBlaze, + cassandraDatastaxDriver, + cassandraDatastaxDriverPureConfig, + doobieHikari, + doobieHikariPureConfig, + example, + flyway, + flywayPureConfig, + http4sClientBlazePureConfig, + http4sClientMonixCatnap, + monixCatnapPureConfig, + micrometerJmxPureConfig, + sslConfig + ) + .settings(BuildSettings.common) + .settings(BuildSettings.microsite) + .settings( + publish / skip := true, + scalacOptions := scalacOptions.value.filterNot(_ == "-Xfatal-warnings") + ) + lazy val sslConfig = project .in(file("ssl-config")) - .settings(commonSettings) + .settings(BuildSettings.common) .settings( name := "sst-ssl-config", libraryDependencies ++= Seq( @@ -384,4 +401,4 @@ lazy val sslConfig = project ) addCommandAlias("checkAll", "; scalafmtSbtCheck; scalafmtCheckAll; compile:scalafix --check; test:scalafix --check; test") -addCommandAlias("fixAll", "; compile:scalafix; test:scalafix; scalafmtSbt; scalafmtAll; example/mdoc") +addCommandAlias("fixAll", "; compile:scalafix; test:scalafix; scalafmtSbt; scalafmtAll") diff --git a/docs/http4s.md b/docs/http4s.md deleted file mode 100644 index e65b2c3ec..000000000 --- a/docs/http4s.md +++ /dev/null @@ -1,97 +0,0 @@ -# http4s - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-http4s-server-blaze_2.12)](https://repo1.maven.org/maven2/com/avast/sst-http4s-server-blaze_2.12/) - -`libraryDependencies += "com.avast" %% "sst-http4s-server-blaze" % ""` - -There are `http4s-*` subprojects that provide easy initialization of a server and a client. Http4s is an interface with multiple possible -implementations - for now we provide only implementations based on [Blaze](https://github.com/http4s/blaze). - -Both server and client are configured via configuration `case class` which contains default values taken from the underlying implementations. - -```scala -import cats.effect._ -import com.avast.sst.http4s.client._ -import com.avast.sst.http4s.server._ -import com.avast.sst.jvm.execution.ExecutorModule -import com.avast.sst.jvm.system.console.ConsoleModule -import org.http4s.dsl.Http4sDsl -import org.http4s.HttpRoutes -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.interop.catz.implicits._ -import zio.Task - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -val dsl = Http4sDsl[Task] // this is just needed in example -import dsl._ - -val routes = Http4sRouting.make { - HttpRoutes.of[Task] { - case GET -> Root / "hello" => Ok("Hello World!") - } -} - -val resource = for { - executorModule <- ExecutorModule.makeDefault[Task] - console = ConsoleModule.make[Task] - server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext) - client <- Http4sBlazeClientModule.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext) -} yield (server, client, console) - -val program = resource - .use { - case (server, client, console) => - client - .expect[String](s"http://127.0.0.1:${server.address.getPort}/hello") - .flatMap(console.printLine) - } -``` - -```scala -runtime.unsafeRun(program) -// Hello World! -``` - -## Middleware - -### Correlation ID Middleware - -```scala -import cats.effect._ -import com.avast.sst.http4s.server._ -import com.avast.sst.http4s.server.middleware.CorrelationIdMiddleware -import com.avast.sst.jvm.execution.ExecutorModule -import org.http4s.dsl.Http4sDsl -import org.http4s.HttpRoutes -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.interop.catz.implicits._ -import zio.Task - -val dsl = Http4sDsl[Task] // this is just needed in example -import dsl._ - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -for { - middleware <- Resource.liftF(CorrelationIdMiddleware.default[Task]) - executorModule <- ExecutorModule.makeDefault[Task] - routes = Http4sRouting.make { - middleware.wrap { - HttpRoutes.of[Task] { - case GET -> Root => - // val correlationId = middleware.retrieveCorrelationId(req) - ??? - } - } - } - server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig.localhost8080, routes, executorModule.executionContext) -} yield server -``` - -## Circuit Breaker - -It is a good practice to wrap any communication with external system with circuit breaking mechanism to prevent spreading of errors and -bad latency. See [monix-catnap](monix-catnap.md) for one of the options. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 3442ebcd6..000000000 --- a/docs/index.md +++ /dev/null @@ -1,96 +0,0 @@ -\# Scala Server Toolkit Documentation - -* [Getting Started](#getting-started) -* [Rationale](rationale.md) -* [Subproject Structure](#subproject-structure) -* [Bundles](#bundles) -* [http4s](http4s.md) -* [JVM](jvm.md) -* [Micrometer](micrometer.md) -* [PureConfig](pureconfig.md) -* [Datastax Cassandra Driver](cassandra-datastax-driver.md) -* [SSL Config](ssl-config.md) -* [doobie](doobie.md) -* [Flyway](flyway.md) -* [monix-catnap - CircuitBreaker](monix-catnap.md) - -## Getting Started - -Creating a simple HTTP server using [http4s](https://http4s.org) and [ZIO](https://zio.dev) is as easy as this: - -#### build.sbt - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-bundle-zio-http4s-blaze_2.12)](https://repo1.maven.org/maven2/com/avast/sst-bundle-zio-http4s-blaze_2.12/) - -`libraryDependencies += "com.avast" %% "sst-bundle-zio-http4s-blaze" % ""` - -#### Main - -```scala -import cats.effect._ -import com.avast.sst.http4s.client._ -import com.avast.sst.http4s.server._ -import com.avast.sst.jvm.execution.ExecutorModule -import com.avast.sst.jvm.system.console.ConsoleModule -import org.http4s.dsl.Http4sDsl -import org.http4s.HttpRoutes -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.interop.catz.implicits._ -import zio.Task - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -val dsl = Http4sDsl[Task] // this is just needed in example -import dsl._ - -val routes = Http4sRouting.make { - HttpRoutes.of[Task] { - case GET -> Root / "hello" => Ok("Hello World!") - } -} - -val resource = for { - executorModule <- ExecutorModule.makeDefault[Task] - console = ConsoleModule.make[Task] - server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext) - client <- Http4sBlazeClientModule.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext) -} yield (server, client, console) - -val program = resource - .use { - case (server, client, console) => - client - .expect[String](s"http://127.0.0.1:${server.address.getPort}/hello") - .flatMap(console.printLine) - } -``` - -```scala -runtime.unsafeRun(program) -// Hello World! -``` - -## Subproject Structure - -The project is split into many small subprojects based on dependencies. For example code related to loading of configuration files via -[PureConfig](https://pureconfig.github.io) lives in a subproject named `sst-pureconfig` and code related to http4s server implemented using -[Blaze](https://github.com/http4s/blaze) lives in a subproject named `sst-http4s-server-blaze`. - -There are also subprojects that implement interoperability between usually two dependencies. For example we want to configure our HTTP server -using PureConfig so definition of `implicit` `ConfigReader` instances live in subproject named `sst-http4s-server-blaze-pureconfig`. Or to give -another example, monitoring of HTTP server using [Micrometer](https://micrometer.io) lives in subproject named `sst-http4s-server-micrometer`. -Note that such subproject depends on APIs of both http4s server and Micrometer but it does not depend on concrete implementation which allows -you to choose any http4s implementation (Blaze, ...) and any Micrometer implementation (JMX, StatsD, ...). - -## Bundles - -Having many small and independent subprojects is great but in practice everyone wants to use certain combination of dependencies and does not -want to worry about many small dependencies. There are "bundles" for such use case - either the ones provided by this project or custom -ones created by the user. - -One of the main decisions (dependency-wise) is to choose the effect data type. This project does not force you into specific data type and -supports both [ZIO](https://zio.dev) and [Monix](https://monix.io) out-of-the-box. So there are two main bundles one for each effect data -type that also bring in http4s server/client (Blaze), PureConfig and Micrometer. - -Unless you have specific needs take one of these bundles and write your server application using them - it will be the simplest way. diff --git a/example/mdoc/cassandra-datastax-driver.md b/example/mdoc/cassandra-datastax-driver.md deleted file mode 100644 index 84745e94d..000000000 --- a/example/mdoc/cassandra-datastax-driver.md +++ /dev/null @@ -1,34 +0,0 @@ -# Datastax Cassandra Driver - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-cassandra-datastax-driver_2.12)](https://repo1.maven.org/maven2/com/avast/sst-cassandra-datastax-driver_2.12/) - -This module initializes Datastax Cassandra driver's `Session`. - -`libraryDependencies += "com.avast" %% "sst-cassandra-datastax-driver" % ""` - -```scala mdoc:silent -import cats.effect.Resource -import com.avast.sst.datastax.CassandraDatastaxDriverModule -import com.avast.sst.datastax.config.CassandraDatastaxDriverConfig -import com.avast.sst.datastax.pureconfig.implicits._ -import com.avast.sst.pureconfig.PureConfigModule -import zio._ -import zio.interop.catz._ - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -for { - configuration <- Resource.liftF(PureConfigModule.makeOrRaise[Task, CassandraDatastaxDriverConfig]) - db <- CassandraDatastaxDriverModule.make[Task](configuration) -} yield db -``` - -```HOCON -basic { - contact-points = ["localhost:9042"] - - load-balancing-policy { - local-datacenter = "datacenter1" - } -} -``` \ No newline at end of file diff --git a/example/mdoc/doobie.md b/example/mdoc/doobie.md deleted file mode 100644 index 5211dc22a..000000000 --- a/example/mdoc/doobie.md +++ /dev/null @@ -1,38 +0,0 @@ -# Doobie - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-doobie-hikari_2.12)](https://repo1.maven.org/maven2/com/avast/sst-doobie-hikari_2.12/) - -`libraryDependencies += "com.avast" %% "sst-doobie-hikari" % ""` - -This module initializes a doobie `Transactor`: - -```scala mdoc:silent -import cats.effect.Resource -import com.avast.sst.doobie.DoobieHikariModule -import com.avast.sst.example.config.Configuration -import com.avast.sst.jvm.execution.ConfigurableThreadFactory.Config -import com.avast.sst.jvm.execution.{ConfigurableThreadFactory, ExecutorModule} -import com.avast.sst.micrometer.jmx.MicrometerJmxModule -import com.avast.sst.pureconfig.PureConfigModule -import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory -import scala.concurrent.ExecutionContext -import zio._ -import zio.interop.catz._ - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -for { - configuration <- Resource.liftF(PureConfigModule.makeOrRaise[Task, Configuration]) - executorModule <- ExecutorModule.makeFromExecutionContext[Task](runtime.platform.executor.asEC) - meterRegistry <- MicrometerJmxModule.make[Task](configuration.jmx) - boundedConnectExecutionContext <- executorModule - .makeThreadPoolExecutor( - configuration.boundedConnectExecutor, - new ConfigurableThreadFactory(Config(Some("hikari-connect-%02d"))) - ) - .map(ExecutionContext.fromExecutorService) - hikariMetricsFactory = new MicrometerMetricsTrackerFactory(meterRegistry) - doobieTransactor <- DoobieHikariModule - .make[Task](configuration.database, boundedConnectExecutionContext, executorModule.blocker, Some(hikariMetricsFactory)) -} yield doobieTransactor -``` diff --git a/example/mdoc/flyway.md b/example/mdoc/flyway.md deleted file mode 100644 index 8a52a1345..000000000 --- a/example/mdoc/flyway.md +++ /dev/null @@ -1,24 +0,0 @@ -# Flyway - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-flyway_2.12)](https://repo1.maven.org/maven2/com/avast/sst-flyway_2.12/) - -`libraryDependencies += "com.avast" %% "sst-flyway" % ""` - -This module initializes `Flyway` which can be used to do automated SQL DB migrations. See the [documentation of Flyway](https://flywaydb.org/documentation/) -on how to go about that. - -The method `make` requires `javax.sql.DataSource` which you can for example obtain from `doobie-hikari` module: - -```scala mdoc:compile-only -import cats.effect.Resource -import com.avast.sst.doobie.DoobieHikariModule -import com.avast.sst.flyway.FlywayModule -import zio.Task -import zio.interop.catz._ - -for { - doobieTransactor <- DoobieHikariModule.make[Task](???, ???, ???, ???) - flyway <- Resource.liftF(FlywayModule.make[Task](doobieTransactor.kernel, ???)) - _ <- Resource.liftF(Task.effect(flyway.migrate())) -} yield () -``` diff --git a/example/mdoc/index.md b/example/mdoc/index.md deleted file mode 100644 index 0ca088d79..000000000 --- a/example/mdoc/index.md +++ /dev/null @@ -1,101 +0,0 @@ -# Scala Server Toolkit Documentation - -* [Getting Started](#getting-started) -* [Rationale](rationale.md) -* [Module Structure](#module-structure) -* [Bundles](#bundles) -* [Modules http4s](http4s.md) -* [Module JVM](jvm.md) -* [Modules Micrometer](micrometer.md) -* [Module PureConfig](pureconfig.md) -* [Module Datastax Cassandra Driver](cassandra-datastax-driver.md) -* [Module SSL Config](ssl-config.md) -* [Module doobie](doobie.md) -* [Module Flyway](flyway.md) -* [Module monix-catnap - CircuitBreaker](monix-catnap.md) - -## Getting Started - -Creating a simple HTTP server using [http4s](https://http4s.org) and [ZIO](https://zio.dev) is as easy as this: - -#### build.sbt - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-bundle-zio-http4s-blaze_2.12)](https://repo1.maven.org/maven2/com/avast/sst-bundle-zio-http4s-blaze_2.12/) - -`libraryDependencies += "com.avast" %% "sst-bundle-zio-http4s-blaze" % ""` - -#### Main - -```scala mdoc:silent:reset-class -import cats.effect._ -import com.avast.sst.http4s.client._ -import com.avast.sst.http4s.server._ -import com.avast.sst.jvm.execution.ExecutorModule -import com.avast.sst.jvm.system.console.ConsoleModule -import org.http4s.dsl.Http4sDsl -import org.http4s.HttpRoutes -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.interop.catz.implicits._ -import zio.Task - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -val dsl = Http4sDsl[Task] // this is just needed in example -import dsl._ - -val routes = Http4sRouting.make { - HttpRoutes.of[Task] { - case GET -> Root / "hello" => Ok("Hello World!") - } -} - -val resource = for { - executorModule <- ExecutorModule.makeDefault[Task] - console = ConsoleModule.make[Task] - server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext) - client <- Http4sBlazeClientModule.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext) -} yield (server, client, console) - -val program = resource - .use { - case (server, client, console) => - client - .expect[String](s"http://127.0.0.1:${server.address.getPort}/hello") - .flatMap(console.printLine) - } -``` - -```scala mdoc -runtime.unsafeRun(program) -``` - -Or you can use the [official Giter8 template](https://github.com/avast/sst-seed.g8): - -```bash -sbt new avast/sst-seed.g8 -``` - -## Module Structure - -The project is split into many small modules based on dependencies. For example code related to loading of configuration files via -[PureConfig](https://pureconfig.github.io) lives in module named `sst-pureconfig` and code related to http4s server implemented using -[Blaze](https://github.com/http4s/blaze) lives in module named `sst-http4s-server-blaze`. - -There are also modules that implement interoperability between usually two dependencies. For example we want to configure our HTTP server -using PureConfig so definition of `implicit` `ConfigReader` instances lives in module named `sst-http4s-server-blaze-pureconfig`. Or to give -another example, monitoring of HTTP server using [Micrometer](https://micrometer.io) lives in module named `sst-http4s-server-micrometer`. -Note that such module depends on APIs of both http4s server and Micrometer but it does not depend on concrete implementation which allows -you to choose any http4s implementation (Blaze, ...) and any Micrometer implementation (JMX, StatsD, ...). - -## Bundles - -Having many small and independent modules is great but in practice everyone wants to use certain combination of dependencies and does not -want to worry about many small dependencies. There are "bundles" for such use case - either the ones provided by this project or custom -ones created by the user. - -One of the main decisions (dependency-wise) is to choose the effect data type. This project does not force you into specific data type and -supports both [ZIO](https://zio.dev) and [Monix](https://monix.io) out-of-the-box. So there are two main bundles one for each effect data -type that also bring in http4s server/client (Blaze), PureConfig and Micrometer. - -Unless you have specific needs take one of these bundles and write your server application using them - it will be the simplest way. diff --git a/example/mdoc/jvm.md b/example/mdoc/jvm.md deleted file mode 100644 index 13b9e5a8d..000000000 --- a/example/mdoc/jvm.md +++ /dev/null @@ -1,32 +0,0 @@ -# Module JVM - -![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-jvm_2.12) - -`libraryDependencies += "com.avast" %% "sst-jvm" % ""` - -Module `sst-jvm` provides pure implementations of different JVM-related utilities: - -* creation of thread pools, -* standard in/out/err, -* and random number generation. - -```scala mdoc:silent -import com.avast.sst.jvm.system.console.ConsoleModule -import com.avast.sst.jvm.system.random.RandomModule -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.Task - -val program = for { - random <- RandomModule.makeRandom[Task](1234L) // do not ever use seed like this! - randomNumber <- random.nextInt - console = ConsoleModule.make[Task] - _ <- console.printLine(s"Random number: $randomNumber") -} yield () - -val runtime = new DefaultRuntime {} // this is just needed in example -``` - -```scala mdoc -runtime.unsafeRun(program) -``` diff --git a/example/mdoc/micrometer.md b/example/mdoc/micrometer.md deleted file mode 100644 index e00dd6c5b..000000000 --- a/example/mdoc/micrometer.md +++ /dev/null @@ -1,46 +0,0 @@ -# Modules Micrometer - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-micrometer-jmx_2.12)](https://repo1.maven.org/maven2/com/avast/sst-micrometer-jmx_2.12/) - -`libraryDependencies += "com.avast" %% "sst-micrometer-jmx" % ""` - -This module allows you to monitor your applications using [Micrometer](https://micrometer.io). There are many actual implementations of -the Micrometer API one of which is JMX. Module `sst-micrometer-jmx` implements the initialization of Micrometer for JMX. There are also -interop modules such as `sst-http4s-server-micrometer` which implement monitoring of HTTP server and individual routes using Micrometer. - -```scala mdoc:silent -import cats.effect.{Clock, Resource} -import com.avast.sst.http4s.server._ -import com.avast.sst.http4s.server.micrometer.MicrometerHttp4sServerMetricsModule -import com.avast.sst.jvm.execution.ExecutorModule -import com.avast.sst.jvm.micrometer.MicrometerJvmModule -import com.avast.sst.micrometer.jmx._ -import org.http4s.dsl.Http4sDsl -import org.http4s.HttpRoutes -import org.http4s.server.Server -import zio.DefaultRuntime -import zio.interop.catz._ -import zio.interop.catz.implicits._ -import zio.Task - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -val dsl = Http4sDsl[Task] // this is just needed in example -import dsl._ - -for { - executorModule <- ExecutorModule.makeFromExecutionContext[Task](runtime.platform.executor.asEC) - clock = Clock.create[Task] - jmxMeterRegistry <- MicrometerJmxModule.make[Task](MicrometerJmxConfig("com.avast")) - _ <- Resource.liftF(MicrometerJvmModule.make[Task](jmxMeterRegistry)) - serverMetricsModule <- Resource.liftF(MicrometerHttp4sServerMetricsModule.make[Task](jmxMeterRegistry, clock)) - routes = Http4sRouting.make { - serverMetricsModule.serverMetrics { - HttpRoutes.of[Task] { - case GET -> Root / "hello" => Ok("Hello World!") - } - } - } - server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext) -} yield server -``` diff --git a/example/mdoc/monix-catnap.md b/example/mdoc/monix-catnap.md deleted file mode 100644 index e397968d6..000000000 --- a/example/mdoc/monix-catnap.md +++ /dev/null @@ -1,19 +0,0 @@ -# monix-catnap - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-monix-catnap_2.12)](https://repo1.maven.org/maven2/com/avast/sst-monix-catnap_2.12/) - -`libraryDependencies += "com.avast" %% "sst-monix-catnap" % ""` - -This module provides interop between Scala Server Toolkit and [monix-catnap](https://monix.io/docs/3x/#monix-catnap) library. - -## Circuit Breaker - -You can use `CircuitBreakerModule` to instantiate and configure a circuit breaker and you can implement `CircuitBreakerMetrics` to get -monitoring of the circuit breaker. There is an implementation for Micrometer in `sst-monix-catnap-micrometer` module. - -All of this is tied with http4s HTTP client in the `sst-http4s-client-monix-catnap` module so in practice you want to use -`Http4sClientCircuitBreakerModule` which wraps any `Client[F]` with a `CircuitBreaker` (it is recommended to have an enriched `CircuitBreaker` -with logging and metrics - see `CircuitBreakerModule` companion object methods). However the most important feature of the enriched circuit -breaker is that any HTTP failure (according to `HttpStatusClassifier`) is converted to an exception internally which triggers the circuit -breaking mechanism. Failing server is not overloaded by more requests and we do not have to wait for the response if the server is failing -anyway. diff --git a/example/mdoc/pureconfig.md b/example/mdoc/pureconfig.md deleted file mode 100644 index 753f8002e..000000000 --- a/example/mdoc/pureconfig.md +++ /dev/null @@ -1,36 +0,0 @@ -# Module PureConfig - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-pureconfig_2.12)](https://repo1.maven.org/maven2/com/avast/sst-pureconfig_2.12/) - -`libraryDependencies += "com.avast" %% "sst-pureconfig" % ""` - -This module allows you to load your application's configuration file into a case class. It uses [PureConfig](https://pureconfig.github.io) -library to do so which uses [Lightbend Config](https://github.com/lightbend/config) which means that your application's configuration -will be in [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md) format. - -Loading of configuration is side-effectful so it is wrapped in `F` which is `Sync`. This module also tweaks the error messages a little. - -```scala mdoc:silent -import com.avast.sst.pureconfig.PureConfigModule -import pureconfig.ConfigReader -import pureconfig.generic.semiauto.deriveReader -import zio.interop.catz._ -import zio.Task - -final case class ServerConfiguration(listenAddress: String, listenPort: Int) - -implicit val serverConfigurationReader: ConfigReader[ServerConfiguration] = deriveReader - -val maybeConfiguration = PureConfigModule.make[Task, ServerConfiguration] -``` - -Look for `sst-*-pureconfig` modules to get `implicit` instances of `ConfigReader` for specific libraries, e.g.: - -```scala mdoc:silent -import com.avast.sst.http4s.server.pureconfig.implicits._ -``` - -The default `*.pureconfig.implicits._` import contains the default naming convention for PureConfig which is `kebab-case` -for the configuration file and `camelCase` for the case class field names. You can either choose a different naming convention by different -import (e.g. `*.pureconfig.implicits.CamelCase`) or you can [create your own](https://pureconfig.github.io/docs/overriding-behavior-for-case-classes.html#field-mappings) -and `extend` the `ConfigReaders` `trait`. diff --git a/example/mdoc/rationale.md b/example/mdoc/rationale.md deleted file mode 100644 index 53507de14..000000000 --- a/example/mdoc/rationale.md +++ /dev/null @@ -1,42 +0,0 @@ -# Rationale - -Avast backend developers have been using Scala since 2011. Over the years they have went through the usual stages of a Scala developer: - -* **Using Scala as better Java**: imperative code full of mutability and side effects. -* **Idiomatic Scala**: expression-oriented programming, full potential of Scala collections, convenient mix of OOP and FP. -* **FP Scala**: taming side effects, monads and algebraic laws, all about referential transparency, composability and separation of concerns. - -The evolution process from imperative Scala towards FP Scala was very natural. As we have been learning Scala more and more it lead us to -the more functional side of Scala. Now we are in a phase in which we are dedicated to go full FP. - -However not everything was rose-colored. The different styles of programming we have used in the past mean that we have very diverse -codebases and teams/people are used to do things differently. Functional programming is very popular nowadays and the amount of innovation -in that space is incredible but we are still lacking some kind of library that would give us unified approach to programming server -applications. Thus we have decided to write our own and it is called `scala-server-toolkit`. It should help us unify our programming style, -promote best practices, functional programming and we believe it could be useful to others too. - -It should be pointed out that we do not intend to write some full-blown framework that would try to solve all your server-side problems. -We are actually trying to do quite the opposite. Reuse as most open-source libraries as we can and provide only the missing integration -pieces to simplify everyday programming. Most of the code revolves around initialization and integration of existing OSS libraries in -unified way. - -There are certain design decisions and constraints that we put in place to guide the development of this library. - -* Modular design: small, cohesive, orthogonal and composable components. - * The project is split into completely separate modules mostly based on dependencies. Every module provides some functionality, usually - something small. The idea is to compose multiple modules together to get a working application but be able to replace any module - with different implementation in case it is needed. -* Keep the number of dependencies as low as possible. - * Adding a dependency seems like a great idea to avoid having to implement some piece of existing logic however every dependency is also - a burden that can cause a lot of problems. Therefore every dependency should be considered well and only very important and well-maintained - libraries should be added to our core. -* Functional programming. - * We believe in functional programming so this library also utilizes its concepts. - * We do not want to force anyone into any specific effect data type so the code is written in so-called tagless final style. -* Type safe configuration and resource lifecycle. - * Application initialization is often overlooked. By using type safe configuration and proper resource management (to never leak resources) - we can make it correct without much hassle. -* No need for dependency injection. - * Dependency injection is not needed in most cases - plain constructors and good application architecture usually suffice. - * Use a dependency injection framework if your application is huge and it justifies its cost. -* Strive for [Scalazzi Safe Scala Subset](https://slides.yowconference.com/yowwest2014/Morris-ParametricityTypesDocumentationCodeReadability.pdf). diff --git a/example/mdoc/ssl-config.md b/example/mdoc/ssl-config.md deleted file mode 100644 index 72e4b86e6..000000000 --- a/example/mdoc/ssl-config.md +++ /dev/null @@ -1,21 +0,0 @@ -# Module SSL Config - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-ssl-config_2.12)](https://repo1.maven.org/maven2/com/avast/sst-ssl-config_2.12/) - -`libraryDependencies += "com.avast" %% "sst-ssl-config" % ""` - -This module allows you to create SSL context (`javax.net.ssl.SSLContext`) from a configuration file. It uses [SSL Config](https://github.com/lightbend/ssl-config) -library to do so which means that this module is different than others and receives directly `com.typesafe.config.Config` instead of -configuration case classes. See the [documentation of SSL Config](https://lightbend.github.io/ssl-config) for more information. - -Loading of SSL context is side-effectful so it is wrapped in `F` which is `Sync`. - -```scala mdoc:silent -import com.avast.sst.ssl.SslContextModule -import com.typesafe.config.ConfigFactory -import zio.interop.catz._ -import zio.Task - -val config = ConfigFactory.load().getConfig("ssl-config") -val sslContext = SslContextModule.make[Task](config) -``` diff --git a/monix-catnap/src/main/scala/com/avast/sst/monix/catnap/CircuitBreakerModule.scala b/monix-catnap/src/main/scala/com/avast/sst/monix/catnap/CircuitBreakerModule.scala index 228491cf9..baf13f9d3 100644 --- a/monix-catnap/src/main/scala/com/avast/sst/monix/catnap/CircuitBreakerModule.scala +++ b/monix-catnap/src/main/scala/com/avast/sst/monix/catnap/CircuitBreakerModule.scala @@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory class CircuitBreakerModule[F[_]](implicit F: Sync[F]) { - /** Makes [[monix.catnap.CircuitBreaker]] initialized with the given config and [[cats.effect.Clock]]. */ + /** Makes [[monix.catnap.CircuitBreaker]] initialized with the given config and `cats.effect.Clock`. */ def make(config: CircuitBreakerConfig, clock: Clock[F], onRejected: F[Unit] = F.unit, diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index 0b151507f..1da973ad2 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -1,17 +1,26 @@ +import com.typesafe.sbt.site.SitePlugin.autoImport._ +import mdoc.MdocPlugin.autoImport._ +import microsites.CdnDirectives +import microsites.MicrositesPlugin.autoImport._ import sbt.Keys._ -import sbt._ +import sbt.{Def, _} +import sbtunidoc.ScalaUnidocPlugin.autoImport._ import scalafix.sbt.ScalafixPlugin.autoImport._ +import wartremover.Wart import wartremover.WartRemover.autoImport._ object BuildSettings { - lazy val common = Seq( - ThisBuild / scalaVersion := "2.12.10", + lazy val common: Seq[Def.Setting[_]] = Seq( + fork := true, libraryDependencies ++= Seq( compilerPlugin(Dependencies.kindProjector), compilerPlugin(Dependencies.silencer), compilerPlugin(scalafixSemanticdb), // for Scalafix - Dependencies.silencerLib + Dependencies.silencerLib, + Dependencies.catsEffect, + Dependencies.logbackClassic % Test, + Dependencies.scalaTest % Test ), Compile / compile / wartremoverErrors ++= Warts.all filterNot Set( Wart.Null, // checked by Scalafix @@ -42,4 +51,38 @@ object BuildSettings { ) ) + lazy val microsite: Seq[Def.Setting[_]] = Seq( + micrositeCompilingDocsTool := WithMdoc, + micrositeName := "scala-server-toolkit", + micrositeDescription := "Functional programming toolkit for building server applications in Scala.", + micrositeAuthor := "Avast", + micrositeOrganizationHomepage := "https://avast.com", + micrositeGithubOwner := "avast", + micrositeGithubRepo := "scala-server-toolkit", + micrositeUrl := "https://avast.github.io", + micrositeDocumentationUrl := "api/latest", + micrositeDocumentationLabelDescription := "API ScalaDoc", + micrositeBaseUrl := "/scala-server-toolkit", + micrositeTwitter := "@avast_devs", + micrositeGitterChannel := false, + micrositeTheme := "pattern", + micrositeHighlightTheme := "github", + micrositeCDNDirectives := CdnDirectives( + cssList = List( + "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/github.min.css" + ) + ), + micrositeShareOnSocial := false, + mdoc / fork := true, + mdocIn := file("docs"), + mdocVariables := Map("VERSION" -> version.value), + mdocAutoDependency := true, + micrositeDataDirectory := file("site"), + ScalaUnidoc / siteSubdirName := "api/latest", + addMappingsToSiteDir( + ScalaUnidoc / packageDoc / mappings, + ScalaUnidoc / siteSubdirName + ) + ) + } diff --git a/project/plugins.sbt b/project/plugins.sbt index 80097944f..4559f6e58 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,8 +2,9 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.10") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.11") addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.1") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.1.1") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.0") +addSbtPlugin("com.47deg" % "sbt-microsites" % "1.1.0") +addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") addSbtPlugin("io.chrisdavenport" % "sbt-mima-version-check" % "0.1.2") addSbtPlugin("ch.epfl.scala" % "sbt-missinglink" % "0.2.0") diff --git a/site/docs/bundles.md b/site/docs/bundles.md new file mode 100644 index 000000000..82bb0326f --- /dev/null +++ b/site/docs/bundles.md @@ -0,0 +1,17 @@ +--- +layout: docs +title: "Bundles" +position: 3 +--- + +# Bundles + +Having many small and independent subprojects is great but in practice everyone wants to use certain combination of dependencies and does not +want to worry about many small dependencies. There are "bundles" for such use case - either the ones provided by this project or custom +ones created by the user. + +One of the main decisions (dependency-wise) is to choose the effect data type. This project does not force you into specific data type and +supports both [ZIO](https://zio.dev) and [Monix](https://monix.io) out-of-the-box. So there are two main bundles one for each effect data +type that also bring in http4s server/client (Blaze), PureConfig and Micrometer. + +Unless you have specific needs take one of these bundles and write your server application using them - it will be the simplest way. diff --git a/site/docs/getting-started.md b/site/docs/getting-started.md new file mode 100644 index 000000000..1d233d0c8 --- /dev/null +++ b/site/docs/getting-started.md @@ -0,0 +1,65 @@ +--- +layout: docs +title: "Getting Started" +position: 0 +--- + +# Getting Started + +Creating a simple HTTP server using [http4s](https://http4s.org) and [ZIO](https://zio.dev) is as easy as this: + +### build.sbt + +`libraryDependencies += "com.avast" %% "sst-bundle-zio-http4s-blaze" % "@VERSION@"` + +### Main + +```scala mdoc:silent:reset-class +import cats.effect._ +import com.avast.sst.http4s.client._ +import com.avast.sst.http4s.server._ +import com.avast.sst.jvm.execution.ExecutorModule +import com.avast.sst.jvm.system.console.ConsoleModule +import org.http4s.dsl.Http4sDsl +import org.http4s.HttpRoutes +import zio.DefaultRuntime +import zio.interop.catz._ +import zio.interop.catz.implicits._ +import zio.Task + +implicit val runtime = new DefaultRuntime {} // this is just needed in example + +val dsl = Http4sDsl[Task] // this is just needed in example +import dsl._ + +val routes = Http4sRouting.make { + HttpRoutes.of[Task] { + case GET -> Root / "hello" => Ok("Hello World!") + } +} + +val resource = for { + executorModule <- ExecutorModule.makeDefault[Task] + console = ConsoleModule.make[Task] + server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext) + client <- Http4sBlazeClientModule.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext) +} yield (server, client, console) + +val program = resource + .use { + case (server, client, console) => + client + .expect[String](s"http://127.0.0.1:${server.address.getPort}/hello") + .flatMap(console.printLine) + } +``` + +```scala mdoc +runtime.unsafeRun(program) +``` + +Or you can use the [official Giter8 template](https://github.com/avast/sst-seed.g8): + +```bash +sbt new avast/sst-seed.g8 +``` diff --git a/site/docs/index.md b/site/docs/index.md new file mode 100644 index 000000000..defbca0ba --- /dev/null +++ b/site/docs/index.md @@ -0,0 +1,35 @@ +--- +layout: home +title: "Home" +--- + +This project is a culmination of years of Scala development at Avast and tries to represent the best practices of Scala server development +we have gained together with tools that allow us to be effective. It is a set of small, flexible and cohesive building blocks that fit +together well and allow you to build reliable server applications. + +# Jump Right In + +You can use the [official Giter8 template](https://github.com/avast/sst-seed.g8) to get started: + +```bash +sbt new avast/sst-seed.g8 +``` + +Or **[read documentation](getting-started.md)** or [deep dive into example code](https://github.com/avast/scala-server-toolkit/tree/master/example). + +# SBT Dependency + +```sbt +libraryDependencies += "com.avast" %% "sst-bundle-zio-http4s-blaze" % "@VERSION@" +``` + +# Resources + +## Articles + +* [Introducing Scala Server Toolkit](https://engineering.avast.io/introducing-scala-server-toolkit) (Avast Engineering) +* [SST - Creating HTTP Server](https://engineering.avast.io/scala-server-toolkit-creating-http-server) (Avast Engineering) + +## Talks +* [Intro to Scala Server Toolkit](https://www.youtube.com/watch?v=T4xKu2bFUv0) (Functional JVM Meetup, Prague, January 23, 2020) + * [slides](https://speakerdeck.com/jakubjanecek/intro-to-scala-server-toolkit) diff --git a/docs/rationale.md b/site/docs/rationale.md similarity index 94% rename from docs/rationale.md rename to site/docs/rationale.md index e773bb0de..0189179c9 100644 --- a/docs/rationale.md +++ b/site/docs/rationale.md @@ -1,3 +1,9 @@ +--- +layout: docs +title: "Rationale" +position: 1 +--- + # Rationale Avast backend developers have been using Scala since 2011. Over the years they have went through the usual stages of a Scala developer: @@ -24,7 +30,7 @@ There are certain design decisions and constraints that we put in place to guide * Modular design: small, cohesive, orthogonal and composable components. * The project is split into completely separate subprojects mostly based on dependencies. Every subproject provides some functionality, usually - something small. The idea is to compose multiple subprojects together to get a working application but be able to replace any subproject + something small. The idea is to compose multiple modules together to get a working application but be able to replace any module with different implementation in case it is needed. * Keep the number of dependencies as low as possible. * Adding a dependency seems like a great idea to avoid having to implement some piece of existing logic however every dependency is also diff --git a/site/docs/structure.md b/site/docs/structure.md new file mode 100644 index 000000000..0ddd27384 --- /dev/null +++ b/site/docs/structure.md @@ -0,0 +1,17 @@ +--- +layout: docs +title: "Structure" +position: 2 +--- + +# Structure + +The project is split into many small subprojects based on dependencies. For example code related to loading of configuration files via +[PureConfig](https://pureconfig.github.io) lives in subproject named `sst-pureconfig` and code related to http4s server implemented using +[Blaze](https://github.com/http4s/blaze) lives in subproject named `sst-http4s-server-blaze`. + +There are also subprojects that implement interoperability between usually two dependencies. For example we want to configure our HTTP server +using PureConfig so definition of `implicit` `ConfigReader` instances lives in subproject named `sst-http4s-server-blaze-pureconfig`. Or to give +another example, monitoring of HTTP server using [Micrometer](https://micrometer.io) lives in subproject named `sst-http4s-server-micrometer`. +Note that such subproject depends on APIs of both http4s server and Micrometer but it does not depend on concrete implementation which allows +you to choose any http4s implementation (Blaze, ...) and any Micrometer implementation (JMX, StatsD, ...). diff --git a/site/docs/subprojects.md b/site/docs/subprojects.md new file mode 100644 index 000000000..8b92ddde9 --- /dev/null +++ b/site/docs/subprojects.md @@ -0,0 +1,9 @@ +--- +layout: docs +title: "Subprojects" +position: 4 +--- + +# Subprojects + +Scala Server Toolkit provides many subprojects for different well-known libraries. diff --git a/docs/cassandra-datastax-driver.md b/site/docs/subprojects/cassandra-datastax-driver.md similarity index 77% rename from docs/cassandra-datastax-driver.md rename to site/docs/subprojects/cassandra-datastax-driver.md index d41e85e60..a67d64380 100644 --- a/docs/cassandra-datastax-driver.md +++ b/site/docs/subprojects/cassandra-datastax-driver.md @@ -1,34 +1,37 @@ -# Datastax Cassandra Driver - -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-cassandra-datastax-driver_2.12)](https://repo1.maven.org/maven2/com/avast/sst-cassandra-datastax-driver_2.12/) - -This subproject initializes Datastax Cassandra driver's `Session`. - -`libraryDependencies += "com.avast" %% "sst-cassandra-datastax-driver" % ""` - -```scala -import cats.effect.Resource -import com.avast.sst.datastax.CassandraDatastaxDriverModule -import com.avast.sst.datastax.config.CassandraDatastaxDriverConfig -import com.avast.sst.datastax.pureconfig.implicits._ -import com.avast.sst.pureconfig.PureConfigModule -import zio._ -import zio.interop.catz._ - -implicit val runtime = new DefaultRuntime {} // this is just needed in example - -for { - configuration <- Resource.liftF(PureConfigModule.makeOrRaise[Task, CassandraDatastaxDriverConfig]) - db <- CassandraDatastaxDriverModule.make[Task](configuration) -} yield db -``` - -```HOCON -basic { - contact-points = ["localhost:9042"] - - load-balancing-policy { - local-datacenter = "datacenter1" - } -} -``` \ No newline at end of file +--- +layout: docs +title: "Datastax Cassandra Driver" +--- + +# Datastax Cassandra Driver + +This subproject initializes Datastax Cassandra driver's `Session`. + +`libraryDependencies += "com.avast" %% "sst-cassandra-datastax-driver" % "@VERSION@"` + +```scala mdoc:silent +import cats.effect.Resource +import com.avast.sst.datastax.CassandraDatastaxDriverModule +import com.avast.sst.datastax.config.CassandraDatastaxDriverConfig +import com.avast.sst.datastax.pureconfig.implicits._ +import com.avast.sst.pureconfig.PureConfigModule +import zio._ +import zio.interop.catz._ + +implicit val runtime = new DefaultRuntime {} // this is just needed in example + +for { + configuration <- Resource.liftF(PureConfigModule.makeOrRaise[Task, CassandraDatastaxDriverConfig]) + db <- CassandraDatastaxDriverModule.make[Task](configuration) +} yield db +``` + +```HOCON +basic { + contact-points = ["localhost:9042"] + + load-balancing-policy { + local-datacenter = "datacenter1" + } +} +``` diff --git a/docs/doobie.md b/site/docs/subprojects/doobie.md similarity index 87% rename from docs/doobie.md rename to site/docs/subprojects/doobie.md index 6e35d8139..56b75fd3b 100644 --- a/docs/doobie.md +++ b/site/docs/subprojects/doobie.md @@ -1,12 +1,15 @@ -# Doobie +--- +layout: docs +title: "Doobie" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-doobie-hikari_2.12)](https://repo1.maven.org/maven2/com/avast/sst-doobie-hikari_2.12/) +# Doobie -`libraryDependencies += "com.avast" %% "sst-doobie-hikari" % ""` +`libraryDependencies += "com.avast" %% "sst-doobie-hikari" % "@VERSION@"` This subproject initializes a doobie `Transactor`: -```scala +```scala mdoc:silent import cats.effect.Resource import com.avast.sst.doobie.DoobieHikariModule import com.avast.sst.example.config.Configuration diff --git a/docs/flyway.md b/site/docs/subprojects/flyway.md similarity index 76% rename from docs/flyway.md rename to site/docs/subprojects/flyway.md index 863ca3cd4..18997daa0 100644 --- a/docs/flyway.md +++ b/site/docs/subprojects/flyway.md @@ -1,15 +1,18 @@ -# Flyway +--- +layout: docs +title: "Flyway" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-flyway_2.12)](https://repo1.maven.org/maven2/com/avast/sst-flyway_2.12/) +# Flyway -`libraryDependencies += "com.avast" %% "sst-flyway" % ""` +`libraryDependencies += "com.avast" %% "sst-flyway" % "@VERSION@"` This subproject initializes `Flyway` which can be used to do automated SQL DB migrations. See the [documentation of Flyway](https://flywaydb.org/documentation/) on how to go about that. The method `make` requires `javax.sql.DataSource` which you can for example obtain from `doobie-hikari` subproject: -```scala +```scala mdoc:compile-only import cats.effect.Resource import com.avast.sst.doobie.DoobieHikariModule import com.avast.sst.flyway.FlywayModule diff --git a/example/mdoc/http4s.md b/site/docs/subprojects/http4s.md similarity index 89% rename from example/mdoc/http4s.md rename to site/docs/subprojects/http4s.md index e5fa7f7d7..e5599ccbf 100644 --- a/example/mdoc/http4s.md +++ b/site/docs/subprojects/http4s.md @@ -1,10 +1,13 @@ -# Module http4s +--- +layout: docs +title: "http4s" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-http4s-server-blaze_2.12)](https://repo1.maven.org/maven2/com/avast/sst-http4s-server-blaze_2.12/) +# http4s -`libraryDependencies += "com.avast" %% "sst-http4s-server-blaze" % ""` +`libraryDependencies += "com.avast" %% "sst-http4s-server-blaze" % "@VERSION@"` -There are `http4s-*` modules that provide easy initialization of a server and a client. Http4s is an interface with multiple possible +There are multiple `http4s-*` subprojects that provide easy initialization of a server and a client. Http4s is an interface with multiple possible implementations - for now we provide only implementations based on [Blaze](https://github.com/http4s/blaze). Both server and client are configured via configuration `case class` which contains default values taken from the underlying implementations. diff --git a/docs/jvm.md b/site/docs/subprojects/jvm.md similarity index 78% rename from docs/jvm.md rename to site/docs/subprojects/jvm.md index e6dba3906..abbc277b6 100644 --- a/docs/jvm.md +++ b/site/docs/subprojects/jvm.md @@ -1,8 +1,11 @@ -# JVM +--- +layout: docs +title: "JVM" +--- -![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-jvm_2.12) +# JVM -`libraryDependencies += "com.avast" %% "sst-jvm" % ""` +`libraryDependencies += "com.avast" %% "sst-jvm" % "@VERSION@"` Subproject `sst-jvm` provides pure implementations of different JVM-related utilities: @@ -10,7 +13,7 @@ Subproject `sst-jvm` provides pure implementations of different JVM-related util * standard in/out/err, * and random number generation. -```scala +```scala mdoc:silent import com.avast.sst.jvm.system.console.ConsoleModule import com.avast.sst.jvm.system.random.RandomModule import zio.DefaultRuntime @@ -27,7 +30,6 @@ val program = for { val runtime = new DefaultRuntime {} // this is just needed in example ``` -```scala +```scala mdoc runtime.unsafeRun(program) -// Random number: -1517918040 ``` diff --git a/docs/micrometer.md b/site/docs/subprojects/micrometer.md similarity index 88% rename from docs/micrometer.md rename to site/docs/subprojects/micrometer.md index 89345fcd2..80a1fae6c 100644 --- a/docs/micrometer.md +++ b/site/docs/subprojects/micrometer.md @@ -1,14 +1,17 @@ -# Micrometer +--- +layout: docs +title: "Micrometer" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-micrometer-jmx_2.12)](https://repo1.maven.org/maven2/com/avast/sst-micrometer-jmx_2.12/) +# Micrometer -`libraryDependencies += "com.avast" %% "sst-micrometer-jmx" % ""` +`libraryDependencies += "com.avast" %% "sst-micrometer-jmx" % "@VERSION@"` This subproject allows you to monitor your applications using [Micrometer](https://micrometer.io). There are many actual implementations of the Micrometer API one of which is JMX. Subproject `sst-micrometer-jmx` implements the initialization of Micrometer for JMX. There are also interop subprojects such as `sst-http4s-server-micrometer` which implement monitoring of HTTP server and individual routes using Micrometer. -```scala +```scala mdoc:silent import cats.effect.{Clock, Resource} import com.avast.sst.http4s.server._ import com.avast.sst.http4s.server.micrometer.MicrometerHttp4sServerMetricsModule diff --git a/docs/monix-catnap.md b/site/docs/subprojects/monix-catnap.md similarity index 83% rename from docs/monix-catnap.md rename to site/docs/subprojects/monix-catnap.md index 993cc4c3e..3a2588251 100644 --- a/docs/monix-catnap.md +++ b/site/docs/subprojects/monix-catnap.md @@ -1,8 +1,11 @@ -# monix-catnap +--- +layout: docs +title: "monix-catnap" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-monix-catnap_2.12)](https://repo1.maven.org/maven2/com/avast/sst-monix-catnap_2.12/) +# monix-catnap -`libraryDependencies += "com.avast" %% "sst-monix-catnap" % ""` +`libraryDependencies += "com.avast" %% "sst-monix-catnap" % "@VERSION@"` This subproject provides interop between Scala Server Toolkit and [monix-catnap](https://monix.io/docs/3x/#monix-catnap) library. diff --git a/docs/pureconfig.md b/site/docs/subprojects/pureconfig.md similarity index 82% rename from docs/pureconfig.md rename to site/docs/subprojects/pureconfig.md index 703181ea5..7e75ce693 100644 --- a/docs/pureconfig.md +++ b/site/docs/subprojects/pureconfig.md @@ -1,16 +1,19 @@ -# PureConfig +--- +layout: docs +title: "PureConfig" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-pureconfig_2.12)](https://repo1.maven.org/maven2/com/avast/sst-pureconfig_2.12/) +# PureConfig -`libraryDependencies += "com.avast" %% "sst-pureconfig" % ""` +`libraryDependencies += "com.avast" %% "sst-pureconfig" % "@VERSION@"` This subproject allows you to load your application's configuration file into a case class. It uses [PureConfig](https://pureconfig.github.io) library to do so which uses [Lightbend Config](https://github.com/lightbend/config) which means that your application's configuration will be in [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md) format. -Loading of configuration is side-effectful so it is wrapped in `F` which is `Sync`. This subproject also tweaks the error messages a little. +Loading of configuration is side-effectful so it is wrapped in `F` which is `Sync`. This module also tweaks the error messages a little. -```scala +```scala mdoc:silent import com.avast.sst.pureconfig.PureConfigModule import pureconfig.ConfigReader import pureconfig.generic.semiauto.deriveReader @@ -26,7 +29,7 @@ val maybeConfiguration = PureConfigModule.make[Task, ServerConfiguration] Look for `sst-*-pureconfig` subprojects to get `implicit` instances of `ConfigReader` for specific libraries, e.g.: -```scala +```scala mdoc:silent import com.avast.sst.http4s.server.pureconfig.implicits._ ``` diff --git a/docs/ssl-config.md b/site/docs/subprojects/ssl-config.md similarity index 63% rename from docs/ssl-config.md rename to site/docs/subprojects/ssl-config.md index 600461fef..bfd320986 100644 --- a/docs/ssl-config.md +++ b/site/docs/subprojects/ssl-config.md @@ -1,16 +1,19 @@ -# SSL Config +--- +layout: docs +title: "SSL Config" +--- -[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-ssl-config_2.12)](https://repo1.maven.org/maven2/com/avast/sst-ssl-config_2.12/) +# SSL Config -`libraryDependencies += "com.avast" %% "sst-ssl-config" % ""` +`libraryDependencies += "com.avast" %% "sst-ssl-config" % "@VERSION@"` This subproject allows you to create SSL context (`javax.net.ssl.SSLContext`) from a configuration file. It uses [SSL Config](https://github.com/lightbend/ssl-config) -library to do so which means that this subproject is different than others and receives directly `com.typesafe.config.Config` instead of +library to do so which means that this module is different than others and receives directly `com.typesafe.config.Config` instead of configuration case classes. See the [documentation of SSL Config](https://lightbend.github.io/ssl-config) for more information. Loading of SSL context is side-effectful so it is wrapped in `F` which is `Sync`. -```scala +```scala mdoc:silent import com.avast.sst.ssl.SslContextModule import com.typesafe.config.ConfigFactory import zio.interop.catz._ diff --git a/site/menu.yml b/site/menu.yml new file mode 100644 index 000000000..0df2c3ed1 --- /dev/null +++ b/site/menu.yml @@ -0,0 +1,30 @@ +options: + - title: Getting Started + url: getting-started + - title: Rationale + url: rationale + - title: Structure + url: structure + - title: Bundles + url: bundles + - title: Subprojects + url: subprojects + nested_options: + - title: http4s + url: subprojects/http4s + - title: JVM + url: subprojects/jvm + - title: Micrometer + url: subprojects/micrometer + - title: PureConfig + url: subprojects/pureconfig + - title: Datastax Cassandra Driver + url: subprojects/cassandra-datastax-driver + - title: SSL Config + url: subprojects/ssl-config + - title: doobie + url: subprojects/doobie + - title: Flyway + url: subprojects/flyway + - title: monix-catnap - Circuit Breaker + url: subprojects/monix-catnap diff --git a/ssl-config/src/test/resources/application.conf b/ssl-config/src/test/resources/application.conf index eca22b67c..fb509df6c 100644 --- a/ssl-config/src/test/resources/application.conf +++ b/ssl-config/src/test/resources/application.conf @@ -3,7 +3,7 @@ ssl-config { stores = [ { type = "JKS" - path = "ssl-config/src/test/resources/truststore.jks" + path = "src/test/resources/truststore.jks" password = "CanNotMakeJKSWithoutPass" } ]