Web/gRPC 2020. 7. 15. 20:27

 

이번 시간에 다루어볼 내용은 proto message로 생성한 Java를 Json String으로 변환하는 방법이다. proto로 생성한 java 인스턴스를 아래와 같이 json string으로 바꾸려면 예외가 발생한다.

 

#protoJava - proto로 생성한 java instance
ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(protoJava);

 

그렇기 때문에 protobuf의 JsonFormat으로 jsonString을 변환해주면 된다.

 

#protoJava - proto file로 생성한 java instance
final String jsonString = JsonFormat.printer().print(protoJava);

'Web > gRPC' 카테고리의 다른 글

gRPC - Protobuf란? 구글 프로토콜 버퍼(protocol buffers)  (0) 2020.05.03
gRPC - java gRPC 간단한 사용법  (0) 2020.05.03
gRPC - gRPC란 무엇인가?  (0) 2020.05.02
posted by 여성게
:
Web/gRPC 2020. 5. 3. 14:41

이전 포스팅에서는 grpc를 이용하여 간단하게 client-server 애플리케이션을 작성하였다. 간단하게 감을 익혀봤으니, 세세한 부분을 스터디 해본다. 오늘은 Protobuf(proto3)에 대해 다루어본다.

 

메시지 유형 정의

간단한 예를 보자. 검색 요청 메시지이며, 쿼리 문자열과 페이징을 위한 필드를 가지고 있다.

 

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

 

첫 줄은 proto3 syntax를 사용하고 있음을 지정한다. default는 proto2이다. 그리고 message SearchRequest를 정의한다. message는 자바로 비교하면 하나의 dto(model) 클래스라고 생각하면 좋다. 그리고 해당 message안에 필요한 필드를 선언한다. 위의 모든 필드는 scala type으로 지정되어 있지만, enum 및 다른 message 유형을 참조하여 복합 유형을 지정할 수도 있다. 

 

그리고 하나의 proto 파일에 여러 message 유형을 정의할 수 있다.

 

필드 번호

message의 각 필드에는 번호가 할당되어 있다. 이 필드 번호는 message가 이진형식으로 직렬화될때 필드를 식별하는데 사용되는 번호이다.

 

주석

proto 파일은 아래와 같이 주석 작성이 가능하다.

 

syntax = "proto3";

/*
검색 요청을 위한 요청 객체를 표현하는 message 정의
 */
message SearchRequest {
  string query = 1; //쿼리 문자열
  int32 page_number = 2;
  int32 result_per_page = 3;
}

 

Scala value types

스칼라 타입 유형은 아래와 같이 지원하고 있다.

 

TYPE

Notes

Java

Python

double

 

double

float

float

 

float

float

int32

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

int

int

int64

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

long

int/long[3]

uint32

Uses variable-length encoding.

int[1]

int/long[3]

uint64

Uses variable-length encoding.

long[1]

int/long[3]

sint32

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

int

int

sint64

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

long

int/long[3]

fixed32

Always four bytes. More efficient than uint32 if values are often greater than 228.

int[1]

int/long[3]

fixed64

Always eight bytes. More efficient than uint64 if values are often greater than 256.

long[1]

int/long[3]

sfixed32

Always four bytes.

int

int

sfixed64

Always eight bytes.

long

int/long[3]

bool

 

boolean

bool

string

A string must always contain UTF-8 encoded or 7-bit ASCII text, and cannot be longer than 232.

String

str/unicode[4]

bytes

May contain any arbitrary sequence of bytes no longer than 232.

ByteString

str

 

Default Value

messagefmf deserialize할때, 값이 없다면 각 타입에 대해 아래와 같은 기본 값을 가진다.

 

Type Default Value
string ""
bytes empty bytes
boolean false
numeric 0
enums 첫번째 정의된 enum value(must be 0)

 

Enum Type

protobuf에서도 자바와 같은 Enum 타입을 생성할 수 있다.

 

syntax = "proto3";

/*
검색 요청을 위한 요청 객체를 표현하는 message 정의
 */
message SearchRequest {
  string query = 1; //쿼리 문자열
  int32 page_number = 2;
  int32 result_per_page = 3;
  Corpus corpus = 4;
}

enum Corpus {
  UNIVERSAL = 0;
  WEB = 1;
  IMAGES = 2;
  LOCAL = 3;
  NEWS = 4;
  PRODUCTS = 5;
  VIDEO = 6;
}

 

열거형은 반드시 시작을 0으로 시작해야한다. 그래야 역직렬화할때, 값이 존재하지 않을경우 0에 할당된 값을 기본값으로 쓰기 때문이다. 

 

다른 message 유형 사용

다른 message 타입을 필드 타입으로 사용할 수 있다.

 

message SearchResponse {
  SearchResult results = 1;
}

message SearchResult {
  string url = 1;
  string title = 2;
}

 

그렇다면 다른 .proto 파일에 있는 message 타입을 사용하려면 어떻게 해야할까?

 

#SearchResult.proto
syntax = "proto3";

option java_multiple_files = true;
option java_outer_classname = "SearchResultProto";
option java_package = "com.levi.yoon.proto";

package grpc.sample;

message SearchResult {
  string url = 1;
  string title = 2;
}

#SearchProto.proto
syntax = "proto3";
import "SearchResult.proto";

option java_multiple_files = true;
option java_outer_classname = "SearchProto";
option java_package = "com.levi.yoon.proto";

package grpc.sample;

/*
검색 요청을 위한 요청 객체를 표현하는 message 정의
 */
message SearchRequest {
  string query = 1; //쿼리 문자열
  int32 page_number = 2;
  int32 result_per_page = 3;
  Corpus corpus = 4;
}

enum Corpus {
  UNIVERSAL = 0;
  WEB = 1;
  IMAGES = 2;
  LOCAL = 3;
  NEWS = 4;
  PRODUCTS = 5;
  VIDEO = 6;
}

message SearchResponse {
  SearchResult results = 1;
}

 

import 문을 통해서 다른 .proto 파일을 가져와서 사용할 수 있다.

 

중첩 타입

message 타입을 중첩해서 사용가능하다. 마치 자바에서 클래스 안에 클래스를 가지듯 !

 

syntax = "proto3";

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
  }
  repeated Result results = 1;
}

 

Map type

필드 타입으로 Map을 사용할 수 있다.

 

syntax = "proto3";

option java_multiple_files = true;
option java_outer_classname = "ExampleRequestProto";
option java_package = "com.levi.yoon.proto";

package grpc.sample;

message ExampleRequest {
  map<string, string> requests = 1;
}

 

해당 필드를 set/get 할때는 아래와 같은 메서드를 지원한다.

 

ExampleRequest exampleRequest = ExampleRequest.newBuilder()
        .putRequests("a", "a")
        .putRequests("b", "b")
        .build();
exampleRequest.getRequestsMap();
exampleRequest.getRequestsOrDefault("a", "defaultValue");
exampleRequest.getRequestsOrThrow("a");

 

주의해야할 점은 Map 타입은 repeated를 사용할 수 없다.

 

Package

protobuf message 타입간 이름 충돌을 피하기 위해 파일에 선택적으로 package 지정자를 추가할 수 있다.

 

#ExampleRequestProto1.proto
syntax = "proto3";

package grpc.sample1;

message ExampleRequest {
  map<string, string> requests = 1;
}

#ExampleRequestProto2.proto
syntax = "proto3";

package grpc.sample2;

message ExampleRequest {
  map<string, string> requests = 1;
}

 

서비스 정의

client에서 stub 객체로 호출할 원격 프로시져(서비스)를 정의할 수 있다.

 

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

 

우리가 정의한 SearchRequest message를 매개변수로 받고, 응답으로 SearchResponse를 반환하는 서비스를 정의하였다. 해당 interface를 gRPC server에서 override하여 구현하고, client는 Stub 객체 안에 해당 서비스이름의 메서드를 콜하여 원격 gRPC service를 호출하게 된다.

 

기타 옵션

option java_multiple_files = true; -> proto 파일안의 message와 enum, service 등이 각 java 파일로 생성된다.

option java_outer_classname = "SearchProto"-> 생성될 자바코드의 클래스명이 된다.

option java_package = "com.levi.yoon.proto"-> 생성된 자바코드의 package 경로가 된다.

 

여기까지 간단하게 Protobuf에 대한 기능들 몇가지를 다루어보았다. 미쳐 다루지 못한 것은 실제 grpc 실습에서 다루어본다.

'Web > gRPC' 카테고리의 다른 글

gRPC - convert proto generate java to jsonString  (0) 2020.07.15
gRPC - java gRPC 간단한 사용법  (0) 2020.05.03
gRPC - gRPC란 무엇인가?  (0) 2020.05.02
posted by 여성게
:
Web/gRPC 2020. 5. 3. 14:02

이번 포스팅은 gRPC의 세세한 기능을 다루기 이전에 간단하게 java로 gRPC 서버와 클라이언트 코드를 작성해보고 감을 익혀보는 포스팅이다. 오늘 구성할 프로젝트 구조는 아래와 같다.

 

  1. grpc-common : .proto 파일을 이용하여 client와 server가 공통으로 사용할 소스를 generate한 프로젝트. client와 server에서 submodule로 추가할 프로젝트이다.
  2. grpc-server : server역할을 할 gRPC 서버 애플리케이션이다.
  3. grpc-client : client역할을 할 gRPC 클라이언트 애플리케이션이다.

 

grpc-common

모든 소스코드는 아래 깃헙 주소를 참고하면 된다.

 

 

yoonyeoseong/grpc-common

Contribute to yoonyeoseong/grpc-common development by creating an account on GitHub.

github.com

우선 gradle 프로젝트를 생성해준다. 그 이후 build.gradle을 아래와 같이 세팅한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}
 
plugins {
    id 'java'
}
 
apply plugin: 'com.google.protobuf'
 
group 'org.example'
version '1.0-SNAPSHOT'
 
repositories {
    mavenCentral()
}
 
dependencies {
    /**
     * gRPC
     */
    compile group: 'io.grpc', name: 'grpc-netty-shaded', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-stub', version: '1.21.0'
    implementation "com.google.protobuf:protobuf-java-util:3.8.0"
    compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.8.0'
 
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
 
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.7.1"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.21.0'
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}
 
cs

 

gRPC를 위한 의존서을 추가해주었고, .proto 파일에 작성된 리소스를 자바코드로 generate하기 위한 설정들이 들어가있다. 

 

이제 src/main/proto 디렉토리를 하나 생성해준다. 그 이후에 아래와 같은 .proto 파일을 작성해준다.

 

syntax = "proto3";

option java_multiple_files = true;
option java_outer_classname = "SampleProto";
option java_package = "com.levi.yoon.proto";

package grpc.sample;

message SampleRequest {
  string userId = 1;
  string message = 2;
}

message SampleResponse {
  string message = 1;
}

service SampleService {
  rpc SampleCall (SampleRequest) returns (SampleResponse) {}
}

 

아직은 해당 파일에 대한 세세한 내용은 다루지 않는다. 추후에 다루어 볼것이니 일단 몰라도 넘어가자. proto 파일을 생성해주었으면 generateProto task를 실행시켜준다. 이후 소스가 잘 생성되었는지 확인하자.

 

 

grpc-server

 

 

yoonyeoseong/grpc-server

Contribute to yoonyeoseong/grpc-server development by creating an account on GitHub.

github.com

 

이제는 grpc 서버 역할을 할 애플리케이션을 작성한다. 우선 바로 위에서 생성한 프로젝트를 submodule로 추가해준다.

 

> git submodule add <grpc-common 깃 주소>

 

필자는 spring의 injection을 사용하기 위해서 스프링부트 프로젝트로 생성해주었다. 일반 gradle 프로젝트도 상관없으니 편할대로 생성해주면 될듯하다.

 

build.gradle에 grpc-common에 생성된 소스를 sourceSets로 넣는 설정을 하나 넣어준다. 그리고 필요한 dependency를 추가한다. 물론 grpc-server에 필요하지 않은 의존성이 있긴하지만 우선 추가하자.

 

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

apply plugin: 'com.google.protobuf'

group = 'com.levi'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    /**
     * gRPC
     */
    compile group: 'io.grpc', name: 'grpc-netty-shaded', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-stub', version: '1.21.0'
    implementation "com.google.protobuf:protobuf-java-util:3.8.0"
    compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.8.0'

    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.projectreactor:reactor-test'
}

test {
    useJUnitPlatform()
}

sourceSets {
    main {
        java {
            srcDirs += [
                    'grpc-common/build/generated/source/proto/main/grpc',
                    'grpc-common/build/generated/source/proto/main/java'
            ]
        }
    }
}

 

이제 우리가 정의한 service를 구현해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@Service
public class SampleServiceImpl extends SampleServiceGrpc.SampleServiceImplBase {
 
    @Override
    public void sampleCall(SampleRequest request, StreamObserver<SampleResponse> responseObserver) {
        log.info("SampleServiceImpl#sampleCall - {}, {}", request.getUserId(), request.getMessage());
        SampleResponse sampleResponse = SampleResponse.newBuilder()
                .setMessage("grpc service response")
                .build();
 
        responseObserver.onNext(sampleResponse);
        responseObserver.onCompleted();
    }
}
cs

 

단순히 우리가 정의한 service(sampleCall)을 구현하는 것 뿐이다. 요청으로 SampleRequest 객체를 받고, 응답으로 SampleResponse를 내보내주면 된다. 비즈니스 로직은 없고 단순히 로그를 찍고 SampleResponse를 리턴하는 단순한 로직이다. 사실 client-server 통신 방법은 크게 4가지가 있는데, 4가지중 "client : server = 1 : 1" 로 통신하는 구조로 만들었다.

 

gRPC 서버를 구동하기 위한 클래스를 하나 작성한다. 뭐 여러가지 튜닝할 요소가 있을지는 모르겠지만, 우선 띄우는 것에 집중하자. gRPC-server로 결국은 server이다. 포트를 할당 받아서 서버형태로 띄운다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class GrpcRunner implements ApplicationRunner {
 
    private static final int PORT = 3030;
    private static final Server SERVER = ServerBuilder.forPort(PORT)
            .addService(new SampleServiceImpl())
            .build();
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        SERVER.start();
    }
}
cs

 

이제 spring application을 실행시키면 grpc server가 구동된다.

 

grpc-client

 

 

yoonyeoseong/grpc-client

Contribute to yoonyeoseong/grpc-client development by creating an account on GitHub.

github.com

 

마지막으로 grpc-server와 통신할 grpc-client이다. grpc-common 프로젝트를 submodule로 추가한다.

 

> git submodule add <grpc-common 깃 주소>

 

이제 build.gradle을 작성하자. grpc-server와 같이 필요없는 의존성이 있을 수 있지만 일단 추가하자.

 

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

apply plugin: 'com.google.protobuf'

group = 'com.levi'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    /**
     * gRPC
     */
    compile group: 'io.grpc', name: 'grpc-netty-shaded', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.21.0'
    compile group: 'io.grpc', name: 'grpc-stub', version: '1.21.0'
    implementation "com.google.protobuf:protobuf-java-util:3.8.0"
    compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.8.0'

    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.projectreactor:reactor-test'
}

sourceSets {
    main {
        java {
            srcDirs += [
                    'grpc-common/build/generated/source/proto/main/grpc',
                    'grpc-common/build/generated/source/proto/main/java'
            ]
        }
    }
}

test {
    useJUnitPlatform()
}

 

이제 stub 객체를 이용하여 grpc-server의 원격 메서드를 호출하는 client 코드를 간단하게 작성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Slf4j
@Service
public class GrpcClient {
    private static final int PORT = 3030;
    public static final String HOST = "localhost";
    private final SampleServiceGrpc.SampleServiceStub asyncStub = SampleServiceGrpc.newStub(
            ManagedChannelBuilder.forAddress(HOST, PORT)
            .usePlaintext()
            .build()
    );
 
    public String sampleCall() {
        final SampleRequest sampleRequest = SampleRequest.newBuilder()
                .setUserId("levi.yoon")
                .setMessage("grpc request")
                .build();
 
        asyncStub.sampleCall(sampleRequest, new StreamObserver<SampleResponse>() {
            @Override
            public void onNext(SampleResponse value) {
                log.info("GrpcClient#sampleCall - {}", value);
            }
 
            @Override
            public void onError(Throwable t) {
                log.error("GrpcClient#sampleCall - onError");
            }
 
            @Override
            public void onCompleted() {
                log.info("GrpcClient#sampleCall - onCompleted");
            }
        });
        return "string";
    }
}
cs

 

우선 client에서는 크게 2가지가 등장한다. (Stub & Channel)

 

  1. Stub : Remote procedure call을 하기 위한 가짜 객체라고 생각하면 된다. 마치 자신의 메서드를 호출하는 듯한 느낌이지만, 실제 원격(grpc-server) 메서드를 호출한다. Stub에는 async, blocking, future등 여러 스텁 종류가 존재하지만, 우리는 asyncStub을 이용한다.
  2. Channel : 결국 Stub도 원격 서버의 메서드를 호출한다. 그렇기 때문에 grpc-server와 통신할 channel을 정의해준다.

 

우리가 정의한 service의 스텁 객체를 생성해주고, 매개변수로 Channel을 생성해서 넣어준다. 우리가 띄울 grpc-server의 host와 port를 넣는다.

 

.proto로 message를 정의하면 java에서 사용할 수 있는 빌더패턴의 메서드, setter&getter등을 만들어준다. 요청을 위한 SampleRequest 객체를 만들었다. 이제 stub의 sampleCall(우리가 정의한 서비스)를 호출한다. 그런데 매개변수로 StreamObserver가 들어가는데, 해당 Stub은 asyncStub이기 때문에 callback 객체를 넣어주는 것이다. 간단히 로그를 찍는 callback 로직을 넣어주었다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@SpringBootApplication
@RequiredArgsConstructor
public class GrpcApplication {
 
    private final GrpcClient grpcClient;
 
    public static void main(String[] args) {
        SpringApplication.run(GrpcApplication.class, args);
    }
 
    @GetMapping("/")
    public Mono<String> test() {
        return Mono.just(grpcClient.sampleCall());
    }
 
}
cs

 

위는 간단하게 요청을 받을 controller를 하나 정의하였다.

 

실행

이제 grpc-server와 grpc-client를 띄우고 요청을 보내보자.

 

> curl http://localhost:9090/

#grpc-server
2020-05-03 13:59:18.781  INFO : SampleServiceImpl#sampleCall - levi.yoon, grpc request

#grpc-client
2020-05-03 13:57:09.014  INFO : GrpcClient#sampleCall - message: "grpc service response"
2020-05-03 13:57:09.032  INFO : GrpcClient#sampleCall - onCompleted

 

와우 ! client-server 간의 통신이 잘 이루어졌고, 우리가 찍었던 로그가 잘 보인다. 지금까지 간단하게 java 기반의 grpc를 다루어보았다. 사실 다룰 내용이 훨씬 많다. 그렇지만 이렇게 한번 간단하게 사용해보면 추후에 세세한 부분을 공부할때, 더 수월할 수도 있을 것 같다.

posted by 여성게
:
Web/gRPC 2020. 5. 2. 22:14

오늘은 gRPC가 무엇인지 알아본다. 그동안 스터디한다고 마음만 먹고 매일 미루기만 했는데, 황금연휴에 맘잡고 gRPC에 대해 다루어 볼 것이다.

 

개요

gRPC를 사용하면 클라이언트 애플리케이션에서 마치 자신의 메서드를 호출하는 것처럼 원격서버(gRPC서버)의 메서드를 직접 호출 할 수 있으므로 MSA환경의 서비스를 보다 쉽게 만들 수 있다. 여타 다른 RPC와 마찬가지로 gRPC는 IDL(Interface Definition Language)를 이용하여 서비스를 정의하고 페이로드를 정의하며 gRPC서버는 이 인터페이스를 구현하고 클라이언트 호출을 처리하기 위해 gRPC서버를 실행한다. 클라이언트 측에서 클라이언트는 서버와 동일한 인터페이스를 가지는 스텁 객체를 가지고 있다.

 

 

ProtoBuf

gRPC는 기본적으로 구조화된 데이터를 직렬화하기 위해 Google의 ProtoBuf를 사용한다. 

 

ProtoBuf로 작업할 때는 첫번째로 .proto 파일에 직렬화하려는 데이터의 구조를 정의한다. 메시지라는 오브젝트(?)로 구성되며, 각 메시지는 데이터 타입과 key&value쌍을 이루는 필드를 하나이상 가지고 있다.

 

message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}

 

그 이후 protoc를 사용하여 원하는 언어로 컴파일이 가능하다. 컴파일된 소스는 각 필드에 대한 간단한 setter/getter를 제공한다. 또한 .proto 파일을 이용하여 위 proto message가 매개변수, 리턴 값으로 사용된 service를 정의할 수 있다.

 

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

 

 

일반 RPC와 다를게 없을 수도 있는데, gRPC가 왜 좋은가? 

 

성능

gRPC 메시지는 효율적인 이진 메시지 형식인 ProtoBuf를 사용하여 직렬화된다. protobuf는 서버와 클라이언트에서 엄청 빠르게 직렬화된다. 또한 적은 용량의 페이로드로 형성되어 있어, 제한된 대역폭에서 아주 중요한 역할을 한다.

 

코드 생성

모든 gRPC 프레임워크는 코드 생성에 대한 최고 수준의 지원을 제공한다. gRPC 개발에 대한 핵심 파일은 gRPC 서비스 및 메시지의 계약을 정의하는 .proto file이다. 이 파일에서 gRPC 프레임워크는 서비스 기본 클래스, 메시지 및 전체 클라이언트를 코드 생성한다.

서버와 클라이언트 간에 proto 파일을 공유하여 메시지와 클라이언트 코드를 종단 간에 생성할 수 있다. 클라이언트의 코드 생성은 클라이언트와 서버에서 메시지의 중복을 제거하고 강력한 형식의 클라이언트를 만든다. 클라이언트를 작성하지 않아도 되므로 많은 서비스를 갖춘 응용 프로그램의 개발 시간이 상당히 절감된다.

 

엄격한 사양

일반적으로 JSON을 주고 받는 HTTP는 엄격한 사양이 존재하지 않는다. 그래서 개발자끼리 URL, HTTP payload, 응답코드 등을 논의해야한다. 하지만 gRPC는 gRPC가 따라야하는 명확한 형식이 있기 때문에 HTTP와 같이 큰 논의가 필요없다. 단순히 생성된 코드를 사양에 맞게 사용하면 된다.

 

그렇다면 gRPC는 어떠한 상황에서 사용하는 것이 좋을까?

 

  • 마이크로 서비스 - gRPC는 대기 시간이 짧고 처리량이 높은 통신을 위해 설계되었습니다. gRPC는 효율성이 중요한 경량 마이크로 서비스에 적합합니다.
  • gRPC – 지점 간 실시간 통신은 양방향 스트리밍을 위한 뛰어난 지원 기능을 제공합니다. gRPC 서비스는 폴링을 사용하지 않고 실시간으로 메시지를 푸시할 수 있습니다.
  • Polyglot 환경 – gRPC 도구는 널리 사용되는 모든 개발 언어를 지원하며, 따라서 gRPC는 다중 언어 환경에 적합합니다.
  • 네트워크 제한 환경 – gRPC 메시지는 경량 메시지 형식인 Protobuf를 사용하여 직렬화됩니다. gRPC 메시지는 항상 해당하는 JSON 메시지보다 작습니다.

 

여기까지 gRPC가 무엇이고, 어떠한 특징을 가지며 장점이 무엇인지 다루어보았다. 다음 포스팅부턴 제대로된 실습을 해본다.

posted by 여성게
: