Hello I'm starting a project with ZIO, zio-http, Tapir and Scala 3, I'm configuring ZLayers:
my routes:
object DeckRoute:
val deckRoutes: List[ZServerEndpoint[Any, Any]] =
List(
listRoute(),
findByIdRoute(),
insertRoute(),
updateRoute(),
deleteRoute()
)
private def listRoute(): ZServerEndpoint[Any, Any] =
def listRouteLogic() =
ListDeck
.list()
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
d => d.map(_.into[DeckListResponse].transform)
)
.either
.provideLayer(appEnvironment)
DeckEndpoint.listEndpoint.serverLogic(_ => listRouteLogic())
private def findByIdRoute(): ZServerEndpoint[Any, Any] =
def findByIdRouteLogic(id: Long) =
FindByIdDeck
.findById(id)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckDetailsResponse].transform
)
.either
.provideLayer(appEnvironment)
DeckEndpoint.findByIdEndpoint.serverLogic(p => findByIdRouteLogic(p))
private def insertRoute(): ZServerEndpoint[Any, Any] =
def insertRouteLogic(request: DeckInsertRequest) =
InsertDeck
.insert(request.into[InsertDeckDomain].transform)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckInsertedResponse].transform
)
.either
.provideLayer(appEnvironment)
DeckEndpoint.insertEndpoint.serverLogic(p => insertRouteLogic(p))
private def updateRoute(): ZServerEndpoint[Any, Any] =
def updateRouteLogic(id: Long, request: DeckUpdateRequest) =
UpdateDeck
.update(
request.into[UpdateDeckDomain].withFieldConst(_.id, id).transform
)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckUpdatedResponse].transform
)
.either
.provideLayer(appEnvironment)
DeckEndpoint.updateEndpoint.serverLogic(p => updateRouteLogic(p._1, p._2))
private def deleteRoute(): ZServerEndpoint[Any, Any] =
def deleteRouteLogic(id: Long) =
DeleteDeck
.delete(id)
.orElseFail(DeckError.GenericError("", "", 500, OffsetDateTime.now()))
.either
.provideLayer(appEnvironment)
DeckEndpoint.deleteEndpoint.serverLogic(p => deleteRouteLogic(p))
my layers config
object Environment:
type AppType = ListDeck & FindByIdDeck & InsertDeck & UpdateDeck & DeleteDeck
val appEnvironment: ZLayer[Any, Nothing, AppType] =
ZLayer.make[AppType](DeckService.layer, DeckQueryService.layer)
my main class
object App extends ZIOAppDefault:
private val routes: Routes[Any, Response] =
ZioHttpInterpreter().toHttp(swaggerEndpoints ++ deckRoutes)
val run: ZIO[Any, Throwable, Nothing] = Server
.serve(routes)
.provide(
ZLayer.succeed(Server.Config.default.port(8080)),
Server.live
)
Im my DeckRoutes I defined for each route .provideLayer(appEnvironment)
. I wanted do use a global config to define my layers insteade of set for each endpoint, it's possible ?
my dependencies:
ThisBuild / scalaVersion := "3.3.4"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "br.com.flashcards"
ThisBuild / organizationName := "zio-flashcards"
val tapirVersion = "1.11.9"
val zioVersion = "2.1.13"
val circeVersion = "0.14.10"
val catsVersion = "2.12.0"
lazy val root = (project in file("."))
.settings(
name := "zio-flashcards",
libraryDependencies ++= Seq(
"dev.zio" %% "zio" % zioVersion,
"dev.zio" %% "zio-http" % "3.0.1",
"com.softwaremill.sttp.tapir" %% "tapir-zio" % tapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % tapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % tapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % tapirVersion,
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion,
"io.scalaland" %% "chimney" % "1.5.0",
"dev.zio" %% "zio-test" % zioVersion % Test
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
To solve my problem I follow a example shared by @Gastón Schabas, I transformed my route object in a Class, create a ZLayer and insert it in a ZIO provide in a Main class :
package br.com.flashcards
import br.com.flashcards.adapter.endpoint.DeckEndpoint
import br.com.flashcards.config.EndpointConfig
import br.com.flashcards.core.service.impl.DeckService
import br.com.flashcards.core.service.query.impl.DeckQueryService
import sttp.tapir.server.interceptor.cors.CORSConfig.AllowedOrigin
import sttp.tapir.server.interceptor.cors.{CORSConfig, CORSInterceptor}
import sttp.tapir.server.ziohttp.{ZioHttpInterpreter, ZioHttpServerOptions}
import zio.*
import zio.http.*
object App extends ZIOAppDefault:
override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
val options: ZioHttpServerOptions[Any] =
ZioHttpServerOptions.customiseInterceptors
.corsInterceptor(
CORSInterceptor.customOrThrow(
CORSConfig.default.copy(
allowedOrigin = AllowedOrigin.All
)
)
)
.options
(for {
endpoints <- ZIO.service[EndpointConfig]
httpApp = ZioHttpInterpreter(options).toHttp(endpoints.endpoints)
actualPort <- Server.install(httpApp)
_ <- Console.printLine(s"Application zio-flashcards started")
_ <- Console.printLine(
s"Go to http://localhost:8080/docs to open SwaggerUI"
)
_ <- ZIO.never
} yield ())
.provide(
EndpointConfig.layer,
DeckRoute.layer,
DeckService.layer,
DeckQueryService.layer,
Server.defaultWithPort(8080)
)
.exitCode
my route. Obs: I refactored my traits with insert, update, delete and find in two traits, Read and Write Traits
package br.com.flashcards.adapter.endpoint
import br.com.flashcards.adapter.endpoint.doc.DeckDocEndpoint
import br.com.flashcards.adapter.endpoint.request.{
DeckInsertRequest,
DeckUpdateRequest
}
import br.com.flashcards.adapter.endpoint.response.error.DeckError
import br.com.flashcards.adapter.endpoint.response.{
DeckDetailsResponse,
DeckInsertedResponse,
DeckListResponse,
DeckUpdatedResponse
}
import br.com.flashcards.core.exception.DeckException
import br.com.flashcards.core.service.query.DeckRead
import br.com.flashcards.core.service.{
DeckWrite,
InsertDeckDomain,
UpdateDeckDomain
}
import io.scalaland.chimney.dsl.*
import sttp.tapir.ztapir.*
import zio.*
import java.time.OffsetDateTime
case class DeckEndpoint(
write: DeckWrite,
read: DeckRead
):
val endpoints: List[ZServerEndpoint[Any, Any]] =
List(
listRoute(),
findByIdRoute(),
insertRoute(),
updateRoute(),
deleteRoute()
)
private def listRoute(): ZServerEndpoint[Any, Any] =
def listRouteLogic() =
read
.list()
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
d => d.map(_.into[DeckListResponse].transform)
)
DeckDocEndpoint.listEndpoint.zServerLogic(_ => listRouteLogic())
private def findByIdRoute(): ZServerEndpoint[Any, Any] =
def findByIdRouteLogic(
id: Long
) =
read
.findById(id)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckDetailsResponse].transform
)
DeckDocEndpoint.findByIdEndpoint.zServerLogic(p => findByIdRouteLogic(p))
private def insertRoute(): ZServerEndpoint[Any, Any] =
def insertRouteLogic(
request: DeckInsertRequest
) =
write
.insert(request.into[InsertDeckDomain].transform)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckInsertedResponse].transform
)
DeckDocEndpoint.insertEndpoint.zServerLogic(p => insertRouteLogic(p))
private def updateRoute(): ZServerEndpoint[Any, Any] =
def updateRouteLogic(
id: Long,
request: DeckUpdateRequest
) =
write
.update(
request.into[UpdateDeckDomain].withFieldConst(_.id, id).transform
)
.mapBoth(
_ => DeckError.GenericError("", "", 500, OffsetDateTime.now()),
_.into[DeckUpdatedResponse].transform
)
DeckDocEndpoint.updateEndpoint.zServerLogic(p =>
updateRouteLogic(p._1, p._2)
)
private def deleteRoute(): ZServerEndpoint[Any, Any] =
def deleteRouteLogic(
id: Long
) =
write
.delete(id)
.orElseFail(DeckError.GenericError("", "", 500, OffsetDateTime.now()))
DeckDocEndpoint.deleteEndpoint.zServerLogic(p => deleteRouteLogic(p))
object DeckRoute:
val layer: ZLayer[
DeckWrite & DeckRead,
DeckException,
DeckRoute
] = ZLayer.fromFunction(DeckEndpoint(_, _))
Thank you :)