From 4a244fb982a66fb6fb5a02897b01b65e49183275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Thu, 23 Jan 2025 13:49:37 +0100 Subject: [PATCH] feat: gRPC endpoint support (#161) Initial setup --- .../java/com/example/UserGrpcServiceImpl.java | 13 ++++ .../com/example/EndpointsDescriptorSpec.scala | 10 ++- .../ComponentAnnotationProcessor.java | 7 +- .../akka-javasdk-parent/pom.xml | 2 +- .../components/grpc/TestGrpcServiceImpl.java | 21 ++++++ .../akkajavasdk/test_grpc_service.proto | 21 ++++++ .../javasdk/annotations/GrpcEndpoint.java | 27 ++++++++ .../impl/GrpcEndpointDescriptorFactory.scala | 65 +++++++++++++++++++ .../scala/akka/javasdk/impl/SdkRunner.scala | 22 ++++++- .../javasdk/impl/reflection/Reflect.scala | 6 +- build.sbt | 7 +- project/Dependencies.scala | 2 +- project/plugins.sbt | 3 +- samples/doc-snippets/pom.xml | 45 ++++++++++++- .../java/com/example/acl/PaymentEndpoint.java | 8 +++ .../example/api/ExampleGrpcEndpointImpl.java | 45 +++++++++++++ .../DelegatingServiceEndpoint.java | 2 +- .../com/example/jwt/HelloJwtEndpoint.java | 4 ++ .../com/example/example_grpc_endpoint.proto | 29 +++++++++ 19 files changed, 326 insertions(+), 13 deletions(-) create mode 100644 akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/main/java/com/example/UserGrpcServiceImpl.java create mode 100644 akka-javasdk-tests/src/test/java/akkajavasdk/components/grpc/TestGrpcServiceImpl.java create mode 100644 akka-javasdk-tests/src/test/protobuf/akkajavasdk/test_grpc_service.proto create mode 100644 akka-javasdk/src/main/java/akka/javasdk/annotations/GrpcEndpoint.java create mode 100644 akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala create mode 100644 samples/doc-snippets/src/main/java/com/example/api/ExampleGrpcEndpointImpl.java create mode 100644 samples/doc-snippets/src/main/protobuf/com/example/example_grpc_endpoint.proto diff --git a/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/main/java/com/example/UserGrpcServiceImpl.java b/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/main/java/com/example/UserGrpcServiceImpl.java new file mode 100644 index 000000000..e0a259937 --- /dev/null +++ b/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/main/java/com/example/UserGrpcServiceImpl.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package com.example; + +import akka.javasdk.annotations.GrpcEndpoint; + +@GrpcEndpoint +public class UserGrpcServiceImpl { + // would extend generated service stub and implement grpc methods but that's not important for this test + +} diff --git a/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/test/scala/com/example/EndpointsDescriptorSpec.scala b/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/test/scala/com/example/EndpointsDescriptorSpec.scala index 66703520d..ad28b88e2 100644 --- a/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/test/scala/com/example/EndpointsDescriptorSpec.scala +++ b/akka-javasdk-annotation-processor-tests/rest-endpoints-descriptors/src/test/scala/com/example/EndpointsDescriptorSpec.scala @@ -11,13 +11,19 @@ import org.scalatest.wordspec.AnyWordSpec class EndpointsDescriptorSpec extends AnyWordSpec with Matchers { "akka-javasdk-components.conf" should { - "contain http endpoints components" in { - val config = ConfigFactory.load("META-INF/akka-javasdk-components.conf") + val config = ConfigFactory.load("META-INF/akka-javasdk-components.conf") + "contain http endpoint components" in { val endpointComponents = config.getStringList("akka.javasdk.components.http-endpoint") endpointComponents.size() shouldBe 2 endpointComponents should contain("com.example.HelloController") endpointComponents should contain("com.example.UserRegistryController") } + + "contain grpc endpoint components" in { + val endpointComponents = config.getStringList("akka.javasdk.components.grpc-endpoint") + endpointComponents.size() shouldBe 1 + endpointComponents should contain("com.example.UserGrpcServiceImpl") + } } } diff --git a/akka-javasdk-annotation-processor/src/main/java/akka/javasdk/tooling/processor/ComponentAnnotationProcessor.java b/akka-javasdk-annotation-processor/src/main/java/akka/javasdk/tooling/processor/ComponentAnnotationProcessor.java index 1b5567181..0b5da1e41 100644 --- a/akka-javasdk-annotation-processor/src/main/java/akka/javasdk/tooling/processor/ComponentAnnotationProcessor.java +++ b/akka-javasdk-annotation-processor/src/main/java/akka/javasdk/tooling/processor/ComponentAnnotationProcessor.java @@ -37,6 +37,7 @@ @SupportedAnnotationTypes( { "akka.javasdk.annotations.http.HttpEndpoint", + "akka.javasdk.annotations.GrpcEndpoint", // all components will have this "akka.javasdk.annotations.ComponentId", // central config/lifecycle class @@ -53,6 +54,7 @@ public class ComponentAnnotationProcessor extends AbstractProcessor { // key of each component type under that parent path, containing a string list of concrete component classes private static final String HTTP_ENDPOINT_KEY = "http-endpoint"; + private static final String GRPC_ENDPOINT_KEY = "grpc-endpoint"; private static final String EVENT_SOURCED_ENTITY_KEY = "event-sourced-entity"; private static final String VALUE_ENTITY_KEY = "key-value-entity"; private static final String TIMED_ACTION_KEY = "timed-action"; @@ -61,7 +63,9 @@ public class ComponentAnnotationProcessor extends AbstractProcessor { private static final String WORKFLOW_KEY = "workflow"; private static final String SERVICE_SETUP_KEY = "service-setup"; - private static final List ALL_COMPONENT_TYPES = List.of(HTTP_ENDPOINT_KEY, EVENT_SOURCED_ENTITY_KEY, VALUE_ENTITY_KEY, TIMED_ACTION_KEY, CONSUMER_KEY, VIEW_KEY, WORKFLOW_KEY, SERVICE_SETUP_KEY); + private static final List ALL_COMPONENT_TYPES = List.of(HTTP_ENDPOINT_KEY, GRPC_ENDPOINT_KEY, + EVENT_SOURCED_ENTITY_KEY, VALUE_ENTITY_KEY, TIMED_ACTION_KEY, CONSUMER_KEY, VIEW_KEY, WORKFLOW_KEY, + SERVICE_SETUP_KEY); private final boolean debugEnabled; @@ -124,6 +128,7 @@ public boolean process(Set annotations, RoundEnvironment private String componentTypeFor(Element annotatedClass, TypeElement annotation) { return switch (annotation.getQualifiedName().toString()) { case "akka.javasdk.annotations.http.HttpEndpoint" -> HTTP_ENDPOINT_KEY; + case "akka.javasdk.annotations.GrpcEndpoint" -> GRPC_ENDPOINT_KEY; case "akka.javasdk.annotations.Setup" -> SERVICE_SETUP_KEY; case "akka.javasdk.annotations.ComponentId" -> componentType(annotatedClass); default -> throw new IllegalArgumentException("Unknown annotation type: " + annotation.getQualifiedName()); diff --git a/akka-javasdk-maven/akka-javasdk-parent/pom.xml b/akka-javasdk-maven/akka-javasdk-parent/pom.xml index 2cf701eaf..e47e49cdc 100644 --- a/akka-javasdk-maven/akka-javasdk-parent/pom.xml +++ b/akka-javasdk-maven/akka-javasdk-parent/pom.xml @@ -38,7 +38,7 @@ 21 - 1.3.0 + 1.3.1-6cd7992 UTF-8 false diff --git a/akka-javasdk-tests/src/test/java/akkajavasdk/components/grpc/TestGrpcServiceImpl.java b/akka-javasdk-tests/src/test/java/akkajavasdk/components/grpc/TestGrpcServiceImpl.java new file mode 100644 index 000000000..7485cbd26 --- /dev/null +++ b/akka-javasdk-tests/src/test/java/akkajavasdk/components/grpc/TestGrpcServiceImpl.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akkajavasdk.components.grpc; + +import akka.javasdk.annotations.GrpcEndpoint; +import akkajavasdk.protocol.*; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +@GrpcEndpoint +public class TestGrpcServiceImpl implements TestGrpcService { + @Override + public CompletionStage simple(TestGrpcServiceOuterClass.In in) { + return CompletableFuture.completedFuture( + TestGrpcServiceOuterClass.Out.newBuilder().setData(in.getData()).build() + ); + } +} diff --git a/akka-javasdk-tests/src/test/protobuf/akkajavasdk/test_grpc_service.proto b/akka-javasdk-tests/src/test/protobuf/akkajavasdk/test_grpc_service.proto new file mode 100644 index 000000000..e101c827f --- /dev/null +++ b/akka-javasdk-tests/src/test/protobuf/akkajavasdk/test_grpc_service.proto @@ -0,0 +1,21 @@ +// Copyright (C) 2021-2024 Lightbend Inc. + +// gRPC interface for a gRPC endpoint component + +syntax = "proto3"; + +package akkajavasdk; + +option java_package = "akkajavasdk.protocol"; + +message In { + string data = 1; +} + +message Out { + string data = 1; +} + +service TestGrpcService { + rpc Simple(In) returns (Out) {}; +} \ No newline at end of file diff --git a/akka-javasdk/src/main/java/akka/javasdk/annotations/GrpcEndpoint.java b/akka-javasdk/src/main/java/akka/javasdk/annotations/GrpcEndpoint.java new file mode 100644 index 000000000..bef015a0a --- /dev/null +++ b/akka-javasdk/src/main/java/akka/javasdk/annotations/GrpcEndpoint.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akka.javasdk.annotations; + +import java.lang.annotation.*; + +/** + * Mark a class to be made available as a gRPC endpoint. The annotated class should extend a gRPC service interface + * generated using Akka gRPC, be public and have a public constructor. + *

+ * Annotated classes can accept the following types to the constructor: + *

    + *
  • {@link akka.javasdk.client.ComponentClient}
  • + *
  • {@link akka.javasdk.http.HttpClientProvider}
  • + *
  • {@link akka.javasdk.timer.TimerScheduler}
  • + *
  • {@link akka.stream.Materializer}
  • + *
  • {@link com.typesafe.config.Config}
  • + *
  • Custom types provided by a {@link akka.javasdk.DependencyProvider} from the service setup
  • + *
+ */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GrpcEndpoint { +} diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala new file mode 100644 index 000000000..88dc0c759 --- /dev/null +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021-2024 Lightbend Inc. + */ + +package akka.javasdk.impl + +import akka.actor.typed.ActorSystem +import akka.grpc.ServiceDescription +import akka.grpc.scaladsl.InstancePerRequestFactory +import akka.http.scaladsl.model.HttpRequest +import akka.http.scaladsl.model.HttpResponse +import akka.runtime.sdk.spi.GrpcEndpointDescriptor +import akka.runtime.sdk.spi.GrpcEndpointRequestConstructionContext + +import scala.concurrent.Future + +object GrpcEndpointDescriptorFactory { + + def apply[T](grpcEndpointClass: Class[T], factory: () => T)(implicit + system: ActorSystem[_]): GrpcEndpointDescriptor[T] = { + // FIXME now way right now to know that it is a gRPC service interface + val serviceDefinitionClass: Class[_] = { + val interfaces = grpcEndpointClass.getInterfaces + if (interfaces.length != 1) { + throw new IllegalArgumentException( + s"Class [${grpcEndpointClass.getName}] must implement exactly one interface, the gRPC service generated by Akka gRPC.") + } + interfaces(0) + } + + // FIXME a derivative should be injectable into user code as well + val instanceFactory = { (_: GrpcEndpointRequestConstructionContext) => + factory() + } + + val handlerFactory = + system.dynamicAccess + .createInstanceFor[InstancePerRequestFactory[T]](serviceDefinitionClass.getName + "ScalaHandlerFactory", Nil) + .get + + // Pick up generated companion object for file descriptor (for reflection) and creating router + // static akka.grpc.ServiceDescription description in generated service interface + val description = serviceDefinitionClass.getField("description").get(null).asInstanceOf[ServiceDescription] + if (description eq null) + throw new RuntimeException( + s"Could not access static description from gRPC service interface [${serviceDefinitionClass.getName}]") + + val routeFactory: (HttpRequest => T) => PartialFunction[HttpRequest, Future[HttpResponse]] = { serviceFactory => + handlerFactory.partialInstancePerRequest( + serviceFactory, + description.name, + // FIXME default error handler, is it fine to leave like this, should runtime define? + PartialFunction.empty, + system) + } + + new GrpcEndpointDescriptor[T]( + grpcEndpointClass.getName, + description.name, + description.descriptor, + instanceFactory, + routeFactory) + } + +} diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala index e34364887..16b5bb692 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/SdkRunner.scala @@ -10,7 +10,6 @@ import java.lang.reflect.Method import java.util import java.util.Optional import java.util.concurrent.CompletionStage - import scala.annotation.nowarn import scala.concurrent.ExecutionContext import scala.concurrent.Future @@ -21,7 +20,6 @@ import scala.jdk.OptionConverters.RichOption import scala.jdk.OptionConverters.RichOptional import scala.reflect.ClassTag import scala.util.control.NonFatal - import akka.Done import akka.actor.typed.ActorSystem import akka.annotation.InternalApi @@ -34,6 +32,7 @@ import akka.javasdk.Principals import akka.javasdk.ServiceSetup import akka.javasdk.Tracing import akka.javasdk.annotations.ComponentId +import akka.javasdk.annotations.GrpcEndpoint import akka.javasdk.annotations.Setup import akka.javasdk.annotations.http.HttpEndpoint import akka.javasdk.client.ComponentClient @@ -201,6 +200,7 @@ private object ComponentType { val KeyValueEntity = "key-value-entity" val Workflow = "workflow" val HttpEndpoint = "http-endpoint" + val GrpcEndpoint = "grpc-endpoint" val Consumer = "consumer" val TimedAction = "timed-action" val View = "view" @@ -225,6 +225,7 @@ private object ComponentLocator { val kalixComponentTypeAndBaseClasses: Map[String, Class[_]] = Map( ComponentType.HttpEndpoint -> classOf[AnyRef], + ComponentType.GrpcEndpoint -> classOf[AnyRef], ComponentType.TimedAction -> classOf[TimedAction], ComponentType.Consumer -> classOf[Consumer], ComponentType.EventSourcedEntity -> classOf[EventSourcedEntity[_, _]], @@ -349,7 +350,7 @@ private final class Sdk( true } else { //additional check to skip logging for endpoints - if (!clz.hasAnnotation[HttpEndpoint]) { + if (!clz.hasAnnotation[HttpEndpoint] && !clz.hasAnnotation[GrpcEndpoint]) { //this could happen when we remove the @ComponentId annotation from the class, //the file descriptor generated by annotation processor might still have this class entry, //for instance when working with IDE and incremental compilation (without clean) @@ -415,6 +416,13 @@ private final class Sdk( HttpEndpointDescriptorFactory(httpEndpointClass, httpEndpointFactory(httpEndpointClass)) } + private val grpcEndpointDescriptors = componentClasses + .filter(Reflect.isGrpcEndpoint) + .map { grpcEndpointClass => + val anyRefClass = grpcEndpointClass.asInstanceOf[Class[AnyRef]] + GrpcEndpointDescriptorFactory(anyRefClass, grpcEndpointFactory(anyRefClass))(system) + } + private var eventSourcedEntityDescriptors = Vector.empty[EventSourcedEntityDescriptor] private var keyValueEntityDescriptors = Vector.empty[EventSourcedEntityDescriptor] private var workflowDescriptors = Vector.empty[WorkflowDescriptor] @@ -596,6 +604,7 @@ private final class Sdk( (eventSourcedEntityDescriptors ++ keyValueEntityDescriptors ++ httpEndpointDescriptors ++ + grpcEndpointDescriptors ++ timedActionDescriptors ++ consumerDescriptors ++ viewDescriptors ++ @@ -709,6 +718,13 @@ private final class Sdk( instance } + private def grpcEndpointFactory[E](grpcEndpointClass: Class[E]): () => E = () => { + wiredInstance(grpcEndpointClass) { + // FIXME missing span from request + sideEffectingComponentInjects(None) + } + } + private def wiredInstance[T](clz: Class[T])(partial: PartialFunction[Class[_], Any]): T = { // only one constructor allowed require(clz.getDeclaredConstructors.length == 1, s"Class [${clz.getSimpleName}] must have only one constructor.") diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/reflection/Reflect.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/reflection/Reflect.scala index c3eec0dd2..57d180c14 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/reflection/Reflect.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/reflection/Reflect.scala @@ -5,6 +5,7 @@ package akka.javasdk.impl.reflection import akka.annotation.InternalApi +import akka.javasdk.annotations.GrpcEndpoint import akka.javasdk.annotations.http.HttpEndpoint import akka.javasdk.client.ComponentClient import akka.javasdk.consumer.Consumer @@ -15,6 +16,7 @@ import akka.javasdk.timedaction.TimedAction import akka.javasdk.view.TableUpdater import akka.javasdk.view.View import akka.javasdk.workflow.Workflow + import java.lang.annotation.Annotation import java.lang.reflect.AnnotatedElement import java.lang.reflect.Method @@ -22,7 +24,6 @@ import java.lang.reflect.Modifier import java.lang.reflect.ParameterizedType import java.util import java.util.Optional - import scala.annotation.tailrec import scala.reflect.ClassTag @@ -60,6 +61,9 @@ private[impl] object Reflect { def isRestEndpoint(cls: Class[_]): Boolean = cls.getAnnotation(classOf[HttpEndpoint]) != null + def isGrpcEndpoint(cls: Class[_]): Boolean = + cls.getAnnotation(classOf[GrpcEndpoint]) != null + def isEntity(cls: Class[_]): Boolean = classOf[EventSourcedEntity[_, _]].isAssignableFrom(cls) || classOf[KeyValueEntity[_]].isAssignableFrom(cls) diff --git a/build.sbt b/build.sbt index 92785971b..cc046108d 100644 --- a/build.sbt +++ b/build.sbt @@ -57,6 +57,7 @@ lazy val akkaJavaSdkTestKit = lazy val akkaJavaSdkTests = Project(id = "akka-javasdk-tests", base = file("akka-javasdk-tests")) + .enablePlugins(AkkaGrpcPlugin) .dependsOn(akkaJavaSdk, akkaJavaSdkTestKit) .settings( name := "akka-javasdk-testkit", @@ -67,7 +68,11 @@ lazy val akkaJavaSdkTests = Test / javacOptions ++= Seq("-parameters"), // only tests here publish / skip := true, - doc / sources := Seq.empty) + doc / sources := Seq.empty, + // generating test service + Test / akkaGrpcGeneratedLanguages := Seq(AkkaGrpc.Java), + Test / akkaGrpcGeneratedSources := Seq(AkkaGrpc.Client, AkkaGrpc.Server), + Test / PB.protoSources ++= (Compile / PB.protoSources).value) .settings(inConfig(Test)(JupiterPlugin.scopedSettings)) .settings(Dependencies.tests) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b240a3a17..1ecb13a2e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val ProtocolVersionMinor = 1 val RuntimeImage = "gcr.io/kalix-public/kalix-runtime" // Remember to bump kalix-runtime.version in akka-javasdk-maven/akka-javasdk-parent if bumping this - val RuntimeVersion = sys.props.getOrElse("kalix-runtime.version", "1.3.0") + val RuntimeVersion = sys.props.getOrElse("kalix-runtime.version", "1.3.1-6cd7992") } // NOTE: embedded SDK should have the AkkaVersion aligned, when updating RuntimeVersion, make sure to check // if AkkaVersion and AkkaHttpVersion are aligned diff --git a/project/plugins.sbt b/project/plugins.sbt index 500bf9dc3..097fd094f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,8 @@ resolvers += "Akka repository".at("https://repo.akka.io/maven") addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1") -addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "2.4.4") +// Note: akka-grpc must be carefully kept in sync with the version used in the runtime +addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "2.5.1") addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.7.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0") diff --git a/samples/doc-snippets/pom.xml b/samples/doc-snippets/pom.xml index 4438557ea..6f4e148eb 100644 --- a/samples/doc-snippets/pom.xml +++ b/samples/doc-snippets/pom.xml @@ -5,7 +5,7 @@ io.akka akka-javasdk-parent - 3.0.2 + 3.1.0-a7630cc-37-b1638f53-dev-SNAPSHOT com.example @@ -15,4 +15,47 @@ doc-snippets + + + 2.5.1 + + + + akka-repository + Akka library repository + https://repo.akka.io/maven + + + + + com.lightbend.akka.grpc + akka-grpc-runtime_2.13 + ${akka.grpc.version} + + + + + + com.lightbend.akka.grpc + akka-grpc-maven-plugin + ${akka.grpc.version} + + + + true + + + + + + + generate + + + + + + + diff --git a/samples/doc-snippets/src/main/java/com/example/acl/PaymentEndpoint.java b/samples/doc-snippets/src/main/java/com/example/acl/PaymentEndpoint.java index 708f20414..240c22987 100644 --- a/samples/doc-snippets/src/main/java/com/example/acl/PaymentEndpoint.java +++ b/samples/doc-snippets/src/main/java/com/example/acl/PaymentEndpoint.java @@ -1,7 +1,9 @@ package com.example.acl; import akka.javasdk.annotations.Acl; +import akka.javasdk.annotations.http.Get; import akka.javasdk.annotations.http.HttpEndpoint; +import akka.javasdk.annotations.http.Post; // tag::endpoint-class[] @@ -9,5 +11,11 @@ @HttpEndpoint("/payments") public class PaymentEndpoint { //... + // end::endpoint-class[] + @Get + public String method() { + return "Example"; + } + // tag::endpoint-class[] } // end::endpoint-class[] \ No newline at end of file diff --git a/samples/doc-snippets/src/main/java/com/example/api/ExampleGrpcEndpointImpl.java b/samples/doc-snippets/src/main/java/com/example/api/ExampleGrpcEndpointImpl.java new file mode 100644 index 000000000..4a520ed03 --- /dev/null +++ b/samples/doc-snippets/src/main/java/com/example/api/ExampleGrpcEndpointImpl.java @@ -0,0 +1,45 @@ +package com.example.api; + +import akka.NotUsed; +import akka.javasdk.annotations.GrpcEndpoint; +import akka.stream.Materializer; +import akka.stream.javadsl.Sink; +import akka.stream.javadsl.Source; +import com.example.grpc.ExampleGrpcEndpoint; +import com.example.grpc.HelloReply; +import com.example.grpc.HelloRequest; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +@GrpcEndpoint +public class ExampleGrpcEndpointImpl implements ExampleGrpcEndpoint { + + private final Materializer materializer; + + public ExampleGrpcEndpointImpl(Materializer materializer) { + this.materializer = materializer; + } + + @Override + public CompletionStage sayHello(HelloRequest in) { + return CompletableFuture.completedFuture(HelloReply.newBuilder().setMessage("Hello " + in.getName()).build()); + } + + @Override + public CompletionStage itKeepsTalking(Source in) { + return in.runWith(Sink.head(), materializer).thenApply(firstStreamedHello -> + HelloReply.newBuilder().setMessage("Hello " + firstStreamedHello.getName()).build() + ); + } + + @Override + public Source itKeepsReplying(HelloRequest in) { + return Source.repeat(HelloReply.newBuilder().setMessage("Hello " + in.getName()).build()); + } + + @Override + public Source streamHellos(Source in) { + return in.map(streamedHello -> HelloReply.newBuilder().setMessage("Hello " + streamedHello.getName()).build()); + } +} diff --git a/samples/doc-snippets/src/main/java/com/example/callanotherservice/DelegatingServiceEndpoint.java b/samples/doc-snippets/src/main/java/com/example/callanotherservice/DelegatingServiceEndpoint.java index bb6160c22..9662cfe45 100644 --- a/samples/doc-snippets/src/main/java/com/example/callanotherservice/DelegatingServiceEndpoint.java +++ b/samples/doc-snippets/src/main/java/com/example/callanotherservice/DelegatingServiceEndpoint.java @@ -29,7 +29,7 @@ record IncreaseRequest(int increaseBy) {} // model for the JSON the upstream service responds with record Counter(int value) {} - @Post("/delegate/counter/{counter_id}/increase") + @Post("/delegate/counter/{counterId}/increase") public CompletionStage addAndReturn(String counterId, IncreaseRequest request) { CompletionStage result = httpClient.POST("/counter/" + counterId + "/increase") // <3> diff --git a/samples/doc-snippets/src/main/java/com/example/jwt/HelloJwtEndpoint.java b/samples/doc-snippets/src/main/java/com/example/jwt/HelloJwtEndpoint.java index 033fdb0df..0d1def1ed 100644 --- a/samples/doc-snippets/src/main/java/com/example/jwt/HelloJwtEndpoint.java +++ b/samples/doc-snippets/src/main/java/com/example/jwt/HelloJwtEndpoint.java @@ -1,7 +1,9 @@ package com.example.jwt; import akka.javasdk.annotations.Acl; +import akka.javasdk.annotations.http.Get; import akka.javasdk.annotations.http.HttpEndpoint; +import akka.javasdk.annotations.http.Post; import akka.javasdk.http.AbstractHttpEndpoint; import akka.javasdk.annotations.JWT; @@ -12,6 +14,7 @@ bearerTokenIssuers = "my-issuer") // <1> public class HelloJwtEndpoint extends AbstractHttpEndpoint { + @Post public String message(String msg) { //.. // end::bearer-token[] @@ -19,6 +22,7 @@ public String message(String msg) { // tag::bearer-token[] } + @Post("with-issuer") @JWT(validate = JWT.JwtMethodMode.BEARER_TOKEN, bearerTokenIssuers = "my-other-issuer") public String messageWithIssuer(String msg) { // <3> diff --git a/samples/doc-snippets/src/main/protobuf/com/example/example_grpc_endpoint.proto b/samples/doc-snippets/src/main/protobuf/com/example/example_grpc_endpoint.proto new file mode 100644 index 000000000..2f7734ec0 --- /dev/null +++ b/samples/doc-snippets/src/main/protobuf/com/example/example_grpc_endpoint.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.example.grpc"; +option java_outer_classname = "ExampleGrpc"; + +package com.example; + +// The greeting service definition. +service ExampleGrpcEndpoint { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + + rpc ItKeepsTalking (stream HelloRequest) returns (HelloReply) {} + + rpc ItKeepsReplying (HelloRequest) returns (stream HelloReply) {} + + rpc StreamHellos (stream HelloRequest) returns (stream 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; +}