Skip to content

Commit

Permalink
Feat: add Reaktoro integration (#11)
Browse files Browse the repository at this point in the history
* feat(all): integrate ReactionService & fix comments

* feat(tests): fix tests using service integration

* feat(reaktoro): add Reaktoro service & redo arch (separation of concerns)

* fix(preprocessor): fix  JSON decoding issue & improve services' pipelines

* feat(env): create .env example

* feat(reaktoro): integrate Reaktoro service & fix tests

* fix(docker): add repositories explicitly

* fix(reaktoro): include routes into the Main & fix tests
  • Loading branch information
sobakavosne authored Nov 18, 2024
1 parent 85d87c3 commit 30d9191
Show file tree
Hide file tree
Showing 36 changed files with 1,167 additions and 444 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ RUN sbt update

COPY src/ ./src/

RUN sbt compile
RUN sbt clean compile

COPY . .

Expand Down
13 changes: 11 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Test / scalaSource := baseDirectory.value / "src" / "test" / "scala"

lazy val root = (project in file("."))
.settings(
name := "chemist-flow",
name := ".",
libraryDependencies ++= Seq(
scalaLogging,
scalaTest,
Expand All @@ -40,4 +40,13 @@ lazy val root = (project in file("."))
)
)

resolvers += "Akka library repository".at("https://repo.akka.io/maven")
scalacOptions ++= Seq(
"-Xmax-inlines", "64"
)

resolvers ++= Seq(
"Akka library repository" at "https://repo.akka.io/maven",
"Sonatype OSS Releases" at "https://oss.sonatype.org/content/repositories/releases/",
"Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/",
"Maven Central" at "https://repo1.maven.org/maven2/"
)
13 changes: 13 additions & 0 deletions examples/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
AKKA_LICENSE_KEY=...

CHEMIST_FLOW_HOST=localhost
CHEMIST_FLOW_PORT=8081

POSTGRE_URL=jdbc:postgresql://localhost:5432/chemist_db
POSTGRE_USER=chemist
POSTGRE_PASSWORD=chemist_password
POSTGRE_DRIVER=org.postgresql.Driver

CHEMIST_PREPROCESSOR_BASE_URI=http://localhost:8080

CHEMIST_ENGINE_BASE_URI=http://localhost:8082
9 changes: 8 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4")
addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.17.5")
resolvers += "Scalafmt Releases" at "https://oss.sonatype.org/content/repositories/releases"

resolvers ++= Seq(
"Scalafmt Releases" at "https://oss.sonatype.org/content/repositories/releases",
"Sonatype OSS Releases" at "https://oss.sonatype.org/content/repositories/releases",
"Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/",
"Maven Central" at "https://repo1.maven.org/maven2/",
"Akka repository" at "https://repo.akka.io/maven"
)
49 changes: 0 additions & 49 deletions src/main/scala/api/Endpoints.scala

This file was deleted.

7 changes: 5 additions & 2 deletions src/main/scala/api/ServerBuilder.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package api

import cats.effect.{IO, Resource}

import com.comcast.ip4s.{Host, Port}

import org.http4s.server.Server
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.HttpRoutes

class ServerBuilder(
implicit endpoints: Endpoints
routes: HttpRoutes[IO]
) {

def startServer(
Expand All @@ -17,7 +20,7 @@ class ServerBuilder(
.default[IO]
.withHost(host)
.withPort(port)
.withHttpApp(ErrorHandler(endpoints.routes).orNotFound)
.withHttpApp(ErrorHandler(routes).orNotFound)
.build
}

Expand Down
54 changes: 54 additions & 0 deletions src/main/scala/api/endpoints/flow/ReaktoroEndpoints.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package api.endpoints.flow

import cats.effect.IO

import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.server.Router
import org.http4s.server.middleware.Logger
import org.http4s.circe.CirceEntityEncoder.circeEntityEncoder
import org.http4s.circe.CirceSensitiveDataEntityDecoder.circeEntityDecoder

import io.circe.syntax._
import io.circe.generic.auto.deriveEncoder

import core.services.flow.ReaktoroService
import core.domain.preprocessor.ReactionId
import core.domain.flow.{DataBase, MoleculeAmountList}

case class ComputePropsRequest(
reactionId: ReactionId,
database: DataBase,
amounts: MoleculeAmountList
)

object ComputePropsRequest {
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

implicit val decoder: Decoder[ComputePropsRequest] = deriveDecoder
implicit val encoder: Encoder[ComputePropsRequest] = deriveEncoder
}

class ReaktoroEndpoints(
reaktoroService: ReaktoroService[IO]
) {

private val computeSystemPropsForReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> Root / "system" / "properties" =>
req.as[ComputePropsRequest].flatMap {
case ComputePropsRequest(reactionId, database, amounts) =>
reaktoroService
.computeSystemPropsForReaction(reactionId, database, amounts)
.flatMap(result => Ok(result.asJson))
.handleErrorWith(ex => InternalServerError(("InternalError", ex.getMessage).asJson))
}
}

val routes: HttpRoutes[IO] = Logger.httpRoutes(logHeaders = true, logBody = true)(
Router(
"/api" -> computeSystemPropsForReactionRoute
)
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package api.endpoints.preprocessor

import cats.effect.IO
import cats.syntax.semigroupk.toSemigroupKOps

import io.circe.syntax.EncoderOps

import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.server.Router
import org.http4s.server.middleware.Logger
import org.http4s.circe.CirceEntityEncoder.circeEntityEncoder
import org.http4s.circe.CirceSensitiveDataEntityDecoder.circeEntityDecoder

import core.services.preprocessor.{MechanismService, ReactionService}
import core.domain.preprocessor.{MechanismDetails, Reaction, ReactionDetails}
import core.errors.http.preprocessor.ReactionError

class PreprocessorEndpoints(
reactionService: ReactionService[IO],
mechanismService: MechanismService[IO]
) {

private val getReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "reaction" / id =>
validateId(id) match {
case Some(validId) =>
reactionService.getReaction(validId).flatMap {
(reactionDetails: ReactionDetails) => Ok(reactionDetails.asJson)
}.handleErrorWith {
case _: ReactionError.NotFoundError => NotFound(("NotFound", s"Reaction with ID $validId not found"))
case ex => InternalServerError(("InternalError", ex.getMessage))
}
case None => BadRequest(("BadRequest", "ID must be an integer"))
}
}

private val postReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> Root / "reaction" =>
req.as[Reaction].flatMap { reaction =>
reactionService.createReaction(reaction).flatMap {
createdReaction => Created(createdReaction.asJson)
}.handleErrorWith {
case _: ReactionError.CreationError => BadRequest(("CreationError", "Failed to create reaction"))
case ex => InternalServerError(("InternalError", ex.getMessage))
}
}
}

private val deleteReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case DELETE -> Root / "reaction" / id =>
validateId(id) match {
case Some(validId) =>
reactionService.deleteReaction(validId).flatMap {
case Right(_) => NoContent()
case Left(error) => BadRequest(("DeletionError", error.message))
}
case None => BadRequest(("BadRequest", "ID must be an integer"))
}
}

private val getMechanismRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "mechanism" / id =>
validateId(id) match {
case Some(validId) =>
mechanismService.getMechanism(validId).flatMap {
(mechanismDetails: MechanismDetails) => Ok(mechanismDetails.asJson)
}.handleErrorWith {
case _: ReactionError.NotFoundError => NotFound(("NotFound", s"Mechanism with ID $validId not found"))
case ex => InternalServerError(("InternalError", ex.getMessage))
}
case None => BadRequest(("BadRequest", "ID must be an integer"))
}
}

private def validateId(id: String): Option[Int] = id.toIntOption

val routes: HttpRoutes[IO] = Logger.httpRoutes(logHeaders = false, logBody = true)(
Router(
"/api" -> (getReactionRoute <+> postReactionRoute <+> deleteReactionRoute <+> getMechanismRoute)
)
)

}
Loading

0 comments on commit 30d9191

Please sign in to comment.