Spring boot로 Grpc를 사용해보자

코딩하는범이·2021년 10월 13일
11
post-thumbnail

오늘은 Grpc에 대해서 글을 써보려고한다.

Grpc를 간단하게 설명하면 서로 다른 위치에 존재하는 공간에서 동일한 객체를 가져다 와서 사용하는 것이다. Grpc에 대해 궁금하다면 정리를 해놓은 글들이 많으니 찾아서 보기 바란다.

Spring boot에는 아주 훌륭한 Grpc Spring Boot Starter 라이브러리가 존재한다. 예제도 훌륭하게 잘 되어있어 문서를 보고 구현하면 된다.

Github 사이트 : 깃허브 사이트
예제 사이트 : 예제 문서

들어가기 앞서 필자는 모든 프로젝트를 Gradle로 생성할 것이다.
다른 프로젝트로 할 수 있지만 환경을 고려해 우리는 총 3개의 자바 프로젝트를 생성 할 것이다.

  1. Grpc Interface
  2. Grpc Server
  3. Grpc Client

1번 Grpc Interface는 Grpc Server와 Grpc Client가 공통으로 사용하는 부분이다.
Grpc Interface에서 Proto 파일을 정의 할 것이다. 그리고 Interface를 .jar파일로 만들어 다른 프로젝트에서 Import 해서 사용 할 것이다.

Grpc Interface

일단 Grpc Interface는 Spring 구성이 필요 없기 때문에 Gradle 프로젝트로 생성을 하겠다.

그리고 나서 build.gradle을 아래와 같이 수정한다

build.gradle (Grpc Interface)
buildscript {
    ext {
        protobufVersion = '3.14.0'
        protobufPluginVersion = '0.8.14'
        grpcVersion = '1.35.0'
    }
}

plugins {
    id 'java'
    id 'com.google.protobuf' version "${protobufPluginVersion}"
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "io.grpc:grpc-protobuf:${grpcVersion}"
    implementation "io.grpc:grpc-stub:${grpcVersion}"
    compileOnly 'jakarta.annotation:jakarta.annotation-api:1.3.5' // Java 9+ compatibility - Do NOT update to 2.0.0
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${protobufVersion}"
    }
    generatedFilesBaseDir = "$projectDir/src/generated"
    clean {
        delete generatedFilesBaseDir
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc{}
        }
    }
}

test {
    useJUnitPlatform()
}

위에서 중요한 부분은 grpc 관련된 종속성을 추가한 부분과 protobuf 부분이다.

그리고 main 폴더 안에 proto 폴더를 생성하고 proto 파일을 작성한다

helloworld.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.chb.examples.lib";
option java_outer_classname = "HelloWorldProto";

service Simple {
  rpc SayHello (HelloRequest) returns (HelloReply) {
  }
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

proto 파일에는 Simple 이란 서비스가 존재한다.
잠시 살펴 보자면 syntax 로 proto3 문법을 사용할 것이라고 정의했고 3가지의 option을 주었다. 각 옵션은 짐작 할수 있겠지만 여기를 통해 확인하기 바란다. 아래 Simple 서비스를 보면 HelloRequest란 메세지를 받을 수있고 HelloReply를 되돌려준다.

그러면 gradle build 명령어를 통해서 빌드를 해보자. Gradle 명령어를 사용 할 수 없다면 개발툴에서 지원해주는 build를 이용해도 된다.
빌드가 완료되면 프로젝트 폴더에 build 폴더와 src 폴더 아래에 generated 폴더가 생성된 모습을 볼 수 있다. 우리는 build/libs 폴더에 있는 .jar 파일을 이제 Grpc Server와 Grpc Client에 추가할 것이다.

Grpc Server

빈 스프링 프로젝트를 생성하고 Grpc Interface에 만들어 놨던 .jar파일을 프로젝트 폴더에서 libs 폴더를 만들고 추가한다. 그리고 build.gradle을 아래와 같이 작성한다

build.gradle (Grpc Server)
plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'net.devh:grpc-server-spring-boot-starter:2.12.0.RELEASE'
    implementation files('libs/GrpcInterface-1.0-SNAPSHOT.jar')
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

위에서 implementation files('libs/GrpcInterface-1.0-SNAPSHOT.jar') 와 같이 복사한 .jar 파일을 추가해준다.

그리고 Service를 구현하도록 하겠다.

GrpcServerService
@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder()
                .setMessage("Hello ==> " + request.getName())
                .build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

Grpc Interface에서 등록한 sayHello 함수와 객체들을 사용 할수 있는것을 볼 수있다.
onNext를 통해서 데이터를 넘겨주고 onCompleted를 통해서 성공 여부를 알려주고 있다.

추가로 grpc 서버의 포트만 변경하고 진행하겠다. 아래와 같이 grpc 서버의 포트 번호를 변경 할 수 있다.

application.yml(Grpc Server)
grpc:
  server:
    port: 9090

Grpc Client

서버와 동일하게 .jar 파일을 추가한다. build.gradle은 아래와 같다.

build.gradle (Grpc Client)
plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'net.devh:grpc-client-spring-boot-starter:2.12.0.RELEASE'
    implementation files('libs/GrpcInterface-1.0-SNAPSHOT.jar')
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

Grpc Client에서 Grpc Server에서 사용하는 port 와 중복되지 않기 위해 port 번호를 8081로 변경하고 필요 속성을 추가하겠다.

application.yml(Grpc Server)
server:
  port: 8081
grpc:
  client:
    test:
      address: 'static://127.0.0.1:9090'
      negotiationtype: plaintext

위에서 작성한 test는 본인이 원하는 이름으로 변경이 가능하다. 이후에 나오는 @GrpcClient 주석 매핑을 위해 사용된다. 하위 속성의 address에는 Grpc Server의 주소를 사용한다.

이제 Service와 Controller를 구현해 보겠다.

GrpcClientService (Grpc Server)
@Service
public class GrpcClientService {

    @GrpcClient("test")
    private SimpleGrpc.SimpleBlockingStub simpleStub;

    public String sendMessage(final String name) {
        try{
            HelloReply response = this.simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (StatusRuntimeException e) {
            return "FAILED with " + e.getStatus().getCode().name();
        }
    }
}

@GrpcClient 주석에 yml에서 지정한 이름을 넣어주면 자동으로 바인딩이 된다.
sendMessage 서비스 함수를 만들어서 Controller에서 사용 해 보겠다.

GrpcClientController (Grpc Server)
@RestController
@RequiredArgsConstructor
public class GrpcClientController {

    private final GrpcClientService grpcClientService;

    @GetMapping("/test")
    public String printMessage() {
        return grpcClientService.sendMessage("test");
    }
}

위와 같이 작성하고 해당 페이지로 테스트를 해보면 Hello ==> test 가 찍히는 것을 볼 수 있다.

개인적인 필자 생각으로는 Grpc를 웹 개발에서 그렇게 많이 사용 할 것 같지는 않았다. 기존의 브라우저들이 HTTP2 를 지원하지 않기 때문에 사용하려면 Grpc-web을 구현해서 사용해야 한다. Google에서 제공하는 Grpc-web 구현체가 있기 때문에 proxy 서버를 이용 해 사용 할 수도 있기는 하다. 이렇게 까지 사용해야하나...?

마이크로 서비스를 구현하기 위해서는 Grpc 통신이 적합하다고 생각이 들고 HTTP2를 사용한다는 것부터 API 방식과 성능에서 차이가 나기 때문에 고려를 해볼 수도 있을것 같다.

끗!!!!

profile
기록 그리고 기억

2개의 댓글

comment-user-thumbnail
2022년 2월 12일

깔끔한 예제가 인상적이네요 ㅎㅎ 잘보고갑니다~

1개의 답글