본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Spring framework/Spring boot

SpringBoot gRPC 체험 후기 - 1

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2023. 3. 8.

gRPC는 데이터를 주고받기 위한 HTTP프로토콜을 "서비스(Serivice)" 라는 개념으로 정의한 개념의 규칙 입니다.

구글링하면 워낙 훌륭한 글 들이 많아서...

자세한 기능과 설명은 여러 포스팅을 보는 것이 좋을 것 같습니다.

  * 해당 포스팅은 설명 보다는 개발환경을 구축해서 테스트하는 내용 위주로 작성되어 있습니다.

gRPC를 구현하여 어떠한 기능을 하는지 살펴보기 위해 개발환경을 구축하여 보았습니다.

# 개발환경
- OS : 윈도우 11
- Java : 17
- SpringBoot : 부트버전에 크게 영향받지 않습니다.
- 개발도구 : STS
- 빌드 : maven

 

#1. protoc 설치

가장 처음에 할 일은 protoc를 설치해야 합니다.

윈도우 환경에서는 사실 압축풀기 + 환경변수 잡기 정도의 개념 입니다.

아래 깃허브 주소에서 본인의 운영체제에 맞는 버전을 다운받도록 합니다. 여기서는 윈도우라..윈도우 버전을 받았습니다.

https://github.com/protocolbuffers/protobuf/releases

 

Releases · protocolbuffers/protobuf

Protocol Buffers - Google's data interchange format - protocolbuffers/protobuf

github.com

* 윈도우 버전 파일이 안보이면 "Show all 14 assets" 항목을 클릭하면 보입니다. (14에서 바뀔수도 있겠죠?)

 

해당 파일을 받고 나면 압축을 풀어서 bin 디렉토리를 환경변수에 추가하여 줍니다.

요기 exe 파일이 어디서든 실행되게 해 줍니다.

 

환경변수에 제대로 등록되었는지 protoc 를 입력하여 동작여부를 확인합니다.

만약 명령이 제대로 실행되지 않았다면 환경변수 부분을 다시 확인합니다.

요렇게 뭐라뭐라 나오면 됩니다..ㅋ

 

#2. 개발도구 설정

sts 개발도구를 활용하여 진행하였습니다.

당연히 이클립스나 전자정부프레임워크 기반의 개발도구에서도 적용 가능 합니다.

먼저 마켓 플레이스에서 "protobuf-dt" 라고 검색하여 나온 모듈을 설치하여 줍니다.

설치하는 데 생각보다 시간이 많이 걸리므로 느긋하게 설치하여줍니다. warning 같은 내용의 메시지와 확인버튼이 나오는데...역시나 "OK" 를 눌러 줍니다.

저는 이미 설치를해서 installed 라고 나오네요

 

여기까지 하였다면 개발도구의 1차 설정이 끝났다고 할 수 있습니다.

이제 가장 중요한 maven(gradle) 설정 입니다!

 

#3. pom.xml 설정

gRPC 구현을 위해서 추가해 주어야 하는 라이브러리는 아래와 같습니다.

<!-- 기본 core 모듈들 -->
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.53.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.53.0</version>
</dependency>
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.22.0</version>
</dependency>
<!-- 서버 구현을 위한 netty -->
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty-shaded</artifactId>
    <version>1.52.1</version>
</dependency>
<!-- proto 파일 빌드에서 컴파일 후 에노테이션 표기를 위해  -->
<dependency> 
    <groupId>org.apache.tomcat</groupId>
    <artifactId>annotations-api</artifactId>
    <version>6.0.53</version>
    <scope>provided</scope>
</dependency>

 

버전은 최신버전을 사용해도 무방하며, 위 라이브러리들은 전부 반드시 필요한 항목 입니다.

이렇게 해 놓은 다음에 proto 파일을 알아서 스스로 잘 생성(?)하기 위한 build 옵션을 추가하여 줍니다.

 build 엘리먼트의 plugins 항목을 추가하여 줍니다.

 

#3-1. 첫번째 컴파일 옵션

<build>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.22.0:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.31.0:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

 

첫번째 빌드 옵션 입니다.

해당 옵션은 프로젝트를 빌드하거나 실행할 때 쓰이는 옵션 입니다.

해당 옵션에서 protocArtifact의 버전 값은 dependency의 그룹아이디 com.google.protobuf 의 버전을 맞추어 주면 되고, pluginArtifact의 버전 값은 그룹아이디 io.grpc의 버전을 맞추어 주면 됩니다.

 

#3-2. 두번째 컴파일 옵션

<plugin>
    <groupId>com.github.os72</groupId>
    <artifactId>protoc-jar-maven-plugin</artifactId>
    <version>3.11.4</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <includeMavenTypes>direct</includeMavenTypes>
                <inputDirectories>
                    <include>src/main/resources</include>
                </inputDirectories>
                <outputTargets>
                    <outputTarget>
                        <type>java</type>
                        <outputDirectory>src/main/java</outputDirectory>
                    </outputTarget>
                    <outputTarget>
                        <type>grpc-java</type>
                        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.15.0</pluginArtifact>
                        <outputDirectory>src/main/java</outputDirectory>
                    </outputTarget>
                </outputTargets>
            </configuration>
        </execution>
    </executions>
</plugin>

 

두번째 옵션은 proto 타입의 파일을 maven update 또는 Maven generate-source 등의 명령을 실행 할 때 proto 파일의 내용을 맞추어 클래스 파일을 생성하기 위한 옵션 입니다.

그러므로 pom.xml에는 위 2개의 내용을 합친 내용이 들어가야 하겠습니다.

 

#3. proto 파일 생성

pom.xml에 보면 proto 파일의 경로를 resources 디렉토리로 바라보게 하였습니다.

그리므로 proto 파일은 resources 디렉토리를 잡아주면 됩니다.

요기 입니다.

 

샘플로 만들어본 test.proto 파일의 내용 입니다.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "kr.com.rts.proto";
option java_outer_classname = "HelloPrt";

package protocc;

service MyProtoService {
    rpc giveMeData1(RequestMessage) returns (ResponseMessageStyle1); //송신 및 수신용 메소드1
    rpc giveMeData2(RequestMessage) returns (ResponseMessageStyle2); //송신 및 수신용 메소드2
}

message RequestMessage { //요청하는 객체
    string id = 1;
    string name = 2;
    float price = 3;
}

message ResponseMessageStyle1 {  //결과받는 객체1
    string result = 1;
    string status = 2;
}


message ResponseMessageStyle2 { //결과받는 객체2
    string result = 1;
    string status = 2;
    float statusCode = 3;
    RequestMessage yourRequest = 4;
}

 

gRPC는 주고 받는 요청을 서비스라는 형태로 관리 합니다.

또한 실제 http 통신을 통해서 주고받는 객체는 위 코드처럼 message 라는 항목의 객체로 관리 합니다.

위 proto 파일을 보면 service는 1개의 클래스와 2개의 메소드로 자동 생성이 되고, 아래 message 값들은 해당 서비스(MyProtoService라고 이름 준)에서 요청과 응답에서의 클래스로 사용이 됩니다.

proto 파일을 생성한 뒤에 maven update를 진행하면 클래스 파일이 자동으로 생성이 됩니다.

클래스가 알아서 뿅~

 

#4. 서버 구동을 위한 서비스 생성

실제 서버를 동작시키기 위해서는 서비스 파일이 필요 합니다. gRPC는 등록한 서비스 기반으로 동작하기 때문 입니다.

서비스는 maven build를 통해 생성된 클래스를 사용 해 주면 됩니다.

proto 파일에서 서비스 이름을 MyProtoService 이라고 주었습니다.

그러면 MyProtoServiceGrpc 라는 클래스가 생성이 되고, 해당 클래스의 내부 클래스로 MyProtoServiceImplBase라는 클래스가 자동으로 생성 됩니다.

 

그러면 여기서 볼 수 있는 규칙은, proto 파일에서 정해준 클래스 이름에는, *Gprc 값이 붙은 1차 클래스가 만들어지고

실제 서비스를 구동하기 위해서는 *ImplBase 클래스를 사용하면 된 다는 점 입니다.

 

사용자의 요청으로부터 데이터를 받아서 간단하게 출력하는 클래스를 만들어 보았습니다.

* 서비스 클래스

import io.grpc.stub.StreamObserver;
import kr.com.rts.proto.*;
import kr.com.rts.proto.RequestMessage;
import kr.com.rts.proto.ResponseMessageStyle1;
import kr.com.rts.proto.ResponseMessageStyle2;

public class TestService extends MyProtoServiceGrpc.MyProtoServiceImplBase {

	@Override
	public void giveMeData1(RequestMessage request, StreamObserver<ResponseMessageStyle1> observer) {
		
		System.out.println(request.getId());
		System.out.println(request.getName());
		System.out.println(request.getPrice());

		ResponseMessageStyle1 res = ResponseMessageStyle1.newBuilder().setStatus("good").setResult("ok").build();
		observer.onNext(res);
		observer.onCompleted();
	}

	@Override
	public void giveMeData2(RequestMessage request, StreamObserver<ResponseMessageStyle2> observer) {
		
		System.out.println(request.getId());
		System.out.println(request.getName());
		System.out.println(request.getPrice());		
		
		ResponseMessageStyle2 res = ResponseMessageStyle2.newBuilder().setStatus("good").setResult("ok")
				.setStatusCode(200f).setYourRequest(request).build();
		observer.onNext(res);
		observer.onCompleted();
	}

}

 

사용자의 요청은 proto 파일에서 message RequestMessage 라고 하였습니다.

그러므로 서비스에서도 해당 이름에 맞는 요청객체가 생성된 것을 볼 수 있습니다.

또한  proto 파일에서 서비스에 정의된 giveMeData1, giveMeData2 메소드도 잘 생성된 것을 볼 수 있습니다.

 

StreamObserver는 서버에서 수신받은 데이터를 어떻게 할지에 대한 클래스 입니다.

onNext를 통해서 응답할 모양을 담아서 발행하고, onComplete를 통해서 해당 응답이 끝났음을 알려 줍니다.

서비스까지 만들었다면 이제 서버 클래스를 만들도록 합니다.

 

* 서비스를 사용하는 서버 클래스

import java.io.IOException;

import io.grpc.Server;
import io.grpc.ServerBuilder;


public class TestGrpcServer {

    private Server server;

    public void runServer() {
        int port = 10777;
        try {
            server = ServerBuilder.forPort(port)
            .addService(new TestService())
            //.addService()  이런식으로 n개의 서비스를 등록..
            .build()
            .start();
            server.awaitTermination();  //요게 없으면 1번 실행하고 끝납니다. 계속 수신대기 하는 기능 입니다.
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            try {
                if(server != null) {
                    server.shutdownNow();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }));
    }
}

 

동작하는 서버코드는 의외로 간단 합니다. 만들어준 서비스를 위 addService 메소드에 추가하여 프로토콜에 대한 정의를 이어 갈 수 있습니다.

이제 위 서버를 동작시킨 다음에 실제로 잘 동작하는지 요청하여 봅니다.

 

* 서버에게 요청을 날려보는 테스트 클래스

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import kr.com.rts.proto.MyProtoServiceGrpc;
import kr.com.rts.proto.RequestMessage;
import kr.com.rts.proto.ResponseMessageStyle1;
import kr.com.rts.proto.ResponseMessageStyle2;

@SpringBootTest
class GRpcJavaApplicationTests {

    @Test
    void contextLoads() {
        String url = "127.0.0.1:10777";

        try {
            ManagedChannel channel = ManagedChannelBuilder.forTarget(url)
                .usePlaintext()
                .build();	
            RequestMessage req = RequestMessage.newBuilder().setId("첫번째 메시지").setName("첫번째 메시지2").setPrice(1234f).build();
            RequestMessage req2 = RequestMessage.newBuilder().setId("두번째 메시지").setName("두번째 메시지2").setPrice(554f).build();

            MyProtoServiceGrpc.MyProtoServiceBlockingStub stub = MyProtoServiceGrpc.newBlockingStub(channel);

            ResponseMessageStyle1 response1 = stub.giveMeData1(req);
            ResponseMessageStyle2 response2 = stub.giveMeData2(req2);

            assertThat(response1);
            assertThat(response2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

데이터 요청을 보낼 때에도 기존에 사용한 서비스에서의 stub 형태의 클래스를 사용해야 합니다.

즉 데이터를 받는곳, 데이터를 보내는 곳 모두 이처럼 proto 파일에서 정의한 서비스 규칙을 따라야 한다는 것 입니다.

테스트를 한번 진행하여 보면 아래처럼 서버에 데이터가 잘 나오는 것을 볼 수 있습니다.

 

아직은 범용적으로 쓰이지 않는 것은 브라우저에서의 보내는 방법, 또한 수신 및 발신을 위해서는 같은 서비스를 가지고 있어야 한다는 점 때문 인 것 같습니다.

http2 프로토콜을 사용하기 때 문에 뭐 빠르다 좋다 하는데...

좀더 이것저것 해 보아야 알 것 같습니다.

 

아래 깃허브 주소는 위 내용에 사용된 소스코드를 올려놓은 주소 입니다.

https://github.com/TaeSeungRyu/sample/tree/main/gRPC샘플코드

 

GitHub - TaeSeungRyu/sample

Contribute to TaeSeungRyu/sample development by creating an account on GitHub.

github.com

 

SpringBoot gRPC 체험 후기를 작성 해 보았습니다.

아직까지 이것저것 해 보는 중이라 내용이 다소 틀릴 수 있습니다.

틀린부분 또는 다른 의견은 언제든 환영 입니다! 👻

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글