Usage in Play Framework

Akka gRPC has special support for both consuming gRPC services through an Akka gRPC client and for implementing your own gRPC service as a part of a Play Framework application.

Using a gRPC client in a Play Framework app

Akka gRPC has special support to allow for seamless injection of generated clients in Play. To enable this, you need first to enable the gRPC plugin as described in the client docs and then add a source generator in build.sbt:

Scala
import akka.grpc.gen.scaladsl.play.PlayScalaClientCodeGenerator
akkaGrpcExtraGenerators += PlayScalaClientCodeGenerator
Java
import akka.grpc.gen.javadsl.play.PlayJavaClientCodeGenerator
akkaGrpcExtraGenerators += PlayJavaClientCodeGenerator

This will generate a Play module that provides all generated clients for injection. The module must be enabled by adding it to the enabled modules in the application.conf.

You can then put the following helloworld.proto file in app/protobuf:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "example.myapp.helloworld.grpc";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service GreeterService {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}
Full source at GitHub

The module file is generated in example.myapp.helloworld.grpc.helloworld.AkkaGrpcClientModule by default for Scala example.myapp.helloworld.grpc.AkkaGrpcClientModule by default for Java, which corresponds to the default value of flat_package for JavaScala. You can read more about this in Services.

The exact package of the module will be based on the package the proto files are generated in, configured through the java_package option in the proto-file (if there are multiple different gRPC generated clients the module will be generated in the longest package prefix shared between the clients).

To hook it into Play, in application.conf:

Scala
// enable the client module
play.modules.enabled += example.myapp.helloworld.grpc.helloworld.AkkaGrpcClientModule
Full source at GitHub
Java
// enable the client module
play.modules.enabled += example.myapp.helloworld.grpc.AkkaGrpcClientModule
Full source at GitHub

The clients are configured with entries under akka.grpc.client named after the client (gRPC package name dot ServiceName), again, in application.conf:

akka.grpc.client {
  "helloworld.GreeterService" {
    host = "example.com"
    port = 9000
    # By default we connect over TLS
    #use-tls = false
  }
}
Full source at GitHub

See Client Configuration for more information on the available options. If the configuration is not present for that client and it is used by some other component, the application start will fail with an exception when injecting the client (see #271).

You can now use the client in a controller by injecting it:

Scala
package controllers

import akka.actor.ActorSystem
import akka.stream.Materializer
import example.myapp.helloworld.grpc.helloworld.{ GreeterServiceClient, HelloRequest }
import javax.inject.{ Inject, Singleton }
import play.api.mvc.{ AbstractController, ControllerComponents }

import scala.concurrent.ExecutionContext

@Singleton
class MyController @Inject() (implicit greeterClient: GreeterServiceClient, cc: ControllerComponents, mat: Materializer, exec: ExecutionContext) extends AbstractController(cc) {

  def sayHello(name: String) = Action.async { implicit request =>
    greeterClient.sayHello(HelloRequest(name))
      .map { reply =>
        Ok(s"response: ${reply.message}")
      }
  }

}
Full source at GitHub
Java
package controllers;

import example.myapp.helloworld.grpc.GreeterServiceClient;
import example.myapp.helloworld.grpc.HelloRequest;
import play.mvc.Controller;
import play.mvc.Result;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.concurrent.CompletionStage;

@Singleton
public class MyController extends Controller {

  private final GreeterServiceClient greeterServiceClient;

  @Inject
  public MyController(GreeterServiceClient greeterServiceClient) {
    this.greeterServiceClient = greeterServiceClient;
  }

  public CompletionStage<Result> sayHello(String name) {
    return greeterServiceClient.sayHello(
        HelloRequest.newBuilder()
            .setName(name)
            .build()
    ).thenApply(response ->
      ok("response: " + response.getMessage())
    );
  };

}
Full source at GitHub

Serving gRPC from a Play Framework app

To be able to serve gRPC from a Play Framework app you must enable HTTP/2 Support with HTTPS and the ALPN agent. (This is still somewhat involved and we hope to simplify it).

Warning

To use gRPC in Play Framework you must enable HTTP/2 Support.

Generating classes from the gRPC service definition is done buy adding the Akka gRPC plugin to your sbt build:

sbt
// in project/plugins.sbt:
addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "0.4.2")

Then you need to enable the Play server side code generator in build.sbt:

Scala
enablePlugins(AkkaGrpcPlugin)
import akka.grpc.gen.scaladsl.play.PlayScalaServerCodeGenerator
akkaGrpcExtraGenerators += PlayScalaServerCodeGenerator
Java
enablePlugins(AkkaGrpcPlugin)
import akka.grpc.gen.javadsl.play.PlayJavaServerCodeGenerator
akkaGrpcExtraGenerators += PlayJavaServerCodeGenerator

The plugin will look for .proto service descriptors in app/protobuf and output an abstract class per service that you then implement, so for example for the following protobuf descriptor:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "example.myapp.helloworld.grpc";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service GreeterService {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}
Full source at GitHub

You will get an abstract class named example.myapp.helloworld.grpc.helloworld.AbstractGreeterServiceRouter example.myapp.helloworld.grpc.AbstractGreeterServiceRouterCreate a concrete subclass implementing this wherever you see fit in your project, let’s say controller.GreeterServiceImpl like so:

Scala
package controllers

import akka.stream.Materializer
import example.myapp.helloworld.grpc.helloworld.{ AbstractGreeterServiceRouter, HelloReply, HelloRequest }
import javax.inject.{ Inject, Singleton }

import scala.concurrent.Future

/** User implementation, with support for dependency injection etc */
@Singleton
class GreeterServiceImpl @Inject() (implicit mat: Materializer) extends AbstractGreeterServiceRouter(mat) {

  override def sayHello(in: HelloRequest): Future[HelloReply] = Future.successful(HelloReply(s"Hello, ${in.name}!"))

}
Full source at GitHub
Java
package controllers;

import akka.stream.Materializer;
import com.google.inject.Inject;
import example.myapp.helloworld.grpc.AbstractGreeterServiceRouter;
import example.myapp.helloworld.grpc.HelloReply;
import example.myapp.helloworld.grpc.HelloRequest;

import javax.inject.Singleton;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/** User implementation, with support for dependency injection etc */
@Singleton
public class GreeterServiceImpl extends AbstractGreeterServiceRouter {

  @Inject
  public GreeterServiceImpl(Materializer mat) {
    super(mat);
  }

  @Override
  public CompletionStage<HelloReply> sayHello(HelloRequest in) {
    String message = String.format("Hello, %s!", in.getName());
    HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
    return CompletableFuture.completedFuture(reply);
  }

}
Full source at GitHub

And then add the router to your Play conf/routes file. Note that the router already knows its own path since it is based on the package name and service name of the service and therefore the path / is enough to get it to end up in the right place (in this example the path will be /helloworld.GreeterService). It cannot be added at an arbitrary path (if you try to do so an exception will be thrown when the router is started).

->     /   controllers.GreeterServiceImpl

A gRPC client can now connect to the server and call the provided services.

Gradle support

To enable the Play support in a Gradle project you need to set the option generatePlay to true. This will make sure play-specific code is generated in addition to plain Akka gRPC code:

akkaGrpc {
  language = "Java"
  generateClient = true
  generateServer = true
  generatePlay = true
}

For a full example project see the Quickstart project. Quickstart project.

See the gradle support docs for details about the other options.

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.