Skip to content

Commit f1e4eae

Browse files
add spring boot grpc with tests
1 parent fc673a8 commit f1e4eae

10 files changed

Lines changed: 312 additions & 4 deletions

File tree

build.gradle

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
id 'org.jetbrains.kotlin.plugin.spring' version '2.2.21'
66
id 'org.jetbrains.kotlin.kapt' version '2.2.21'
77
id 'com.google.osdetector' version '1.7.3'
8+
id 'com.google.protobuf' version '0.9.5'
89
id 'groovy'
910
id 'application'
1011
}
@@ -28,6 +29,9 @@ ext {
2829
set('micrometerTracingBridgeOtelVersion', '1.6.3')
2930
set('micrometerContextPropagationVersion', '1.2.1')
3031
set('nettyResolverDnsNativeMacosVersion', '4.2.3.Final')
32+
set('springGrpcVersion', "1.0.2")
33+
set('grpcServicesVersion', '1.79.0')
34+
set('grpcKotlinVersion', '1.5.0')
3135
}
3236

3337
group = 'com.softeno'
@@ -129,12 +133,20 @@ dependencies {
129133
implementation "io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:${logbackAppenderVersion}"
130134
implementation "io.micrometer:micrometer-tracing-bridge-otel:${micrometerTracingBridgeOtelVersion}"
131135
implementation "io.micrometer:context-propagation:${micrometerContextPropagationVersion}"
136+
137+
// grpc
138+
implementation "io.grpc:grpc-services:${grpcServicesVersion}"
139+
implementation "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}"
140+
implementation "io.grpc:grpc-kotlin-stub:${grpcKotlinVersion}"
141+
implementation "org.springframework.grpc:spring-grpc-spring-boot-starter:${springGrpcVersion}"
142+
testImplementation "org.springframework.grpc:spring-grpc-test:${springGrpcVersion}"
132143
}
133144

134145
dependencyManagement {
135146
imports {
136147
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
137148
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
149+
mavenBom "org.springframework.grpc:spring-grpc-dependencies:${springGrpcVersion}"
138150
}
139151
}
140152

@@ -144,4 +156,28 @@ tasks.named('test') {
144156

145157
kapt {
146158
annotationProcessor("org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor")
159+
}
160+
161+
protobuf {
162+
protoc {
163+
artifact = 'com.google.protobuf:protoc'
164+
}
165+
plugins {
166+
grpc {
167+
artifact = 'io.grpc:protoc-gen-grpc-java'
168+
}
169+
grpckt {
170+
artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}:jdk8@jar"
171+
}
172+
}
173+
generateProtoTasks {
174+
all()*.plugins {
175+
grpc {
176+
option '@generated=omit'
177+
}
178+
grpckt {
179+
option '@generated=omit'
180+
}
181+
}
182+
}
147183
}

http/external.http

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,24 @@ Authorization: Bearer {{oauthToken}}
2424

2525
### NO FALLBACK
2626
GET {{host}}/external/throw-on-fallback/{{$random.integer(0, 1000)}}
27-
Authorization: Bearer {{oauthToken}}
27+
Authorization: Bearer {{oauthToken}}
28+
29+
### HTTP -> GRPC
30+
GET {{host}}/external/grpc/{{$random.integer(0, 1000)}}
31+
Authorization: Bearer {{oauthToken}}
32+
33+
### GRPC Unary call
34+
GRPC localhost:9080/com.softeno.template.grpc.SampleGrpcService/Echo
35+
36+
{
37+
"data": "123"
38+
}
39+
40+
### GRPC Server streaming
41+
GRPC localhost:9080/com.softeno.template.grpc.SampleGrpcService/EchoServerStream
42+
43+
{
44+
"data": "hello"
45+
}
46+
47+
### GRPC Client streaming & Bidirectional streaming - not supported in IDEA

http/grpc.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
# Unary
4+
grpcurl -d '{ "data":"123" }' -plaintext localhost:9080 com.softeno.template.grpc.SampleGrpcService.Echo
5+
6+
# Server Streaming
7+
grpcurl -plaintext \
8+
-d '{ "data":"hello" }' \
9+
localhost:9080 \
10+
com.softeno.template.grpc.SampleGrpcService.EchoServerStream
11+
12+
# Client Streaming: { "data": "one" } <enter> (...) ctrl-d
13+
grpcurl -plaintext \
14+
-d @ \
15+
localhost:9080 \
16+
com.softeno.template.grpc.SampleGrpcService.EchoClientStream
17+
18+
#Bidirectional Streaming
19+
grpcurl -plaintext \
20+
-d @ \
21+
localhost:9080 \
22+
com.softeno.template.grpc.SampleGrpcService.EchoBidirectional

http/rsc/rsc-0.9.1.jar

-14.7 MB
Binary file not shown.

src/main/kotlin/com/softeno/template/sample/http/external/coroutine/ExternalController.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.softeno.template.sample.http.external.coroutine
22

3+
import com.softeno.template.grpc.SampleGrpcServiceGrpcKt
4+
import com.softeno.template.grpc.SampleRequest
35
import com.softeno.template.sample.http.external.config.ExternalClientConfig
46
import com.softeno.template.sample.http.internal.reactive.SampleResponseDto
57
import kotlinx.coroutines.reactor.awaitSingle
@@ -20,7 +22,8 @@ class ExternalServiceException(message: String) : RuntimeException(message)
2022
class ExternalController(
2123
@param:Qualifier(value = "external") private val webClient: WebClient,
2224
private val reactiveCircuitBreakerFactory: ReactiveCircuitBreakerFactory<*, *>,
23-
private val config: ExternalClientConfig
25+
private val config: ExternalClientConfig,
26+
private val stub: SampleGrpcServiceGrpcKt.SampleGrpcServiceCoroutineStub
2427
) {
2528
private val log = LogFactory.getLog(javaClass)
2629

@@ -119,4 +122,19 @@ class ExternalController(
119122
return response
120123
}
121124

125+
@GetMapping("/grpc/{data}")
126+
suspend fun getGrpcHandler(@PathVariable data: String): SampleResponseDto {
127+
log.info("[external]: GET grpc request: $data")
128+
129+
val response = stub.echo(
130+
SampleRequest.newBuilder()
131+
.setData(data)
132+
.build()
133+
).data
134+
135+
log.info("[external]: received: $response")
136+
137+
return SampleResponseDto(data = response)
138+
}
139+
122140
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.softeno.template.sample.proto.api
2+
3+
import com.softeno.template.grpc.SampleGrpcServiceGrpcKt
4+
import com.softeno.template.grpc.SampleRequest
5+
import com.softeno.template.grpc.SampleResponse
6+
import kotlinx.coroutines.delay
7+
import kotlinx.coroutines.flow.Flow
8+
import kotlinx.coroutines.flow.flow
9+
import kotlinx.coroutines.flow.toList
10+
import org.apache.commons.logging.LogFactory
11+
import org.springframework.grpc.server.service.GrpcService
12+
13+
@GrpcService
14+
class SampleGrpcServiceImpl :
15+
SampleGrpcServiceGrpcKt.SampleGrpcServiceCoroutineImplBase() {
16+
private val log = LogFactory.getLog(javaClass)
17+
18+
// Unary
19+
override suspend fun echo(request: SampleRequest): SampleResponse {
20+
log.info("[grpc]: echo: $request")
21+
22+
return SampleResponse.newBuilder()
23+
.setData("Echo: ${request.data}")
24+
.build()
25+
}
26+
27+
// Server Streaming
28+
override fun echoServerStream(request: SampleRequest): Flow<SampleResponse> = flow {
29+
log.info("[grpc]: echoServerStream [request]: $request")
30+
31+
repeat(5) { index ->
32+
val response = "Stream[$index]: ${request.data}"
33+
34+
log.info("[grpc]: echoServerStream [response]: $response")
35+
emit(
36+
SampleResponse.newBuilder()
37+
.setData(response)
38+
.build()
39+
)
40+
delay(500) // simulate async work
41+
}
42+
}
43+
44+
// Client Streaming
45+
override suspend fun echoClientStream(
46+
requests: Flow<SampleRequest>
47+
): SampleResponse {
48+
49+
val collected = requests
50+
.toList()
51+
.joinToString(", ") { it.data }
52+
53+
log.info("[grpc]: echoClientStream [request]: $collected")
54+
55+
return SampleResponse.newBuilder()
56+
.setData("Collected: $collected")
57+
.build()
58+
}
59+
60+
// Bidirectional Streaming
61+
override fun echoBidirectional(
62+
requests: Flow<SampleRequest>
63+
): Flow<SampleResponse> = flow {
64+
65+
requests.collect { incoming ->
66+
log.info("[grpc]: echoBidirectional [request]: $incoming")
67+
68+
val requestData = "Reply: ${incoming.data}"
69+
70+
log.info("[grpc]: echoBidirectional [response]: $requestData")
71+
72+
emit(
73+
SampleResponse.newBuilder()
74+
.setData(requestData)
75+
.build()
76+
)
77+
}
78+
}
79+
}
80+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.softeno.template.sample.proto.config
2+
3+
import com.softeno.template.grpc.SampleGrpcServiceGrpcKt
4+
import io.grpc.Channel
5+
import org.springframework.context.annotation.Bean
6+
import org.springframework.context.annotation.Configuration
7+
import org.springframework.grpc.client.GrpcChannelFactory
8+
9+
10+
@Configuration
11+
class GrpcClientConfig(
12+
private val channelFactory: GrpcChannelFactory,
13+
) {
14+
15+
@Bean
16+
fun externalStub(): SampleGrpcServiceGrpcKt.SampleGrpcServiceCoroutineStub {
17+
val channel: Channel = channelFactory.createChannel("external")
18+
return SampleGrpcServiceGrpcKt.SampleGrpcServiceCoroutineStub(channel)
19+
}
20+
}

src/main/proto/schema.proto

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
syntax = "proto3";
2+
3+
package com.softeno.template.grpc;
4+
5+
option java_multiple_files = true;
6+
option java_package = "com.softeno.template.grpc";
7+
option java_outer_classname = "SampleGrpc";
8+
9+
message SampleRequest {
10+
string data = 1;
11+
}
12+
13+
message SampleResponse {
14+
string data = 1;
15+
}
16+
17+
service SampleGrpcService {
18+
// Unary
19+
rpc Echo (SampleRequest) returns (SampleResponse) {}
20+
21+
// Server streaming
22+
rpc EchoServerStream (SampleRequest) returns (stream SampleResponse) {}
23+
24+
// Client streaming
25+
rpc EchoClientStream (stream SampleRequest) returns (SampleResponse) {}
26+
27+
// Bidirectional streaming
28+
rpc EchoBidirectional (stream SampleRequest) returns (stream SampleResponse) {}
29+
}

src/main/resources/application.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ spring.config.import=optional:classpath:config/env.properties
22
spring.main.allow-bean-definition-overriding=false
33

44
server.port=8080
5+
6+
spring.grpc.server.port=9080
7+
spring.grpc.client.channels.external.address=localhost:9082
8+
59
spring.application.name=SoftenoReactiveMongoApp
610

711
spring.mongodb.host=${MONGO_HOST}

0 commit comments

Comments
 (0)