diff --git a/basic/barrier/pom.xml b/basic/barrier/pom.xml index 322845c60..086b0b88c 100644 --- a/basic/barrier/pom.xml +++ b/basic/barrier/pom.xml @@ -73,8 +73,7 @@ org.springframework.boot - spring-boot-starter-rabbitmq - 4.1.0-SNAPSHOT + spring-boot-starter-amqp compile diff --git a/basic/grpc-client/README.md b/basic/grpc-client/README.md new file mode 100644 index 000000000..57949fe30 --- /dev/null +++ b/basic/grpc-client/README.md @@ -0,0 +1,58 @@ +gRPC Client Sample +================== + +This example demonstrates the use of Spring Integration's **gRPC Outbound Gateway** for client-side communication. + +It uses Java configuration and the Spring Integration DSL to configure the gRPC client adapters. + +The sample demonstrates two gRPC communication patterns: + +1. **Single Response** - Simple request/reply pattern where the client sends a request and receives a single response +2. **Streaming Response** - The client sends a request and receives a stream of responses + +## Prerequisites + +This sample requires a gRPC server to be running. You can use any gRPC server that implements the `Simple` service defined in `hello.proto`, or run the test which uses an in-process gRPC server. + +## Running the sample + +### Command Line Using Gradle + +To run the sample using Gradle, execute: + + $ gradlew :grpc-client:bootRun + +This will start the Spring Boot application using the [Spring Boot Gradle Plugin](https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/). + +## Configuration + +The application expects a gRPC server to be available. By default, it connects to `localhost:9090`. You can configure the server address in `application.properties`: + +```properties +spring.grpc.client.channels.spring-integration.address=static://localhost:9090 +``` + +## Output + +The application demonstrates two patterns: + +### Single Response Pattern + +The client sends a `HelloRequest` with name "Jack" and receives a single `HelloReply`: + + Single response reply: message: Hello Jack + +### Streaming Response Pattern + +The client sends a `HelloRequest` with name "Jane" and receives multiple `HelloReply` messages: + + Stream received reply: Hello Jane + Stream received reply: Hello Again + +## Running the Tests + +The test uses an in-process gRPC server and does not require an external server: + + $ gradlew :grpc-client:test + +The test validates both single-response and streaming-response patterns. diff --git a/basic/grpc-client/pom.xml b/basic/grpc-client/pom.xml new file mode 100644 index 000000000..ff9cfced6 --- /dev/null +++ b/basic/grpc-client/pom.xml @@ -0,0 +1,455 @@ + + + 4.0.0 + org.springframework.integration.samples + grpc-client + 7.1.0 + jar + https://github.com/spring-projects/spring-integration-samples + + Spring IO + https://spring.io/projects/spring-integration + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + artembilan + Artem Bilan + artem.bilan@broadcom.com + + project lead + + + + garyrussell + Gary Russell + github@gprussell.net + + project lead emeritus + + + + markfisher + Mark Fisher + mark.ryan.fisher@gmail.com + + project founder and lead emeritus + + + + cppwfs + Glenn Renfro + glenn.renfro@broadcom.com + + project committer + + + + + scm:git:scm:git:git://github.com/spring-projects/spring-integration-samples.git + scm:git:scm:git:ssh://git@github.com:spring-projects/spring-integration-samples.git + https://github.com/spring-projects/spring-integration-samples + + + GitHub + https://github.com/spring-projects/spring-integration-samples/issues + + + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.grpc + spring-grpc-client-spring-boot-starter + + + org.springframework.integration + spring-integration-grpc + + + com.google.protobuf + protobuf-java + 4.29.4 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.hamcrest + hamcrest-library + 2.2 + test + + + org.mockito + mockito-core + 5.21.0 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.integration + spring-integration-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.grpc + spring-grpc-test + test + + + org.springframework.grpc + spring-grpc-server-spring-boot-starter + test + + + org.junit.jupiter + junit-jupiter-engine + runtime + + + org.junit.platform + junit-platform-launcher + runtime + + + + + + org.springframework.integration + spring-integration-jdbc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ws + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-redis + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-r2dbc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-rsocket + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-event + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-cassandra + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-scripting + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-websocket + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-graphql + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jpa + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-test + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-cloudevents + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-http + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zookeeper + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ip + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mail + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-camel + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mongodb + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-stream + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-stomp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-file + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-syslog + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-smb + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jmx + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-webflux + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-sftp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-mqtt + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-ftp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-kafka + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-groovy + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-xmpp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-feed + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-debezium + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zeromq + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-hazelcast + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-test-support + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-amqp + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-grpc + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-xml + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-core + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-zip + 7.1.0-SNAPSHOT + + + org.springframework.integration + spring-integration-jms + 7.1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-dependencies + 4.1.0-SNAPSHOT + import + pom + + + org.springframework.grpc + spring-grpc-dependencies + 1.0.2 + import + pom + + + org.junit + junit-bom + 6.0.2 + import + pom + + + tools.jackson + jackson-bom + 3.0.3 + import + pom + + + com.fasterxml.jackson + jackson-bom + 2.21.0 + import + pom + + + org.springframework + spring-framework-bom + 7.0.5 + import + pom + + + org.springframework.integration + spring-integration-bom + 7.1.0-SNAPSHOT + import + pom + + + + + 17 + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:4.29.4:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:1.70.0:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + org.springframework.boot + spring-boot-starter-parent + 4.1.0-SNAPSHOT + + + + repo.spring.io.milestone + Spring Framework Maven Milestone Repository + https://repo.spring.io/milestone + + + repo.spring.io.snapshot + Spring Framework Maven Snapshot Repository + https://repo.spring.io/snapshot + + + diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java new file mode 100644 index 000000000..77da01321 --- /dev/null +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/Application.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples.grpc.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Run the gRPC client sample application. + *

+ * Demonstrates how to use Spring Integration with gRPC for client-side communication. + * + * @author Glenn Renfro + */ +@SpringBootApplication +public class Application { + + /** + * Run the Spring Boot application. + * @param args the command line arguments + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java new file mode 100644 index 000000000..9e109ce05 --- /dev/null +++ b/basic/grpc-client/src/main/java/org/springframework/integration/samples/grpc/client/GrpcClientConfiguration.java @@ -0,0 +1,127 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples.grpc.client; + +import io.grpc.ManagedChannel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Flux; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelFactory; +import org.springframework.integration.channel.FluxMessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.grpc.GrpcHeaders; +import org.springframework.integration.grpc.dsl.Grpc; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +/** + * Service for demonstrating gRPC client functionality with Spring Integration. + * + * @author Glenn Renfro + */ +@Configuration(proxyBeanMethods = false) +public class GrpcClientConfiguration { + + private static final Log LOGGER = LogFactory.getLog(GrpcClientConfiguration.class); + + @Bean + ManagedChannel managedChannel(GrpcChannelFactory factory) { + return factory.createChannel("spring-integration"); + } + + /** + * Create an integration flow for outbound gRPC requests with single responses. + * This flow specifies the method name to be used. + * @param managedChannel the gRPC managed channel + * @return the integration flow + */ + @Bean + IntegrationFlow grpcOutboundFlowSingleResponse(ManagedChannel managedChannel) { + return flow -> flow + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class) + .methodName("SayHello")); + } + + /** + * Create an integration flow for outbound gRPC requests with streaming responses. + * This flow does not specify the method name to be used, this means that the method + * name is extracted from the message headers. + * @param managedChannel the gRPC managed channel + * @return the integration flow + */ + @Bean + IntegrationFlow grpcOutboundFlowStreamResponse(ManagedChannel managedChannel) { + return flow -> flow + .handle(Grpc.outboundGateway(managedChannel, SimpleGrpc.class)); + } + + /** + * Create an application runner that sends a single gRPC request and receives a single response. + * @param grpcInputChannelSingleResponse the message channel for single response requests + * @return the application runner + */ + @Bean + ApplicationRunner grpcClientSingleResponse( + @Autowired @Qualifier("grpcOutboundFlowSingleResponse.input") MessageChannel grpcInputChannelSingleResponse) { + + return args -> { + HelloReply reply = new MessagingTemplate().convertSendAndReceive(grpcInputChannelSingleResponse, + HelloRequest.newBuilder().setName("Jack").build(), + HelloReply.class); + + LOGGER.info("Single response reply: " + (reply != null ? reply.getMessage() : "No reply received")); + }; + } + + /** + * Create an application runner that sends a gRPC request and receives a stream of responses. + * @param grpcInputChannel the message channel for streaming response requests + * @return the application runner + */ + @Bean + ApplicationRunner grpcClientStreamResponse( + @Autowired @Qualifier("grpcOutboundFlowStreamResponse.input") MessageChannel grpcInputChannel) { + + return args -> { + FluxMessageChannel grpcStreamOutputChannel = new FluxMessageChannel(); + Flux.from(grpcStreamOutputChannel) + .map(message -> (HelloReply) message.getPayload()) + .map(HelloReply::getMessage) + .doOnNext(msg -> LOGGER.info("Stream received reply: " + msg)) + .doOnError(error -> { + LOGGER.error("Error in stream: " + error.getMessage(), error); + }) + .subscribe(); + + Message requestMessage = MessageBuilder + .withPayload(HelloRequest.newBuilder().setName("Jane").build()) + .setReplyChannel(grpcStreamOutputChannel) + .setHeader(GrpcHeaders.SERVICE_METHOD, "StreamHello") + .build(); + grpcInputChannel.send(requestMessage); + }; + } + +} diff --git a/basic/grpc-client/src/main/proto/hello.proto b/basic/grpc-client/src/main/proto/hello.proto new file mode 100644 index 000000000..950e549b0 --- /dev/null +++ b/basic/grpc-client/src/main/proto/hello.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.springframework.integration.samples.grpc.client"; +option java_outer_classname = "HelloWorldProto"; + +// The greeting service definition. +service Simple { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) { + } + rpc StreamHello(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; +} diff --git a/basic/grpc-client/src/main/resources/application.properties b/basic/grpc-client/src/main/resources/application.properties new file mode 100644 index 000000000..d1c9371d0 --- /dev/null +++ b/basic/grpc-client/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# gRPC Client Configuration +spring.grpc.client.channels.spring-integration.address=static://localhost:9090 diff --git a/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java new file mode 100644 index 000000000..2d479852b --- /dev/null +++ b/basic/grpc-client/src/test/java/org/springframework/integration/samples/grpc/client/GrpcClientTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.samples.grpc.client; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +/** + * Integration test for gRPC client functionality. + * + * @author Glenn Renfro + */ +@SpringBootTest(properties = { + "spring.grpc.test.inprocess.enabled=true", + "spring.grpc.test.inprocess.exclusive=true" +}) +@DirtiesContext +@ExtendWith(OutputCaptureExtension.class) +public class GrpcClientTests { + + /** + * Verify the gRPC service returns the expected greeting message. + * @param output the captured console output + */ + @Test + void verifyMessage(CapturedOutput output) { + await() + .atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> { + assertThat(output.getAll()) + .contains("Single response reply: Hello Jack") + .contains("Stream received reply: Hello Jane") + .contains("Stream received reply: Hello Again"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class TestGrpcServerConfig { + + /** + * Create a test gRPC service implementation for the Simple service. + * @return the gRPC service implementation + */ + @Bean + SimpleGrpc.SimpleImplBase simpleService() { + return new SimpleGrpc.SimpleImplBase() { + + @Override + public void sayHello(HelloRequest request, io.grpc.stub.StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + request.getName()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + @Override + public void streamHello(HelloRequest request, + io.grpc.stub.StreamObserver responseObserver) { + + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + request.getName()) + .build(); + responseObserver.onNext(reply); + reply = HelloReply.newBuilder() + .setMessage("Hello Again") + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + }; + } + } + +} diff --git a/build.gradle b/build.gradle index f1e57a748..8be0c41b0 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ buildscript { classpath 'org.gretty:gretty:4.1.10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.6" } } @@ -306,6 +307,7 @@ subprojects { subproject -> flexjsonVersion = '3.3' graalvmVersion = '25.0.2' groovyVersion = '5.0.3' + grpcVersion = '1.79.0' hsqldbVersion = '2.7.4' h2Version = '2.4.240' jacksonVersion = '2.21.0' @@ -325,8 +327,10 @@ subprojects { subproject -> mongoDriverVersion = '5.6.2' oracleDriverVersion = '23.26.0.0.0' postgresVersion = '42.7.9' + protobufVersion = '4.29.4' slf4jVersion = '2.0.17' springCloudVersion = '2025.1.0' + springGrpc = '1.0.2' springIntegrationVersion = '7.1.0-SNAPSHOT' set('spring-integration.version', "$springIntegrationVersion") springIntegrationSocialTwiterVersion = '1.0.1.BUILD-SNAPSHOT' @@ -349,6 +353,7 @@ subprojects { subproject -> mavenBom "com.fasterxml.jackson:jackson-bom:$jacksonVersion" mavenBom "tools.jackson:jackson-bom:$jackson3Version" mavenBom "org.junit:junit-bom:$junitJupiterVersion" + mavenBom "org.springframework.grpc:spring-grpc-dependencies:$springGrpc" } } @@ -877,6 +882,65 @@ project('cafe-dsl') { } } +project('grpc-client') { + description = 'gRPC client Basic Sample' + + apply plugin: 'org.springframework.boot' + apply plugin: 'com.google.protobuf' + + dependencies { + implementation 'org.springframework.boot:spring-boot-starter-integration' + implementation "org.springframework.grpc:spring-grpc-client-spring-boot-starter" + implementation 'org.springframework.integration:spring-integration-grpc' + + implementation("com.google.protobuf:protobuf-java:$protobufVersion") + + //Test + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "org.springframework.grpc:spring-grpc-test" + + configurations { + testRuntimeOnly.exclude group: 'io.grpc', module: 'grpc-netty' + } + } + + + protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" + } + } + generateProtoTasks { + all()*.plugins { + grpc { + option '@generated=omit' + } + } + } + } + // Exclude generated protobuf/grpc code from checkstyle + checkstyleMain { + source = fileTree('src/main/java') + } + + checkstyleTest { + source = fileTree('src/test/java') + } + + springBoot { + mainClass = 'org.springframework.integration.samples.Application' + } + + tasks.register('run', JavaExec) { + mainClass = 'org.springframework.integration.samples.Application' + classpath = sourceSets.main.runtimeClasspath + } + +} project('jdbc') { description = 'JDBC Basic Sample' diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index da1d0cb5e..8bcb99eab 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -25,4 +25,5 @@ +