본문 바로가기
프로그래밍/기타

[gRPC + java or kotlin] gRPC를 직접 구현재보자 - 공통 모델편

by 사바라다 2021. 7. 31.

안녕하세요. 오늘부터는 gRPC를 직접 구현해보는 시간을 가져보도록 하겠습니다.

총 3개의 챕터로 진행될 예정이며 이번 포스팅은 공통 모델, 서버, 클라이언트 편으로 나누어서 진행하려고 합니다.

오늘은 protocol buffer를 이용해서 java에서 공통된 모델을 만드는 과정을 보도록 하겠습니다.

IDL(Interface Defintion Language)

gRPC는 Interface를 먼저 정의해야한는 언어라고 말씀드렸습니다. 이부분에 대해서는 이전 시간에 [gRPC] protocol buffer3를 실제로 사용해보자 포스팅을 참고하시면 좀 더 자세한 내용을 알 수 있습니다. 오늘 사용할 protocol buffer 파일은 아래와 같습니다. 각 라인의 설명은 코드에 달아두었습니다. 위 링크에서 proto buffer의 좀 더 자세한 설명을 보실 수 있습니다.

syntax = "proto3";

package me.karol; // package

option java_outer_classname = "Karol";                                                  // 클래스 이름
option java_multiple_files = true;                                                      // 1개의 proto 파일에서 여러개의 클래스 파일이 생성 가능

/**
 * Karol Service
 */
service KarolService {
    rpc SingleRequestSingleResponse (HelloRequest) returns (HelloResponse);             // 단일 요청 단일 응답 RPC
    rpc SingleRequestManyResponse (HelloRequest) returns (stream HelloResponse);        // 단일 요청 멀티 응답 RPC
    rpc ManyRequestSingleResponse (stream HelloRequest) returns (HelloResponse);        // 멀티 요청 단일 응답 RPC
    rpc ManyRequestManyResponse (stream HelloRequest) returns (stream HelloResponse);   // 멀티 요청 멀티 응답 RPC
}

message HelloRequest { // 요청 메시지 포맷
    string hello = 1;
}

message HelloResponse { // 응답 메시지 포맷
    string world = 1;
}

gradle dsl 설정

이렇게 파일을 만들었으면 이제 사용을 해야합니다. 대부분의 다른 블로그를 봤을 때 gradle로 작성되어 있는것을 확인하였습니다. 그래서 저는 여기서 gradle kotlin dsl을 이용하여 빌드 도구를 설정을 하려고합니다. 각 버전은 해당 포스터를 작성하고 있는 2021년 7월 31일 기준 가장 최신버전입니다. 아래의 설정 파일 전체를 보시면 아시겠지만 의존성만 넣으면 정상적으로 돌아가는 일반적인 설정과는 조금 어려운 부분이 있습니다. 하나하나 gRPC 사용을 위한 설정에 대해서 뜯어보도록 합시다.

import com.google.protobuf.gradle.generateProtoTasks
import com.google.protobuf.gradle.id
import com.google.protobuf.gradle.ofSourceSet
import com.google.protobuf.gradle.plugins
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc

plugins {
    id("com.google.protobuf") version "0.8.17"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.google.protobuf:protobuf-java:3.17.3")
    implementation("io.grpc:grpc-protobuf:1.39.0")
    implementation("io.grpc:grpc-stub:1.39.0")
    api("org.apache.tomcat:annotations-api:6.0.53") // necessary for Java 9+
}

tasks.getByName<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
    enabled = false
}

sourceSets{
    getByName("main"){
        java {
            srcDirs(
                "build/generated/source/proto/main/grpc",
                "build/generated/source/proto/main/java"
            )
        }
    }
}

protobuf {
    protoc {
        // The artifact spec for the Protobuf Compiler
        artifact = "com.google.protobuf:protoc:3.17.3"
    }

    plugins {
        // Optional: an artifact spec for a protoc plugin, with "grpc" as
        // the identifier, which can be referred to in the "plugins"
        // container of the "generateProtoTasks" closure.
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:1.39.0"
        }
    }
    generateProtoTasks {
        ofSourceSet("main").forEach {
            it.plugins {
                // Apply the "grpc" plugin whose spec is defined above, without options.
                id("grpc")
            }
        }
    }
}

상세 내용

먼저 플러그인을 추가로 설정할 필요가 있습니다. 추가적인 플러그인이 필요한 이유는 우리가 protocol buffer 파일만을 이용하여 자바에서 이용할 수 없기 때문입니다. 자바에서 이용하기 위해서는 .java 파일로 만들어져야합니다. protocol buffer 파일을 .java 파일로 만들어주는 protoc의 역할을 해당 플러그인을 통해 빌드 도구에 추가할 수 있는 것입니다. 또한 이렇게 만들어진 java 파일을 함께 컴파일 되어질 수 있도록 sourceSet 설정도 할 수 있습니다.

plugins {
    id("com.google.protobuf") version "0.8.17"
}

그리고 의존성을 보도록 하겠습니다. 의존성 역시 protocol buffer 파일을 java 파일로 만드는데 필요한 의존성입니다.

dependencies {
    implementation("com.google.protobuf:protobuf-java:3.17.3")          // protocol buffer를 java 파일로 컴파일하는데 사용되는 의존성
    implementation("io.grpc:grpc-protobuf:1.39.0")                      // protobuf-java로 만들어지는 서버 입장의 파일에서 필요한 메서드 등을 포함하고 있으는 의존성
    implementation("io.grpc:grpc-stub:1.39.0")                          // protobuf-java로 만들어지는 클라이언트 입장의 파일에서 필요한 메서드 등을 포함하고 있는 의존성
    api("org.apache.tomcat:annotations-api:6.0.53")                     // grpc를 사용할 때 java 9 이상에서 사용하기 위해서 필요한 의존성
}

아래 설정은 만들어지는 java 파일을 어디에 위치시킬것인지 부분입니다. build을 아래에 위치시켜 일반적인 파일들과 컴파일을 함께 한다고 보시면 됩니다.

sourceSets{
    getByName("main"){
        java {
            srcDirs(
                "build/generated/source/proto/main/grpc",
                "build/generated/source/proto/main/java"
            )
        }
    }
}

아래 설정은 protobuf가 어떻게 실행될 것인지 커스텀 하는 부분입니다. 내용을 보시면 protoc를 어디서 가져올지에 대한 설정과 추가적인 플러그인을 설정하는것, 그리고 어디에 생성할지의 정보를 정하는 것을 알 수 있읍니다.

protobuf {
    protoc { // protocol buffer를 컴파일하는 protoc의 스펙을 지정
        artifact = "com.google.protobuf:protoc:3.17.3"
    }

    plugins {
        // 컴파일 과정중 추가할 부분, 해당 프로젝트는 grpc 모델이 만들어여야하므로 아래의 설정이 있음.
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:1.39.0"
        }
    }

    generateProtoTasks {
        ofSourceSet("main").forEach {
            it.plugins {
                id("grpc")
            }
        }
    }
}

gradle을 통해서 proto -> java로 변화하는 방법

그렇다면 위처럼 설정한 빌드 도구를 실행해봅시다.

먼저 proto의 파일을 아래의 이미지 처럼 위치시킵니다.

gradle 명령어를 실행시킵니다. generateProto를 실행시켜주시면 java 파일이 만들어지게 됩니다.

생성된 위치를 확인합시다. 생성된 위치는 sourceSets 위치입니다.

마무리

오늘은 이렇게 grpc를 사용하는 방법중 protocol buffer 파일을 gRPC용 java 파일로 만드는 것을 확인해보았습니다.

감사합니다.

참조

https://github.com/google/protobuf-gradle-plugin

https://github.com/grpc/grpc-java

댓글