반응형
목표
- 스프링 부트, java를 사용해서 간단한 gRPC를 구현해 봅니다.
- 클라이언트와 서버 측을 만들어 보고 localhost에서 실행해 봅니다
개발 환경
Spring Boot version | 3.4.0 |
java version | Java 17 |
build tool | Gradle 8.11 |
database | h2 |
os | macOS Sonoma 14.4.1 |
IDE | IntelliJ IDEA CE 2023.3.3 |
🦄 SERVER
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.6'
id 'io.spring.dependency-management' version '1.1.6'
id 'com.google.protobuf' version '0.9.4' // ✅
}
- 플러그인에 com.google.protobuf 추가
- .proto 파일의 컴파일과 코드 생성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE' // ✅
implementation 'javax.annotation:javax.annotation-api:1.3.2' // ✅
implementation 'com.google.protobuf:protobuf-java:3.24.0' // ✅
implementation 'io.grpc:grpc-protobuf:1.58.0' // ✅
implementation 'io.grpc:grpc-stub:1.58.0'// ✅
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
- net.devh:grpc-server-spring-boot-starter
- Spring Boot 애플리케이션에서 gRPC 서버를 설정하고 실행할 수 있게 해줌
- gRPC 서버를 Spring Boot 스타일로 설정하여 관리할 수 있게 지원
- Auto Configuration을 제공
- javax.annotation:javax.annotation-api
- 자바 표준 주석 제공
- Protobuf, gRPC 관련 코드에서 javax.annotation을 필요로 하는 경우가 있어 추가
- com.google.protobuf:protobuf-java
- Protobuf 메시지 (직렬화/역직렬화) 처리를 위한 핵심 라이브러리
- .proto파일에서 생성된 java 클래스의 동작을 지원
- io.grpc:grpc-protobuf
- .proto파일에서 생성된 gRPC Stub 클래스의 동작 지원
- Protobuf 메시지와 gRPC Stub의 결합 처리를 위해 필요
- gRPC 서버 및 클라이언트 간 데이터 직렬화/역직렬화를 담당
- io.grpc:grpc-stub
- 서버/클라이언트 Stub을 생성하고 관리하기 위해 필요
- gRPC 서비스 Stub 코드에서 사용됨
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.24.0"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.58.0"
}
}
generateProtoTasks {
all().each { task ->
task.plugins {
grpc {}
}
}
}
}
- protobuf {}
- 프로토콜 버퍼 관련 설정을 포함하는 블록
- 데이터를 직렬화하는데 사용되는 프로토콜 퍼버 도구를 의미
- protoc {}
- 프로토콜 버퍼 컴파일러 정의
- 메시지 클래스인 java dto를 생성
- plugins {}
- 추가 플러그인
- protoc-gen-grp-java라는 플러그인은 gRPC 관련 코드를 생성
- protoc가 .proto 컴파일시 gRPC service 및 stub코드를 추가로 생성하도록 지시
- gRPC 서버 및 클라이언트의 인터페이스와 구현 코드를 생성
- generateProtoTasks
- 자동화된 작업을 정의
- .protobuf 작업에 대해 gRPC 플러그인을 적용하여 필요한 코드를 생성
- all()*.plugins
- 모든 protobuf 파일에 대해 작업에서 사용할 플러그로 grpc 플러그인 활성화
- all()*.plugins
application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
server.port=8081
grpc.server.port=50051
- database 설정과 서버 측 포트 설정
- server의 포트는 8081, grpc 서버는 50051로 설정
- 클라이언트 측에서 grpc 서버 포트는 50051로 동일하게 설정 (server 포트인 8081로 설정 ❌)
.proto 정의
syntax = "proto3";
option java_package = "com.youable.bank_server";
option java_outer_classname = "AccountProto";
service AccountService {
// 계좌 등록
rpc registAccount (RegistAccountRequest) returns (RegistAccountResponse);
}
message RegistAccountRequest {
string balance = 1;
int64 userId = 2;
}
message RegistAccountResponse {
string accountNumber = 1;
int64 userId = 2;
}
- 메세지와 서비스 인터페이스 정의
https://protobuf.dev/programming-guides/proto3/
- 언어별 값 타입은 위 문서 참고
gRPC 코드 생성
- Gradle > other > generateProto 를 사용해 gRPC 구현에 필요한 코드를 생성한다
ServiceImpl
import com.youable.bank_server.AccountProto;
import com.youable.bank_server.AccountServiceGrpc;
import com.youable.bank_server.account.domain.Account;
import com.youable.bank_server.account.domain.repository.AccountRepository;
import com.youable.bank_server.common.util.BigDecimalConverter;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import net.devh.boot.grpc.server.service.GrpcService;
import java.math.BigDecimal;
import java.util.UUID;
@GrpcService // ✅
@RequiredArgsConstructor
public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBase {
private final AccountRepository accountRepository;
@Override
public void registAccount(AccountProto.RegistAccountRequest request, StreamObserver<AccountProto.RegistAccountResponse> responseObserver) {
BigDecimal balance = BigDecimalConverter.fromString(request.getBalance());
long userId = request.getUserId();
Account account = Account.builder()
.accountNumber(UUID.randomUUID().toString())
.balance(balance)
.userId(userId)
.build();
accountRepository.save(account);
AccountProto.RegistAccountResponse response = AccountProto.RegistAccountResponse.newBuilder()
.setAccountNumber(account.getAccountNumber())
.setUserId(account.getUserId())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
- @GrpcService 애너테이션 추가
- 자동으로 생성된 ServiceGrpc의 ServiceImplBase를 상속하여 구현
- Client로 부터 .proto의 정의한 RegistAccountRequest를 전달 받으면 이를 이용해 비즈니스 로직과 데이터베이스 접근 관련 로직을 처리한 뒤 responseObserver를 사용해 응답한다
- .proto에 정의한 응답은 newBuilder를 사용해 생성해 줄 수 있다
- responseObserver.onCompleted()를 보내 통신을 완료한다
public interface StreamObserver<V> {
void onNext(V var1);
void onError(Throwable var1);
void onCompleted();
}
- StreamObserver에는 세 가지가 정의되어 있는데, 값을 전달할 때 onNext, 에러를 전달할 때 onError, 완료시 onCompleted를 사용할 수 있다
🌴 Client
build.gradle
buildscript {
ext {
protobufVersion = '3.24.0'
protobufPluginVersion = '0.9.4'
grpcVersion = '1.58.0'
}
}
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.6'
id 'com.google.protobuf' version "${protobufPluginVersion}" // ✅
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "io.grpc:grpc-protobuf:${grpcVersion}" // ✅
implementation "io.grpc:grpc-stub:${grpcVersion}" // ✅
implementation "com.google.protobuf:protobuf-java:${protobufVersion}" // ✅
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}" // ✅
implementation 'javax.annotation:javax.annotation-api:1.3.2' // ✅
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
// ✅
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.24.0"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.58.0"
}
}
generateProtoTasks {
all().each { task ->
task.plugins {
grpc {}
}
}
}
}
- Server side와 다른 점이 있다면 net.devh:grpc-server-spring-boot-starter가 없고 netty 통신을 위한 io.grpc:grpc-netty-shaded가 추가됨
.proto
- 서버에서 지정한 .proto와 동일하게 지정 후 generateProto 로 파일 자동 생성
Controller
@RequestMapping("/api/v0/account")
@RequiredArgsConstructor
@RestController
public class AccountController {
private final GrpcAccountClient client;
@PostMapping("/regist")
public String regist() {
AccountProto.RegistAccountRequest request = AccountProto.RegistAccountRequest
.newBuilder()
.setUserId(1)
.setBalance("2000")
.build();
AccountProto.RegistAccountResponse accountResponse = client.registAccount(request);
return accountResponse.getAccountNumber();
}
}
- 유저가 REST API 호출 할 수 있게 Controller 코드 생성
- (테스트를 간단하게 해볼려고 컨트롤러에서 값을 지정한 request 객체를 생성해서 사용했습니다 🥲 추후 수정 예정..)
GrpcAccountClient
import com.youable.bank_server.AccountProto;
import com.youable.bank_server.AccountServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import org.springframework.stereotype.Component;
@Component
public class GrpcAccountClient {
private final AccountServiceGrpc.AccountServiceBlockingStub blockingStub;
public GrpcAccountClient() {
ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
blockingStub = AccountServiceGrpc.newBlockingStub(channel);
}
public AccountProto.RegistAccountResponse registAccount(AccountProto.RegistAccountRequest request) {
return blockingStub.registAccount(request);
}
}
- Grpc 통신을 위한 Client 객체 생성
- channel에 grpc 서버 정보를 제공해 준다
- Grpc 통신시 사용할 Stub을 생성후 이를 이용
- .proto 인터페이스에서 정의한 요청 객체를 생성후 stub을 사용해 request 한다
728x90
반응형
'BE > 🍃 Spring' 카테고리의 다른 글
[gRPC]에 대해 알아보기 (1) | 2024.11.24 |
---|---|
[Spring Boot] AOP를 활용한 커스텀 애노테이션 만들기 (0) | 2024.11.19 |
[Spring Boot] AOP 사용 방법과 예시코드 (0) | 2024.11.18 |
[Spring Boot] AOP 개념 정리 (0) | 2024.11.17 |
[MQTT] Spring Boot 에서 MQTT(mosquitto) 사용하기 (4) | 2024.11.13 |
댓글