Recommendation: Talks by Mete Atamel (Google)
https://drive.google.com/file/d/0B6j6Te0viCnHQVlFNUpDOGhlRzg
via ScalaPB
compiler plugin
proto runtime dependencies
gRPC runtime dependencies
ScalaJS support
Spark support
JSON conversion
+ grpc-java
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.12")
libraryDependencies += "com.trueaccord.scalapb" %% "compilerplugin" % "0.6.6"
plugins.sbt
// minimum for protobuf compilation
PB.targets in Compile := Seq(
scalapb.gen() -> (sourceManaged in Compile).value
)
libraryDependencies ++= Seq(
// required for customizations and access to google/protobuf/*.proto
"com.trueaccord.scalapb" %% "scalapb-runtime"
% com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf",
// next two lines are for gRPC
"com.trueaccord.scalapb" %% "scalapb-runtime-grpc"
% com.trueaccord.scalapb.compiler.Version.scalapbVersion,
"io.grpc" % "grpc-netty"
% com.trueaccord.scalapb.compiler.Version.grpcJavaVersion
)
build.sbt
syntax = "proto3";
package io.ontherocks.hellogrpc;
service HelloWorld {
rpc SayHello(ToBeGreeted) returns (Greeting) {}
}
message ToBeGreeted {
string person = 1;
}
message Greeting {
string message = 1;
}
src/main/protobuf/helloworld.proto
sbt compile
libraryDependencies += "com.trueaccord.scalapb" %% "scalapb-runtime" %
com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf"
import "scalapb/scalapb.proto";
option (scalapb.options) = {
// use a custom Scala package name
package_name: "io.ontherocks.awesomegrpc"
// don't append file name to package
flat_package: true
// generate one Scala file for all messages (services still get their own file)
single_file: true
// add imports to generated file
// useful when extending traits or using custom types
import: "io.ontherocks.hellogrpc.RockingMessage"
// code to put at the top of generated file
// works only with `single_file: true`
preamble: "sealed trait SomeSealedTrait"
};
src/main/protobuf/customizations.proto
syntax = "proto3";
package io.ontherocks.hellogrpc;
message ToBeGreeted {
message Person {
string name = 1;
}
Person person = 1;
string msg = 2;
}
src/main/protobuf/helloworld.proto
final case class ToBeGreeted(
person: scala.Option[ToBeGreeted.Person] = None,
msg: String = ""
) extends com.trueaccord.scalapb.GeneratedMessage with ...
src_managed/main/scala/io/ontherocks/hellogrpc/helloworld/ToBeGreeted.scala
"Note that for scalar message fields, once a message is parsed there's no way of telling whether a field was explicitly set to the default value or just not set at all."
message ToBeGreeted {
message Person {
string name = 1;
}
Person person = 1;
string msg = 2;
}
src/main/protobuf/helloworld.proto
final case class ToBeGreeted(
person: scala.Option[ToBeGreeted.Person] = None,
msg: String = ""
) extends com.trueaccord.scalapb.GeneratedMessage with ...
src_managed/main/scala/io/ontherocks/hellogrpc/helloworld/ToBeGreeted.scala
import "google/protobuf/wrappers.proto";
message ToBeGreeted {
message Person {
string name = 1;
}
Person person = 1;
google.protobuf.StringValue msg = 2;
}
src/main/protobuf/helloworld.proto
final case class ToBeGreeted(
person: scala.Option[ToBeGreeted.Person] = None,
msg: scala.Option[String] = None
) extends com.trueaccord.scalapb.GeneratedMessage with ...
src_managed/main/scala/io/ontherocks/hellogrpc/helloworld/ToBeGreeted.scala
package io.ontherocks.hellogrpc
trait RockingMessage {
val rocking = "I rock!"
}
src/main/scala/io/ontherocks/hellogrpc/RockingMessage.scala
syntax = "proto3";
import "scalapb/scalapb.proto";
package io.ontherocks.hellogrpc;
message ToBeGreeted {
option (scalapb.message).extends = "io.ontherocks.hellogrpc.RockingMessage";
string person = 1;
}
src/main/protobuf/helloworld.proto
package io.ontherocks.hellogrpc.helloworld
@SerialVersionUID(0L)
final case class ToBeGreeted(
person: scala.Option[String] = None
) extends com.trueaccord.scalapb.GeneratedMessage
with com.trueaccord.scalapb.Message[ToBeGreeted]
with com.trueaccord.lenses.Updatable[ToBeGreeted]
with io.ontherocks.hellogrpc.RockingMessage { ...
src_managed/main/scala/io/ontherocks/hellogrpc/helloworld/ToBeGreeted.scala
message Person {
string name = 1;
string birthday = 2 [(scalapb.field).type = "io.ontherocks.hellogrpc.Birthday"];
}
service HelloWorld {
rpc SayHello(ToBeGreeted) returns (Greeting) {}
}
helloworld.proto
...
trait HelloWorld extends _root_.com.trueaccord.scalapb.grpc.AbstractService {
def sayHello(request: ToBeGreeted): Future[Greeting]
}
...
HelloWorldGrpc.scala
class HelloWorldService extends HelloWorldGrpc.HelloWorld {
def sayHello(request: ToBeGreeted): Future[Greeting] = {
val greetedPerson = request.person.getOrElse("anonymous")
Future.successful(Greeting(message = s"Hello $greetedPerson"))
}
}
HelloWorldServer.scala
val channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext(true)
.build
val stub = HelloWorldGrpc.stub(channel)
stub.sayHello(ToBeGreeted(Some(Person(Some("Bob"))).foreach(println)
HelloWorldClient.scala
service Clock {
rpc StreamTime(TimeRequest) returns (stream TimeResponse) {}
}
clock.proto
trait Clock extends _root_.com.trueaccord.scalapb.grpc.AbstractService {
def streamTime(
request: TimeRequest,
responseObserver: _root_.io.grpc.stub.StreamObserver[TimeResponse]
): Unit
}
ClockGrpc.scala
class ClockService extends Clock {
def streamTime(r: TimeRequest, resObserver: StreamObserver[TimeResponse]): Unit =
scheduler.scheduleWithFixedDelay(0.seconds, 3.seconds) {
resObserver.onNext(TimeResponse(System.currentTimeMillis()))
}
}
ClockServer.scala
val client = ClockGrpc.stub(channel)
val observer = new StreamObserver[TimeResponse] {
def onError(t: Throwable): Unit = println(s"Received error: $t")
def onCompleted(): Unit = println("Stream completed.")
def onNext(response: TimeResponse): Unit =
println(s"Received current time: ${response.currentTime}")
}
client.streamTime(TimeRequest(), observer)
ClockClient.scala
service Sum {
rpc Add(stream SumRequest) returns (stream SumResponse) {}
}
sum.proto
trait Sum extends _root_.com.trueaccord.scalapb.grpc.AbstractService {
def add(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest]
}
SumGrpc.scala
class SumService extends SumGrpc.Sum {
def add(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest] =
new StreamObserver[SumRequest] {
def onError(t: Throwable): Unit = ???
def onCompleted(): Unit = ???
def onNext(value: SumRequest): Unit = ???
}
}
SumServer.scala
val client = SumGrpc.stub(channel)
val responseObserver = new StreamObserver[SumResponse] {
def onError(t: Throwable): Unit = ???
def onCompleted(): Unit = ???
def onNext(value: SumResponse): Unit = ???
}
val requestObserver = client.add(responseObserver)
// call requestObserver.onNext to send/stream requests to server
SumClient.scala