From 2384da38db164019b410095ecceec68b9de7e8ef Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 14:41:21 +0200 Subject: [PATCH 01/29] Add versioning and release --- .github/workflows/publish.yml | 34 ++++++++++++++++++++++++++++++++++ .scalafmt.conf | 1 + build.sbt | 19 ++++++++++++++++--- project/plugins.sbt | 3 +++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .scalafmt.conf create mode 100644 project/plugins.sbt diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ef6b076 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,34 @@ +name: Publish + +on: + push: + branches: + - master + - main + # for testing the GH Action without merging to main, + # in some cases + - test-publish-snapshots + tags: ["*"] + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + run: |- + sbt publish + env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..834f2d2 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1 @@ +version = 2.7.5 \ No newline at end of file diff --git a/build.sbt b/build.sbt index 8f70910..0134618 100644 --- a/build.sbt +++ b/build.sbt @@ -1,13 +1,26 @@ -name := "workshop" - -version := "0.1" +name := "boring" scalaVersion := "2.13.6" +name := "boring" +scalaVersion := "2.13.3" +version := "0.0.1" +organization := "Prom3th3us" + +// configs for sbt-github-packages plugin +githubOwner := "Prom3th3us" +githubRepository := "boring" +githubTokenSource := TokenSource.GitConfig("github.token") + libraryDependencies ++= List( "com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.15", "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", "io.circe" %% "circe-generic" % "0.14.1" ) +// https://mvnrepository.com/artifact/com.typesafe/config +libraryDependencies += "com.typesafe" % "config" % "1.4.2" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime + +enablePlugins(SemVerPlugin) +gitVersioningSnapshotLowerBound in ThisBuild := "0.0.1" diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..06bec05 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.2") +resolvers += Resolver.bintrayIvyRepo("rallyhealth", "sbt-plugins") +addSbtPlugin("com.rallyhealth.sbt" % "sbt-git-versioning" % "1.6.0") From f954cd91931ebf9d83cbfb4b684d4888502a05dc Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 18:09:01 +0200 Subject: [PATCH 02/29] CI-CD. Normal commits will bump the version as patches, and minor and major versions will be triggered not by commits but manually on Github. --- .github/workflows/major.yml | 33 ++++++++++++ .github/workflows/minor.yml | 33 ++++++++++++ .github/workflows/{publish.yml => patch.yml} | 18 +++---- build.sbt | 14 +++-- project/VersionBump.scala | 57 ++++++++++++++++++++ project/plugins.sbt | 1 - version.sbt | 1 + versionBump.sbt | 16 ++++++ 8 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/major.yml create mode 100644 .github/workflows/minor.yml rename .github/workflows/{publish.yml => patch.yml} (59%) create mode 100644 project/VersionBump.scala create mode 100644 version.sbt create mode 100644 versionBump.sbt diff --git a/.github/workflows/major.yml b/.github/workflows/major.yml new file mode 100644 index 0000000..e9769a1 --- /dev/null +++ b/.github/workflows/major.yml @@ -0,0 +1,33 @@ +# This is a basic workflow to help you get started with Actions + +name: release/major + +on: + workflow_dispatch: + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' + run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump major" + git add version.sbt + git commit -m "Bump version" + git push origin main + sbt publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} diff --git a/.github/workflows/minor.yml b/.github/workflows/minor.yml new file mode 100644 index 0000000..8df7ce6 --- /dev/null +++ b/.github/workflows/minor.yml @@ -0,0 +1,33 @@ +# This is a basic workflow to help you get started with Actions + +name: release/minor + +on: + workflow_dispatch: + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' + run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump minor" + git add version.sbt + git commit -m "Bump version" + git push origin main + sbt publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/patch.yml similarity index 59% rename from .github/workflows/publish.yml rename to .github/workflows/patch.yml index ef6b076..097e26b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/patch.yml @@ -1,13 +1,9 @@ -name: Publish +name: release/patch on: push: branches: - - master - main - # for testing the GH Action without merging to main, - # in some cases - - test-publish-snapshots tags: ["*"] jobs: @@ -25,10 +21,14 @@ jobs: with: java-version: adopt@1.11.0-9 - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump patch" + git add version.sbt + git commit -m "Bump version" + git push origin main sbt publish env: - PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} - PGP_SECRET: ${{ secrets.PGP_SECRET }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} \ No newline at end of file + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} \ No newline at end of file diff --git a/build.sbt b/build.sbt index 0134618..52b472b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,23 @@ +import sbtghpackages.TokenSource.{Environment, GitConfig} + +import scala.language.postfixOps +import scala.util.Try + name := "boring" scalaVersion := "2.13.6" name := "boring" scalaVersion := "2.13.3" -version := "0.0.1" organization := "Prom3th3us" // configs for sbt-github-packages plugin githubOwner := "Prom3th3us" githubRepository := "boring" -githubTokenSource := TokenSource.GitConfig("github.token") +githubTokenSource := TokenSource.Or( + GitConfig("github.token"), + Environment("PUBLISH_TOKEN") +) libraryDependencies ++= List( "com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.15", @@ -21,6 +28,3 @@ libraryDependencies ++= List( libraryDependencies += "com.typesafe" % "config" % "1.4.2" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime - -enablePlugins(SemVerPlugin) -gitVersioningSnapshotLowerBound in ThisBuild := "0.0.1" diff --git a/project/VersionBump.scala b/project/VersionBump.scala new file mode 100644 index 0000000..12ec08e --- /dev/null +++ b/project/VersionBump.scala @@ -0,0 +1,57 @@ +import sbt.Keys.version + +import scala.util.Try + +class VersionBump(currentVersion: String) { + + def apply(arg: Option[String]): Option[Unit] = + for { + argument <- arg // + done <- argument match { + case "patch" => + Versioning.increasePatchVersion + case "minor" => + Versioning.increaseMinorVersion + case "major" => + Versioning.increaseMajorVersion + case _ => + None + } + } yield done + + case class Version(major: Int, minor: Int, patch: Int) { + override def toString = s"""version := "$major.$minor.$patch"""" + def majorRelease = copy(major = major + 1, minor = 0, patch = 0) + def minorRelease = copy(minor = minor + 1, patch = 0) + def patchRelease = copy(patch = patch + 1) + } + object Version { + def apply(version: String): Option[Version] = { + version.split('.').toList match { + case major :: minor :: patch :: ignored => + for { + major <- Try(major.toInt).toOption + minor <- Try(minor.toInt).toOption + patch <- Try(patch.toInt).toOption + } yield Version(major, minor, patch) + case _ => None + } + } + } + + object Versioning { + import java.io.PrintWriter + private def write(filename: String, text: String) = + new PrintWriter(filename) { write(text); close() } + + private def perform(upgrade: Version => Version): Option[Unit] = + Version(currentVersion).map { version => + write("version.sbt", s"${upgrade(version)}") + } + + def increaseMajorVersion: Option[Unit] = perform(_.majorRelease) + def increaseMinorVersion: Option[Unit] = perform(_.minorRelease) + def increasePatchVersion: Option[Unit] = perform(_.patchRelease) + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 06bec05..cf2c920 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,2 @@ addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.2") resolvers += Resolver.bintrayIvyRepo("rallyhealth", "sbt-plugins") -addSbtPlugin("com.rallyhealth.sbt" % "sbt-git-versioning" % "1.6.0") diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..b117764 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version := "1.0.0" diff --git a/versionBump.sbt b/versionBump.sbt new file mode 100644 index 0000000..daad65b --- /dev/null +++ b/versionBump.sbt @@ -0,0 +1,16 @@ +import sbt._ +import sbt.inputKey +import complete.DefaultParsers._ + +val versionBump = inputKey[Unit](""" + |ie.: sbt "versionBump major" + |ie.: sbt "versionBump minor" + |ie.: sbt "versionBump patch" + |""".stripMargin) +versionBump := { + new VersionBump( + currentVersion = version.value + ).apply( + arg = spaceDelimited("").parsed.headOption + ) +} From b54faa4e0d6882e9e31bd4110ce634312191c1e4 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 16:10:26 +0000 Subject: [PATCH 03/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index b117764..aca15cf 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.0" +version := "1.0.1" \ No newline at end of file From f8a78142a44adbf3f84ca843d5e9cd383c13eee4 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 18:59:48 +0200 Subject: [PATCH 04/29] Add PubSub --- .../arch/infra/router/simple/PubSub.scala | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/scala/arch/infra/router/simple/PubSub.scala diff --git a/src/main/scala/arch/infra/router/simple/PubSub.scala b/src/main/scala/arch/infra/router/simple/PubSub.scala new file mode 100644 index 0000000..0c77dc6 --- /dev/null +++ b/src/main/scala/arch/infra/router/simple/PubSub.scala @@ -0,0 +1,55 @@ +package arch.infra.router.simple + +import scala.collection.mutable + +trait PubSub[Topic, Message] { + type Callback = Message => Unit + def publish: (Topic, Message) => Unit + def subscribe: Topic => Callback => Unit +} + +object PubSub { + + class PubSubMock[Topic, Message] extends PubSub[Topic, Message] { + + val callbacksPerTopic = + mutable.HashMap.empty[Topic, mutable.Set[Callback]] + val messageQueuePerTopic = + mutable.HashMap.empty[Topic, mutable.ListBuffer[Message]] + + override def publish = { (topic, message) => + messageQueuePerTopic.get(topic) match { + case Some(queue) => queue.addOne(message) + case None => + messageQueuePerTopic.addOne( + topic, + mutable.ListBuffer.empty[Message].addOne(message) + ) + } + + callbacksPerTopic.get(topic).foreach { callbacks => + callbacks.foreach { callback => + callback(message) + } + } + } + + override def subscribe = { topic => callback => + callbacksPerTopic.get(topic) match { + case Some(callbacks) => callbacks.addOne(callback) + case None => + callbacksPerTopic.addOne( + topic, + mutable.Set.empty[Callback].addOne(callback) + ) + } + + messageQueuePerTopic.get(topic).foreach { messages => + messages.foreach { message => + callback(message) + } + } + } + } + +} From 6afd634213f75beba103b9f95425f5495f38d263 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 19:00:00 +0200 Subject: [PATCH 05/29] Add Router --- .../arch/infra/router/simple/Router.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/scala/arch/infra/router/simple/Router.scala diff --git a/src/main/scala/arch/infra/router/simple/Router.scala b/src/main/scala/arch/infra/router/simple/Router.scala new file mode 100644 index 0000000..8227a7f --- /dev/null +++ b/src/main/scala/arch/infra/router/simple/Router.scala @@ -0,0 +1,24 @@ +package arch.infra.router.simple + +trait Router[In, Out] { + def route: In => Out +} + +object Router { + class RouterMock[In, Out](handler: In => Out) extends Router[In, Out] { + override def route = { in => + handler(in) + } + } + + object Example { + case class RequestJson(name: String) + case class ResponseJson(hello: String) + + val example = new RouterMock[RequestJson, ResponseJson](handler = { in => + ResponseJson(s"hello, ${in.name}") + }) + + example.route(RequestJson("name")) + } +} From 4ca5c214abeab1e2c12bdd75d95a6e0b54960ce3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 17:01:08 +0000 Subject: [PATCH 06/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index aca15cf..21bdce0 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.1" \ No newline at end of file +version := "1.0.2" \ No newline at end of file From 6d72fa90ea635fe15c0633a688720b6eac428a29 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 19:35:06 +0200 Subject: [PATCH 07/29] Only abstracted out concepts --- build.sbt | 18 +------ src/main/scala/arch/app/Runner.scala | 43 --------------- src/main/scala/arch/common/Context.scala | 3 ++ src/main/scala/arch/common/Program.scala | 46 ++++++---------- .../scala/arch/common/ProgramBuilder.scala | 52 ------------------ src/main/scala/arch/common/ProgramError.scala | 35 ++++++++++++ src/main/scala/arch/common/ProgramLive.scala | 12 ----- src/main/scala/arch/common/package.scala | 13 +++++ src/main/scala/arch/domain/Model.scala | 9 ---- src/main/scala/arch/domain/Repo.scala | 7 --- src/main/scala/arch/domain/Repository.scala | 6 +++ .../arch/domain/modules/user/UserConfig.scala | 23 -------- .../arch/domain/modules/user/UserRepoF.scala | 28 ---------- .../domain/modules/user/UserRepoLive.scala | 9 ---- .../domain/modules/user/model/UserModel.scala | 18 ------- .../modules/user/service/UserAction.scala | 28 ---------- .../modules/user/service/UserService.scala | 17 ------ .../modules/user/service/UserServiceF.scala | 42 --------------- .../user/service/UserServiceLive.scala | 9 ---- .../scala/arch/infra/config/ConfigF.scala | 26 --------- .../scala/arch/infra/json/JsonLibraryF.scala | 37 ------------- .../arch/infra/json/JsonLibraryLive.scala | 8 --- .../scala/arch/infra/logging/LoggingF.scala | 21 -------- .../arch/infra/logging/LoggingLibrary.scala | 2 +- .../arch/infra/logging/LoggingLive.scala | 8 --- .../arch/infra/monitoring/MonitoringF.scala | 38 ------------- .../infra/monitoring/MonitoringLibrary.scala | 9 ++-- .../infra/monitoring/MonitoringLive.scala | 8 --- src/main/scala/arch/infra/router/Action.scala | 5 -- .../arch/infra/router/ActionHandler.scala | 5 -- .../infra/router/{simple => }/PubSub.scala | 2 +- src/main/scala/arch/infra/router/Router.scala | 24 +++++++-- .../scala/arch/infra/router/RouterF.scala | 54 ------------------- .../scala/arch/infra/router/RouterLive.scala | 9 ---- .../arch/infra/router/simple/Router.scala | 24 --------- 35 files changed, 103 insertions(+), 595 deletions(-) delete mode 100644 src/main/scala/arch/app/Runner.scala create mode 100644 src/main/scala/arch/common/Context.scala delete mode 100644 src/main/scala/arch/common/ProgramBuilder.scala create mode 100644 src/main/scala/arch/common/ProgramError.scala delete mode 100644 src/main/scala/arch/common/ProgramLive.scala create mode 100644 src/main/scala/arch/common/package.scala delete mode 100644 src/main/scala/arch/domain/Model.scala delete mode 100644 src/main/scala/arch/domain/Repo.scala create mode 100644 src/main/scala/arch/domain/Repository.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserConfig.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserRepoF.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserRepoLive.scala delete mode 100644 src/main/scala/arch/domain/modules/user/model/UserModel.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserAction.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserService.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceF.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala delete mode 100644 src/main/scala/arch/infra/config/ConfigF.scala delete mode 100644 src/main/scala/arch/infra/json/JsonLibraryF.scala delete mode 100644 src/main/scala/arch/infra/json/JsonLibraryLive.scala delete mode 100644 src/main/scala/arch/infra/logging/LoggingF.scala delete mode 100644 src/main/scala/arch/infra/logging/LoggingLive.scala delete mode 100644 src/main/scala/arch/infra/monitoring/MonitoringF.scala delete mode 100644 src/main/scala/arch/infra/monitoring/MonitoringLive.scala delete mode 100644 src/main/scala/arch/infra/router/Action.scala delete mode 100644 src/main/scala/arch/infra/router/ActionHandler.scala rename src/main/scala/arch/infra/router/{simple => }/PubSub.scala (97%) delete mode 100644 src/main/scala/arch/infra/router/RouterF.scala delete mode 100644 src/main/scala/arch/infra/router/RouterLive.scala delete mode 100644 src/main/scala/arch/infra/router/simple/Router.scala diff --git a/build.sbt b/build.sbt index 52b472b..a13aabd 100644 --- a/build.sbt +++ b/build.sbt @@ -1,17 +1,9 @@ import sbtghpackages.TokenSource.{Environment, GitConfig} -import scala.language.postfixOps -import scala.util.Try - -name := "boring" - -scalaVersion := "2.13.6" - name := "boring" scalaVersion := "2.13.3" organization := "Prom3th3us" -// configs for sbt-github-packages plugin githubOwner := "Prom3th3us" githubRepository := "boring" githubTokenSource := TokenSource.Or( @@ -19,12 +11,4 @@ githubTokenSource := TokenSource.Or( Environment("PUBLISH_TOKEN") ) -libraryDependencies ++= List( - "com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.15", - "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", - "io.circe" %% "circe-generic" % "0.14.1" -) -// https://mvnrepository.com/artifact/com.typesafe/config -libraryDependencies += "com.typesafe" % "config" % "1.4.2" - -libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime +libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" diff --git a/src/main/scala/arch/app/Runner.scala b/src/main/scala/arch/app/Runner.scala deleted file mode 100644 index dfbeb93..0000000 --- a/src/main/scala/arch/app/Runner.scala +++ /dev/null @@ -1,43 +0,0 @@ -package arch.app - -import arch.common.ProgramLive -import arch.domain.modules.user.service.UserAction -import com.typesafe.config.{Config, ConfigFactory} - -object Runner { - import arch.common.ProgramBuilder._ - import scala.jdk.CollectionConverters._ - - val prod = true - val config: Config = ConfigFactory.parseMap(Map( - "user.flag" -> true, - "user.value" -> 1 - ).asJava) - - def main(args: Array[String]): Unit = { - val result = if(prod) { - println(s"RUNNING ENV=prod") - // DEFINITIONS - import scala.concurrent.Await - import scala.concurrent.duration._ - type Env[A] = ProgramLive.App[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - val scheduler = monix.execution.Scheduler.Implicits.global - Await.result(actionResult.value.runToFuture(scheduler), 1.second) - } else { - println(s"RUNNING ENV=test") - // DEFINITIONS - type Env[A] = ProgramLive.Test[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - actionResult - } - - println(result) - } -} diff --git a/src/main/scala/arch/common/Context.scala b/src/main/scala/arch/common/Context.scala new file mode 100644 index 0000000..c7bd6e5 --- /dev/null +++ b/src/main/scala/arch/common/Context.scala @@ -0,0 +1,3 @@ +package arch.common + +case class Context(name: String, metadata: Map[String, Any] = Map.empty) diff --git a/src/main/scala/arch/common/Program.scala b/src/main/scala/arch/common/Program.scala index b3b378f..c3a24fe 100644 --- a/src/main/scala/arch/common/Program.scala +++ b/src/main/scala/arch/common/Program.scala @@ -1,48 +1,36 @@ package arch.common -import cats.MonadError - import scala.util.{Failure, Success, Try} object Program { - val unknownErrorCode = 0 - - type MError[F[_]] = MonadError[F, ProgramError] - case class Context(name: String, metadata: Map[String, Any] = Map.empty) - case class ProgramError(error: Any, msg: String, ctx: Context, errorCode: Int = unknownErrorCode, stackTrace: Seq[String] = Seq.empty) - - object ProgramError { - def fromThrowable(exception: Throwable): Context => Int => ProgramError = ctx => errorCode => { - val stackTrace = Seq.empty // exception.getStackTrace.map(_.toString).take(10) - ProgramError(exception, "error message: " + exception.getMessage, ctx, errorCode, stackTrace) - } - def fromError[E](e: E): Context => Int => ProgramError = ctx => errorCode => { - ProgramError(e, "error message: " + e.toString, ctx, errorCode, stackTrace = Seq.empty) - } - } - - object MError { - def apply[F[_]](implicit m: MError[F]): MError[F] = m - } - object App { - def fromEither[F[_]: MError, E, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = + def fromEither[F[_]: MError, E, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = MError[F].fromEither[A]( a match { - case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) + case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) case Right(value) => Right(value) } ) - def fromTry[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = + def fromTry[F[_]: MError, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = MError[F].fromEither[A]( a match { - case Failure(exception) => Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) + case Failure(exception) => + Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) case Success(value) => Right(value) } ) - def lift[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) - def apply[F[_]: MError, A](ctx: Context, errorCode: => Int = unknownErrorCode)(a: => A): F[A] = lift(ctx)(errorCode)(a) + def lift[F[_]: MError, A](ctx: Context)( + errorCode: => Int = unknownErrorCode + )(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) + def apply[F[_]: MError, A]( + ctx: Context, + errorCode: => Int = unknownErrorCode + )(a: => A): F[A] = lift(ctx)(errorCode)(a) def unit[F[_]: MError](ctx: Context): F[Unit] = apply(ctx)() } -} \ No newline at end of file +} diff --git a/src/main/scala/arch/common/ProgramBuilder.scala b/src/main/scala/arch/common/ProgramBuilder.scala deleted file mode 100644 index 1784308..0000000 --- a/src/main/scala/arch/common/ProgramBuilder.scala +++ /dev/null @@ -1,52 +0,0 @@ -package arch.common - -import arch.common.ProgramLive.{App, Test} -import arch.domain.modules.user.UserConfig.{UserConfigApp, UserConfigF, UserConfigTest} -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.UserRepoLive.{UserRepoApp, UserRepoTest} -import arch.domain.modules.user.service.UserAction.UserActionHandler -import arch.domain.modules.user.service.UserServiceLive.{UserServiceApp, UserServiceTest} -import arch.domain.modules.user.service.{UserAction, UserServiceF} -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.{JsonLibraryApp, JsonLibraryTest} -import arch.infra.logging.LoggingLibrary -import arch.infra.logging.LoggingLive.{LoggingApp, LoggingTest} -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.monitoring.MonitoringLive.{MonitoringApp, MonitoringTest} -import arch.infra.router.RouterF -import arch.infra.router.RouterLive.{RouterApp, RouterTest} -import com.typesafe.config.Config - -object ProgramBuilder { - trait ProgramBuilder[F[_]] { - def buildApp(config: Config): RouterF[F] - } - - implicit lazy val production: ProgramBuilder[App] = (config: Config) => { - import ProgramLive.App - implicit lazy val userRepoLive: UserRepoF[App] = UserRepoApp - implicit lazy val jsonLibraryLive: JsonLibraryF[App] = JsonLibraryApp - implicit lazy val monitoring: MonitoringLibrary[App] = MonitoringApp - implicit lazy val userConfigF: UserConfigF[App] = UserConfigApp - implicit lazy val runService: UserServiceF[App] = new UserServiceApp(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[App] = LoggingApp - implicit lazy val router: RouterF[App] = new RouterApp() - .subscribe[UserAction](new UserActionHandler[App]()) - .subscribe[UserAction](new UserActionHandler[App]()) // this is only added for debugging purposes - router - } - - implicit lazy val testing: ProgramBuilder[Test] = (config: Config) => { - import ProgramLive.Test - implicit lazy val userRepoLive: UserRepoF[Test] = UserRepoTest - implicit lazy val jsonLibraryLive: JsonLibraryF[Test] = JsonLibraryTest - implicit lazy val monitoring: MonitoringLibrary[Test] = MonitoringTest - implicit lazy val userConfigF: UserConfigF[Test] = UserConfigTest - implicit lazy val runService: UserServiceF[Test] = new UserServiceTest(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[Test] = LoggingTest - implicit lazy val router: RouterF[Test] = new RouterTest() - .subscribe[UserAction](new UserActionHandler[Test]()) - .subscribe[UserAction](new UserActionHandler[Test]()) // this is only added for debugging purposes - router - } -} diff --git a/src/main/scala/arch/common/ProgramError.scala b/src/main/scala/arch/common/ProgramError.scala new file mode 100644 index 0000000..4cf26fe --- /dev/null +++ b/src/main/scala/arch/common/ProgramError.scala @@ -0,0 +1,35 @@ +package arch.common + +case class ProgramError( + error: Any, + msg: String, + ctx: Context, + errorCode: Int = unknownErrorCode, + stackTrace: Seq[String] = Seq.empty +) + +object ProgramError { + def fromThrowable(exception: Throwable): Context => Int => ProgramError = + ctx => + errorCode => { + val stackTrace = + Seq.empty // exception.getStackTrace.map(_.toString).take(10) + ProgramError( + exception, + "error message: " + exception.getMessage, + ctx, + errorCode, + stackTrace + ) + } + def fromError[E](e: E): Context => Int => ProgramError = ctx => + errorCode => { + ProgramError( + e, + "error message: " + e.toString, + ctx, + errorCode, + stackTrace = Seq.empty + ) + } +} diff --git a/src/main/scala/arch/common/ProgramLive.scala b/src/main/scala/arch/common/ProgramLive.scala deleted file mode 100644 index 2e3db8a..0000000 --- a/src/main/scala/arch/common/ProgramLive.scala +++ /dev/null @@ -1,12 +0,0 @@ -package arch.common - -import arch.common.Program.ProgramError -import cats.data.EitherT -import monix.eval.Task - -object ProgramLive { - - // each type should be a subtype of MonadError - type App[A] = EitherT[Task, ProgramError, A] - type Test[A] = Either[ProgramError, A] -} diff --git a/src/main/scala/arch/common/package.scala b/src/main/scala/arch/common/package.scala new file mode 100644 index 0000000..9518808 --- /dev/null +++ b/src/main/scala/arch/common/package.scala @@ -0,0 +1,13 @@ +package arch + +import cats.MonadError + +package object common { + val unknownErrorCode = -1 + + type MError[F[_]] = MonadError[F, ProgramError] + object MError { + def apply[F[_]](implicit m: MError[F]): MError[F] = m + } + +} diff --git a/src/main/scala/arch/domain/Model.scala b/src/main/scala/arch/domain/Model.scala deleted file mode 100644 index 99f8507..0000000 --- a/src/main/scala/arch/domain/Model.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain - -trait Model { - type Entity - type Id - trait Identifiable[E <: Entity] { - def id(a: Entity): Id - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/Repo.scala b/src/main/scala/arch/domain/Repo.scala deleted file mode 100644 index ed1ec28..0000000 --- a/src/main/scala/arch/domain/Repo.scala +++ /dev/null @@ -1,7 +0,0 @@ -package arch.domain - -trait Repo[F[_]] { - type M <: Model - def set(a: M#Entity)(implicit id: M#Identifiable[M#Entity]): F[Unit] - def get(id: M#Id): F[Option[M#Entity]] -} diff --git a/src/main/scala/arch/domain/Repository.scala b/src/main/scala/arch/domain/Repository.scala new file mode 100644 index 0000000..f4949e9 --- /dev/null +++ b/src/main/scala/arch/domain/Repository.scala @@ -0,0 +1,6 @@ +package arch.domain + +trait Repository[Key, Value] { + def set(key: Key, value: Value): Unit + def get(key: Key): Value +} diff --git a/src/main/scala/arch/domain/modules/user/UserConfig.scala b/src/main/scala/arch/domain/modules/user/UserConfig.scala deleted file mode 100644 index dfdfb1f..0000000 --- a/src/main/scala/arch/domain/modules/user/UserConfig.scala +++ /dev/null @@ -1,23 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program.MError -import arch.common.ProgramLive.{App, Test} -import arch.infra.config.ConfigF -import cats.implicits._ -import com.typesafe.config.Config - -case class UserConfig(flag: Boolean, value: Option[Int]) - -object UserConfig { - class UserConfigF[F[_]: MError] extends ConfigF[UserConfig, F] { - override def fromConfig(config: Config): F[UserConfig] = { - for { - flag <- ConfigF.parse[Boolean, F](config)(_.getBoolean("flag")) - value <- ConfigF.parseOpt[Int, F](config)(_.getInt("value")) - } yield UserConfig(flag, value) - } - } - - object UserConfigApp extends UserConfigF[App] - object UserConfigTest extends UserConfigF[Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/UserRepoF.scala b/src/main/scala/arch/domain/modules/user/UserRepoF.scala deleted file mode 100644 index aafa954..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoF.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program -import arch.common.Program.{Context, MError} -import arch.domain.Repo -import arch.domain.modules.user.model.UserModel -import arch.domain.modules.user.model.UserModel.User - -// IMPL class on top Map -class UserRepoF[F[_]: MError] extends Repo[F] { - - type M = UserModel.type - - private val module = Context("user") - private val userRepoErrorCode = 1 - - private var users: Map[M#Id, User] = Map.empty[M#Id, User] - - override def set(a: User)(implicit id: M#Identifiable[User]): F[Unit] = - Program.App[F, Unit](module, userRepoErrorCode) { - users = users.updated(id.id(a), a) - } - - override def get(id: M#Id): F[Option[User]] = - Program.App[F, Option[User]](module, userRepoErrorCode) { - users.get(id) - } -} diff --git a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala b/src/main/scala/arch/domain/modules/user/UserRepoLive.scala deleted file mode 100644 index 2d8e736..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user - -import arch.common.ProgramLive.{App, Test} - -object UserRepoLive { - object UserRepoApp extends UserRepoF[App] - object UserRepoTest extends UserRepoF[Test] -} - diff --git a/src/main/scala/arch/domain/modules/user/model/UserModel.scala b/src/main/scala/arch/domain/modules/user/model/UserModel.scala deleted file mode 100644 index d2f8f75..0000000 --- a/src/main/scala/arch/domain/modules/user/model/UserModel.scala +++ /dev/null @@ -1,18 +0,0 @@ -package arch.domain.modules.user.model - -import arch.domain.Model -import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} -import io.circe.{Decoder, Encoder} - -object UserModel extends Model { - type Id = String - type Entity = User - - case class User(id: Id) - - object User { - implicit val userDecoder: Decoder[User] = deriveDecoder[User] - implicit val userEncoder: Encoder[User] = deriveEncoder[User] - implicit val identifier: Identifiable[User] = (user: User) => user.id - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserAction.scala b/src/main/scala/arch/domain/modules/user/service/UserAction.scala deleted file mode 100644 index c685930..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserAction.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.MError -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.{Action, ActionHandler} -import io.circe.Json - -case class UserAction(id: Int) extends Action { - type ReturnType = (Option[String], Option[JsonLibraryTest.JsonType], User) -} - -object UserAction { - class UserActionHandler[F[_]: MError]( - implicit - service: UserServiceF[F], - userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ) extends ActionHandler[F, UserAction] { - override def handle(a: UserAction): F[(Option[String], Option[Json], User)] = service.run(a) - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserService.scala b/src/main/scala/arch/domain/modules/user/service/UserService.scala deleted file mode 100644 index b856fdb..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserService.scala +++ /dev/null @@ -1,17 +0,0 @@ -package arch.domain.modules.user.service - -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.infra.json.JsonLibraryF -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.Action - -trait UserService[F[_], A <: Action] { - def run(args: A)( - // @TODO revisit if this should go into constructor - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[A#ReturnType] -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala deleted file mode 100644 index 9560ee9..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala +++ /dev/null @@ -1,42 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.{Context, MError, ProgramError} -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import cats.implicits._ -import com.typesafe.config.Config - -class UserServiceF[F[_]: MError](c: Config) extends UserService[F, UserAction] { - def run(args: UserAction)( - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[(Option[String], Option[JsonLibraryTest.JsonType], User)] = { - val ctx = Context("run") - - val user = User("Franco") - val userJsonFromTo = jsonLibrary.jsonFromTo[User] - val counter = monitoring.counter("counter") - for { - config <- userConfig.fromConfig(c) - _ <- counter.increment() - _ = println(s"user config = $config") - _ <- userRepo.set(user) - u1 <- userRepo.get(user.id) - u2 <- userRepo.get("Euge") - js1 = u1.map(userJsonFromTo.from).map(jsonLibrary.prettyPrint) - parsedJson <- { - js1.map(jsonLibrary.parse) match { - case Some(maybeParsed) => maybeParsed.flatMap(userJsonFromTo.to) - case None => MError[F].raiseError(ProgramError((), "js1 missing", ctx)) - } - } - js2 = u2.map(userJsonFromTo.from) - } yield (js1, js2, parsedJson) - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala deleted file mode 100644 index 82d023d..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.ProgramLive.{App, Test} -import com.typesafe.config.Config - -object UserServiceLive { - class UserServiceApp(c: Config) extends UserServiceF[App](c) - class UserServiceTest(c: Config) extends UserServiceF[Test](c) -} diff --git a/src/main/scala/arch/infra/config/ConfigF.scala b/src/main/scala/arch/infra/config/ConfigF.scala deleted file mode 100644 index 48b4ce8..0000000 --- a/src/main/scala/arch/infra/config/ConfigF.scala +++ /dev/null @@ -1,26 +0,0 @@ -package arch.infra.config - -import arch.common.Program.{Context, MError, ProgramError} -import com.typesafe.config.Config - -import scala.util.{Failure, Success, Try} - -trait ConfigF[A, F[_]] { - def fromConfig(config: Config): F[A] -} - -object ConfigF { - val ctx: Context = Context("config_parser") - - def parse[A, F[_]: MError](cfg: Config)(fn: Config => A): F[A] = { - val either = Try(fn(cfg)) match { - case Failure(exception) => Left(ProgramError(exception, exception.getMessage, ctx)) - case Success(value) => Right(value) - } - MError[F].fromEither(either) - } - - def parseOpt[A, F[_]: MError](cfg: Config)(fn: Config => A): F[Option[A]] = { - MError[F].pure(Try(fn(cfg)).toOption) - } -} diff --git a/src/main/scala/arch/infra/json/JsonLibraryF.scala b/src/main/scala/arch/infra/json/JsonLibraryF.scala deleted file mode 100644 index 8b04e4f..0000000 --- a/src/main/scala/arch/infra/json/JsonLibraryF.scala +++ /dev/null @@ -1,37 +0,0 @@ -package arch.infra.json - -import arch.common.Program -import arch.common.Program.{Context, MError} -import io.circe.{Decoder, Encoder, Json, Printer, parser} - -// IMPL class on top of circe -class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { - type JsonType = Json - - val ctx: Context = Context("json") - val jsonDecodeErrorCode = 1 - val jsonParseErrorCode = 2 - - class To[A](implicit d: Decoder[A]) extends JsonTo[A] { - override def to(json: JsonType): F[A] = - Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)(d.decodeJson(json)) - } - class From[A](implicit e: Encoder[A]) extends JsonFrom[A] { - override def from(from: A): JsonType = e.apply(from) - } - class JsonFromToF[A](t: To[A], f: From[A]) extends JsonFromTo[A] { - override def to(json: JsonType): F[A] = t.to(json) - override def from(from: A): JsonType = f.from(from) - } - object PrettyPrinter extends JsonPrinter { - override def prettyPrint(json: Json): String = Printer.spaces2.print(json) - } - object JsonParserF extends JsonParser { - override def parse(str: String): F[Json] = - Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)(parser.parse(str)) - } - - def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = new JsonFromToF[A](new To(), new From()) - def prettyPrint(json: Json): String = PrettyPrinter.prettyPrint(json) - def parse(json: String): F[Json] = JsonParserF.parse(json) -} diff --git a/src/main/scala/arch/infra/json/JsonLibraryLive.scala b/src/main/scala/arch/infra/json/JsonLibraryLive.scala deleted file mode 100644 index eb13eec..0000000 --- a/src/main/scala/arch/infra/json/JsonLibraryLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.json - -import arch.common.ProgramLive.{App, Test} - -object JsonLibraryLive { - object JsonLibraryApp extends JsonLibraryF[App] - object JsonLibraryTest extends JsonLibraryF[Test] -} diff --git a/src/main/scala/arch/infra/logging/LoggingF.scala b/src/main/scala/arch/infra/logging/LoggingF.scala deleted file mode 100644 index 425dc9d..0000000 --- a/src/main/scala/arch/infra/logging/LoggingF.scala +++ /dev/null @@ -1,21 +0,0 @@ -package arch.infra.logging - -import arch.common.Program -import arch.common.Program.MError -import org.slf4j.LoggerFactory - -// TODO fix why ctx is not being logged -case class LoggingF[F[_]: MError]() extends LoggingLibrary[F] { - private val logger = LoggerFactory.getLogger("main-logger") - override def logDebug(msg: String): Program.Context => F[Unit] = - ctx => MError[F].pure(logger.debug(msg, ctx)) - - override def logError(msg: String): Program.Context => F[Unit] = - ctx => MError[F].pure(logger.error(msg, ctx)) - - override def logInfo(msg: String): Program.Context => F[Unit] = - ctx => MError[F].pure(logger.info(msg, ctx)) - - override def logWarn(msg: String): Program.Context => F[Unit] = - ctx => MError[F].pure(logger.warn(msg, ctx)) -} diff --git a/src/main/scala/arch/infra/logging/LoggingLibrary.scala b/src/main/scala/arch/infra/logging/LoggingLibrary.scala index 0a7f707..8f2258a 100644 --- a/src/main/scala/arch/infra/logging/LoggingLibrary.scala +++ b/src/main/scala/arch/infra/logging/LoggingLibrary.scala @@ -1,6 +1,6 @@ package arch.infra.logging -import arch.common.Program.Context +import arch.common.Context trait LoggingLibrary[F[_]] { def logDebug(msg: String): Context => F[Unit] diff --git a/src/main/scala/arch/infra/logging/LoggingLive.scala b/src/main/scala/arch/infra/logging/LoggingLive.scala deleted file mode 100644 index 9271d48..0000000 --- a/src/main/scala/arch/infra/logging/LoggingLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.logging - -import arch.common.ProgramLive.{App, Test} - -object LoggingLive { - object LoggingApp extends LoggingF[App] - object LoggingTest extends LoggingF[Test] -} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringF.scala b/src/main/scala/arch/infra/monitoring/MonitoringF.scala deleted file mode 100644 index 86f9976..0000000 --- a/src/main/scala/arch/infra/monitoring/MonitoringF.scala +++ /dev/null @@ -1,38 +0,0 @@ -package arch.infra.monitoring - -import arch.common.Program.MError -import arch.infra.monitoring.MonitoringLibrary.{CounterF, GaugeF, HistogramF} -import cats.implicits._ - -// @TODO implement on top of some dependency (ie: grafana, kamon) -class MonitoringF[F[_]: MError] extends MonitoringLibrary[F] { - override def counter(name: String, context: Map[String, String]): CounterF[F] = new CounterF[F] { - override def increment(): F[Unit] = - MError[F].pure(println(s"counter increment")) - - override def add(num: Int): F[Unit] = - MError[F].pure(println(s"counter add $num")) - } - - override def gauge(name: String): GaugeF[F] = new GaugeF[F] { - override def increment(): F[Unit] = - MError[F].pure(println(s"gauge increment")) - - override def decrement(): F[Unit] = - MError[F].pure(println(s"gauge decrement")) - - override def add(num: Int): F[Unit] = - MError[F].pure(println(s"gauge add $num")) - - override def subtract(num: Int): F[Unit] = - MError[F].pure(println(s"gauge subtract $num")) - - override def set(num: Int): F[Unit] = - MError[F].pure(println(s"gauge set $num")) - } - - override def histogram(name: String): HistogramF[F] = new HistogramF[F] { - override def record(value: Long): F[Unit] = - MError[F].pure(println(s"histogram record $value")) - } -} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala index 3c72685..95ceb1a 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala @@ -1,11 +1,14 @@ package arch.infra.monitoring -import arch.common.Program.MError +import arch.common.MError import cats.implicits._ trait MonitoringLibrary[F[_]] { import MonitoringLibrary._ - def counter(name: String, context: Map[String, String] = Map.empty): CounterF[F] + def counter( + name: String, + context: Map[String, String] = Map.empty + ): CounterF[F] def gauge(name: String): GaugeF[F] def histogram(name: String): HistogramF[F] } @@ -42,7 +45,7 @@ object MonitoringLibrary { for { result <- codeToBenchmark _ = record(System.currentTimeMillis() - before) - } yield result + } yield result } } } diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala b/src/main/scala/arch/infra/monitoring/MonitoringLive.scala deleted file mode 100644 index 26255a2..0000000 --- a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.monitoring - -import arch.common.ProgramLive - -object MonitoringLive { - object MonitoringApp extends MonitoringF[ProgramLive.App] - object MonitoringTest extends MonitoringF[ProgramLive.Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala deleted file mode 100644 index 6a1ad58..0000000 --- a/src/main/scala/arch/infra/router/Action.scala +++ /dev/null @@ -1,5 +0,0 @@ -package arch.infra.router - -trait Action { - type ReturnType -} diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala deleted file mode 100644 index 3729f3c..0000000 --- a/src/main/scala/arch/infra/router/ActionHandler.scala +++ /dev/null @@ -1,5 +0,0 @@ -package arch.infra.router - -trait ActionHandler[F[_], A <: Action] { - def handle(a: A): F[A#ReturnType] -} diff --git a/src/main/scala/arch/infra/router/simple/PubSub.scala b/src/main/scala/arch/infra/router/PubSub.scala similarity index 97% rename from src/main/scala/arch/infra/router/simple/PubSub.scala rename to src/main/scala/arch/infra/router/PubSub.scala index 0c77dc6..c6fdc17 100644 --- a/src/main/scala/arch/infra/router/simple/PubSub.scala +++ b/src/main/scala/arch/infra/router/PubSub.scala @@ -1,4 +1,4 @@ -package arch.infra.router.simple +package arch.infra.router import scala.collection.mutable diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 905712d..1364339 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -1,8 +1,24 @@ package arch.infra.router -import scala.reflect.ClassTag +trait Router[In, Out] { + def route: In => Out +} + +object Router { + class RouterMock[In, Out](handler: In => Out) extends Router[In, Out] { + override def route = { in => + handler(in) + } + } + + object Example { + case class RequestJson(name: String) + case class ResponseJson(hello: String) + + val example = new RouterMock[RequestJson, ResponseJson](handler = { in => + ResponseJson(s"hello, ${in.name}") + }) -trait Router[F[_]] { - def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): RouterF[F] - def publish[A <: Action](action: A): F[A#ReturnType] + example.route(RequestJson("name")) + } } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala deleted file mode 100644 index 65f841e..0000000 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ /dev/null @@ -1,54 +0,0 @@ -package arch.infra.router - -import arch.common.Program.{Context, MError, ProgramError} -import arch.infra.logging.LoggingLibrary - -import scala.reflect.ClassTag - -class RouterF[F[_]: MError]( - onSuccess: String => Unit = _ => { println("success") }, - onFailure: ProgramError => Unit = _ => { println("failure") }, - recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { println("recording latency") }, - handlers: Map[Class[_], Action => F[Any]] = Map.empty[Class[_], Action => F[Any]] -)(implicit logger: LoggingLibrary[F]) extends Router[F] { - private val context: Context = Context("router") - private val actionNotFoundErrorCode = 1 - - override def publish[A <: Action](action: A): F[A#ReturnType] = - handlers - .get(action.getClass) match { - case Some(handler) => handleAction(action, handler) - case None => MError[F].raiseError( - ProgramError("action not found", s"action ${action.getClass.getSimpleName} not found", context, actionNotFoundErrorCode) - ) - } - - override def subscribe[A <: Action : ClassTag](handler: ActionHandler[F, A]): RouterF[F] = { - val classTag = implicitly[ClassTag[A]] - if (handlers.contains(classTag.runtimeClass)) { - logger.logWarn("handler already subscribed")(context.copy( - metadata = context.metadata + ("handler_name" -> handler.getClass.getSimpleName) - )) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers) - } else { - val transformed: Action => F[Any] = (t: Action) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers + (classTag.runtimeClass -> transformed)) - } - } - - private def handleAction[A <: Action](action: A, handler: A => F[Any]): F[A#ReturnType] = { - val before = System.currentTimeMillis() - val maybeResponse: F[A#ReturnType] = MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) - val recoverable = MError[F].recoverWith(maybeResponse) { - case error: ProgramError => - onFailure(error) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) - maybeResponse - } - MError[F].map(recoverable) { result => - onSuccess(action.getClass.getSimpleName) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) - result - } - } -} diff --git a/src/main/scala/arch/infra/router/RouterLive.scala b/src/main/scala/arch/infra/router/RouterLive.scala deleted file mode 100644 index b06db79..0000000 --- a/src/main/scala/arch/infra/router/RouterLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.infra.router - -import arch.common.ProgramLive.{App, Test} -import arch.infra.logging.LoggingLibrary - -object RouterLive { - class RouterApp(implicit logger: LoggingLibrary[App]) extends RouterF[App] - class RouterTest(implicit logger: LoggingLibrary[Test]) extends RouterF[Test] -} diff --git a/src/main/scala/arch/infra/router/simple/Router.scala b/src/main/scala/arch/infra/router/simple/Router.scala deleted file mode 100644 index 8227a7f..0000000 --- a/src/main/scala/arch/infra/router/simple/Router.scala +++ /dev/null @@ -1,24 +0,0 @@ -package arch.infra.router.simple - -trait Router[In, Out] { - def route: In => Out -} - -object Router { - class RouterMock[In, Out](handler: In => Out) extends Router[In, Out] { - override def route = { in => - handler(in) - } - } - - object Example { - case class RequestJson(name: String) - case class ResponseJson(hello: String) - - val example = new RouterMock[RequestJson, ResponseJson](handler = { in => - ResponseJson(s"hello, ${in.name}") - }) - - example.route(RequestJson("name")) - } -} From eb134a3f7ac5fcc64e1cdaf4e96f6b94039f0d7c Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 17:36:44 +0000 Subject: [PATCH 08/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 21bdce0..5507de9 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.2" \ No newline at end of file +version := "1.0.3" \ No newline at end of file From eb9c8fb71d547b70dbc199aab207c423f2ce3930 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 21:47:51 +0200 Subject: [PATCH 09/29] Recover not-abstract code --- build.sbt | 12 ++++ src/main/scala/arch/app/Runner.scala | 48 ++++++++++++++++ src/main/scala/arch/common/Context.scala | 3 - src/main/scala/arch/common/Program.scala | 46 ++++++++++------ .../scala/arch/common/ProgramBuilder.scala | 52 ++++++++++++++++++ src/main/scala/arch/common/ProgramError.scala | 35 ------------ src/main/scala/arch/common/ProgramLive.scala | 12 ++++ src/main/scala/arch/common/package.scala | 13 ----- src/main/scala/arch/domain/Model.scala | 9 +++ src/main/scala/arch/domain/Repo.scala | 7 +++ src/main/scala/arch/domain/Repository.scala | 6 -- .../arch/domain/modules/user/UserConfig.scala | 23 ++++++++ .../arch/domain/modules/user/UserRepoF.scala | 28 ++++++++++ .../domain/modules/user/UserRepoLive.scala | 9 +++ .../domain/modules/user/model/UserModel.scala | 18 ++++++ .../modules/user/service/UserAction.scala | 28 ++++++++++ .../modules/user/service/UserService.scala | 17 ++++++ .../modules/user/service/UserServiceF.scala | 42 ++++++++++++++ .../user/service/UserServiceLive.scala | 9 +++ .../scala/arch/infra/config/ConfigF.scala | 26 +++++++++ .../scala/arch/infra/json/JsonLibraryF.scala | 37 +++++++++++++ .../arch/infra/json/JsonLibraryLive.scala | 8 +++ .../scala/arch/infra/logging/LoggingF.scala | 21 +++++++ .../arch/infra/logging/LoggingLibrary.scala | 2 +- .../arch/infra/logging/LoggingLive.scala | 8 +++ .../arch/infra/monitoring/MonitoringF.scala | 38 +++++++++++++ .../infra/monitoring/MonitoringLibrary.scala | 9 +-- .../infra/monitoring/MonitoringLive.scala | 8 +++ src/main/scala/arch/infra/router/Action.scala | 5 ++ .../arch/infra/router/ActionHandler.scala | 5 ++ src/main/scala/arch/infra/router/PubSub.scala | 55 ------------------- src/main/scala/arch/infra/router/Router.scala | 24 ++------ .../scala/arch/infra/router/RouterF.scala | 54 ++++++++++++++++++ .../scala/arch/infra/router/RouterLive.scala | 9 +++ 34 files changed, 570 insertions(+), 156 deletions(-) create mode 100644 src/main/scala/arch/app/Runner.scala delete mode 100644 src/main/scala/arch/common/Context.scala create mode 100644 src/main/scala/arch/common/ProgramBuilder.scala delete mode 100644 src/main/scala/arch/common/ProgramError.scala create mode 100644 src/main/scala/arch/common/ProgramLive.scala delete mode 100644 src/main/scala/arch/common/package.scala create mode 100644 src/main/scala/arch/domain/Model.scala create mode 100644 src/main/scala/arch/domain/Repo.scala delete mode 100644 src/main/scala/arch/domain/Repository.scala create mode 100644 src/main/scala/arch/domain/modules/user/UserConfig.scala create mode 100644 src/main/scala/arch/domain/modules/user/UserRepoF.scala create mode 100644 src/main/scala/arch/domain/modules/user/UserRepoLive.scala create mode 100644 src/main/scala/arch/domain/modules/user/model/UserModel.scala create mode 100644 src/main/scala/arch/domain/modules/user/service/UserAction.scala create mode 100644 src/main/scala/arch/domain/modules/user/service/UserService.scala create mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceF.scala create mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala create mode 100644 src/main/scala/arch/infra/config/ConfigF.scala create mode 100644 src/main/scala/arch/infra/json/JsonLibraryF.scala create mode 100644 src/main/scala/arch/infra/json/JsonLibraryLive.scala create mode 100644 src/main/scala/arch/infra/logging/LoggingF.scala create mode 100644 src/main/scala/arch/infra/logging/LoggingLive.scala create mode 100644 src/main/scala/arch/infra/monitoring/MonitoringF.scala create mode 100644 src/main/scala/arch/infra/monitoring/MonitoringLive.scala create mode 100644 src/main/scala/arch/infra/router/Action.scala create mode 100644 src/main/scala/arch/infra/router/ActionHandler.scala delete mode 100644 src/main/scala/arch/infra/router/PubSub.scala create mode 100644 src/main/scala/arch/infra/router/RouterF.scala create mode 100644 src/main/scala/arch/infra/router/RouterLive.scala diff --git a/build.sbt b/build.sbt index a13aabd..40e079b 100644 --- a/build.sbt +++ b/build.sbt @@ -12,3 +12,15 @@ githubTokenSource := TokenSource.Or( ) libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" + +libraryDependencies ++= List( + "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", + "io.circe" %% "circe-generic" % "0.14.1" +) + +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime +// https://mvnrepository.com/artifact/com.typesafe/config +libraryDependencies += "com.typesafe" % "config" % "1.4.2" + +// https://mvnrepository.com/artifact/org.slf4j/slf4j-api +libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.36" diff --git a/src/main/scala/arch/app/Runner.scala b/src/main/scala/arch/app/Runner.scala new file mode 100644 index 0000000..f698dc1 --- /dev/null +++ b/src/main/scala/arch/app/Runner.scala @@ -0,0 +1,48 @@ +package arch.app + +import arch.common.{Program, ProgramLive} +import arch.domain.modules.user.model.UserModel +import arch.domain.modules.user.service.UserAction +import cats.effect.IO +import cats.effect.unsafe.implicits.global +import com.typesafe.config.{Config, ConfigFactory} +import io.circe.Json + +object Runner { + import arch.common.ProgramBuilder._ + import scala.jdk.CollectionConverters._ + + val prod = true + val config: Config = ConfigFactory.parseMap( + Map( + "user.flag" -> true, + "user.value" -> 1 + ).asJava + ) + + def main(args: Array[String]): Unit = { + val result = if (prod) { + println(s"RUNNING ENV=prod") + // DEFINITIONS + import scala.concurrent.Await + import scala.concurrent.duration._ + type Env[A] = ProgramLive.App[A] + // EXECUTION + val router = implicitly[ProgramBuilder[Env]].buildApp(config) + val actionResult = router.publish(UserAction(1)) + // OUTPUT + actionResult.value.unsafeRunSync() + } else { + println(s"RUNNING ENV=test") + // DEFINITIONS + type Env[A] = ProgramLive.Test[A] + // EXECUTION + val router = implicitly[ProgramBuilder[Env]].buildApp(config) + val actionResult = router.publish(UserAction(1)) + // OUTPUT + actionResult + } + + println(result) + } +} diff --git a/src/main/scala/arch/common/Context.scala b/src/main/scala/arch/common/Context.scala deleted file mode 100644 index c7bd6e5..0000000 --- a/src/main/scala/arch/common/Context.scala +++ /dev/null @@ -1,3 +0,0 @@ -package arch.common - -case class Context(name: String, metadata: Map[String, Any] = Map.empty) diff --git a/src/main/scala/arch/common/Program.scala b/src/main/scala/arch/common/Program.scala index c3a24fe..b3b378f 100644 --- a/src/main/scala/arch/common/Program.scala +++ b/src/main/scala/arch/common/Program.scala @@ -1,36 +1,48 @@ package arch.common +import cats.MonadError + import scala.util.{Failure, Success, Try} object Program { + val unknownErrorCode = 0 + + type MError[F[_]] = MonadError[F, ProgramError] + case class Context(name: String, metadata: Map[String, Any] = Map.empty) + case class ProgramError(error: Any, msg: String, ctx: Context, errorCode: Int = unknownErrorCode, stackTrace: Seq[String] = Seq.empty) + + object ProgramError { + def fromThrowable(exception: Throwable): Context => Int => ProgramError = ctx => errorCode => { + val stackTrace = Seq.empty // exception.getStackTrace.map(_.toString).take(10) + ProgramError(exception, "error message: " + exception.getMessage, ctx, errorCode, stackTrace) + } + def fromError[E](e: E): Context => Int => ProgramError = ctx => errorCode => { + ProgramError(e, "error message: " + e.toString, ctx, errorCode, stackTrace = Seq.empty) + } + } + + object MError { + def apply[F[_]](implicit m: MError[F]): MError[F] = m + } + object App { - def fromEither[F[_]: MError, E, A]( - ctx: Context - )(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = + def fromEither[F[_]: MError, E, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = MError[F].fromEither[A]( a match { - case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) + case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) case Right(value) => Right(value) } ) - def fromTry[F[_]: MError, A]( - ctx: Context - )(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = + def fromTry[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = MError[F].fromEither[A]( a match { - case Failure(exception) => - Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) + case Failure(exception) => Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) case Success(value) => Right(value) } ) - def lift[F[_]: MError, A](ctx: Context)( - errorCode: => Int = unknownErrorCode - )(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) - def apply[F[_]: MError, A]( - ctx: Context, - errorCode: => Int = unknownErrorCode - )(a: => A): F[A] = lift(ctx)(errorCode)(a) + def lift[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) + def apply[F[_]: MError, A](ctx: Context, errorCode: => Int = unknownErrorCode)(a: => A): F[A] = lift(ctx)(errorCode)(a) def unit[F[_]: MError](ctx: Context): F[Unit] = apply(ctx)() } -} +} \ No newline at end of file diff --git a/src/main/scala/arch/common/ProgramBuilder.scala b/src/main/scala/arch/common/ProgramBuilder.scala new file mode 100644 index 0000000..1784308 --- /dev/null +++ b/src/main/scala/arch/common/ProgramBuilder.scala @@ -0,0 +1,52 @@ +package arch.common + +import arch.common.ProgramLive.{App, Test} +import arch.domain.modules.user.UserConfig.{UserConfigApp, UserConfigF, UserConfigTest} +import arch.domain.modules.user.UserRepoF +import arch.domain.modules.user.UserRepoLive.{UserRepoApp, UserRepoTest} +import arch.domain.modules.user.service.UserAction.UserActionHandler +import arch.domain.modules.user.service.UserServiceLive.{UserServiceApp, UserServiceTest} +import arch.domain.modules.user.service.{UserAction, UserServiceF} +import arch.infra.json.JsonLibraryF +import arch.infra.json.JsonLibraryLive.{JsonLibraryApp, JsonLibraryTest} +import arch.infra.logging.LoggingLibrary +import arch.infra.logging.LoggingLive.{LoggingApp, LoggingTest} +import arch.infra.monitoring.MonitoringLibrary +import arch.infra.monitoring.MonitoringLive.{MonitoringApp, MonitoringTest} +import arch.infra.router.RouterF +import arch.infra.router.RouterLive.{RouterApp, RouterTest} +import com.typesafe.config.Config + +object ProgramBuilder { + trait ProgramBuilder[F[_]] { + def buildApp(config: Config): RouterF[F] + } + + implicit lazy val production: ProgramBuilder[App] = (config: Config) => { + import ProgramLive.App + implicit lazy val userRepoLive: UserRepoF[App] = UserRepoApp + implicit lazy val jsonLibraryLive: JsonLibraryF[App] = JsonLibraryApp + implicit lazy val monitoring: MonitoringLibrary[App] = MonitoringApp + implicit lazy val userConfigF: UserConfigF[App] = UserConfigApp + implicit lazy val runService: UserServiceF[App] = new UserServiceApp(config.getConfig("user")) + implicit lazy val logger: LoggingLibrary[App] = LoggingApp + implicit lazy val router: RouterF[App] = new RouterApp() + .subscribe[UserAction](new UserActionHandler[App]()) + .subscribe[UserAction](new UserActionHandler[App]()) // this is only added for debugging purposes + router + } + + implicit lazy val testing: ProgramBuilder[Test] = (config: Config) => { + import ProgramLive.Test + implicit lazy val userRepoLive: UserRepoF[Test] = UserRepoTest + implicit lazy val jsonLibraryLive: JsonLibraryF[Test] = JsonLibraryTest + implicit lazy val monitoring: MonitoringLibrary[Test] = MonitoringTest + implicit lazy val userConfigF: UserConfigF[Test] = UserConfigTest + implicit lazy val runService: UserServiceF[Test] = new UserServiceTest(config.getConfig("user")) + implicit lazy val logger: LoggingLibrary[Test] = LoggingTest + implicit lazy val router: RouterF[Test] = new RouterTest() + .subscribe[UserAction](new UserActionHandler[Test]()) + .subscribe[UserAction](new UserActionHandler[Test]()) // this is only added for debugging purposes + router + } +} diff --git a/src/main/scala/arch/common/ProgramError.scala b/src/main/scala/arch/common/ProgramError.scala deleted file mode 100644 index 4cf26fe..0000000 --- a/src/main/scala/arch/common/ProgramError.scala +++ /dev/null @@ -1,35 +0,0 @@ -package arch.common - -case class ProgramError( - error: Any, - msg: String, - ctx: Context, - errorCode: Int = unknownErrorCode, - stackTrace: Seq[String] = Seq.empty -) - -object ProgramError { - def fromThrowable(exception: Throwable): Context => Int => ProgramError = - ctx => - errorCode => { - val stackTrace = - Seq.empty // exception.getStackTrace.map(_.toString).take(10) - ProgramError( - exception, - "error message: " + exception.getMessage, - ctx, - errorCode, - stackTrace - ) - } - def fromError[E](e: E): Context => Int => ProgramError = ctx => - errorCode => { - ProgramError( - e, - "error message: " + e.toString, - ctx, - errorCode, - stackTrace = Seq.empty - ) - } -} diff --git a/src/main/scala/arch/common/ProgramLive.scala b/src/main/scala/arch/common/ProgramLive.scala new file mode 100644 index 0000000..7eb624c --- /dev/null +++ b/src/main/scala/arch/common/ProgramLive.scala @@ -0,0 +1,12 @@ +package arch.common + +import arch.common.Program.ProgramError +import cats.data.EitherT +import cats.effect.IO + +object ProgramLive { + + // each type should be a subtype of MonadError + type App[A] = EitherT[IO, ProgramError, A] + type Test[A] = Either[ProgramError, A] +} diff --git a/src/main/scala/arch/common/package.scala b/src/main/scala/arch/common/package.scala deleted file mode 100644 index 9518808..0000000 --- a/src/main/scala/arch/common/package.scala +++ /dev/null @@ -1,13 +0,0 @@ -package arch - -import cats.MonadError - -package object common { - val unknownErrorCode = -1 - - type MError[F[_]] = MonadError[F, ProgramError] - object MError { - def apply[F[_]](implicit m: MError[F]): MError[F] = m - } - -} diff --git a/src/main/scala/arch/domain/Model.scala b/src/main/scala/arch/domain/Model.scala new file mode 100644 index 0000000..99f8507 --- /dev/null +++ b/src/main/scala/arch/domain/Model.scala @@ -0,0 +1,9 @@ +package arch.domain + +trait Model { + type Entity + type Id + trait Identifiable[E <: Entity] { + def id(a: Entity): Id + } +} \ No newline at end of file diff --git a/src/main/scala/arch/domain/Repo.scala b/src/main/scala/arch/domain/Repo.scala new file mode 100644 index 0000000..ed1ec28 --- /dev/null +++ b/src/main/scala/arch/domain/Repo.scala @@ -0,0 +1,7 @@ +package arch.domain + +trait Repo[F[_]] { + type M <: Model + def set(a: M#Entity)(implicit id: M#Identifiable[M#Entity]): F[Unit] + def get(id: M#Id): F[Option[M#Entity]] +} diff --git a/src/main/scala/arch/domain/Repository.scala b/src/main/scala/arch/domain/Repository.scala deleted file mode 100644 index f4949e9..0000000 --- a/src/main/scala/arch/domain/Repository.scala +++ /dev/null @@ -1,6 +0,0 @@ -package arch.domain - -trait Repository[Key, Value] { - def set(key: Key, value: Value): Unit - def get(key: Key): Value -} diff --git a/src/main/scala/arch/domain/modules/user/UserConfig.scala b/src/main/scala/arch/domain/modules/user/UserConfig.scala new file mode 100644 index 0000000..dfdfb1f --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/UserConfig.scala @@ -0,0 +1,23 @@ +package arch.domain.modules.user + +import arch.common.Program.MError +import arch.common.ProgramLive.{App, Test} +import arch.infra.config.ConfigF +import cats.implicits._ +import com.typesafe.config.Config + +case class UserConfig(flag: Boolean, value: Option[Int]) + +object UserConfig { + class UserConfigF[F[_]: MError] extends ConfigF[UserConfig, F] { + override def fromConfig(config: Config): F[UserConfig] = { + for { + flag <- ConfigF.parse[Boolean, F](config)(_.getBoolean("flag")) + value <- ConfigF.parseOpt[Int, F](config)(_.getInt("value")) + } yield UserConfig(flag, value) + } + } + + object UserConfigApp extends UserConfigF[App] + object UserConfigTest extends UserConfigF[Test] +} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/UserRepoF.scala b/src/main/scala/arch/domain/modules/user/UserRepoF.scala new file mode 100644 index 0000000..aafa954 --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/UserRepoF.scala @@ -0,0 +1,28 @@ +package arch.domain.modules.user + +import arch.common.Program +import arch.common.Program.{Context, MError} +import arch.domain.Repo +import arch.domain.modules.user.model.UserModel +import arch.domain.modules.user.model.UserModel.User + +// IMPL class on top Map +class UserRepoF[F[_]: MError] extends Repo[F] { + + type M = UserModel.type + + private val module = Context("user") + private val userRepoErrorCode = 1 + + private var users: Map[M#Id, User] = Map.empty[M#Id, User] + + override def set(a: User)(implicit id: M#Identifiable[User]): F[Unit] = + Program.App[F, Unit](module, userRepoErrorCode) { + users = users.updated(id.id(a), a) + } + + override def get(id: M#Id): F[Option[User]] = + Program.App[F, Option[User]](module, userRepoErrorCode) { + users.get(id) + } +} diff --git a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala b/src/main/scala/arch/domain/modules/user/UserRepoLive.scala new file mode 100644 index 0000000..2d8e736 --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/UserRepoLive.scala @@ -0,0 +1,9 @@ +package arch.domain.modules.user + +import arch.common.ProgramLive.{App, Test} + +object UserRepoLive { + object UserRepoApp extends UserRepoF[App] + object UserRepoTest extends UserRepoF[Test] +} + diff --git a/src/main/scala/arch/domain/modules/user/model/UserModel.scala b/src/main/scala/arch/domain/modules/user/model/UserModel.scala new file mode 100644 index 0000000..d2f8f75 --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/model/UserModel.scala @@ -0,0 +1,18 @@ +package arch.domain.modules.user.model + +import arch.domain.Model +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder} + +object UserModel extends Model { + type Id = String + type Entity = User + + case class User(id: Id) + + object User { + implicit val userDecoder: Decoder[User] = deriveDecoder[User] + implicit val userEncoder: Encoder[User] = deriveEncoder[User] + implicit val identifier: Identifiable[User] = (user: User) => user.id + } +} diff --git a/src/main/scala/arch/domain/modules/user/service/UserAction.scala b/src/main/scala/arch/domain/modules/user/service/UserAction.scala new file mode 100644 index 0000000..c685930 --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/service/UserAction.scala @@ -0,0 +1,28 @@ +package arch.domain.modules.user.service + +import arch.common.Program.MError +import arch.domain.modules.user.UserConfig.UserConfigF +import arch.domain.modules.user.UserRepoF +import arch.domain.modules.user.model.UserModel.User +import arch.infra.json.JsonLibraryF +import arch.infra.json.JsonLibraryLive.JsonLibraryTest +import arch.infra.monitoring.MonitoringLibrary +import arch.infra.router.{Action, ActionHandler} +import io.circe.Json + +case class UserAction(id: Int) extends Action { + type ReturnType = (Option[String], Option[JsonLibraryTest.JsonType], User) +} + +object UserAction { + class UserActionHandler[F[_]: MError]( + implicit + service: UserServiceF[F], + userRepo: UserRepoF[F], + jsonLibrary: JsonLibraryF[F], + userConfig: UserConfigF[F], + monitoring: MonitoringLibrary[F] + ) extends ActionHandler[F, UserAction] { + override def handle(a: UserAction): F[(Option[String], Option[Json], User)] = service.run(a) + } +} diff --git a/src/main/scala/arch/domain/modules/user/service/UserService.scala b/src/main/scala/arch/domain/modules/user/service/UserService.scala new file mode 100644 index 0000000..b856fdb --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/service/UserService.scala @@ -0,0 +1,17 @@ +package arch.domain.modules.user.service + +import arch.domain.modules.user.UserConfig.UserConfigF +import arch.domain.modules.user.UserRepoF +import arch.infra.json.JsonLibraryF +import arch.infra.monitoring.MonitoringLibrary +import arch.infra.router.Action + +trait UserService[F[_], A <: Action] { + def run(args: A)( + // @TODO revisit if this should go into constructor + implicit userRepo: UserRepoF[F], + jsonLibrary: JsonLibraryF[F], + userConfig: UserConfigF[F], + monitoring: MonitoringLibrary[F] + ): F[A#ReturnType] +} diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala new file mode 100644 index 0000000..9560ee9 --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala @@ -0,0 +1,42 @@ +package arch.domain.modules.user.service + +import arch.common.Program.{Context, MError, ProgramError} +import arch.domain.modules.user.UserConfig.UserConfigF +import arch.domain.modules.user.UserRepoF +import arch.domain.modules.user.model.UserModel.User +import arch.infra.json.JsonLibraryF +import arch.infra.json.JsonLibraryLive.JsonLibraryTest +import arch.infra.monitoring.MonitoringLibrary +import cats.implicits._ +import com.typesafe.config.Config + +class UserServiceF[F[_]: MError](c: Config) extends UserService[F, UserAction] { + def run(args: UserAction)( + implicit userRepo: UserRepoF[F], + jsonLibrary: JsonLibraryF[F], + userConfig: UserConfigF[F], + monitoring: MonitoringLibrary[F] + ): F[(Option[String], Option[JsonLibraryTest.JsonType], User)] = { + val ctx = Context("run") + + val user = User("Franco") + val userJsonFromTo = jsonLibrary.jsonFromTo[User] + val counter = monitoring.counter("counter") + for { + config <- userConfig.fromConfig(c) + _ <- counter.increment() + _ = println(s"user config = $config") + _ <- userRepo.set(user) + u1 <- userRepo.get(user.id) + u2 <- userRepo.get("Euge") + js1 = u1.map(userJsonFromTo.from).map(jsonLibrary.prettyPrint) + parsedJson <- { + js1.map(jsonLibrary.parse) match { + case Some(maybeParsed) => maybeParsed.flatMap(userJsonFromTo.to) + case None => MError[F].raiseError(ProgramError((), "js1 missing", ctx)) + } + } + js2 = u2.map(userJsonFromTo.from) + } yield (js1, js2, parsedJson) + } +} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala new file mode 100644 index 0000000..82d023d --- /dev/null +++ b/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala @@ -0,0 +1,9 @@ +package arch.domain.modules.user.service + +import arch.common.ProgramLive.{App, Test} +import com.typesafe.config.Config + +object UserServiceLive { + class UserServiceApp(c: Config) extends UserServiceF[App](c) + class UserServiceTest(c: Config) extends UserServiceF[Test](c) +} diff --git a/src/main/scala/arch/infra/config/ConfigF.scala b/src/main/scala/arch/infra/config/ConfigF.scala new file mode 100644 index 0000000..48b4ce8 --- /dev/null +++ b/src/main/scala/arch/infra/config/ConfigF.scala @@ -0,0 +1,26 @@ +package arch.infra.config + +import arch.common.Program.{Context, MError, ProgramError} +import com.typesafe.config.Config + +import scala.util.{Failure, Success, Try} + +trait ConfigF[A, F[_]] { + def fromConfig(config: Config): F[A] +} + +object ConfigF { + val ctx: Context = Context("config_parser") + + def parse[A, F[_]: MError](cfg: Config)(fn: Config => A): F[A] = { + val either = Try(fn(cfg)) match { + case Failure(exception) => Left(ProgramError(exception, exception.getMessage, ctx)) + case Success(value) => Right(value) + } + MError[F].fromEither(either) + } + + def parseOpt[A, F[_]: MError](cfg: Config)(fn: Config => A): F[Option[A]] = { + MError[F].pure(Try(fn(cfg)).toOption) + } +} diff --git a/src/main/scala/arch/infra/json/JsonLibraryF.scala b/src/main/scala/arch/infra/json/JsonLibraryF.scala new file mode 100644 index 0000000..8b04e4f --- /dev/null +++ b/src/main/scala/arch/infra/json/JsonLibraryF.scala @@ -0,0 +1,37 @@ +package arch.infra.json + +import arch.common.Program +import arch.common.Program.{Context, MError} +import io.circe.{Decoder, Encoder, Json, Printer, parser} + +// IMPL class on top of circe +class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { + type JsonType = Json + + val ctx: Context = Context("json") + val jsonDecodeErrorCode = 1 + val jsonParseErrorCode = 2 + + class To[A](implicit d: Decoder[A]) extends JsonTo[A] { + override def to(json: JsonType): F[A] = + Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)(d.decodeJson(json)) + } + class From[A](implicit e: Encoder[A]) extends JsonFrom[A] { + override def from(from: A): JsonType = e.apply(from) + } + class JsonFromToF[A](t: To[A], f: From[A]) extends JsonFromTo[A] { + override def to(json: JsonType): F[A] = t.to(json) + override def from(from: A): JsonType = f.from(from) + } + object PrettyPrinter extends JsonPrinter { + override def prettyPrint(json: Json): String = Printer.spaces2.print(json) + } + object JsonParserF extends JsonParser { + override def parse(str: String): F[Json] = + Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)(parser.parse(str)) + } + + def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = new JsonFromToF[A](new To(), new From()) + def prettyPrint(json: Json): String = PrettyPrinter.prettyPrint(json) + def parse(json: String): F[Json] = JsonParserF.parse(json) +} diff --git a/src/main/scala/arch/infra/json/JsonLibraryLive.scala b/src/main/scala/arch/infra/json/JsonLibraryLive.scala new file mode 100644 index 0000000..eb13eec --- /dev/null +++ b/src/main/scala/arch/infra/json/JsonLibraryLive.scala @@ -0,0 +1,8 @@ +package arch.infra.json + +import arch.common.ProgramLive.{App, Test} + +object JsonLibraryLive { + object JsonLibraryApp extends JsonLibraryF[App] + object JsonLibraryTest extends JsonLibraryF[Test] +} diff --git a/src/main/scala/arch/infra/logging/LoggingF.scala b/src/main/scala/arch/infra/logging/LoggingF.scala new file mode 100644 index 0000000..425dc9d --- /dev/null +++ b/src/main/scala/arch/infra/logging/LoggingF.scala @@ -0,0 +1,21 @@ +package arch.infra.logging + +import arch.common.Program +import arch.common.Program.MError +import org.slf4j.LoggerFactory + +// TODO fix why ctx is not being logged +case class LoggingF[F[_]: MError]() extends LoggingLibrary[F] { + private val logger = LoggerFactory.getLogger("main-logger") + override def logDebug(msg: String): Program.Context => F[Unit] = + ctx => MError[F].pure(logger.debug(msg, ctx)) + + override def logError(msg: String): Program.Context => F[Unit] = + ctx => MError[F].pure(logger.error(msg, ctx)) + + override def logInfo(msg: String): Program.Context => F[Unit] = + ctx => MError[F].pure(logger.info(msg, ctx)) + + override def logWarn(msg: String): Program.Context => F[Unit] = + ctx => MError[F].pure(logger.warn(msg, ctx)) +} diff --git a/src/main/scala/arch/infra/logging/LoggingLibrary.scala b/src/main/scala/arch/infra/logging/LoggingLibrary.scala index 8f2258a..0a7f707 100644 --- a/src/main/scala/arch/infra/logging/LoggingLibrary.scala +++ b/src/main/scala/arch/infra/logging/LoggingLibrary.scala @@ -1,6 +1,6 @@ package arch.infra.logging -import arch.common.Context +import arch.common.Program.Context trait LoggingLibrary[F[_]] { def logDebug(msg: String): Context => F[Unit] diff --git a/src/main/scala/arch/infra/logging/LoggingLive.scala b/src/main/scala/arch/infra/logging/LoggingLive.scala new file mode 100644 index 0000000..9271d48 --- /dev/null +++ b/src/main/scala/arch/infra/logging/LoggingLive.scala @@ -0,0 +1,8 @@ +package arch.infra.logging + +import arch.common.ProgramLive.{App, Test} + +object LoggingLive { + object LoggingApp extends LoggingF[App] + object LoggingTest extends LoggingF[Test] +} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringF.scala b/src/main/scala/arch/infra/monitoring/MonitoringF.scala new file mode 100644 index 0000000..86f9976 --- /dev/null +++ b/src/main/scala/arch/infra/monitoring/MonitoringF.scala @@ -0,0 +1,38 @@ +package arch.infra.monitoring + +import arch.common.Program.MError +import arch.infra.monitoring.MonitoringLibrary.{CounterF, GaugeF, HistogramF} +import cats.implicits._ + +// @TODO implement on top of some dependency (ie: grafana, kamon) +class MonitoringF[F[_]: MError] extends MonitoringLibrary[F] { + override def counter(name: String, context: Map[String, String]): CounterF[F] = new CounterF[F] { + override def increment(): F[Unit] = + MError[F].pure(println(s"counter increment")) + + override def add(num: Int): F[Unit] = + MError[F].pure(println(s"counter add $num")) + } + + override def gauge(name: String): GaugeF[F] = new GaugeF[F] { + override def increment(): F[Unit] = + MError[F].pure(println(s"gauge increment")) + + override def decrement(): F[Unit] = + MError[F].pure(println(s"gauge decrement")) + + override def add(num: Int): F[Unit] = + MError[F].pure(println(s"gauge add $num")) + + override def subtract(num: Int): F[Unit] = + MError[F].pure(println(s"gauge subtract $num")) + + override def set(num: Int): F[Unit] = + MError[F].pure(println(s"gauge set $num")) + } + + override def histogram(name: String): HistogramF[F] = new HistogramF[F] { + override def record(value: Long): F[Unit] = + MError[F].pure(println(s"histogram record $value")) + } +} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala index 95ceb1a..3c72685 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala @@ -1,14 +1,11 @@ package arch.infra.monitoring -import arch.common.MError +import arch.common.Program.MError import cats.implicits._ trait MonitoringLibrary[F[_]] { import MonitoringLibrary._ - def counter( - name: String, - context: Map[String, String] = Map.empty - ): CounterF[F] + def counter(name: String, context: Map[String, String] = Map.empty): CounterF[F] def gauge(name: String): GaugeF[F] def histogram(name: String): HistogramF[F] } @@ -45,7 +42,7 @@ object MonitoringLibrary { for { result <- codeToBenchmark _ = record(System.currentTimeMillis() - before) - } yield result + } yield result } } } diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala b/src/main/scala/arch/infra/monitoring/MonitoringLive.scala new file mode 100644 index 0000000..26255a2 --- /dev/null +++ b/src/main/scala/arch/infra/monitoring/MonitoringLive.scala @@ -0,0 +1,8 @@ +package arch.infra.monitoring + +import arch.common.ProgramLive + +object MonitoringLive { + object MonitoringApp extends MonitoringF[ProgramLive.App] + object MonitoringTest extends MonitoringF[ProgramLive.Test] +} \ No newline at end of file diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala new file mode 100644 index 0000000..6a1ad58 --- /dev/null +++ b/src/main/scala/arch/infra/router/Action.scala @@ -0,0 +1,5 @@ +package arch.infra.router + +trait Action { + type ReturnType +} diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala new file mode 100644 index 0000000..3729f3c --- /dev/null +++ b/src/main/scala/arch/infra/router/ActionHandler.scala @@ -0,0 +1,5 @@ +package arch.infra.router + +trait ActionHandler[F[_], A <: Action] { + def handle(a: A): F[A#ReturnType] +} diff --git a/src/main/scala/arch/infra/router/PubSub.scala b/src/main/scala/arch/infra/router/PubSub.scala deleted file mode 100644 index c6fdc17..0000000 --- a/src/main/scala/arch/infra/router/PubSub.scala +++ /dev/null @@ -1,55 +0,0 @@ -package arch.infra.router - -import scala.collection.mutable - -trait PubSub[Topic, Message] { - type Callback = Message => Unit - def publish: (Topic, Message) => Unit - def subscribe: Topic => Callback => Unit -} - -object PubSub { - - class PubSubMock[Topic, Message] extends PubSub[Topic, Message] { - - val callbacksPerTopic = - mutable.HashMap.empty[Topic, mutable.Set[Callback]] - val messageQueuePerTopic = - mutable.HashMap.empty[Topic, mutable.ListBuffer[Message]] - - override def publish = { (topic, message) => - messageQueuePerTopic.get(topic) match { - case Some(queue) => queue.addOne(message) - case None => - messageQueuePerTopic.addOne( - topic, - mutable.ListBuffer.empty[Message].addOne(message) - ) - } - - callbacksPerTopic.get(topic).foreach { callbacks => - callbacks.foreach { callback => - callback(message) - } - } - } - - override def subscribe = { topic => callback => - callbacksPerTopic.get(topic) match { - case Some(callbacks) => callbacks.addOne(callback) - case None => - callbacksPerTopic.addOne( - topic, - mutable.Set.empty[Callback].addOne(callback) - ) - } - - messageQueuePerTopic.get(topic).foreach { messages => - messages.foreach { message => - callback(message) - } - } - } - } - -} diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 1364339..905712d 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -1,24 +1,8 @@ package arch.infra.router -trait Router[In, Out] { - def route: In => Out -} - -object Router { - class RouterMock[In, Out](handler: In => Out) extends Router[In, Out] { - override def route = { in => - handler(in) - } - } - - object Example { - case class RequestJson(name: String) - case class ResponseJson(hello: String) - - val example = new RouterMock[RequestJson, ResponseJson](handler = { in => - ResponseJson(s"hello, ${in.name}") - }) +import scala.reflect.ClassTag - example.route(RequestJson("name")) - } +trait Router[F[_]] { + def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): RouterF[F] + def publish[A <: Action](action: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala new file mode 100644 index 0000000..65f841e --- /dev/null +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -0,0 +1,54 @@ +package arch.infra.router + +import arch.common.Program.{Context, MError, ProgramError} +import arch.infra.logging.LoggingLibrary + +import scala.reflect.ClassTag + +class RouterF[F[_]: MError]( + onSuccess: String => Unit = _ => { println("success") }, + onFailure: ProgramError => Unit = _ => { println("failure") }, + recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { println("recording latency") }, + handlers: Map[Class[_], Action => F[Any]] = Map.empty[Class[_], Action => F[Any]] +)(implicit logger: LoggingLibrary[F]) extends Router[F] { + private val context: Context = Context("router") + private val actionNotFoundErrorCode = 1 + + override def publish[A <: Action](action: A): F[A#ReturnType] = + handlers + .get(action.getClass) match { + case Some(handler) => handleAction(action, handler) + case None => MError[F].raiseError( + ProgramError("action not found", s"action ${action.getClass.getSimpleName} not found", context, actionNotFoundErrorCode) + ) + } + + override def subscribe[A <: Action : ClassTag](handler: ActionHandler[F, A]): RouterF[F] = { + val classTag = implicitly[ClassTag[A]] + if (handlers.contains(classTag.runtimeClass)) { + logger.logWarn("handler already subscribed")(context.copy( + metadata = context.metadata + ("handler_name" -> handler.getClass.getSimpleName) + )) + new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers) + } else { + val transformed: Action => F[Any] = (t: Action) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) + new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers + (classTag.runtimeClass -> transformed)) + } + } + + private def handleAction[A <: Action](action: A, handler: A => F[Any]): F[A#ReturnType] = { + val before = System.currentTimeMillis() + val maybeResponse: F[A#ReturnType] = MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) + val recoverable = MError[F].recoverWith(maybeResponse) { + case error: ProgramError => + onFailure(error) + recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + maybeResponse + } + MError[F].map(recoverable) { result => + onSuccess(action.getClass.getSimpleName) + recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + result + } + } +} diff --git a/src/main/scala/arch/infra/router/RouterLive.scala b/src/main/scala/arch/infra/router/RouterLive.scala new file mode 100644 index 0000000..b06db79 --- /dev/null +++ b/src/main/scala/arch/infra/router/RouterLive.scala @@ -0,0 +1,9 @@ +package arch.infra.router + +import arch.common.ProgramLive.{App, Test} +import arch.infra.logging.LoggingLibrary + +object RouterLive { + class RouterApp(implicit logger: LoggingLibrary[App]) extends RouterF[App] + class RouterTest(implicit logger: LoggingLibrary[Test]) extends RouterF[Test] +} From 4b466a58c170c3b835a2a31dc41e96e1aa02954b Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 19:49:23 +0000 Subject: [PATCH 10/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 5507de9..3783aed 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.3" \ No newline at end of file +version := "1.0.4" \ No newline at end of file From a1fa833bbf0e754ccb01b139d34980dd3b331fc8 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 23:01:54 +0200 Subject: [PATCH 11/29] Moved example to branch 'example' --- .bsp/sbt.json | 1 + src/main/scala/arch/app/Runner.scala | 48 ----------------- .../scala/arch/common/ProgramBuilder.scala | 52 ------------------- src/main/scala/arch/common/ProgramLive.scala | 12 ----- src/main/scala/arch/domain/Model.scala | 9 ---- src/main/scala/arch/domain/Repo.scala | 7 --- .../arch/domain/modules/user/UserConfig.scala | 23 -------- .../arch/domain/modules/user/UserRepoF.scala | 28 ---------- .../domain/modules/user/UserRepoLive.scala | 9 ---- .../domain/modules/user/model/UserModel.scala | 18 ------- .../modules/user/service/UserAction.scala | 28 ---------- .../modules/user/service/UserService.scala | 17 ------ .../modules/user/service/UserServiceF.scala | 42 --------------- .../user/service/UserServiceLive.scala | 9 ---- .../arch/infra/json/JsonLibraryLive.scala | 8 --- .../arch/infra/logging/LoggingLive.scala | 8 --- .../infra/monitoring/MonitoringLive.scala | 8 --- .../scala/arch/infra/router/RouterLive.scala | 9 ---- version.sbt | 2 +- 19 files changed, 2 insertions(+), 336 deletions(-) create mode 100644 .bsp/sbt.json delete mode 100644 src/main/scala/arch/app/Runner.scala delete mode 100644 src/main/scala/arch/common/ProgramBuilder.scala delete mode 100644 src/main/scala/arch/common/ProgramLive.scala delete mode 100644 src/main/scala/arch/domain/Model.scala delete mode 100644 src/main/scala/arch/domain/Repo.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserConfig.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserRepoF.scala delete mode 100644 src/main/scala/arch/domain/modules/user/UserRepoLive.scala delete mode 100644 src/main/scala/arch/domain/modules/user/model/UserModel.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserAction.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserService.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceF.scala delete mode 100644 src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala delete mode 100644 src/main/scala/arch/infra/json/JsonLibraryLive.scala delete mode 100644 src/main/scala/arch/infra/logging/LoggingLive.scala delete mode 100644 src/main/scala/arch/infra/monitoring/MonitoringLive.scala delete mode 100644 src/main/scala/arch/infra/router/RouterLive.scala diff --git a/.bsp/sbt.json b/.bsp/sbt.json new file mode 100644 index 0000000..51af022 --- /dev/null +++ b/.bsp/sbt.json @@ -0,0 +1 @@ +{"name":"sbt","version":"1.6.2","bspVersion":"2.0.0-M5","languages":["scala"],"argv":["/Users/anastasia/.sdkman/candidates/java/11.0.14-zulu/zulu-11.jdk/Contents/Home/bin/java","-Xms100m","-Xmx100m","-classpath","/Users/anastasia/Library/Application Support/JetBrains/IdeaIC2021.3/plugins/Scala/launcher/sbt-launch.jar","xsbt.boot.Boot","-bsp","--sbt-launch-jar=/Users/anastasia/Library/Application%20Support/JetBrains/IdeaIC2021.3/plugins/Scala/launcher/sbt-launch.jar"]} \ No newline at end of file diff --git a/src/main/scala/arch/app/Runner.scala b/src/main/scala/arch/app/Runner.scala deleted file mode 100644 index f698dc1..0000000 --- a/src/main/scala/arch/app/Runner.scala +++ /dev/null @@ -1,48 +0,0 @@ -package arch.app - -import arch.common.{Program, ProgramLive} -import arch.domain.modules.user.model.UserModel -import arch.domain.modules.user.service.UserAction -import cats.effect.IO -import cats.effect.unsafe.implicits.global -import com.typesafe.config.{Config, ConfigFactory} -import io.circe.Json - -object Runner { - import arch.common.ProgramBuilder._ - import scala.jdk.CollectionConverters._ - - val prod = true - val config: Config = ConfigFactory.parseMap( - Map( - "user.flag" -> true, - "user.value" -> 1 - ).asJava - ) - - def main(args: Array[String]): Unit = { - val result = if (prod) { - println(s"RUNNING ENV=prod") - // DEFINITIONS - import scala.concurrent.Await - import scala.concurrent.duration._ - type Env[A] = ProgramLive.App[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - actionResult.value.unsafeRunSync() - } else { - println(s"RUNNING ENV=test") - // DEFINITIONS - type Env[A] = ProgramLive.Test[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - actionResult - } - - println(result) - } -} diff --git a/src/main/scala/arch/common/ProgramBuilder.scala b/src/main/scala/arch/common/ProgramBuilder.scala deleted file mode 100644 index 1784308..0000000 --- a/src/main/scala/arch/common/ProgramBuilder.scala +++ /dev/null @@ -1,52 +0,0 @@ -package arch.common - -import arch.common.ProgramLive.{App, Test} -import arch.domain.modules.user.UserConfig.{UserConfigApp, UserConfigF, UserConfigTest} -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.UserRepoLive.{UserRepoApp, UserRepoTest} -import arch.domain.modules.user.service.UserAction.UserActionHandler -import arch.domain.modules.user.service.UserServiceLive.{UserServiceApp, UserServiceTest} -import arch.domain.modules.user.service.{UserAction, UserServiceF} -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.{JsonLibraryApp, JsonLibraryTest} -import arch.infra.logging.LoggingLibrary -import arch.infra.logging.LoggingLive.{LoggingApp, LoggingTest} -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.monitoring.MonitoringLive.{MonitoringApp, MonitoringTest} -import arch.infra.router.RouterF -import arch.infra.router.RouterLive.{RouterApp, RouterTest} -import com.typesafe.config.Config - -object ProgramBuilder { - trait ProgramBuilder[F[_]] { - def buildApp(config: Config): RouterF[F] - } - - implicit lazy val production: ProgramBuilder[App] = (config: Config) => { - import ProgramLive.App - implicit lazy val userRepoLive: UserRepoF[App] = UserRepoApp - implicit lazy val jsonLibraryLive: JsonLibraryF[App] = JsonLibraryApp - implicit lazy val monitoring: MonitoringLibrary[App] = MonitoringApp - implicit lazy val userConfigF: UserConfigF[App] = UserConfigApp - implicit lazy val runService: UserServiceF[App] = new UserServiceApp(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[App] = LoggingApp - implicit lazy val router: RouterF[App] = new RouterApp() - .subscribe[UserAction](new UserActionHandler[App]()) - .subscribe[UserAction](new UserActionHandler[App]()) // this is only added for debugging purposes - router - } - - implicit lazy val testing: ProgramBuilder[Test] = (config: Config) => { - import ProgramLive.Test - implicit lazy val userRepoLive: UserRepoF[Test] = UserRepoTest - implicit lazy val jsonLibraryLive: JsonLibraryF[Test] = JsonLibraryTest - implicit lazy val monitoring: MonitoringLibrary[Test] = MonitoringTest - implicit lazy val userConfigF: UserConfigF[Test] = UserConfigTest - implicit lazy val runService: UserServiceF[Test] = new UserServiceTest(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[Test] = LoggingTest - implicit lazy val router: RouterF[Test] = new RouterTest() - .subscribe[UserAction](new UserActionHandler[Test]()) - .subscribe[UserAction](new UserActionHandler[Test]()) // this is only added for debugging purposes - router - } -} diff --git a/src/main/scala/arch/common/ProgramLive.scala b/src/main/scala/arch/common/ProgramLive.scala deleted file mode 100644 index 7eb624c..0000000 --- a/src/main/scala/arch/common/ProgramLive.scala +++ /dev/null @@ -1,12 +0,0 @@ -package arch.common - -import arch.common.Program.ProgramError -import cats.data.EitherT -import cats.effect.IO - -object ProgramLive { - - // each type should be a subtype of MonadError - type App[A] = EitherT[IO, ProgramError, A] - type Test[A] = Either[ProgramError, A] -} diff --git a/src/main/scala/arch/domain/Model.scala b/src/main/scala/arch/domain/Model.scala deleted file mode 100644 index 99f8507..0000000 --- a/src/main/scala/arch/domain/Model.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain - -trait Model { - type Entity - type Id - trait Identifiable[E <: Entity] { - def id(a: Entity): Id - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/Repo.scala b/src/main/scala/arch/domain/Repo.scala deleted file mode 100644 index ed1ec28..0000000 --- a/src/main/scala/arch/domain/Repo.scala +++ /dev/null @@ -1,7 +0,0 @@ -package arch.domain - -trait Repo[F[_]] { - type M <: Model - def set(a: M#Entity)(implicit id: M#Identifiable[M#Entity]): F[Unit] - def get(id: M#Id): F[Option[M#Entity]] -} diff --git a/src/main/scala/arch/domain/modules/user/UserConfig.scala b/src/main/scala/arch/domain/modules/user/UserConfig.scala deleted file mode 100644 index dfdfb1f..0000000 --- a/src/main/scala/arch/domain/modules/user/UserConfig.scala +++ /dev/null @@ -1,23 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program.MError -import arch.common.ProgramLive.{App, Test} -import arch.infra.config.ConfigF -import cats.implicits._ -import com.typesafe.config.Config - -case class UserConfig(flag: Boolean, value: Option[Int]) - -object UserConfig { - class UserConfigF[F[_]: MError] extends ConfigF[UserConfig, F] { - override def fromConfig(config: Config): F[UserConfig] = { - for { - flag <- ConfigF.parse[Boolean, F](config)(_.getBoolean("flag")) - value <- ConfigF.parseOpt[Int, F](config)(_.getInt("value")) - } yield UserConfig(flag, value) - } - } - - object UserConfigApp extends UserConfigF[App] - object UserConfigTest extends UserConfigF[Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/UserRepoF.scala b/src/main/scala/arch/domain/modules/user/UserRepoF.scala deleted file mode 100644 index aafa954..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoF.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program -import arch.common.Program.{Context, MError} -import arch.domain.Repo -import arch.domain.modules.user.model.UserModel -import arch.domain.modules.user.model.UserModel.User - -// IMPL class on top Map -class UserRepoF[F[_]: MError] extends Repo[F] { - - type M = UserModel.type - - private val module = Context("user") - private val userRepoErrorCode = 1 - - private var users: Map[M#Id, User] = Map.empty[M#Id, User] - - override def set(a: User)(implicit id: M#Identifiable[User]): F[Unit] = - Program.App[F, Unit](module, userRepoErrorCode) { - users = users.updated(id.id(a), a) - } - - override def get(id: M#Id): F[Option[User]] = - Program.App[F, Option[User]](module, userRepoErrorCode) { - users.get(id) - } -} diff --git a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala b/src/main/scala/arch/domain/modules/user/UserRepoLive.scala deleted file mode 100644 index 2d8e736..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user - -import arch.common.ProgramLive.{App, Test} - -object UserRepoLive { - object UserRepoApp extends UserRepoF[App] - object UserRepoTest extends UserRepoF[Test] -} - diff --git a/src/main/scala/arch/domain/modules/user/model/UserModel.scala b/src/main/scala/arch/domain/modules/user/model/UserModel.scala deleted file mode 100644 index d2f8f75..0000000 --- a/src/main/scala/arch/domain/modules/user/model/UserModel.scala +++ /dev/null @@ -1,18 +0,0 @@ -package arch.domain.modules.user.model - -import arch.domain.Model -import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} -import io.circe.{Decoder, Encoder} - -object UserModel extends Model { - type Id = String - type Entity = User - - case class User(id: Id) - - object User { - implicit val userDecoder: Decoder[User] = deriveDecoder[User] - implicit val userEncoder: Encoder[User] = deriveEncoder[User] - implicit val identifier: Identifiable[User] = (user: User) => user.id - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserAction.scala b/src/main/scala/arch/domain/modules/user/service/UserAction.scala deleted file mode 100644 index c685930..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserAction.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.MError -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.{Action, ActionHandler} -import io.circe.Json - -case class UserAction(id: Int) extends Action { - type ReturnType = (Option[String], Option[JsonLibraryTest.JsonType], User) -} - -object UserAction { - class UserActionHandler[F[_]: MError]( - implicit - service: UserServiceF[F], - userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ) extends ActionHandler[F, UserAction] { - override def handle(a: UserAction): F[(Option[String], Option[Json], User)] = service.run(a) - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserService.scala b/src/main/scala/arch/domain/modules/user/service/UserService.scala deleted file mode 100644 index b856fdb..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserService.scala +++ /dev/null @@ -1,17 +0,0 @@ -package arch.domain.modules.user.service - -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.infra.json.JsonLibraryF -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.Action - -trait UserService[F[_], A <: Action] { - def run(args: A)( - // @TODO revisit if this should go into constructor - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[A#ReturnType] -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala deleted file mode 100644 index 9560ee9..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala +++ /dev/null @@ -1,42 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.{Context, MError, ProgramError} -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import cats.implicits._ -import com.typesafe.config.Config - -class UserServiceF[F[_]: MError](c: Config) extends UserService[F, UserAction] { - def run(args: UserAction)( - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[(Option[String], Option[JsonLibraryTest.JsonType], User)] = { - val ctx = Context("run") - - val user = User("Franco") - val userJsonFromTo = jsonLibrary.jsonFromTo[User] - val counter = monitoring.counter("counter") - for { - config <- userConfig.fromConfig(c) - _ <- counter.increment() - _ = println(s"user config = $config") - _ <- userRepo.set(user) - u1 <- userRepo.get(user.id) - u2 <- userRepo.get("Euge") - js1 = u1.map(userJsonFromTo.from).map(jsonLibrary.prettyPrint) - parsedJson <- { - js1.map(jsonLibrary.parse) match { - case Some(maybeParsed) => maybeParsed.flatMap(userJsonFromTo.to) - case None => MError[F].raiseError(ProgramError((), "js1 missing", ctx)) - } - } - js2 = u2.map(userJsonFromTo.from) - } yield (js1, js2, parsedJson) - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala deleted file mode 100644 index 82d023d..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.ProgramLive.{App, Test} -import com.typesafe.config.Config - -object UserServiceLive { - class UserServiceApp(c: Config) extends UserServiceF[App](c) - class UserServiceTest(c: Config) extends UserServiceF[Test](c) -} diff --git a/src/main/scala/arch/infra/json/JsonLibraryLive.scala b/src/main/scala/arch/infra/json/JsonLibraryLive.scala deleted file mode 100644 index eb13eec..0000000 --- a/src/main/scala/arch/infra/json/JsonLibraryLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.json - -import arch.common.ProgramLive.{App, Test} - -object JsonLibraryLive { - object JsonLibraryApp extends JsonLibraryF[App] - object JsonLibraryTest extends JsonLibraryF[Test] -} diff --git a/src/main/scala/arch/infra/logging/LoggingLive.scala b/src/main/scala/arch/infra/logging/LoggingLive.scala deleted file mode 100644 index 9271d48..0000000 --- a/src/main/scala/arch/infra/logging/LoggingLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.logging - -import arch.common.ProgramLive.{App, Test} - -object LoggingLive { - object LoggingApp extends LoggingF[App] - object LoggingTest extends LoggingF[Test] -} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala b/src/main/scala/arch/infra/monitoring/MonitoringLive.scala deleted file mode 100644 index 26255a2..0000000 --- a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.monitoring - -import arch.common.ProgramLive - -object MonitoringLive { - object MonitoringApp extends MonitoringF[ProgramLive.App] - object MonitoringTest extends MonitoringF[ProgramLive.Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/infra/router/RouterLive.scala b/src/main/scala/arch/infra/router/RouterLive.scala deleted file mode 100644 index b06db79..0000000 --- a/src/main/scala/arch/infra/router/RouterLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.infra.router - -import arch.common.ProgramLive.{App, Test} -import arch.infra.logging.LoggingLibrary - -object RouterLive { - class RouterApp(implicit logger: LoggingLibrary[App]) extends RouterF[App] - class RouterTest(implicit logger: LoggingLibrary[Test]) extends RouterF[Test] -} diff --git a/version.sbt b/version.sbt index 5507de9..3783aed 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.3" \ No newline at end of file +version := "1.0.4" \ No newline at end of file From 7ffd8c1bb399a1bd84d776d790d3b895a66fb48d Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 21:04:14 +0000 Subject: [PATCH 12/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 3783aed..f7bebd7 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.4" \ No newline at end of file +version := "1.0.5" \ No newline at end of file From 90fe4e6ff7c03387ef9b8131c0f45ddb9044083e Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Thu, 14 Apr 2022 23:07:17 +0200 Subject: [PATCH 13/29] Allow manual dispatch of release/patch workflow --- .github/workflows/patch.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 097e26b..1d234c2 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -1,6 +1,7 @@ name: release/patch on: + workflow_dispatch: push: branches: - main From 59d180195f7bd358313ca985951c6ac05e4f154d Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 21:11:06 +0000 Subject: [PATCH 14/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index f7bebd7..1d06160 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.5" \ No newline at end of file +version := "1.0.6" \ No newline at end of file From 9ff78c7941e695554232f09850cfe260999241d6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 21:54:37 +0000 Subject: [PATCH 15/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 1d06160..ae2fff1 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.6" \ No newline at end of file +version := "1.0.7" \ No newline at end of file From 451fdbbf956152da8d6da85cf45914e3db94c339 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Fri, 15 Apr 2022 00:16:25 +0200 Subject: [PATCH 16/29] Because the Github Action for package deployment stopped working, removing code related to the task. --- build.sbt | 6 +++--- project/plugins.sbt | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 40e079b..392dad5 100644 --- a/build.sbt +++ b/build.sbt @@ -1,15 +1,15 @@ -import sbtghpackages.TokenSource.{Environment, GitConfig} - name := "boring" scalaVersion := "2.13.3" organization := "Prom3th3us" +/* +import sbtghpackages.TokenSource.{Environment, GitConfig} githubOwner := "Prom3th3us" githubRepository := "boring" githubTokenSource := TokenSource.Or( GitConfig("github.token"), Environment("PUBLISH_TOKEN") -) +)*/ libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" diff --git a/project/plugins.sbt b/project/plugins.sbt index cf2c920..006867b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1 @@ -addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.2") -resolvers += Resolver.bintrayIvyRepo("rallyhealth", "sbt-plugins") +//addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.2") From 1b33a377488b01c6a08b5c815dcf1437ad03e726 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 14 Apr 2022 22:18:09 +0000 Subject: [PATCH 17/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index ae2fff1..1006fd9 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.7" \ No newline at end of file +version := "1.0.8" \ No newline at end of file From c11c46a93826d008d4a3c2efc38b62f1366f5b11 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Fri, 15 Apr 2022 03:19:37 +0200 Subject: [PATCH 18/29] Make Router impure --- src/main/scala/arch/infra/router/Router.scala | 2 +- .../scala/arch/infra/router/RouterF.scala | 67 +++++++++++++------ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 905712d..aa5d103 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -3,6 +3,6 @@ package arch.infra.router import scala.reflect.ClassTag trait Router[F[_]] { - def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): RouterF[F] + def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): Unit def publish[A <: Action](action: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index 65f841e..bc5601f 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -3,51 +3,80 @@ package arch.infra.router import arch.common.Program.{Context, MError, ProgramError} import arch.infra.logging.LoggingLibrary +import scala.collection.mutable import scala.reflect.ClassTag class RouterF[F[_]: MError]( - onSuccess: String => Unit = _ => { println("success") }, - onFailure: ProgramError => Unit = _ => { println("failure") }, - recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { println("recording latency") }, - handlers: Map[Class[_], Action => F[Any]] = Map.empty[Class[_], Action => F[Any]] -)(implicit logger: LoggingLibrary[F]) extends Router[F] { + onSuccess: String => Unit = _ => { println("success") }, + onFailure: ProgramError => Unit = _ => { println("failure") }, + recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { + println("recording latency") + } +)(implicit logger: LoggingLibrary[F]) + extends Router[F] { private val context: Context = Context("router") private val actionNotFoundErrorCode = 1 + val handlers: mutable.HashMap[Class[_], Action => F[Any]] = + mutable.HashMap.empty[Class[_], Action => F[Any]] + override def publish[A <: Action](action: A): F[A#ReturnType] = handlers .get(action.getClass) match { case Some(handler) => handleAction(action, handler) - case None => MError[F].raiseError( - ProgramError("action not found", s"action ${action.getClass.getSimpleName} not found", context, actionNotFoundErrorCode) - ) + case None => + MError[F].raiseError( + ProgramError( + "action not found", + s"action ${action.getClass.getSimpleName} not found", + context, + actionNotFoundErrorCode + ) + ) } - override def subscribe[A <: Action : ClassTag](handler: ActionHandler[F, A]): RouterF[F] = { + override def subscribe[A <: Action: ClassTag]( + handler: ActionHandler[F, A] + ): Unit = { val classTag = implicitly[ClassTag[A]] if (handlers.contains(classTag.runtimeClass)) { - logger.logWarn("handler already subscribed")(context.copy( - metadata = context.metadata + ("handler_name" -> handler.getClass.getSimpleName) - )) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers) + logger.logWarn("handler already subscribed")( + context.copy( + metadata = + context.metadata + ("handler_name" -> handler.getClass.getSimpleName) + ) + ) } else { - val transformed: Action => F[Any] = (t: Action) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers + (classTag.runtimeClass -> transformed)) + val transformed: Action => F[Any] = (t: Action) => + MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) + handlers.addOne((classTag.runtimeClass -> transformed)) } } - private def handleAction[A <: Action](action: A, handler: A => F[Any]): F[A#ReturnType] = { + private def handleAction[A <: Action]( + action: A, + handler: A => F[Any] + ): F[A#ReturnType] = { val before = System.currentTimeMillis() - val maybeResponse: F[A#ReturnType] = MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) + val maybeResponse: F[A#ReturnType] = + MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) val recoverable = MError[F].recoverWith(maybeResponse) { case error: ProgramError => onFailure(error) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + recordLatencyInMillis( + action.getClass.getSimpleName, + before, + System.currentTimeMillis() + ) maybeResponse } MError[F].map(recoverable) { result => onSuccess(action.getClass.getSimpleName) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + recordLatencyInMillis( + action.getClass.getSimpleName, + before, + System.currentTimeMillis() + ) result } } From 8dde98b46639efd592d4e8c271d07e4f66b3026f Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 15 Apr 2022 01:20:38 +0000 Subject: [PATCH 19/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 1006fd9..d0f0d6b 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.8" \ No newline at end of file +version := "1.0.9" \ No newline at end of file From dd6ee77e9284ead922e9ebaa9aa65f4c5dea634e Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Fri, 15 Apr 2022 03:29:06 +0200 Subject: [PATCH 20/29] Add typeclass to RouterF --- src/main/scala/arch/infra/router/RouterF.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index bc5601f..ea19833 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -17,8 +17,8 @@ class RouterF[F[_]: MError]( private val context: Context = Context("router") private val actionNotFoundErrorCode = 1 - val handlers: mutable.HashMap[Class[_], Action => F[Any]] = - mutable.HashMap.empty[Class[_], Action => F[Any]] + private val handlers: mutable.HashMap[Class[_], Action => F[Any]] = + mutable.HashMap.empty override def publish[A <: Action](action: A): F[A#ReturnType] = handlers @@ -81,3 +81,7 @@ class RouterF[F[_]: MError]( } } } + +object RouterF { + def apply[F[_]](implicit router: RouterF[F]): RouterF[F] = router +} From 71c214508159467ec52f54950aa95ae2139eba6d Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 15 Apr 2022 01:30:07 +0000 Subject: [PATCH 21/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index d0f0d6b..a3a880d 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.9" \ No newline at end of file +version := "1.0.10" \ No newline at end of file From ea8d791ad6f26bc584a80cb406e4bbad2605e077 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Fri, 15 Apr 2022 22:56:01 +0200 Subject: [PATCH 22/29] Fix .scalafix --- src/main/scala/arch/common/Program.scala | 64 ++++++++++++++----- .../arch/infra/monitoring/MonitoringF.scala | 6 +- .../scala/arch/infra/router/RouterF.scala | 1 + 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/main/scala/arch/common/Program.scala b/src/main/scala/arch/common/Program.scala index b3b378f..ef522c9 100644 --- a/src/main/scala/arch/common/Program.scala +++ b/src/main/scala/arch/common/Program.scala @@ -10,16 +10,38 @@ object Program { type MError[F[_]] = MonadError[F, ProgramError] case class Context(name: String, metadata: Map[String, Any] = Map.empty) - case class ProgramError(error: Any, msg: String, ctx: Context, errorCode: Int = unknownErrorCode, stackTrace: Seq[String] = Seq.empty) + case class ProgramError( + error: Any, + msg: String, + ctx: Context, + errorCode: Int = unknownErrorCode, + stackTrace: Seq[String] = Seq.empty + ) object ProgramError { - def fromThrowable(exception: Throwable): Context => Int => ProgramError = ctx => errorCode => { - val stackTrace = Seq.empty // exception.getStackTrace.map(_.toString).take(10) - ProgramError(exception, "error message: " + exception.getMessage, ctx, errorCode, stackTrace) - } - def fromError[E](e: E): Context => Int => ProgramError = ctx => errorCode => { - ProgramError(e, "error message: " + e.toString, ctx, errorCode, stackTrace = Seq.empty) - } + def fromThrowable(exception: Throwable): Context => Int => ProgramError = + ctx => + errorCode => { + val stackTrace = + Seq.empty // exception.getStackTrace.map(_.toString).take(10) + ProgramError( + exception, + "error message: " + exception.getMessage, + ctx, + errorCode, + stackTrace + ) + } + def fromError[E](e: E): Context => Int => ProgramError = ctx => + errorCode => { + ProgramError( + e, + "error message: " + e.toString, + ctx, + errorCode, + stackTrace = Seq.empty + ) + } } object MError { @@ -27,22 +49,32 @@ object Program { } object App { - def fromEither[F[_]: MError, E, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = + def fromEither[F[_]: MError, E, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = MError[F].fromEither[A]( a match { - case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) + case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) case Right(value) => Right(value) } ) - def fromTry[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = + def fromTry[F[_]: MError, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = MError[F].fromEither[A]( a match { - case Failure(exception) => Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) + case Failure(exception) => + Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) case Success(value) => Right(value) } ) - def lift[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) - def apply[F[_]: MError, A](ctx: Context, errorCode: => Int = unknownErrorCode)(a: => A): F[A] = lift(ctx)(errorCode)(a) - def unit[F[_]: MError](ctx: Context): F[Unit] = apply(ctx)() + def lift[F[_]: MError, A](ctx: Context)( + errorCode: => Int = unknownErrorCode + )(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) + def apply[F[_]: MError, A]( + ctx: Context, + errorCode: => Int = unknownErrorCode + )(a: => A): F[A] = lift(ctx)(errorCode)(a) + def unit[F[_]: MError](ctx: Context): F[Unit] = apply[F, Unit](ctx)(()) } -} \ No newline at end of file +} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringF.scala b/src/main/scala/arch/infra/monitoring/MonitoringF.scala index 86f9976..0a57eef 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringF.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringF.scala @@ -2,11 +2,13 @@ package arch.infra.monitoring import arch.common.Program.MError import arch.infra.monitoring.MonitoringLibrary.{CounterF, GaugeF, HistogramF} -import cats.implicits._ // @TODO implement on top of some dependency (ie: grafana, kamon) class MonitoringF[F[_]: MError] extends MonitoringLibrary[F] { - override def counter(name: String, context: Map[String, String]): CounterF[F] = new CounterF[F] { + override def counter( + name: String, + context: Map[String, String] + ): CounterF[F] = new CounterF[F] { override def increment(): F[Unit] = MError[F].pure(println(s"counter increment")) diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index ea19833..b13edbc 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -46,6 +46,7 @@ class RouterF[F[_]: MError]( context.metadata + ("handler_name" -> handler.getClass.getSimpleName) ) ) + () } else { val transformed: Action => F[Any] = (t: Action) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) From 7f370f47583038e88cd733288606f5bf317cbfae Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 15 Apr 2022 20:57:06 +0000 Subject: [PATCH 23/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index a3a880d..bcd7e48 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.10" \ No newline at end of file +version := "1.0.11" \ No newline at end of file From 8cca2df31341055d1950905021518f2c071529ee Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Fri, 15 Apr 2022 23:03:10 +0200 Subject: [PATCH 24/29] Action now is parametric on the Output type --- src/main/scala/arch/infra/router/Action.scala | 4 ++-- src/main/scala/arch/infra/router/ActionHandler.scala | 2 +- src/main/scala/arch/infra/router/Router.scala | 6 ++++-- src/main/scala/arch/infra/router/RouterF.scala | 12 ++++++------ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala index 6a1ad58..51967e5 100644 --- a/src/main/scala/arch/infra/router/Action.scala +++ b/src/main/scala/arch/infra/router/Action.scala @@ -1,5 +1,5 @@ package arch.infra.router -trait Action { - type ReturnType +trait Action[R] { + type ReturnType = R } diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala index 3729f3c..573a540 100644 --- a/src/main/scala/arch/infra/router/ActionHandler.scala +++ b/src/main/scala/arch/infra/router/ActionHandler.scala @@ -1,5 +1,5 @@ package arch.infra.router -trait ActionHandler[F[_], A <: Action] { +trait ActionHandler[F[_], Output, A <: Action[Output]] { def handle(a: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index aa5d103..936140b 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -3,6 +3,8 @@ package arch.infra.router import scala.reflect.ClassTag trait Router[F[_]] { - def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): Unit - def publish[A <: Action](action: A): F[A#ReturnType] + def subscribe[O, A <: Action[O]: ClassTag]( + handler: ActionHandler[F, O, A] + ): Unit + def publish[O, A <: Action[O]](action: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index b13edbc..2aae450 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -17,10 +17,10 @@ class RouterF[F[_]: MError]( private val context: Context = Context("router") private val actionNotFoundErrorCode = 1 - private val handlers: mutable.HashMap[Class[_], Action => F[Any]] = + private val handlers: mutable.HashMap[Class[_], Action[_] => F[Any]] = mutable.HashMap.empty - override def publish[A <: Action](action: A): F[A#ReturnType] = + override def publish[O, A <: Action[O]](action: A): F[A#ReturnType] = handlers .get(action.getClass) match { case Some(handler) => handleAction(action, handler) @@ -35,8 +35,8 @@ class RouterF[F[_]: MError]( ) } - override def subscribe[A <: Action: ClassTag]( - handler: ActionHandler[F, A] + override def subscribe[O, A <: Action[O]: ClassTag]( + handler: ActionHandler[F, O, A] ): Unit = { val classTag = implicitly[ClassTag[A]] if (handlers.contains(classTag.runtimeClass)) { @@ -48,13 +48,13 @@ class RouterF[F[_]: MError]( ) () } else { - val transformed: Action => F[Any] = (t: Action) => + val transformed: Action[_] => F[Any] = (t: Action[_]) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) handlers.addOne((classTag.runtimeClass -> transformed)) } } - private def handleAction[A <: Action]( + private def handleAction[O, A <: Action[O]]( action: A, handler: A => F[Any] ): F[A#ReturnType] = { From 30abb690f69371c4efad50474c59bd90eb3185a6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 15 Apr 2022 21:04:15 +0000 Subject: [PATCH 25/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index bcd7e48..e09e1bb 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.11" \ No newline at end of file +version := "1.0.12" \ No newline at end of file From 6667b07e8a400f853a0ae2b75361616a82e9cc20 Mon Sep 17 00:00:00 2001 From: miguelemosreverte Date: Sat, 16 Apr 2022 00:21:58 +0200 Subject: [PATCH 26/29] Remove type projection usage --- src/main/scala/arch/infra/router/Action.scala | 4 +--- src/main/scala/arch/infra/router/ActionHandler.scala | 2 +- src/main/scala/arch/infra/router/Router.scala | 2 +- src/main/scala/arch/infra/router/RouterF.scala | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala index 51967e5..9a48290 100644 --- a/src/main/scala/arch/infra/router/Action.scala +++ b/src/main/scala/arch/infra/router/Action.scala @@ -1,5 +1,3 @@ package arch.infra.router -trait Action[R] { - type ReturnType = R -} +trait Action[R] diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala index 573a540..9330c4a 100644 --- a/src/main/scala/arch/infra/router/ActionHandler.scala +++ b/src/main/scala/arch/infra/router/ActionHandler.scala @@ -1,5 +1,5 @@ package arch.infra.router trait ActionHandler[F[_], Output, A <: Action[Output]] { - def handle(a: A): F[A#ReturnType] + def handle(a: A): F[Output] } diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 936140b..1ae45eb 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -6,5 +6,5 @@ trait Router[F[_]] { def subscribe[O, A <: Action[O]: ClassTag]( handler: ActionHandler[F, O, A] ): Unit - def publish[O, A <: Action[O]](action: A): F[A#ReturnType] + def publish[O, A <: Action[O]](action: A): F[O] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index 2aae450..93ea089 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -20,7 +20,7 @@ class RouterF[F[_]: MError]( private val handlers: mutable.HashMap[Class[_], Action[_] => F[Any]] = mutable.HashMap.empty - override def publish[O, A <: Action[O]](action: A): F[A#ReturnType] = + override def publish[O, A <: Action[O]](action: A): F[O] = handlers .get(action.getClass) match { case Some(handler) => handleAction(action, handler) @@ -57,10 +57,10 @@ class RouterF[F[_]: MError]( private def handleAction[O, A <: Action[O]]( action: A, handler: A => F[Any] - ): F[A#ReturnType] = { + ): F[O] = { val before = System.currentTimeMillis() - val maybeResponse: F[A#ReturnType] = - MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) + val maybeResponse: F[O] = + MError[F].map(handler(action))(_.asInstanceOf[O]) val recoverable = MError[F].recoverWith(maybeResponse) { case error: ProgramError => onFailure(error) From e038745e0e7f62425265c18f42f8c5196db165d2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 15 Apr 2022 22:23:02 +0000 Subject: [PATCH 27/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index e09e1bb..d54f9a0 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.12" \ No newline at end of file +version := "1.0.13" \ No newline at end of file From d1932e6734f6ae4f69a8f5ff2e41f36392773a91 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Sat, 16 Apr 2022 02:04:34 +0200 Subject: [PATCH 28/29] TRY remove action handler + use Any in Router --- build.sbt | 52 +++++++++---------- .../scala/arch/infra/config/ConfigF.scala | 3 +- .../scala/arch/infra/json/JsonLibraryF.scala | 11 ++-- .../infra/monitoring/MonitoringLibrary.scala | 7 ++- src/main/scala/arch/infra/router/Action.scala | 4 +- .../arch/infra/router/ActionHandler.scala | 5 -- .../scala/arch/infra/router/DispatcherF.scala | 9 ++++ src/main/scala/arch/infra/router/Router.scala | 6 +-- .../scala/arch/infra/router/RouterF.scala | 20 +++---- version.sbt | 2 +- 10 files changed, 67 insertions(+), 52 deletions(-) delete mode 100644 src/main/scala/arch/infra/router/ActionHandler.scala create mode 100644 src/main/scala/arch/infra/router/DispatcherF.scala diff --git a/build.sbt b/build.sbt index 392dad5..9eaa8e8 100644 --- a/build.sbt +++ b/build.sbt @@ -1,26 +1,26 @@ -name := "boring" -scalaVersion := "2.13.3" -organization := "Prom3th3us" - -/* -import sbtghpackages.TokenSource.{Environment, GitConfig} -githubOwner := "Prom3th3us" -githubRepository := "boring" -githubTokenSource := TokenSource.Or( - GitConfig("github.token"), - Environment("PUBLISH_TOKEN") -)*/ - -libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" - -libraryDependencies ++= List( - "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", - "io.circe" %% "circe-generic" % "0.14.1" -) - -libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime -// https://mvnrepository.com/artifact/com.typesafe/config -libraryDependencies += "com.typesafe" % "config" % "1.4.2" - -// https://mvnrepository.com/artifact/org.slf4j/slf4j-api -libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.36" +name := "boring" +scalaVersion := "2.13.3" +organization := "Prom3th3us" + +/* +import sbtghpackages.TokenSource.{Environment, GitConfig} +githubOwner := "Prom3th3us" +githubRepository := "boring" +githubTokenSource := TokenSource.Or( + GitConfig("github.token"), + Environment("PUBLISH_TOKEN") +)*/ + +libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" + +libraryDependencies ++= List( + "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", + "io.circe" %% "circe-generic" % "0.14.1" +) + +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime +// https://mvnrepository.com/artifact/com.typesafe/config +libraryDependencies += "com.typesafe" % "config" % "1.4.2" + +// https://mvnrepository.com/artifact/org.slf4j/slf4j-api +libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.36" diff --git a/src/main/scala/arch/infra/config/ConfigF.scala b/src/main/scala/arch/infra/config/ConfigF.scala index 48b4ce8..08569a2 100644 --- a/src/main/scala/arch/infra/config/ConfigF.scala +++ b/src/main/scala/arch/infra/config/ConfigF.scala @@ -14,7 +14,8 @@ object ConfigF { def parse[A, F[_]: MError](cfg: Config)(fn: Config => A): F[A] = { val either = Try(fn(cfg)) match { - case Failure(exception) => Left(ProgramError(exception, exception.getMessage, ctx)) + case Failure(exception) => + Left(ProgramError(exception, exception.getMessage, ctx)) case Success(value) => Right(value) } MError[F].fromEither(either) diff --git a/src/main/scala/arch/infra/json/JsonLibraryF.scala b/src/main/scala/arch/infra/json/JsonLibraryF.scala index 8b04e4f..9987d5f 100644 --- a/src/main/scala/arch/infra/json/JsonLibraryF.scala +++ b/src/main/scala/arch/infra/json/JsonLibraryF.scala @@ -14,7 +14,9 @@ class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { class To[A](implicit d: Decoder[A]) extends JsonTo[A] { override def to(json: JsonType): F[A] = - Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)(d.decodeJson(json)) + Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)( + d.decodeJson(json) + ) } class From[A](implicit e: Encoder[A]) extends JsonFrom[A] { override def from(from: A): JsonType = e.apply(from) @@ -28,10 +30,13 @@ class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { } object JsonParserF extends JsonParser { override def parse(str: String): F[Json] = - Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)(parser.parse(str)) + Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)( + parser.parse(str) + ) } - def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = new JsonFromToF[A](new To(), new From()) + def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = + new JsonFromToF[A](new To(), new From()) def prettyPrint(json: Json): String = PrettyPrinter.prettyPrint(json) def parse(json: String): F[Json] = JsonParserF.parse(json) } diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala index 3c72685..3c3ca71 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala @@ -5,7 +5,10 @@ import cats.implicits._ trait MonitoringLibrary[F[_]] { import MonitoringLibrary._ - def counter(name: String, context: Map[String, String] = Map.empty): CounterF[F] + def counter( + name: String, + context: Map[String, String] = Map.empty + ): CounterF[F] def gauge(name: String): GaugeF[F] def histogram(name: String): HistogramF[F] } @@ -42,7 +45,7 @@ object MonitoringLibrary { for { result <- codeToBenchmark _ = record(System.currentTimeMillis() - before) - } yield result + } yield result } } } diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala index 9a48290..51967e5 100644 --- a/src/main/scala/arch/infra/router/Action.scala +++ b/src/main/scala/arch/infra/router/Action.scala @@ -1,3 +1,5 @@ package arch.infra.router -trait Action[R] +trait Action[R] { + type ReturnType = R +} diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala deleted file mode 100644 index 9330c4a..0000000 --- a/src/main/scala/arch/infra/router/ActionHandler.scala +++ /dev/null @@ -1,5 +0,0 @@ -package arch.infra.router - -trait ActionHandler[F[_], Output, A <: Action[Output]] { - def handle(a: A): F[Output] -} diff --git a/src/main/scala/arch/infra/router/DispatcherF.scala b/src/main/scala/arch/infra/router/DispatcherF.scala new file mode 100644 index 0000000..7c32bbc --- /dev/null +++ b/src/main/scala/arch/infra/router/DispatcherF.scala @@ -0,0 +1,9 @@ +package arch.infra.router + +trait DispatcherF[F[_]] { + def dispatch[A <: Action[Any]]( + a: A + )(implicit fn: A => F[A#ReturnType]): F[A#ReturnType] = { + fn(a) + } +} diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 1ae45eb..63e132a 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -3,8 +3,8 @@ package arch.infra.router import scala.reflect.ClassTag trait Router[F[_]] { - def subscribe[O, A <: Action[O]: ClassTag]( - handler: ActionHandler[F, O, A] + def subscribe[A <: Action[Any]: ClassTag]( + handler: A => F[A#ReturnType] ): Unit - def publish[O, A <: Action[O]](action: A): F[O] + def publish[A <: Action[Any]](action: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index 93ea089..3bdf56a 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -17,10 +17,10 @@ class RouterF[F[_]: MError]( private val context: Context = Context("router") private val actionNotFoundErrorCode = 1 - private val handlers: mutable.HashMap[Class[_], Action[_] => F[Any]] = + private val handlers: mutable.HashMap[Class[_], Action[Any] => F[Any]] = mutable.HashMap.empty - override def publish[O, A <: Action[O]](action: A): F[O] = + override def publish[A <: Action[Any]](action: A): F[A#ReturnType] = handlers .get(action.getClass) match { case Some(handler) => handleAction(action, handler) @@ -35,32 +35,32 @@ class RouterF[F[_]: MError]( ) } - override def subscribe[O, A <: Action[O]: ClassTag]( - handler: ActionHandler[F, O, A] + override def subscribe[A <: Action[Any]: ClassTag]( + handler: A => F[A#ReturnType] ): Unit = { val classTag = implicitly[ClassTag[A]] if (handlers.contains(classTag.runtimeClass)) { logger.logWarn("handler already subscribed")( context.copy( metadata = - context.metadata + ("handler_name" -> handler.getClass.getSimpleName) + context.metadata + ("handler_name" -> classTag.runtimeClass.getClass.getSimpleName) ) ) () } else { val transformed: Action[_] => F[Any] = (t: Action[_]) => - MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) + MError[F].map(handler(t.asInstanceOf[A]))(_.asInstanceOf[Any]) handlers.addOne((classTag.runtimeClass -> transformed)) } } - private def handleAction[O, A <: Action[O]]( + private def handleAction[A <: Action[Any]]( action: A, handler: A => F[Any] - ): F[O] = { + ): F[A#ReturnType] = { val before = System.currentTimeMillis() - val maybeResponse: F[O] = - MError[F].map(handler(action))(_.asInstanceOf[O]) + val maybeResponse: F[A#ReturnType] = + MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) val recoverable = MError[F].recoverWith(maybeResponse) { case error: ProgramError => onFailure(error) diff --git a/version.sbt b/version.sbt index d54f9a0..e5b2dad 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.13" \ No newline at end of file +version := "1.0.13" From 3c6a8c9033783e503b8ecefb1ed6ad4ff23cfb8b Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Sat, 16 Apr 2022 00:07:33 +0000 Subject: [PATCH 29/29] Bump version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index e5b2dad..0f83621 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "1.0.13" +version := "1.0.14" \ No newline at end of file