diff --git a/docs/src/main/paradox/gradle.md b/docs/src/main/paradox/buildtools/gradle.md similarity index 96% rename from docs/src/main/paradox/gradle.md rename to docs/src/main/paradox/buildtools/gradle.md index 09dc4e3fc..0b6a27e0f 100644 --- a/docs/src/main/paradox/gradle.md +++ b/docs/src/main/paradox/buildtools/gradle.md @@ -1,6 +1,6 @@ # Gradle -To get started with Akka gRPC read the [client](client.md) or [server](server.md) introductions. +To get started with Akka gRPC read the @ref[client](../client/index.md) or @ref[server](../server/index.md) introductions. ## Configuring what to generate diff --git a/docs/src/main/paradox/buildtools/index.md b/docs/src/main/paradox/buildtools/index.md new file mode 100644 index 000000000..32c23c710 --- /dev/null +++ b/docs/src/main/paradox/buildtools/index.md @@ -0,0 +1,11 @@ +# Build Tool Support + +@@toc + +@@@index + + * [sbt](sbt.md) + * [Gradle](gradle.md) + * [Maven](maven.md) + +@@@ \ No newline at end of file diff --git a/docs/src/main/paradox/maven.md b/docs/src/main/paradox/buildtools/maven.md similarity index 96% rename from docs/src/main/paradox/maven.md rename to docs/src/main/paradox/buildtools/maven.md index 57776682d..d776c6b1d 100644 --- a/docs/src/main/paradox/maven.md +++ b/docs/src/main/paradox/buildtools/maven.md @@ -1,6 +1,6 @@ # Maven -To get started with Akka gRPC read the [client](client.md) or [server](server.md) introductions. +To get started with Akka gRPC read the @ref[client](../client/index.md) or @ref[server](../server/index.md) introductions. ## Configuring what to generate diff --git a/docs/src/main/paradox/sbt.md b/docs/src/main/paradox/buildtools/sbt.md similarity index 76% rename from docs/src/main/paradox/sbt.md rename to docs/src/main/paradox/buildtools/sbt.md index 211eb18c2..0135d4844 100644 --- a/docs/src/main/paradox/sbt.md +++ b/docs/src/main/paradox/buildtools/sbt.md @@ -1,6 +1,6 @@ # sbt -To get started with Akka gRPC read the [client](client.md) or [server](server.md) introductions. +To get started with Akka gRPC read the @ref[client](../client/index.md) or @ref[server](../server/index.md) introductions. ## Configuring what to generate @@ -12,6 +12,7 @@ What language to generate stubs for is also configurable: @@snip[x](/sbt-plugin/src/sbt-test/gen-scala-server/00-interop/build.sbt) { #languages-scala #languages-java #languages-both } +## Passing parameters to the generator Passing generator parameters to the underlying ScalaPB generators can be done through `akkaGrpcCodeGeneratorSettings` setting, any specified options will be passed to all underlying generators that are enabled. By default this setting @@ -29,9 +30,9 @@ By default protobuf files are looked for in `src/main/protobuf` (and `src/main/p You can configure where your .proto files are located like this: ``` -// "sourceDirectory in Compile" is "src/main", so this adds "src/main/proto": +// "sourceDirectory in Compile" is "src/main", so this adds "src/main/proto_custom": inConfig(Compile)(Seq( - PB.protoSources += sourceDirectory.value / "proto" + PB.protoSources += sourceDirectory.value / "proto_custom" )) ``` @@ -41,8 +42,7 @@ Instead of duplicating the `.proto` definitions between server and client projec that contain proto definitions to your build: ```scala -libraryDependencies += - "com.example" %% "my-grpc-service" % "1.0.0" % "protobuf" +libraryDependencies += "com.example" %% "my-grpc-service" % "1.0.0" % "protobuf" ``` ## Starting your Akka gRPC server from sbt @@ -50,15 +50,15 @@ libraryDependencies += As the server requires a special Java agent for ALPN ([see Akka HTTP docs about HTTP/2](https://doc.akka.io/docs/akka-http/current/server-side/http2.html#application-layer-protocol-negotiation-alpn-)) you need to pass this agent with a `-javaagent` flag to the JVM when running the server. -This can be done using the `JavaAgent` sbt plugin. +This can be done automatically using the `JavaAgent` sbt plugin. -Add the plugin `project/plugin.sbt` +Add the plugin to `project/plugin.sbt` -@@snip [plugin.sbt]($root$/../project/plugins.sbt) { #java-agent-plugin } +@@snip [plugin.sbt](/project/plugins.sbt) { #java-agent-plugin } and then tell it to use the ALPN agent: -@@snip [build.sbt]($root$/../project/ProjectExtensions.scala) { #alpn } +@@snip [build.sbt](/project/ProjectExtensions.scala) { #alpn } After that you can run it as usual: diff --git a/docs/src/main/paradox/client.md b/docs/src/main/paradox/client.md deleted file mode 100644 index daa80a82e..000000000 --- a/docs/src/main/paradox/client.md +++ /dev/null @@ -1,302 +0,0 @@ -# Client - -## Setting up - -To get started, you must obtain the @ref[`.proto`](proto.md) file(s) that describe the interface you want to use and add those files to your project. - -Add `.proto` files to your project's @sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] directory. -(See the detailed chapters on @ref[sbt](sbt.md), @ref[Gradle](gradle.md) and @ref[Maven](maven.md) for information on taking .proto definitions from dependencies) - -Then add the following configuration to your build: - -sbt -: @@@vars -```scala -// in project/plugins.sbt: -addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "$projectversion$") -// in build.sbt: -enablePlugins(AkkaGrpcPlugin) -``` -@@@ - -Gradle -: @@@vars -```gradle -buildscript { - dependencies { - // version here is a placeholder, - // it is replaced with a project dependency during integration tests - // by adding --include-build to gradlew - classpath 'com.lightbend.akka.grpc:akka-grpc-gradle-plugin:$projectversion$' - } -} -plugins { - id 'java' - id 'application' -} -apply plugin: 'com.lightbend.akka.grpc.gradle' -``` -@@@ - -Maven -: @@@vars -``` - - 4.0.0 - Project name - com.example - my-grpc-app - 0.1-SNAPSHOT - - 1.8 - 1.8 - $projectversion$ - $grpc.version$ - UTF-8 - - - - com.lightbend.akka.grpc - akka-grpc-runtime_2.12 - ${akka.grpc.version} - - - - io.grpc - grpc-testing - ${grpc.version} - - - - - - com.lightbend.akka.grpc - akka-grpc-maven-plugin - ${akka.grpc.version} - - - - - generate - - - - - - - -``` -@@@ - -For a complete overview of the configuration options see the chapter for your build tool, @ref[sbt](sbt.md), @ref[Gradle](gradle.md) or @ref[Maven](maven.md). - -## Generate and use - -To use a service, such as the Hello World service described in the @ref:[server documentation](server.md), -you only need protobuf definition (`.proto` files) of the service. No additional dependencies to -the server project is needed. - -For example, the a Hello World service: - -@@snip [helloworld.proto]($root$/../plugin-tester-scala/src/main/protobuf/helloworld.proto) - -There are 4 different types of calls: - -* **unary call** - single request that returns a @scala[`Future`]@java[`CompletionStage`] with a single response, - see `sayHello` in above example -* **client streaming call** - `Source` (stream) of requests from the client that returns a - @scala[`Future`]@java[`CompletionStage`] with a single response, - see `itKeepsTalking` in above example -* **server streaming call** - single request that returns a `Source` (stream) of responses, - see `itKeepsReplying` in above example -* **client and server streaming call** - `Source` (stream) of requests from the client that returns a - `Source` (stream) of responses, - see `streamHellos` in above example - -Let's use these 4 calls from a client. Start by generating code from the `.proto` definition with: - -sbt -: ``` -compile -``` - -Gradle -: ``` -./gradlew build -``` - -Maven -: ``` -mvn akka-grpc:generate -``` - -A main program that calls the server with the `GreeterService` looks like this: - -Scala -: @@snip [GreeterClient.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala) { #full-client } - -Java -: @@snip [GreeterClient.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterClient.java) { #full-client } - -## Running - -sbt -: ``` -runMain io.grpc.examples.helloworld.GreeterClient -``` - -Gradle -: ``` -./gradlew run -``` - -Maven -: ``` -mvn akka-grpc:generate compile exec:java -Dexec.mainClass=io.grpc.examples.helloworld.GreeterClient -``` - -### Configuration - -A gRPC client is configured with a `GrpcClientSettings` instance. There are a number of ways of creating one and the API -docs are the best reference. An `ActorSystem` is always required as it is used for default configuration and service discovery. - -The simplest way to create a client is to provide a static host and port. - -Scala -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #simple } - -Java -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #simple } - -Further settings can be added via the `with` methods - -Scala -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #simple-programmatic } - -Java -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #simple-programmatic } - -Instead a client can be defined in configuration. All client configurations need to be under `akka.grpc.client` - -Scala -: @@snip [GrpcClientSettingsSpec]($root$/../runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #client-config } - -Java -: @@snip [GrpcClientSettingsSpec]($root$/../runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #client-config } - -Clients defined in configuration pick up defaults from `reference.conf`: - -Scala -: @@snip [reference]($root$/../runtime/src/main/resources/reference.conf) { #defaults } - -Java -: @@snip [reference]($root$/../runtime/src/main/resources/reference.conf) { #defaults } - -#### Akka Discovery - -The examples above all use a hard coded host and port for the location of the gRPC service which is the default if you do not configure a `service-discovery-mechanism`. -Alternatively [Akka Discovery](https://developer.lightbend.com/docs/akka-management/current/discovery.html) can be used. -This allows a gRPC client to switch between discovering services via DNS, config, Kubernetes and Consul and others by just changing -the configuration. - -To see how to config a particular service discovery mechanism see the [Akka Discovery docs](https://developer.lightbend.com/docs/akka-management/current/discovery.html). -Once it is configured a service discovery mechanism name can either be passed into settings or put in the client's configuration. - -Scala -: @@snip [GrpcClientSettingsSpec]($root$/../runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #config-service-discovery } - -Java -: @@snip [GrpcClientSettingsSpec]($root$/../runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #config-service-discovery } - -The above example configures the client `project.WithConfigServiceDiscovery` to use `config` based service discovery. - -Then to create the `GrpcClientSettings`: - -Scala -: @@snip [GrpcClientSettingsSpec]($root$/../runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #sd-settings } - -Java -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #sd-settings } - -Alternatively if a `SimpleServiceDiscovery` is available else where in your system is can be passed in: - -Scala -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #provide-sd } - -Java -: @@snip [GrpcClientSettingsCompileOnly]($root$/../runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #provide-sd } - - -Currently service discovery is only queried on creation of the client. A client can be automatically re-created, and go via service discovery again, - if a connection can't be established, see the lifecycle section. - -### Lifecycle - -Instances of the generated client may be long-lived and can be used concurrently. -You can keep the client running until your system terminates, or close it earlier. To -avoid leaking in the latter case, you should call `.close()` on the client. - -When the connection breaks, the client will start failing requests and try reconnecting -to the server automatically. If a connection can not be established after the configured number of attempts then -the client closes its self. When this happens the @scala[`Future`]@java[`CompletionStage`] -returned by `closed()` will complete with a failure. You do not need to call `close()` in -this case. The default number of reconnection attempts is infinite. - -If you're using a static name for your server then it will -be re-resolved between connection attempts so infinite is a sensible default. However -if another service discovery mechanism is used then set the reconnection attempts to -a small value i.e. 5 and re-create the client if the `closed()` @scala[`Future`]@java[`CompletionStage`] -is completed exceptionally. A `RestartingClient` utility is included that can wrap any -generated client and do this automatically. When the client is re-created service discovery -will be queried again. It is expected in a later version this will happen transparently. - -Scala -: @@snip [RestartingGreeterClient.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/RestartingGreeterClient.scala) { #restarting-client } - -Java -: @@snip [RestartingGreeterClient.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/RestartingGreeterClient.java) { #restarting-client } - - -To use the client use `withClient`. The actual client isn't exposed as it should not be stored -any where as it can be replaced when a failure happens. - -Scala -: @@snip [RestartingGreeterClient.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/RestartingGreeterClient.scala) { #usage } - -Java -: @@snip [RestartingGreeterClient.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/RestartingGreeterClient.java) { #usage } - - -### Debug logging - -To enable fine grained debug running the following logging configuration can be used. - -Put this in a file `grpc-debug-logging.properties`: - -``` -handlers=java.util.logging.ConsoleHandler -io.grpc.netty.level=FINE -java.util.logging.ConsoleHandler.level=FINE -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -``` - -Run with `-Djava.util.logging.config.file=/path/to/grpc-debug-logging.properties`. - -### Request metadata - -Default request metadata, for example for authentication, can be provided through the -`GrpcClientSettings` passed to the client when it is created, it will be the base metadata used for each request. - -In some cases you will want to provide specific metadata to a single request, this can be done through the "lifted" -client API, each method of the service has an empty parameter list version on the client returning a `RequestBuilder`. - -After adding the required metadata the request is done by calling `invoke` with the request parameters. - -Scala -: @@snip [GreeterClient.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/LiftedGreeterClient.scala) { #with-metadata } - -Java -: @@snip [GreeterClient.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/LiftedGreeterClient.java) { #with-metadata } - - diff --git a/docs/src/main/paradox/client/configuration.md b/docs/src/main/paradox/client/configuration.md new file mode 100644 index 000000000..e4b8c0669 --- /dev/null +++ b/docs/src/main/paradox/client/configuration.md @@ -0,0 +1,93 @@ +# Configuration + +A gRPC client is configured with a `GrpcClientSettings` instance. There are a number of ways of creating one and the API +docs are the best reference. An `ActorSystem` is always required as it is used for default configuration and service discovery. + +## By Code + +The simplest way to create a client is to provide a static host and port. + +Scala +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #simple } + +Java +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #simple } + +Further settings can be added via the `with` methods + +Scala +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #simple-programmatic } + +Java +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #simple-programmatic } + +## By Configuration + +Instead a client can be defined in configuration. All client configurations need to be under `akka.grpc.client` + +Scala +: @@snip [GrpcClientSettingsSpec](/runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #client-config } + +Java +: @@snip [GrpcClientSettingsSpec](/runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #client-config } + +Clients defined in configuration pick up defaults from `reference.conf`: + +Scala +: @@snip [reference](/runtime/src/main/resources/reference.conf) { #defaults } + +Java +: @@snip [reference](/runtime/src/main/resources/reference.conf) { #defaults } + +## Using Akka Discovery for Endpoint Discovery + +The examples above all use a hard coded host and port for the location of the gRPC service which is the default if you do not configure a `service-discovery-mechanism`. +Alternatively [Akka Discovery](https://developer.lightbend.com/docs/akka-management/current/discovery.html) can be used. +This allows a gRPC client to switch between discovering services via DNS, config, Kubernetes and Consul and others by just changing +the configuration. + +To see how to config a particular service discovery mechanism see the [Akka Discovery docs](https://developer.lightbend.com/docs/akka-management/current/discovery.html). +Once it is configured a service discovery mechanism name can either be passed into settings or put in the client's configuration. + +Scala +: @@snip [GrpcClientSettingsSpec](/runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #config-service-discovery } + +Java +: @@snip [GrpcClientSettingsSpec](/runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #config-service-discovery } + +The above example configures the client `project.WithConfigServiceDiscovery` to use `config` based service discovery. + +Then to create the `GrpcClientSettings`: + +Scala +: @@snip [GrpcClientSettingsSpec](/runtime/src/test/scala/akka/grpc/GrpcClientSettingsSpec.scala) { #sd-settings } + +Java +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #sd-settings } + +Alternatively if a `SimpleServiceDiscovery` is available else where in your system is can be passed in: + +Scala +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/scala/docs/akka/grpc/client/GrpcClientSettingsCompileOnly.scala) { #provide-sd } + +Java +: @@snip [GrpcClientSettingsCompileOnly](/runtime/src/test/java/jdocs/akka/grpc/client/GrpcClientSettingsCompileOnly.java) { #provide-sd } + + +Currently service discovery is only queried on creation of the client. A client can be automatically re-created, and go via service discovery again, + if a connection can't be established, see the lifecycle section. + +## Debug logging + +To enable fine grained debug running the following logging configuration can be used. + +Put this in a file `grpc-debug-logging.properties`: + +``` +handlers=java.util.logging.ConsoleHandler +io.grpc.netty.level=FINE +java.util.logging.ConsoleHandler.level=FINE +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +``` + +Run with `-Djava.util.logging.config.file=/path/to/grpc-debug-logging.properties`. diff --git a/docs/src/main/paradox/client/details.md b/docs/src/main/paradox/client/details.md new file mode 100644 index 000000000..a17777991 --- /dev/null +++ b/docs/src/main/paradox/client/details.md @@ -0,0 +1,56 @@ +# Details + +## Client Lifecycle + +Instances of the generated client may be long-lived and can be used concurrently. +You can keep the client running until your system terminates, or close it earlier. To +avoid leaking in the latter case, you should call `.close()` on the client. + +When the connection breaks, the client will start failing requests and try reconnecting +to the server automatically. If a connection can not be established after the configured number of attempts then +the client closes its self. When this happens the @scala[`Future`]@java[`CompletionStage`] +returned by `closed()` will complete with a failure. You do not need to call `close()` in +this case. The default number of reconnection attempts is infinite. + +If you're using a static name for your server then it will +be re-resolved between connection attempts so infinite is a sensible default. However +if another service discovery mechanism is used then set the reconnection attempts to +a small value i.e. 5 and re-create the client if the `closed()` @scala[`Future`]@java[`CompletionStage`] +is completed exceptionally. A `RestartingClient` utility is included that can wrap any +generated client and do this automatically. When the client is re-created service discovery +will be queried again. It is expected in a later version this will happen transparently. + +Scala +: @@snip [RestartingGreeterClient.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/RestartingGreeterClient.scala) { #restarting-client } + +Java +: @@snip [RestartingGreeterClient.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/RestartingGreeterClient.java) { #restarting-client } + + +To use the client use `withClient`. The actual client isn't exposed as it should not be stored +any where as it can be replaced when a failure happens. + +Scala +: @@snip [RestartingGreeterClient.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/RestartingGreeterClient.scala) { #usage } + +Java +: @@snip [RestartingGreeterClient.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/RestartingGreeterClient.java) { #usage } + + +## Request Metadata + +Default request metadata, for example for authentication, can be provided through the +`GrpcClientSettings` passed to the client when it is created, it will be the base metadata used for each request. + +In some cases you will want to provide specific metadata to a single request, this can be done through the "lifted" +client API, each method of the service has an empty parameter list version on the client returning a `RequestBuilder`. + +After adding the required metadata the request is done by calling `invoke` with the request parameters. + +Scala +: @@snip [GreeterClient.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/LiftedGreeterClient.scala) { #with-metadata } + +Java +: @@snip [GreeterClient.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/LiftedGreeterClient.java) { #with-metadata } + + diff --git a/docs/src/main/paradox/client/index.md b/docs/src/main/paradox/client/index.md new file mode 100644 index 000000000..d41cfd6db --- /dev/null +++ b/docs/src/main/paradox/client/index.md @@ -0,0 +1,11 @@ +# Consuming Services (Client) + +@@ toc + +@@@ index + + * [Walkthrough](walkthrough.md) + * [Configuration](configuration.md) + * [Details](details.md) + +@@@ \ No newline at end of file diff --git a/docs/src/main/paradox/client/walkthrough.md b/docs/src/main/paradox/client/walkthrough.md new file mode 100644 index 000000000..326e850c3 --- /dev/null +++ b/docs/src/main/paradox/client/walkthrough.md @@ -0,0 +1,173 @@ +# Walkthrough + +## Setting up + +To get started, you must obtain the @ref[`.proto`](../proto.md) file(s) that describe the interface you want to use and add those files to your project. + +Add `.proto` files to your project's @sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] directory. +See the detailed chapters on @ref[sbt](../buildtools/sbt.md), @ref[Gradle](../buildtools/gradle.md) and @ref[Maven](../buildtools/maven.md) for information on picking up +`.proto` definitions from dependencies automatically. + +Then add the following configuration to your build: + +sbt +: @@@vars +```scala +// in project/plugins.sbt: +addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "$projectversion$") +// in build.sbt: +enablePlugins(AkkaGrpcPlugin) +``` +@@@ + +Gradle +: @@@vars +```gradle +buildscript { + dependencies { + // version here is a placeholder, + // it is replaced with a project dependency during integration tests + // by adding --include-build to gradlew + classpath 'com.lightbend.akka.grpc:akka-grpc-gradle-plugin:$projectversion$' + } +} +plugins { + id 'java' + id 'application' +} +apply plugin: 'com.lightbend.akka.grpc.gradle' +``` +@@@ + +Maven +: @@@vars +``` + + 4.0.0 + Project name + com.example + my-grpc-app + 0.1-SNAPSHOT + + 1.8 + 1.8 + $projectversion$ + $grpc.version$ + UTF-8 + + + + com.lightbend.akka.grpc + akka-grpc-runtime_2.12 + ${akka.grpc.version} + + + + io.grpc + grpc-testing + ${grpc.version} + + + + + + com.lightbend.akka.grpc + akka-grpc-maven-plugin + ${akka.grpc.version} + + + + + generate + + + + + + + +``` +@@@ + +For a complete overview of the configuration options see the chapter for your build tool, @ref[sbt](../buildtools/sbt.md), @ref[Gradle](../buildtools/gradle.md) or @ref[Maven](../buildtools/maven.md). + +## Generating Service Stubs + +To use a service, such as the Hello World service described in the @ref:[server documentation](../server/index.md), +you only need the protobuf definition (the `.proto` files) of the service. No additional dependencies to +the server project are needed. + +For example, this is the definition of a Hello World service: + +@@snip [helloworld.proto](/plugin-tester-scala/src/main/protobuf/helloworld.proto) + +From this definition, Akka gRPC generates interfaces that look like this: + +Scala +: @@snip [helloworld.proto](/plugin-tester-scala/target/scala-2.12/src_managed/main/example/myapp/helloworld/grpc/GreeterService.scala) + +Java +: @@snip [helloworld.proto](/plugin-tester-java/target/scala-2.12/src_managed/main/example/myapp/helloworld/grpc/GreeterService.java) + +and model @scala[case ]classes for `HelloRequest` and `HelloResponse`. + +The service interface is the same for the client and the server side. On the server side, the service implements the interface, +on the client side the Akka gRPC infrastructure implements a stub that will connect to the remote service when called. + +There are 4 different types of calls: + +* **unary call** - single request that returns a @scala[`Future`]@java[`CompletionStage`] with a single response, + see `sayHello` in above example +* **client streaming call** - `Source` (stream) of requests from the client that returns a + @scala[`Future`]@java[`CompletionStage`] with a single response, + see `itKeepsTalking` in above example +* **server streaming call** - single request that returns a `Source` (stream) of responses, + see `itKeepsReplying` in above example +* **client and server streaming call** - `Source` (stream) of requests from the client that returns a + `Source` (stream) of responses, + see `streamHellos` in above example + +## Writing a Client Program + +Let's use these 4 calls from a client. Start by generating code from the `.proto` definition with: + +sbt +: ``` +compile +``` + +Gradle +: ``` +./gradlew build +``` + +Maven +: ``` +mvn akka-grpc:generate +``` + +A main program that calls the server with the `GreeterService` looks like this: + +Scala +: @@snip [GreeterClient.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala) { #full-client } + +Java +: @@snip [GreeterClient.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterClient.java) { #full-client } + +You can run the example with + +sbt +: ``` +runMain io.grpc.examples.helloworld.GreeterClient +``` + +Gradle +: ``` +./gradlew run +``` + +Maven +: ``` +mvn akka-grpc:generate compile exec:java -Dexec.mainClass=io.grpc.examples.helloworld.GreeterClient +``` diff --git a/docs/src/main/paradox/index.md b/docs/src/main/paradox/index.md index 1556c7160..0900f3b28 100644 --- a/docs/src/main/paradox/index.md +++ b/docs/src/main/paradox/index.md @@ -1,69 +1,16 @@ # Akka gRPC -[gRPC](https://grpc.io) is a transport mechanism for request/response -and (non-persistent) streaming use cases. Use it for: - -* connections between internal services -* connecting to external services that expose a gRPC API (even ones written in other languages) -* serving data to web or mobile front-ends - -This library provides support for building streaming gRPC servers and clients on top -of Akka Streams. - -## General overview - -gRPC is a schema-first RPC framework, where your protocol is declared in a -protobuf definition, and requests and responses will be streamed over an HTTP/2 -connection. - -Based on a protobuf service definition, akka-grpc can generate: - -* Model classes (using plain protoc for Java or scalapb for Scala) -* The API @scala[trait]@java[interface], expressed in Akka Streams `Source`s -* On the server side, code to create an Akka HTTP route based on your implementation of the API -* On the client side, a client for the API. - -### gRPC vs REST - -* Where REST is more flexible about encoding, gRPC standardizes on Protobuf. -* Where REST can be either schemaless or use a 3rd-party schema, gRPC always declares the service and messages in a Protobuf schema definition. - -### gRPC vs SOAP - -* Where SOAP is more flexible about transport, gRPC standardizes on HTTP/2. -* Where in SOAP protocols are often set in stone once defined (often requiring a new path for every version of the service), Protobuf is explicitly intended to support schema evolution. - -### gRPC vs Message Bus - -* While built on an efficient non-blocking implementation, gRPC is still 'synchonous' in the sense that it requires both 'sides' of the communication to be available at the same time. When using a (persistent) message bus only the producer and the bus must be up, the consumer does not need to be available, leading to a higher degree of decoupling. -* While gRPC supports bidirectional streaming for each request, when using a message bus the streams are decoupled - -## Project Status - -This library is in preview mode: basic functionality is in place, but API's and -build system plugins are still expected to be improved. - -The API on both sides (Client and Server) is a simple Akka Streams-based one. -We plan to also provide a 'power user' API for each of these ([#191](https://github.com/akka/akka-grpc/issues/191), [#179](https://github.com/akka/akka-grpc/issues/179)). - -The client side is -currently implemented on top of [io.grpc:grpc-netty-shaded](https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded), -we plan to replace this by [io.grpc:grpc-core](https://mvnrepository.com/artifact/io.grpc/grpc-core) and Akka HTTP. - -As for performance, we are currently relying on the JVM TLS implementation, -which is sufficient for many use cases, but is planned to be replaced with -[conscrypt](https://github.com/google/conscrypt) or [netty-tcnative](https://netty.io/wiki/forked-tomcat-native.html). +@@toc @@@ index -* [apidesign](apidesign.md) -* [server](server.md) -* [client](client.md) -* [proto](proto.md) -* [sbt](sbt.md) -* [maven](maven.md) -* [gradle](gradle.md) -* [play-framework](play-framework.md) -* [proxy](proxy.md) + * [Overview](overview.md) + * [Why gRPC](whygrpc.md) + * [Service Descriptors](proto.md) + * [Server](server/index.md) + * [Client](client/index.md) + * [Build Tools](buildtools/index.md) + * [Akka gRPC in Play](play-framework.md) + * [gRPC API Design](apidesign.md) -@@@ +@@@ \ No newline at end of file diff --git a/docs/src/main/paradox/overview.md b/docs/src/main/paradox/overview.md new file mode 100644 index 000000000..0cf1fafa8 --- /dev/null +++ b/docs/src/main/paradox/overview.md @@ -0,0 +1,38 @@ +# Overview + +## gRPC + +[gRPC](https://grpc.io) is a transport mechanism for request/response and (non-persistent) streaming use cases. See +@ref[Why gRPC?](whygrpc.md) for more information about when to use gRPC as your transport. + +## Akka gRPC + +Akka gRPC provides support for building streaming gRPC servers and clients on top +of Akka Streams. + +It features: + + * A generator that starting from a protobuf service definitions, Akka gRPC generates + - Model classes + - The service API as a @scala[Scala trait]@java[Java interface] using Akka Stream `Source`s + - On the @ref[server side](server/index.md), code to create an Akka HTTP route based on your implementation of the service + - On the @ref[client side](client/index.md) side, a client for the service + * gRPC Runtime implementation that uses Akka HTTP/2 support for the server side and grpc-netty-shaded for the client side. + * Support for @ref[sbt](buildtools/sbt.md), @ref[gradle](buildtools/gradle.md), and @ref[Maven](buildtools/maven.md), + and the @ref[Play Framework](play-framework.md). + +## Project Status + +This library is in preview mode: basic functionality is in place, but API's and +build system plugins are still expected to be improved. + +The API on both sides (Client and Server) is a simple Akka Streams-based one. +We plan to also provide a 'power user' API for each of these ([#191](https://github.com/akka/akka-grpc/issues/191), [#179](https://github.com/akka/akka-grpc/issues/179)). + +The client side is +currently implemented on top of [io.grpc:grpc-netty-shaded](https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded), +we plan to replace this by [io.grpc:grpc-core](https://mvnrepository.com/artifact/io.grpc/grpc-core) and Akka HTTP. + +As for performance, we are currently relying on the JVM TLS implementation, +which is sufficient for many use cases, but is planned to be replaced with +[conscrypt](https://github.com/google/conscrypt) or [netty-tcnative](https://netty.io/wiki/forked-tomcat-native.html). diff --git a/docs/src/main/paradox/play-framework.md b/docs/src/main/paradox/play-framework.md index bec743b68..b3b846b54 100644 --- a/docs/src/main/paradox/play-framework.md +++ b/docs/src/main/paradox/play-framework.md @@ -1,4 +1,4 @@ -# Play Framework +# 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. @@ -6,7 +6,7 @@ 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 to first enable the gRPC plugin as described in the @ref[client docs](client.md) and then add an additional +need to first enable the gRPC plugin as described in the @ref[client docs](client/index.md) and then add an additional source generator in `build.sbt`: Scala diff --git a/docs/src/main/paradox/proto.md b/docs/src/main/paradox/proto.md index 06415bc69..bd8d02aa0 100644 --- a/docs/src/main/paradox/proto.md +++ b/docs/src/main/paradox/proto.md @@ -1,4 +1,4 @@ -# Protobuf file format +# Protobuf Service Descriptors gRPC uses the Protobuf `.proto` file format to define your messages, services and some aspects of the code generation. @@ -10,13 +10,13 @@ but here are a few quick examples: Messages are defined in the same way protobuf definitions are used for serialization: -@@snip [helloworld.proto]($root$/../plugin-tester-scala/src/main/protobuf/helloworld.proto) { #messages } +@@snip [helloworld.proto](/plugin-tester-scala/src/main/protobuf/helloworld.proto) { #messages } ## Services Those messages can be used to define services: -@@snip [helloworld.proto]($root$/../plugin-tester-scala/src/main/protobuf/helloworld.proto) { #services } +@@snip [helloworld.proto](/plugin-tester-scala/src/main/protobuf/helloworld.proto) { #services } Both the request and the response consists of either a single message or a stream of messages. @@ -24,7 +24,7 @@ Both the request and the response consists of either a single message or a strea There are a number of options that can be set in the .proto definition that influence how the code is generated: -@@snip [helloworld.proto]($root$/../plugin-tester-scala/src/main/protobuf/helloworld.proto) { #options } +@@snip [helloworld.proto](/plugin-tester-scala/src/main/protobuf/helloworld.proto) { #options } The (optional) ['package'](https://developers.google.com/protocol-buffers/docs/proto#packages) in the `.proto` is used to resolve references from one `.proto` file to another. diff --git a/docs/src/main/paradox/proxy.md b/docs/src/main/paradox/proxy.md deleted file mode 100644 index 8d745bfcd..000000000 --- a/docs/src/main/paradox/proxy.md +++ /dev/null @@ -1,25 +0,0 @@ -# Running Akka gRPC behind a proxy - -As gRPC uses HTTP/2 as it's underlying transport any proxy supporting HTTP/2 should in theory work fine as a proxy -in front of the Akka gRPC server. - -These proxies has been verified to work in between the Akka gRPC client and server. - -## Envoy - -[Envoy proxy](www.envoyproxy.io) has [specific support for gRPC](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/grpc.html). -A sample configuration to proxy gRPC can be found among the [Envoy examples in their github repository](https://github.com/envoyproxy/envoy/blob/3f59fb5c0f6554f8b3f2e73ab4c1437a63d42668/examples/grpc-bridge/config/s2s-grpc-envoy.yaml) - -Note that the TLS connection will always have to be terminated at Envoy, so the options to run gRPC behind Envoy are the following: - -* [gRPC client] → unencrypted → [Envoy] → unencrypted → [gRPC server] - this is not recommended for production systems for security reasons -* [gRPC client] → TLS → [Envoy] → unencrypted → [gRPC server] - TLS is terminated in Envoy -* [gRPC client] → TLS → [Envoy] → TLS → [gRPC server] - TLS is terminated in Envoy and then a new TLS connection is made from Envoy to the gRPC server - -Envoy 1.7 can not handle server streaming (single request and streaming response) because it is missing -support for half closed [Envoy issue #3724](https://github.com/envoyproxy/envoy/issues/3724). - -For such methods you can work around the missing support by making -gRPC methods bidirectional streaming both ways and keep the source from the client running -(this can be done with `prefixAndTail(1)` to capture the first element and then the rest as a new `Source` which can be -kept running with the `ignore` sink). \ No newline at end of file diff --git a/docs/src/main/paradox/server.md b/docs/src/main/paradox/server.md deleted file mode 100644 index 47a6366f9..000000000 --- a/docs/src/main/paradox/server.md +++ /dev/null @@ -1,220 +0,0 @@ -# Server - -## Setting up - -To get started, you must obtain or write the @ref[`.proto`](proto.md) file(s) that describe the interface you want to implement and add those files -to your project. Add `.proto` files to your project's @sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] directory. -(See the detailed chapters on @ref[sbt](sbt.md), @ref[Gradle](gradle.md) and @ref[Maven](maven.md) for information on taking .proto definitions from dependencies) - -Then add the Akka gRPC plugin to your build: - -sbt -: @@@vars - ```scala - // in project/plugins.sbt: - addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "$projectversion$") - addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % "0.1.4") // ALPN agent - // - // in build.sbt: - enablePlugins(AkkaGrpcPlugin) - // ALPN agent - enablePlugins(JavaAgent) - javaAgents += "org.mortbay.jetty.alpn" % "jetty-alpn-agent" % "2.0.7" % "runtime;test" - ``` - @@@ - -Gradle -: @@@vars - ```gradle - buildscript { - repositories { - mavenLocal() - gradlePluginPortal() - } - dependencies { - // see https://plugins.gradle.org/plugin/com.lightbend.akka.grpc.gradle - // for the currently latest version. - classpath 'gradle.plugin.com.lightbend.akka.grpc:akka-grpc-gradle-plugin:$projectversion$' - } - } - plugins { - id 'java' - id 'application' - } - apply plugin: 'com.lightbend.akka.grpc.gradle' - repositories { - mavenLocal() - mavenCentral() - } - ``` - @@@ - -Maven -: @@@vars - ```xml - - 4.0.0 - Project name - com.example - my-grpc-app - 0.1-SNAPSHOT - - 1.8 - 1.8 - $projectversion$ - $grpc.version$ - UTF-8 - - - - com.lightbend.akka.grpc - akka-grpc-runtime_2.12 - ${akka.grpc.version} - - - - - - com.lightbend.akka.grpc - akka-grpc-maven-plugin - ${akka.grpc.version} - - - - generate - - - - - - - - ``` - @@@ - -For a complete overview of the configuration options see the chapter for your build tool, @ref[sbt](sbt.md), @ref[Gradle](gradle.md) or @ref[Maven](maven.md). - -## Generate and implement - -### Proto - -Define the interfaces you want to implement in your project's -@sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] file(s). - -For example, a Hello World service: - -@@snip [helloworld.proto]($root$/../plugin-tester-scala/src/main/protobuf/helloworld.proto) - -There are 4 different types of calls: - -* **unary call** - single request that returns a @scala[`Future`]@java[`CompletionStage`] with a single response, - see `sayHello` in above example -* **client streaming call** - `Source` (stream) of requests from the client that returns a - @scala[`Future`]@java[`CompletionStage`] with a single response, - see `itKeepsTalking` in above example -* **server streaming call** - single request that returns a `Source` (stream) of responses, - see `itKeepsReplying` in above example -* **client and server streaming call** - `Source` (stream) of requests from the client that returns a - `Source` (stream) of responses, - see `streamHellos` in above example - -Let's implement these 4 calls. Start by generating code from the `.proto` definition with: - -sbt -: ``` -sbt compile -``` - -Gradle -: ``` -./gradlew build -``` - -Maven -: ``` -mvn akka-grpc:generate -``` - -### Service implementation - -Implement the methods of the service interface in a new class: - -Scala -: @@snip [GreeterServiceImpl.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServiceImpl.scala) { #full-service-impl } - -Java -: @@snip [GreeterServiceImpl.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java) { #full-service-impl } - -### Server - -That service can then be handled by an Akka HTTP server via the generated `GreeterServiceHandler`, -which is a function from `HttpRequest` to @scala[`Future[HttpResponse]`]@java[`CompletionStage`]. -It returns 404 as response for requests that don't match the path of the service. - -The server will run one instance of the implementation and that is then shared between requests, -this mean that it must be thread safe. In the sample above there is no mutable state, for more about safely implementing -servers with state see [stateful](#stateful-services) - -A main program that starts a Akka HTTP server with the `GreeterService` looks like this: - -Scala -: @@snip [GreeterServiceImpl.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala) { #full-server } - -Java -: @@snip [GreeterServiceImpl.java]($root$/../plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java) { #full-server } - -Note that it's important to enable HTTP/2 in the configuration of the `ActorSystem`. - -``` -akka.http.server.preview.enable-http2 = on -``` - -# Multiple services - -When a server handles several services the handlers must be combined with -@scala[`akka.grpc.scaladsl.ServiceHandler.concatOrNotFound`]@java[`akka.grpc.javadsl.ServiceHandler.concatOrNotFound`] - -Scala -: @@snip [GreeterServiceImpl.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/CombinedServer.scala) { #concatOrNotFound } - -Java -: @@snip [GreeterServiceImpl.java]($root$/../plugin-tester-java/src/main/java/example/myapp/CombinedServer.java) { #import #concatOrNotFound } - -@scala[Note that the `GreeterServiceHandler.partial` and `EchoServiceHandler.partial` are used instead of `apply` -methods to create partial functions that are combined by concatOrNotFound.] - -## Running - -To run the server with HTTP/2 enabled correctly, you will likely have to configure the Jetty ALPN -agent as described @extref[in the Akka HTTP documentation](akka-http:server-side/http2.html#application-layer-protocol-negotiation-alpn-): - -See the detailed chapters on @ref[sbt](sbt.md#starting-your-akka-grpc-server-from-sbt), @ref[Gradle](gradle.md#starting-your-akka-grpc-server-from-gradle) -and @ref[Maven](maven.md#starting-your-akka-grpc-server-from-maven) for details on adding the agent. - - -## Stateful services - -More often than not, the whole point of the implementing a service is to keep state. Since the service implementation -is shared between concurrent incoming requests any state must be thread safe. - -There are two recommended ways to deal with this: - - * Make the state immutable – immutable state that is created before or when the service is instantiated and then never changes is safe - * Put the mutable state inside an actor and interact with it through `ask` from unary methods or `Flow.ask` from streams. - -This is an example based on the hello world above, but allowing users to change the greeting through a unary call: - -Scala -: @@snip [GreeterServiceImpl.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterServiceImpl.scala) { #stateful-service } - -Java -: @@snip [GreeterServiceImpl.java]($root$/../plugin-tester-java/src/main/java/example/myapp/statefulhelloworld/GreeterServiceImpl.java) { #stateful-service } - -The `GreeterActor` is implemented like this: - -Scala -: @@snip [GreeterActor.scala]($root$/../plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala) { #actor } - -Java -: @@snip [GreeterActor.java]($root$/../plugin-tester-java/src/main/java/example/myapp/statefulhelloworld/GreeterActor.java) { #actor } - diff --git a/docs/src/main/paradox/server/index.md b/docs/src/main/paradox/server/index.md new file mode 100644 index 000000000..fad8c0a7a --- /dev/null +++ b/docs/src/main/paradox/server/index.md @@ -0,0 +1,9 @@ +# Providing Services (Server) + +@@ toc + +@@@ index + + * [Walkthrough](walkthrough.md) + +@@@ \ No newline at end of file diff --git a/docs/src/main/paradox/server/walkthrough.md b/docs/src/main/paradox/server/walkthrough.md new file mode 100644 index 000000000..75130e2ef --- /dev/null +++ b/docs/src/main/paradox/server/walkthrough.md @@ -0,0 +1,256 @@ +# Walkthrough + +## Setting up + +To get started, you must obtain or write the @ref[`.proto`](../proto.md) file(s) that describe the interface you want to implement and add those files +to your project. Add `.proto` files to your project's @sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] directory. +(See the detailed chapters on @ref[sbt](../buildtools/sbt.md), @ref[Gradle](../buildtools/gradle.md) and @ref[Maven](../buildtools/maven.md) for information on taking .proto definitions from dependencies) + +Then add the Akka gRPC plugin to your build: + +sbt +: @@@vars + ```scala + // in project/plugins.sbt: + addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "$projectversion$") + addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % "0.1.4") // ALPN agent + // + // in build.sbt: + enablePlugins(AkkaGrpcPlugin) + // ALPN agent + enablePlugins(JavaAgent) + javaAgents += "org.mortbay.jetty.alpn" % "jetty-alpn-agent" % "2.0.7" % "runtime;test" + ``` + @@@ + +Gradle +: @@@vars + ```gradle + buildscript { + repositories { + mavenLocal() + gradlePluginPortal() + } + dependencies { + // see https://plugins.gradle.org/plugin/com.lightbend.akka.grpc.gradle + // for the currently latest version. + classpath 'gradle.plugin.com.lightbend.akka.grpc:akka-grpc-gradle-plugin:$projectversion$' + } + } + plugins { + id 'java' + id 'application' + } + apply plugin: 'com.lightbend.akka.grpc.gradle' + repositories { + mavenLocal() + mavenCentral() + } + ``` + @@@ + +Maven +: @@@vars + ```xml + + 4.0.0 + Project name + com.example + my-grpc-app + 0.1-SNAPSHOT + + 1.8 + 1.8 + $projectversion$ + $grpc.version$ + UTF-8 + + + + com.lightbend.akka.grpc + akka-grpc-runtime_2.12 + ${akka.grpc.version} + + + + + + com.lightbend.akka.grpc + akka-grpc-maven-plugin + ${akka.grpc.version} + + + + generate + + + + + + + + ``` + @@@ + +For a complete overview of the configuration options see the chapter for your build tool, @ref[sbt](../buildtools/sbt.md), @ref[Gradle](../buildtools/gradle.md) or @ref[Maven](../buildtools/maven.md). + +## Writing a service definition + +Define the interfaces you want to implement in your project's +@sbt[`src/main/protobuf`]@gradle[`src/main/proto`]@maven[`src/main/proto`] file(s). + +For example, this is the definition of a Hello World service: + +@@snip [helloworld.proto](/plugin-tester-scala/src/main/protobuf/helloworld.proto) + +## Generating interfaces and stubs + +Start by generating code from the `.proto` definition with: + +sbt +: ``` +sbt compile +``` + +Gradle +: ``` +./gradlew build +``` + +Maven +: ``` +mvn akka-grpc:generate +``` + +From the above definition, Akka gRPC generates interfaces that look like this: + +Scala +: @@snip [helloworld.proto](/plugin-tester-scala/target/scala-2.12/src_managed/main/example/myapp/helloworld/grpc/GreeterService.scala) + +Java +: @@snip [helloworld.proto](/plugin-tester-java/target/scala-2.12/src_managed/main/example/myapp/helloworld/grpc/GreeterService.java) + +and model @scala[case ]classes for `HelloRequest` and `HelloResponse`. + +The service interface is the same for the client and the server side. On the server side, the service implements the interface, +on the client side the Akka gRPC infrastructure implements a stub that will connect to the remote service when called. + +There are 4 different types of calls: + +* **unary call** - single request that returns a @scala[`Future`]@java[`CompletionStage`] with a single response, + see `sayHello` in above example +* **client streaming call** - `Source` (stream) of requests from the client that returns a + @scala[`Future`]@java[`CompletionStage`] with a single response, + see `itKeepsTalking` in above example +* **server streaming call** - single request that returns a `Source` (stream) of responses, + see `itKeepsReplying` in above example +* **client and server streaming call** - `Source` (stream) of requests from the client that returns a + `Source` (stream) of responses, + see `streamHellos` in above example + +## Implementing the service + +Let's implement these 4 calls in a new class: + +Scala +: @@snip [GreeterServiceImpl.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServiceImpl.scala) { #full-service-impl } + +Java +: @@snip [GreeterServiceImpl.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java) { #full-service-impl } + +## Serving the service with Akka HTTP + +Note, how the implementation we just wrote is free from any gRPC related boilerplate. It only uses the generated model and interfaces +from your domain and basic Akka streams classes. We now need to connect this implementation class to the web server to +offer it to clients. + +Akka gRPC servers are implemented with Akka HTTP. In addition to the above `GreeterService`, a @scala[`GreeterServiceHandler`]@java[`GreeterServiceHandlerFactory`] +was generated that wraps the implementation with the gRPC functionality to be plugged into an existing Akka HTTP server +app. + +You create the request handler by calling @scala[`GreeterServiceHandler(yourImpl)`]@java[`GreeterServiceHandlerFactory.create(yourImpl, ...)`]. + +@@@ note + +The server will reuse the given instance of the implementation, which means that it is shared between (potentially concurrent) requests. +Make sure that the implementation is thread-safe. In the sample above there is no mutable state, so it is safe. For more information +about safely implementing servers with state see the advice about [stateful](#stateful-services) below. + +@@@ + +A complete main program that starts an Akka HTTP server with the `GreeterService` looks like this: + +Scala +: @@snip [GreeterServiceImpl.scala](/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala) { #full-server } + +Java +: @@snip [GreeterServiceImpl.java](/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java) { #full-server } + +@@@ note + +It's important to enable HTTP/2 in Akka HTTP in the configuration of the `ActorSystem` by setting + +``` +akka.http.server.preview.enable-http2 = on +``` + +In the example this was done from the `main` method, but you could also do this from within your `application.conf`. + +@@@ + +HTTP/2 can only be served over TLS. That means that you need to configure your server with TLS information to provide certificates. +The example code contains a snippet about how to set up the TLS context from certificates and keys provided from resources on the +classpath. In a real application, you would probably want to load the keys from outside the application jar instead. + +## Serving multiple services + +When a server handles several services the handlers must be combined with +@scala[`akka.grpc.scaladsl.ServiceHandler.concatOrNotFound`]@java[`akka.grpc.javadsl.ServiceHandler.concatOrNotFound`]: + +Scala +: @@snip [GreeterServiceImpl.scala](/plugin-tester-scala/src/main/scala/example/myapp/CombinedServer.scala) { #concatOrNotFound } + +Java +: @@snip [GreeterServiceImpl.java](/plugin-tester-java/src/main/java/example/myapp/CombinedServer.java) { #import #concatOrNotFound } + + +@scala[Note that `GreeterServiceHandler.partial` and `EchoServiceHandler.partial` are used instead of `apply` +methods to create partial functions that are combined by `concatOrNotFound`.] + +## Running the server + +To run the server with HTTP/2 enabled correctly, you will likely have to configure the Jetty ALPN +agent as described @extref[in the Akka HTTP documentation](akka-http:server-side/http2.html#application-layer-protocol-negotiation-alpn-): + +See the detailed chapters on @ref[sbt](../buildtools/sbt.md#starting-your-akka-grpc-server-from-sbt), @ref[Gradle](../buildtools/gradle.md#starting-your-akka-grpc-server-from-gradle) +and @ref[Maven](../buildtools/maven.md#starting-your-akka-grpc-server-from-maven) for details on adding the agent. + +## Stateful services + +More often than not, the whole point of the implementing a service is to keep state. Since the service implementation +is shared between concurrent incoming requests any state must be thread safe. + +There are two recommended ways to deal with this: + + * Put the mutable state inside an actor and interact with it through `ask` from unary methods or `Flow.ask` from streams. + * Keep the state in a thread-safe place. For example, a CRUD application that is backed by a database is thread-safe + when access to the backing database is (which until recently was THE way that applications dealt with request + concurrency). + +This is an example based on the Hello World above, but allowing users to change the greeting through a unary call: + +Scala +: @@snip [GreeterServiceImpl.scala](/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterServiceImpl.scala) { #stateful-service } + +Java +: @@snip [GreeterServiceImpl.java](/plugin-tester-java/src/main/java/example/myapp/statefulhelloworld/GreeterServiceImpl.java) { #stateful-service } + +The `GreeterActor` is implemented like this: + +Scala +: @@snip [GreeterActor.scala](/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala) { #actor } + +Java +: @@snip [GreeterActor.java](/plugin-tester-java/src/main/java/example/myapp/statefulhelloworld/GreeterActor.java) { #actor } + +Now the actor mailbox is used to synchronize accesses to the mutable state. \ No newline at end of file diff --git a/docs/src/main/paradox/whygrpc.md b/docs/src/main/paradox/whygrpc.md new file mode 100644 index 000000000..9a0a0e315 --- /dev/null +++ b/docs/src/main/paradox/whygrpc.md @@ -0,0 +1,49 @@ +# Why gRPC? + +[gRPC](https://grpc.io) is a transport mechanism for request/response +and (non-persistent) streaming use cases. + +It is a schema-first RPC framework, where your protocol is declared in a +@ref[protobuf service descriptor](proto.md), and requests and responses will be streamed over an HTTP/2 +connection. + +It has several advantages: + + * Schema-first design favors well-defined and decoupled service interfaces over brittle ad-hoc solutions. + * Protobuf-based wire protocol is efficient, well-known, and allows compatible schema evolution. + * Based on HTTP/2 which allows multiplexing several data streams over a single connection. + * Streaming requests and responses are first class. + * There are tools available for many languages allowing seamless interoperability between clients and services written + in different languages. + +That makes it well-suited for: + + * Connections between internal services + * Connecting to external services that expose a gRPC API (even ones written in other languages) + * Serving data to web or mobile front-ends + +## gRPC vs REST + +* Where REST is more flexible about encoding, gRPC standardizes on Protobuf. +* Where REST can be either schemaless or use a 3rd-party schema, gRPC always declares the service and messages in a Protobuf schema definition. + +## gRPC vs SOAP + +* Where SOAP is more flexible about transport, gRPC standardizes on HTTP/2. +* Where in SOAP protocols are often set in stone once defined (often requiring a new path for every version of the service), Protobuf is explicitly intended to support schema evolution. + +## gRPC vs Message Bus + +* While built on an efficient non-blocking implementation, gRPC is still 'synchronous' in the sense that it requires both 'sides' of the communication to be available at the same time. When using a (persistent) message bus only the producer and the bus must be up, the consumer does not need to be available, leading to a higher degree of decoupling. +* While gRPC supports bidirectional streaming for each request, when using a message bus the streams are decoupled + +## gRPC vs Akka Remoting + +* While Akka Remoting allows exchanging messages between Akka ActorSystems transparently, it still requires significant effort to support efficient and compatible message serialization. + Large messages can clog the message transport. In contrast to gRPC, streaming is not first-class but needs to be built on top of message passing (e.g. by using [StreamRefs](https://doc.akka.io/docs/akka/current/stream/stream-refs.html)). +* Akka Remoting's wire protocol might change with Akka versions and configuration, so you need to make sure that all parts of your system run similar enough versions. gRPC on the other + hand guarantees longer-term stability of the protocol, so gRPC clients and services are more loosely coupled. +* Where message passing as with Akka Remoting is fire-and-forget which decouples service execution, any kind of RPC requires waiting until the remote procedure call is answered. + Waiting (even non-blocking) for a response which is needed for any case of RPC often binds significant resources. To be fair, (Akka) actor communication is often structured in a + request/response fashion which makes it very similar to more traditional RPC techniques and gives the same disadvantages + (like state that needs to be kept on the "client" side requiring timeouts while waiting for a response). diff --git a/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java b/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java index 92ba3333c..c7a6ba2f3 100644 --- a/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java +++ b/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServer.java @@ -39,20 +39,33 @@ public static void main(String[] args) throws Exception { // important to enable HTTP/2 in ActorSystem's config Config conf = ConfigFactory.parseString("akka.http.server.preview.enable-http2 = on") .withFallback(ConfigFactory.defaultApplication()); + + // Akka ActorSystem Boot ActorSystem sys = ActorSystem.create("HelloWorld", conf); Materializer mat = ActorMaterializer.create(sys); + // Instantiate implementation GreeterService impl = new GreeterServiceImpl(mat); + // Bind implementation to localhost:8080 Http.get(sys).bindAndHandleAsync( GreeterServiceHandlerFactory.create(impl, mat), - ConnectWithHttps.toHostHttps("127.0.0.1", 8080).withCustomHttpsContext(serverHttpContext()), + // HTTP/2 servers are required to use TLS + ConnectWithHttps.toHostHttps("127.0.0.1", 8080) + // provide TLS certificate and keys + .withCustomHttpsContext(serverHttpContext()), mat) .thenAccept(binding -> { System.out.println("gRPC server bound to: " + binding.localAddress()); }); + + // ActorSystem threads will keep the app alive until `system.terminate()` is called } + /** + * Read certificate and keys from resources on the classpath. In a real application you + * would probably want to provide those from outside. + */ private static HttpsConnectionContext serverHttpContext() throws Exception { // FIXME how would end users do this? TestUtils.loadCert? issue #89 String keyEncoded = read(GreeterServer.class.getResourceAsStream("/certs/server1.key")) diff --git a/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java b/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java index 07e4dcf71..577d8a2e9 100644 --- a/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java +++ b/plugin-tester-java/src/main/java/example/myapp/helloworld/GreeterServiceImpl.java @@ -24,7 +24,6 @@ public GreeterServiceImpl(Materializer mat) { this.mat = mat; } - @Override public CompletionStage sayHello(HelloRequest in) { System.out.println("sayHello to " + in.getName()); diff --git a/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala b/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala index 6f7b1ac4e..9f49db7aa 100644 --- a/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala +++ b/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterClient.scala @@ -22,24 +22,27 @@ import scala.util.{ Failure, Success } object GreeterClient { def main(args: Array[String]): Unit = { - + // Boot akka implicit val sys = ActorSystem("HelloWorldClient") implicit val mat = ActorMaterializer() implicit val ec = sys.dispatcher + // Take details how to connect to the service from the config. val clientSettings = GrpcClientSettings.fromConfig(GreeterService.name) - val client = new GreeterServiceClient(clientSettings) + // Create a client-side stub for the service + val client: GreeterService = new GreeterServiceClient(clientSettings) - singleRequestReply() - streamingRequest() - streamingReply() - streamingRequestReply() + // Run examples for each of the exposed service methods. + runSingleRequestReplyExample() + runStreamingRequestExample() + runStreamingReplyExample() + runStreamingRequestReplyExample() sys.scheduler.schedule(1.second, 1.second) { - singleRequestReply() + runSingleRequestReplyExample() } - def singleRequestReply(): Unit = { + def runSingleRequestReplyExample(): Unit = { sys.log.info("Performing request") val reply = client.sayHello(HelloRequest("Alice")) reply.onComplete { @@ -50,7 +53,7 @@ object GreeterClient { } } - def streamingRequest(): Unit = { + def runStreamingRequestExample(): Unit = { val requests = List("Alice", "Bob", "Peter").map(HelloRequest.apply) val reply = client.itKeepsTalking(Source(requests)) reply.onComplete { @@ -61,7 +64,7 @@ object GreeterClient { } } - def streamingReply(): Unit = { + def runStreamingReplyExample(): Unit = { val responseStream = client.itKeepsReplying(HelloRequest("Alice")) val done: Future[Done] = responseStream.runForeach(reply => println(s"got streaming reply: ${reply.message}")) @@ -74,7 +77,7 @@ object GreeterClient { } } - def streamingRequestReply(): Unit = { + def runStreamingRequestReplyExample(): Unit = { val requestStream: Source[HelloRequest, NotUsed] = Source .tick(100.millis, 1.second, "tick") diff --git a/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala b/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala index 517e92051..cd51bdad6 100644 --- a/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala +++ b/plugin-tester-scala/src/main/scala/example/myapp/helloworld/GreeterServer.scala @@ -36,31 +36,37 @@ import example.myapp.helloworld.grpc._ object GreeterServer { def main(args: Array[String]): Unit = { - // important to enable HTTP/2 in ActorSystem's config + // Important: enable HTTP/2 in ActorSystem's config + // We do it here programmatically, but you can also set it in the application.conf val conf = ConfigFactory.parseString("akka.http.server.preview.enable-http2 = on") .withFallback(ConfigFactory.defaultApplication()) val system = ActorSystem("HelloWorld", conf) new GreeterServer(system).run() + // ActorSystem threads will keep the app alive until `system.terminate()` is called } } class GreeterServer(system: ActorSystem) { def run(): Future[Http.ServerBinding] = { - + // Akka boot up code implicit val sys: ActorSystem = system implicit val mat: Materializer = ActorMaterializer() implicit val ec: ExecutionContext = sys.dispatcher + // Create service handler val service: HttpRequest => Future[HttpResponse] = GreeterServiceHandler(new GreeterServiceImpl(mat)) + // Bind service handler server to localhost:8080 val bound = Http().bindAndHandleAsync( service, interface = "127.0.0.1", port = 8080, + // HTTP/2 can only be served with TLS so setup everything needed for that connectionContext = serverHttpContext()) + // report successful binding bound.foreach { binding => println(s"gRPC server bound to: ${binding.localAddress}") } @@ -68,6 +74,10 @@ class GreeterServer(system: ActorSystem) { bound } + /** + * Read certificate and keys from resources on the classpath. In a real application you + * would probably want to provide those from outside. + */ private def serverHttpContext(): HttpsConnectionContext = { // FIXME how would end users do this? TestUtils.loadCert? issue #89 val keyEncoded = read(GreeterServer.getClass.getResourceAsStream("/certs/server1.key")) diff --git a/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala b/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala index ac34c798e..30c151a0d 100644 --- a/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala +++ b/plugin-tester-scala/src/main/scala/example/myapp/statefulhelloworld/GreeterActor.scala @@ -15,11 +15,9 @@ object GreeterActor { case class Greeting(greeting: String) def props(initialGreeting: String) = Props(new GreeterActor(initialGreeting)) - } class GreeterActor(initialGreeting: String) extends Actor { - import GreeterActor._ var greeting = Greeting(initialGreeting) @@ -29,6 +27,5 @@ class GreeterActor(initialGreeting: String) extends Actor { case ChangeGreeting(newGreeting) => greeting = Greeting(newGreeting) } - } // #actor \ No newline at end of file