diff --git a/build.sbt b/build.sbt index adb911759..4684a47a7 100644 --- a/build.sbt +++ b/build.sbt @@ -27,6 +27,8 @@ lazy val root = project example, flyway, flywayPureConfig, + grpcServer, + grpcServerPureConfig, http4sClientBlaze, http4sClientBlazePureConfig, http4sClientMonixCatnap, @@ -168,6 +170,27 @@ lazy val flywayPureConfig = project libraryDependencies += Dependencies.pureConfig ) +lazy val grpcServer = project + .in(file("grpc-server")) + .settings(commonSettings) + .settings( + name := "sst-grpc-server", + libraryDependencies ++= Seq( + Dependencies.grpcNettyShaded, + Dependencies.grpcProtobuf, + Dependencies.grpcStub + ) + ) + +lazy val grpcServerPureConfig = project + .in(file("grpc-server-pureconfig")) + .dependsOn(grpcServer) + .settings(commonSettings) + .settings( + name := "sst-grpc-server-pureconfig", + libraryDependencies += Dependencies.pureConfig + ) + lazy val http4sClientBlaze = project .in(file("http4s-client-blaze")) .settings(commonSettings) diff --git a/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/ConfigReaders.scala b/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/ConfigReaders.scala new file mode 100644 index 000000000..3b2e15192 --- /dev/null +++ b/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/ConfigReaders.scala @@ -0,0 +1,11 @@ +package com.avast.sst.grpc.server.pureconfig + +import com.avast.sst.grpc.server.GrpcServerConfig +import pureconfig.ConfigReader +import pureconfig.generic.semiauto.deriveReader + +trait ConfigReaders { + + implicit val grpcServerGrpcServerConfigReader: ConfigReader[GrpcServerConfig] = deriveReader + +} diff --git a/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/implicits.scala b/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/implicits.scala new file mode 100644 index 000000000..5e8bb0f37 --- /dev/null +++ b/grpc-server-pureconfig/src/main/scala/com/avast/sst/grpc/server/pureconfig/implicits.scala @@ -0,0 +1,20 @@ +package com.avast.sst.grpc.server.pureconfig + +import pureconfig.ConfigFieldMapping +import pureconfig.generic.ProductHint + +/** Contains [[pureconfig.ConfigReader]] instances with default "kebab-case" naming convention. */ +object implicits extends ConfigReaders { + + /** Contains [[pureconfig.ConfigReader]] instances with "kebab-case" naming convention. + * + * This is alias for the default `implicits._` import. + */ + object KebabCase extends ConfigReaders + + /** Contains [[pureconfig.ConfigReader]] instances with "camelCase" naming convention. */ + object CamelCase extends ConfigReaders { + implicit def hint[T]: ProductHint[T] = ProductHint(ConfigFieldMapping(pureconfig.CamelCase, pureconfig.CamelCase)) + } + +} diff --git a/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerConfig.scala b/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerConfig.scala new file mode 100644 index 000000000..4a656a541 --- /dev/null +++ b/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerConfig.scala @@ -0,0 +1,11 @@ +package com.avast.sst.grpc.server + +import java.util.concurrent.TimeUnit + +import scala.concurrent.duration.Duration + +final case class GrpcServerConfig(port: Int, + handshakeTimeout: Duration, + maxInboundMessageSize: Int = 4 * 1024 * 1024, + maxInboundMetadataSize: Int = 8192, + serverShutdownTimeout: Duration = Duration(10, TimeUnit.SECONDS)) diff --git a/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerModule.scala b/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerModule.scala new file mode 100644 index 000000000..1c95afd2c --- /dev/null +++ b/grpc-server/src/main/scala/com/avast/sst/grpc/server/GrpcServerModule.scala @@ -0,0 +1,44 @@ +package com.avast.sst.grpc.server + +import java.util.concurrent.TimeUnit + +import cats.effect.{Resource, Sync} +import io.grpc.{Server, ServerBuilder, ServerInterceptor, ServerServiceDefinition} + +import scala.collection.immutable.Seq +import scala.concurrent.ExecutionContext +object GrpcServerModule { + + /** Makes [[io.grpc.Server]] (Netty) initialized with the given config, services and interceptors. + * + * @param services service implementations to be added to the handler registry + * @param executionContext executor to be used for the server + * @param interceptors that are run for all the services + */ + @SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) + def make[F[_]: Sync](config: GrpcServerConfig, + services: Seq[ServerServiceDefinition], + executionContext: ExecutionContext, + interceptors: Seq[ServerInterceptor] = List.empty): Resource[F, Server] = + Resource.make { + Sync[F].delay { + val builder = ServerBuilder + .forPort(config.port) + .handshakeTimeout(config.handshakeTimeout.toMillis, TimeUnit.MILLISECONDS) + .maxInboundMessageSize(config.maxInboundMessageSize) + .maxInboundMetadataSize(config.maxInboundMetadataSize) + .executor(executionContext.execute) + + services.foreach(builder.addService) + interceptors.foreach(builder.intercept) + + builder.build.start() + } + } { s => + Sync[F].delay { + s.shutdown().awaitTermination(config.serverShutdownTimeout.toMillis, TimeUnit.MILLISECONDS) + () + } + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 175890386..9f862e908 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -7,6 +7,9 @@ object Dependencies { val doobie = "org.tpolecat" %% "doobie-core" % Versions.doobie val doobieHikari = "org.tpolecat" %% "doobie-hikari" % Versions.doobie val flywayCore = "org.flywaydb" % "flyway-core" % "6.1.2" + val grpcNettyShaded = "io.grpc" % "grpc-netty-shaded" % Versions.grpc + val grpcProtobuf = "io.grpc" % "grpc-protobuf" % Versions.grpc + val grpcStub = "io.grpc" % "grpc-stub" % Versions.grpc val http4sBlazeClient = "org.http4s" %% "http4s-blaze-client" % Versions.http4s val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % Versions.http4s val http4sClient = "org.http4s" %% "http4s-client" % Versions.http4s @@ -35,6 +38,7 @@ object Dependencies { val datastaxJavaDriverCore = "4.3.1" val doobie = "0.7.1" + val grpc = "1.25.0" val http4s = "0.20.15" val micrometerCore = "1.3.2" val micrometerJmx = "1.3.2"