본문 바로가기
BE/🍃 Spring

[Spring Boot] 간단한 gRPC 구현하기

by 틴디 2024. 11. 26.
반응형

 

목표

  • 스프링 부트, 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 플러그인 활성화

 

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/

 

Language Guide (proto 3)

Covers how to use the proto3 revision of the Protocol Buffers language in your project.

protobuf.dev

  • 언어별 값 타입은 위 문서 참고

 

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
반응형

댓글