해당 의사결정 내용의 포스팅 - 멀티모듈 방식으로 proto repository를 따로 만들어 관리. 왜?
포트 중복 문제 해결
포트를 중복해서 쓰고있다는 문제 계속됨. 터미널 명령어로는 사용하고 있는 포트가 나오지 않았다.
-> 해결 : 두 서버가 GrpcServerConfig와 grpc-server-spring-boot-starter를 동시에 사용하면서 포트 충돌이 발생한 것이었다. GrpcServerConfig를 삭제하니 정상 실행되었다.
UNIMPLEMENTED: Method not found: payment.PaymentService/GetPaymentInfo 에러 발생
-> 해결 : PaymentServiceImpl에 어노테이션은 @Service에서 @GrpcService 로 변경
.proto 파일syntax = "proto3";
package payment;
option java_package = "com.example.payment.grpc";
option java_outer_classname = "PaymentServiceProto";
message PaymentRequest {
int64 userId = 1;
string orderId = 2;
UserDetails user = 3;
repeated EventDetails event = 4;
}
message UserDetails {
string userRole = 1;
string email = 2;
Address address = 3;
}
message Address {
string street = 1;
string city = 2;
string state = 3;
string zip = 4;
}
message EventDetails {
string eventId = 1;
string title = 2;
string category = 3;
string place = 4;
}
message PaymentResponse {
string orderId = 1;
int64 userId = 2;
int32 amount = 3;
bool success = 4;
string message = 5;
}
service PaymentService {
rpc GetPaymentInfo(PaymentRequest) returns (PaymentResponse); //실제 gRPC 서비스의 메서드를 정의
}
plugins {
id 'java'
id 'com.google.protobuf' version '0.9.2'
}
group = 'com.sparta'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:4.28.3" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.68.1" }
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
java {}
}
task.plugins {
grpc {}
}
}
}
}
dependencies {
// gRPC
implementation "io.grpc:grpc-netty-shaded:1.68.1"
implementation "io.grpc:grpc-protobuf:1.68.1"
implementation "io.grpc:grpc-stub:1.68.1"
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation "com.google.protobuf:protobuf-java:4.28.3"
}
tasks.named('test') {
useJUnitPlatform()
}
위 .proto파일과 build.gradle를 작성하고 gradle build를 하면 아래 이미지처럼 java 파일이 생성된다.

그리고나서! 티켓서버와 결제서버에서 위 grpc-proto 레포지토리를 Gradle Composite Build 사용해 연결하여 프로토파일 공유한다.
또한, 팀원들이 별도로 로컬 경로를 확인하거나 설정하지 않고도 프로젝트를 사용할 수 있게 빌드 자동화 스크립트를 사용했다.

plugins {
id 'java'
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
id 'com.google.protobuf' version '0.9.2'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
sourceSets {
main {
java {
srcDirs += file('grpc-proto/build/generated/source/proto/main/java')
srcDirs += file('grpc-proto/build/generated/source/proto/main/grpc')
}
}
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:4.28.3" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.68.1" }
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
java {}
}
task.plugins {
grpc {}
}
}
}
}
dependencies {
//gRPC
implementation "io.grpc:grpc-netty-shaded:1.68.1"
implementation "io.grpc:grpc-protobuf:1.68.1"
implementation "io.grpc:grpc-stub:1.68.1"
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'net.devh:grpc-spring-boot-starter:3.1.0.RELEASE'
implementation 'com.google.protobuf:protobuf-java:4.28.3'
...
}
// 빌드 전에 grpc-proto의 Java 파일이 생성
tasks.named('compileJava') {
dependsOn gradle.includedBuild('grpc-proto').task(':generateProto')
}
이렇게 티켓 서버와 결제 서버 각각 설정해준 뒤,
./gradlew clean build
명령어를 입력하면, 각 서버에서도 아래와 같이 grpc-proto 폴더 하위 java 파일이 생성된다.

이제 각 서버의 서비스 단에서 로직을 작성해주면 된다.
결제 정보를 요청하는 티켓 서버에서는 아래와 같이 PaymentServiceImpl, PaymentServiceClient 클래스를 작성한다.
PaymentServiceImpl 클래스 
PaymentServiceClient 클래스(테스트를 위해 임의의 값을 넣어줌) 

Spring MVC 컨트롤러를 통해 HTTP 요청을 받고 내부적으로 gRPC 클라이언트를 호출하여 응답을 처리한다.

티켓서버에서 받은 request로 조회한 결제정보 데이터를 반환하는 결제서버의 로직이다.


| 프로토콜 | 요청 수 | 총 소요 시간(s) | 평균응답시간(ms) | 평균 TPS(/sec) | 에러율(%) |
|---|---|---|---|---|---|
| REST | 10000 | 393 | 3843 | 25.5 | 0 |
| gRPC | 10000 | 180 | 1770 | 55.5 | 0 |
| REST | 100000 | 3044 | 15044 | 32.8 | 0 |
| gRPC | 100000 | 990 | 4902 | 101.1 | 0 |
눈물이 찔끔 날 만큼 말도 안되게 어려웠다. 엘라스틱서치를 공부하면서도 스프링과 다른 규모라서 신세계를 맛봤다고 생각했는데 gRPC 통신 개선은 정말 정말 정말 어려웠다.
레파토리도 많이 안나와있고, 정보가 많지 않아서 찾아보기도 어려웠다.
근데... 그래서.... 희열이 나.. 통신 공부라...? 호오... 너무 너무 너무 어려워서 희열이 나.....!
무엇보다 처음에 다른 기술 스택을 사용하는 것보다 gRPC 통신을 공부하고 개선하는 걸 마음먹는 것부터가 결정하기 힘들었는데, 성능테스트를 하며 잘 연결이 되다는 것 그리고 REST 대비 속도가 배수의 차이가 난다는 것을 보고 감격에 젖어 눈물이 (마음속에서) 났다. 이제 더 더 깊이 공부해야할 차례! 아자!
