diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml
index c7becacbd..f56b83214 100644
--- a/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml
+++ b/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml
@@ -21,23 +21,6 @@
org.springframework.boot
spring-boot-starter
-
-
- io.grpc
- grpc-netty
- 1.16.1
-
-
- io.grpc
- grpc-protobuf
- 1.16.1
-
-
- io.grpc
- grpc-stub
- 1.16.1
-
-
org.springframework.cloud
spring-cloud-function-grpc
diff --git a/spring-cloud-function-grpc/README.md b/spring-cloud-function-grpc/README.md
index f56068acb..0f13b5dd2 100644
--- a/spring-cloud-function-grpc/README.md
+++ b/spring-cloud-function-grpc/README.md
@@ -34,6 +34,7 @@ message GrpcMessage {
As you can see it is a very generic structure which can support any type of data amd metadata you wish to exchange.
It alos defines a `MessagingService` allowing you to generate required stubs to support true plolyglot nature of gRPC.
+
```
service MessagingService {
rpc biStream(stream GrpcMessage) returns (stream GrpcMessage);
@@ -73,6 +74,7 @@ public static class SampleConfiguration {
```
After identifying this function via `spring.cloud.function.definition` property (see example [here](https://github.com/spring-cloud/spring-cloud-function/blob/ded02fec0a6d3d66b8ec00f99f28be2a4bbec668/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java)),
you can invoke it using utility method(s) provided in `GrpcUtils` class
+
```java
Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes())
.setHeader("foo", "bar")
@@ -81,6 +83,7 @@ Message reply = GrpcUtils.requestReply(message);
```
You can also provide `spring.cloud.function.definition` property via `Message` headers, to support more dynamic cases.
+
```java
Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes())
.setHeader("foo", "bar")
@@ -102,6 +105,7 @@ public static class SampleConfiguration {
```
After identifying this function via `spring.cloud.function.definition` property (see example [here](https://github.com/spring-cloud/spring-cloud-function/blob/ded02fec0a6d3d66b8ec00f99f28be2a4bbec668/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java)),
you can invoke it using utility method(s) provided in `GrpcUtils` class
+
```java
Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes()).setHeader("foo", "bar").build();
@@ -114,6 +118,7 @@ List> results = reply.collectList().block(Duration.ofSeconds(5))
You can see that gRPC stream is mapped to instance of `Flux` from [project reactor](https://projectreactor.io/)
Similarly to the _request/reply_ you can also provide `spring.cloud.function.definition` property via `Message` headers, to support more dynamic cases.
+
```java
Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes())
.setHeader("foo", "bar")
@@ -143,6 +148,7 @@ public static class SampleConfiguration {
```
After identifying this function via `spring.cloud.function.definition` property (see example [here](https://github.com/spring-cloud/spring-cloud-function/blob/ded02fec0a6d3d66b8ec00f99f28be2a4bbec668/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java)),
you can invoke it using utility method(s) provided in `GrpcUtils` class
+
```java
List> messages = new ArrayList<>();
messages.add(MessageBuilder.withPayload("\"Ricky\"".getBytes()).setHeader("foo", "bar")
@@ -175,6 +181,7 @@ public static class SampleConfiguration {
```
After identifying this function via `spring.cloud.function.definition` property (see example [here](https://github.com/spring-cloud/spring-cloud-function/blob/ded02fec0a6d3d66b8ec00f99f28be2a4bbec668/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java)),
you can invoke it using utility method(s) provided in `GrpcUtils` class
+
```java
List> messages = new ArrayList<>();
messages.add(MessageBuilder.withPayload("\"Ricky\"".getBytes()).setHeader("foo", "bar")
@@ -193,3 +200,34 @@ List> results = clientResponseObserver.collectList().block(Durat
You can see that gRPC stream is mapped to instance of `Flux` from [project reactor](https://projectreactor.io/)
Unlike the _request/reply_ and _server-side streaming_, you can ONLY pass function definition via property or environment variable.
+
+#### Pluggable protobuf extension
+
+While the core data object and its corresponding schema <> are modeled after Spring Message and can represent
+virtually any object, there are times when you may want to plug-in your own protobuf services.
+
+Spring Cloud Function provides such support by allowing you to develop extensions, which once exist could be enabled by simply
+including its dependency in the POM. Such extensions are just another spring-boot project that has dependency on `spring-cloud-function-grpc`
+
+```xml
+
+ org.springframework.cloud
+ spring-cloud-function-grpc
+
+```
+
+It must also contain 3 classes; 1) Its configuration class, 2) Type converter for the actual protobuf 'message'and 3) Service handler
+where you would normally implement your handling functionality. However instead of implementing full functionality you can model your service
+after MessagingService provided by us and if you do you can rely on the existing implementation of the core interaction models provided by gRPC
+
+In fact Spring Cloud Function provides one of such extensions to support [Cloud Events](https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext) proto, so you can model yours after it.
+
+#### Multiple services on classpath
+
+With the protobuf extension mentioned in the previous section you may very well end up with several services on the classpath.
+By default each available service will be enabled. However, if your intention is to only use one, you can specify which one by providing
+its class name via `spring.cloud.function.grpc.service-class-name` property:
+
+```
+--spring.cloud.function.grpc.service-class-name=org.springframework.cloud.function.grpc.ce.CloudEventHandler
+```
\ No newline at end of file
diff --git a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/FunctionGrpcProperties.java b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/FunctionGrpcProperties.java
index 526bbed6c..dd1427688 100644
--- a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/FunctionGrpcProperties.java
+++ b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/FunctionGrpcProperties.java
@@ -28,13 +28,29 @@ import org.springframework.cloud.function.context.FunctionProperties;
@ConfigurationProperties(prefix = FunctionProperties.PREFIX + ".grpc")
public class FunctionGrpcProperties {
+ private final static String GRPC_PREFIX = FunctionProperties.PREFIX + ".grpc";
+ /**
+ * The name of function definition property.
+ */
+ public final static String SERVICE_CLASS_NAME = GRPC_PREFIX + ".service-class-name";
+
/**
* Default gRPC port.
*/
public final static int GRPC_PORT = 6048;
+ /**
+ * gRPC port server will bind to. Default 6048;
+ */
private int port = GRPC_PORT;
+ /**
+ * The fully qualified name of the service you wish to enable/expose.
+ * Setting this property ensures that only a single service is enabled/exposed,
+ * regardless of how many services are available on the classpath.
+ */
+ private String serviceClassName;
+
/**
* Grpc Server port.
*/
@@ -45,4 +61,14 @@ public class FunctionGrpcProperties {
public void setPort(int port) {
this.port = port;
}
+
+
+ public String getServiceClassName() {
+ return serviceClassName;
+ }
+
+
+ public void setServiceClassName(String serviceClassName) {
+ this.serviceClassName = serviceClassName;
+ }
}
diff --git a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcAutoConfiguration.java b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcAutoConfiguration.java
index 902cfdb4a..bf5ebcf30 100644
--- a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcAutoConfiguration.java
+++ b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcAutoConfiguration.java
@@ -25,6 +25,7 @@ import org.springframework.cloud.function.context.FunctionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
import com.google.protobuf.GeneratedMessageV3;
@@ -43,15 +44,24 @@ class GrpcAutoConfiguration {
@Bean
public GrpcServer grpcServer(FunctionGrpcProperties grpcProperties, BindableService[] grpcMessagingServices) {
Assert.notEmpty(grpcMessagingServices, "'grpcMessagingServices' must not be null or empty");
+ if (StringUtils.hasText(grpcProperties.getServiceClassName())) {
+ for (BindableService bindableService : grpcMessagingServices) {
+ if (bindableService.getClass().getName().equals(grpcProperties.getServiceClassName())) {
+ return new GrpcServer(grpcProperties, new BindableService[] {bindableService});
+ }
+ }
+ }
return new GrpcServer(grpcProperties, grpcMessagingServices);
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public BindableService grpcSpringMessageHandler(MessageHandlingHelper helper) {
return new GrpcServerMessageHandler(helper);
}
+ @SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public MessageHandlingHelper grpcMessageHandlingHelper(List> grpcConverters,
FunctionProperties funcProperties, FunctionCatalog functionCatalog) {
diff --git a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcServer.java b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcServer.java
index f5924ef9e..dd97e8cb5 100644
--- a/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcServer.java
+++ b/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcServer.java
@@ -32,6 +32,9 @@ import io.grpc.protobuf.services.ProtoReflectionService;
/**
*
* @author Oleg Zhurakousky
+ * @author Dave Syer
+ *
+ * @since 3.2
*
*/
class GrpcServer implements SmartLifecycle {
@@ -70,6 +73,7 @@ class GrpcServer implements SmartLifecycle {
logger.info("gRPC server is listening on port " + this.grpcProperties.getPort());
}
catch (Exception e) {
+ stop();
throw new IllegalStateException(e);
}
});
diff --git a/spring-cloud-function-samples/function-sample-grpc-cloudevent/src/main/java/com/example/grpc/demo/DemoGrpcApplication.java b/spring-cloud-function-samples/function-sample-grpc-cloudevent/src/main/java/com/example/grpc/demo/DemoGrpcApplication.java
index 77c79c9af..83d7bcc33 100644
--- a/spring-cloud-function-samples/function-sample-grpc-cloudevent/src/main/java/com/example/grpc/demo/DemoGrpcApplication.java
+++ b/spring-cloud-function-samples/function-sample-grpc-cloudevent/src/main/java/com/example/grpc/demo/DemoGrpcApplication.java
@@ -30,7 +30,8 @@ public class DemoGrpcApplication {
public static void main(String[] args) throws Exception {
- SpringApplication.run(DemoGrpcApplication.class, args);
+ SpringApplication.run(DemoGrpcApplication.class,
+ "--spring.cloud.function.grpc.service-class-name=org.springframework.cloud.function.grpc.ce.CloudEventHandler");
CloudEvent cloudEvent = CloudEvent.newBuilder()
.setTextData("{\"event_name\":\"SCF supports CloudEvent gRPC\"}")