gRPC

gRPC Libraries for ScalaPB#

This page covers ScalaPB's gRPC support. This support is a thin wrapper around grpc-java, and provides you with an interface that is based on Scala's standard library Future, while streaming is based on the Observer pattern.

There are additional gRPC libraries built on top of ScalaPB that provide integration with other concurrency frameworks and effect systems:

  • ZIO gRPC enables you to build gRPC servers and clients using ZIO.
  • fs2-grpc provides gGRPC support for FS2 and Cats Effect.
  • Akka gRPC provides support for building streaming gRPC servers and clients on top of Akka Streams and Akka HTTP.
  • Pekko gRPC provides support for building streaming gRPC servers and clients on top of Pekko Streams and Pekko HTTP.
  • http4s-grpc enables you to build gRPC clients and servers with Cats Effect and FS2. It is implemented on top of http4s Ember and runs on JVM, Node.js, and Scala Native.

Project Setup#

Install ScalaPB as usual. Add the following to your build.sbt:

libraryDependencies ++= Seq(
"io.grpc" % "grpc-netty" % scalapb.compiler.Version.grpcJavaVersion,
"com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion
)

Creating a service#

When you run sbt compile, ScalaPB will generate code for your servers. This includes an asynchronous client, a blocking and a base trait you can extend to implement the server side.

For example, if your proto file in src/main/protobuf/hello.proto looks like this:

syntax = "proto3";
package com.example.protos;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

The generated code will look like this (simplified and commented):

object GreeterGrpc {
// Base trait for an asynchronous client and server.
trait Greeter extends AbstractService {
def serviceCompanion = Greeter
def sayHello(request: HelloRequest): Future[HelloReply]
}
object Greeter extends ServiceCompanion[Greeter] {
def descriptor = ...
}
// Abstract trait for a blocking client:
trait GreeterBlockingClient {
def serviceCompanion = Greeter
def sayHello(request: HelloRequest): HelloReply
}
// Concrete blocking client:
class GreeterBlockingStub(channel, options) extends [...] with GreeterBlockingClient {
// omitted
}
// Concrete asynchronous client:
class GreeterStub(channel, options) extends io.grpc.stub.AbstractStub[GreeterStub](channel, options) with Greeter {
// omitted
}
// Creates a new blocking client.
def blockingStub(channel: io.grpc.Channel): GreeterBlockingStub = new GreeterBlockingStub(channel)
// Creates a new asynchronous client.
def stub(channel: io.grpc.Channel): GreeterStub = new GreeterStub(channel)
}

Using the client#

Creating a channel:

val channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build
val request = HelloRequest(name = "World")

Blocking call:

val blockingStub = GreeterGrpc.blockingStub(channel)
val reply: HelloReply = blockingStub.sayHello(request)
println(reply)

Async call:

val stub = GreeterGrpc.stub(channel)
val f: Future[HelloReply] = stub.sayHello(request)
f onComplete println

Writing a server#

Implement the service:

private class GreeterImpl extends GreeterGrpc.Greeter {
override def sayHello(req: HelloRequest) = {
val reply = HelloReply(message = "Hello " + req.name)
Future.successful(reply)
}
}

See complete example server here.

Streaming clients, streaming servers, bidi#

Scalapb-grpc supports both client and server streaming. The Scala API follows closely the offical grpc-java API. Example project coming soon.

grpc-netty issues#

In certain situations (for example when you have a fat jar), you may see the following exception:

Exception in thread "main" io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional server found. Try adding a dependency on the grpc-netty artifact

To work around this issue, try the following solutions:

  1. Create a NettyServer explicitly using io.grpc.netty.NettyServerBuilder.

Example:

NettyServerBuilder
.forPort(9000)
.keepAliveTime(500, TimeUnit.SECONDS)
  1. If using SBT, try the following merge conflict strategy:
assemblyMergeStrategy in assembly := {
case x if x.contains("io.netty.versions.properties") => MergeStrategy.discard
case x =>
val oldStrategy = (assemblyMergeStrategy in assembly).value
oldStrategy(x)
}