Protocol Buffers 로 통신하는 Spring REST API 구현해보기

Jake Seo·2021년 1월 15일
1

네트워크

목록 보기
5/16

개요

Protocol Buffers는 프로그래밍 언어와 플랫폼에 구애받지 않으면서 구조가 있는 데이터를 직렬화 역직렬화하는 매커니즘이다. Google이 만들어냈다. XMLJSON과 같은 것들보다 빠르다.

binary-based 메세지 구조의 이점을 활용하여 어떻게 REST API를 만드는지 알아보자.

프로토콜 버퍼

이 섹션은 프로토콜 버퍼에 대한 기본적인 정보를 주고 자바 에코시스템에 그것들을 어떻게 녹여내야 할지 알려줄 것이다.

프로토콜 버퍼에 대한 소개

프로토콜 버퍼의 활용을 위해서, 우리는 .proto 파일에 메세지 구조를 정의해야 한다. 각각의 파일은 하나의 노드에서 다른 노드로 전송되거나 혹은 저장될 수 있는 데이터의 명세이다. 다음 코드는 .proto 파일의 예시이다. src/example/demo/protobuf에 저장될 것이다. 이 파일은 나중에 튜토리얼에서 쓰일 것이다.

syntax = "proto3";
package protobuf;
option java_package = "com.example.demo";
option java_outer_classname = "SchoolProto";

message Course {
  int32 id = 1;
  string course_name = 2;
  repeated Student student = 3;
}

message Student {
  int32 id = 1;
  string first_name = 2;
  string last_name = 3;
  string email = 4;
  repeated PhoneNumber phone = 5;

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  enum PhoneType {
    MOBILE = 0;
    LANDLINE = 1;
  }
}

이 튜토리얼에서, 우리는 버전3의 프로토콜 버퍼 컴파일러와 프로토콜 버퍼 언어를 사용한다. 그러므로 .proto파일의 시작은 반드시 syntax = "proto3"과 같은 선언으로 시작되어야 한다. 만일 컴파일러 버전이 2라면, 선언이 생략될 것이다. 다음으로 다른 프로젝트들과 네이밍 충돌을 피하기 위해 사용하는 메세지 구조를 위한 네임스페이스인 package 선언이 온다.

뒤의 2가지 선언은 자바에서만 사용된다. java_package 옵션은 생성된 클래스가 어디에 위치할지를 결정하며 java_outer_classname 옵션은 .proto 파일에 있는 모든 타입을 감싸는 클래스의 이름을 말한다.

추후에 남은 요소들과 어떻게 자바 코드로 컴파일되는지를 알아볼 것이다.

자바로 프로토콜 버퍼 사용하기

메세지 구조가 정의된 이후에, 이 언어를 자바 코드에 중립적인 내용으로 바꾸는 컴파일러가 필요하다. 적절한 컴파일러 버전을 얻기 위해서 프로토콜 버퍼 리포지토리에 있는 지시사항을 따르면 된다. Maven Central Repository에서 미리 빌드되어 있는 것을 다운로드해보자. com.google.protobuf:protoc 를 검색하고, 플랫폼에 적합한 버전을 선택하면 된다.

다음으로 컴파일러를 프로젝트의 src/main 디렉토리로 이동시키고, 다음과 같은 명령어를 입력해보자.

protoc --java_out=java resources/jake.proto

위 명령어가 java_outer_classname 으로 지정된 클래스를 위한 소스파일을 만들어낼 것이고, 해당 소스파일은 java_package주소 아래의 package로 자동으로 이동될 것이다.

컴파일러에 추가로, Protocol Buffers runtime이 필요로 된다. 다음과 같은 의존성을 추가함으로써 해당 런타임을 추가시킬 수 있다.

의존성 관리 도구로 Gradle을 사용하는 경우에는 위와 같이 세팅해주면 되고, Maven을 사용하는 경우에는 Maven Central Repository에서 검색해서 사용하면 된다.

메세지 기술을 컴파일하기

컴파일러를 사용함으로써, .proto 파일 내부의 메세지들이 static한 중첩된 자바 클래스들로 컴파일될 수 있다. 위의 예제에서는 CourseStudent 메세지들이 CourseStudent 자바 클래스들로 각각 변환된다. 동시에, 메세지들의 필드는 생성된 타입들 내부에 JavaBeans 스타일의 getters와 setters를 갖게 된다. 메세지들에서 = 기호 옆에 있던 숫자는 관련된 필드를 2진 형태로 인코딩하는데 사용되는 고유한 태그이다.

메세지의 타입을 가진 필드들이 어떻게 접근자 메소드로 변환되는지 한번 살펴보자.

Course 메세지부터 시작해보자. 이 메세지엔 두 개의 Simple 필드가 있다. idcourse_name이다. 이들의 protocol buffer type은 int32string이다. 이것들은 자바의 int 타입과 String 타입으로 변환될 것이다. 다음은 컴파일 이후에 그들의 getter가 어떻게 생겼는지에 대한 간결한 코드이다.

public int getId();
public java.lang.String getCourseName();

처음에 .proto 파일에서는 snake_case로 작성했던 것들이 자바 컨벤션에 따라 camelCase로 변환된 것을 주의깊게 보자.

Course의 마지막 필드인 studentComplex 타입이다. repeated 키워드가 앞에 와서, 몇번이고 반복되어 들어갈 수 있다. student에 관한 getter 코드는 다음과 같이 작성된다.

public java.util.List<com.example.demo.protobuf.SchoolProto.Student> getStudentList();
public int getStudentCount();
public com.example.demo.protobuf.SchoolProto.Student getStudent(int index);

이제 Student 메세지를 살펴보자. Student 메세지는 Course 메세지의 student 필드의 복잡한 타임으로 사용되고 있다. 이 메세지에서 Simple 필드는 id, first_name, last_name 그리고 email이 사용되고 있다. Simple 필드에 대한 getter 코드는 다음과 같다.

public int getId();
public java.lang.String getFirstName();
public java.lang.String getLastName();
public java.lang.String getEmail();

마지막 필드인 phoneComplex PhoneNumber 타입이다. Coursestudnet 필드와 비슷하다. 이 필드는 반복적이고 몇가지 관련된 메소드들을 갖는다.

public java.util.List<com.example.demo.protobuf.SchoolProto.PhoneNumber>
getPhoneList();
public int getPhoneCount();
public com.example.demo.protobuf.SchoolProto.PhoneNumber getPhone(int index);

PhoneNumber 메세지는 SchoolProto.Student.PhoneNumber에 중첩된 타입으로 컴파일된다. 메세지 필드에 두가지 getter를 갖고 간다.

public java.lang.String getNumber();
public com.example.demo.protobuf.SchoolProto.Student.PhoneType getType();

PhoneTypePhoneNumber 메세지의 complex 필드 type이다. 메세지에서도 enum 타입이며, 컴파일 후에도 자바에서의 enum 타입과 똑같다.

public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
  MOBILE(0),
  LANDLINE(1),
  UNRECOGNIZED(-1),
  ;
}

스프링 REST API에서 Protobuf 사용해보기

createTestStudents 작성하기

createTestCousrses 작성하기

CourseRepository 작성하기

CourseRepository 빈은 API를 위한 테스트 데이터들을 갖고 있다.

여기서 중요한 것은 우리가 POJOs가 아닌 Protocol Buffer에 대한 데이터를 갖고 운영하고 있다는 것이다.

protobufHttpMessageConverter 작성하기

ProtobufHttpMessageConverter 빈은 @RequestMapping 어노테이션 메소드에 의해 반환되는 응답을 protocol buffer 메세지로 바꾸는데 사용된다.

CourseController 작성하기

여기서 중요한 건, 컨트롤러가 반환하는게 standard POJO가 아니라는 것이다. 그래서 컨트롤러에서 클라이언트에게 반환되기 전에, protocol buffer 메세지로 변환되어 갈 것이다. 실제로 해당 라우트 주소를 크롬에서 접속하면 binary 파일이 다운로드 된다.

Rest 클라이언트와 테스팅

여기까지 간단한 API 구현에 대해서 알아보았고, 클라이언트 사이드에서 프로토콜 버퍼 메세지들을 역직렬화 해보자. 두가지 메소드를 쓸 건데,

첫번째 메소드는 자동적으로 메세지를 변환하기 위해 미리 설정해둔 ProtocolbufHttpMessageConverter 빈을 RestTemplate API에 넣어서 사용한다.

두번째는 수동으로 Protocol Buffer 응답을 JSON 문서 형태로 변환하기 위해 protobuf-java-format을 사용한다.

먼저, 스프링부트에 Application 클래스에 있는 설정 정보들을 알려주고 통합 테스트를 구축해야 한다. 그러기 위해서 다음과 같이 테스트 코드를 작성해보자.

RestTemplate 빈 등록하기

테스트코드 작성하기

결과

자동으로 수신된 프로토콜 버퍼 메세지를 변환시키기 위해서 ProtobufHttpMessageConverter 타입이 필요하다. 이 빈은 이전에 clientserver가 공유했던 그 객체와 같은 것이다.

우리는 RestTemplate 빈을 선언하고 미리 선언해두었던 위 빈을 그 안에 넣어 재사용한 것이다.

Http 클라이언트로 테스팅해보기

HttpClient API를 사용하고 수동으로 프로토콜 버퍼 메세지를 사용하기 위한 첫 단계는 두 개의 의존성을 먼저 추가해주는 것이다.

protobuf-java-formathttpclient 라이브러리 중 최신버전을 깔아주자.

executeHttpRequest 메소드 작성하기

클라이언트에서 GET 요청을 실행하고, 응답을 InputStream 인스턴스로 바꾸어주자.

convertProtobufMessageStreamToJsonString 메소드 작성하기

결과

{"id": 1,"course_name": "REST with spring","student": [{"first_name": "JAKE","last_name": "SEO","email": "abc@abc.com","phone": [{"number": "010-0000-0000"}]}]}

json 형태로 잘 나왔다.
레퍼런스: https://www.baeldung.com/spring-rest-api-with-protocol-buffers

여기까지 작성한 소스코드는 이 깃허브 링크에서 볼 수 있다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

1개의 댓글

comment-user-thumbnail
2022년 11월 23일

혹시 깃허브 링크 지우셨는지 ㅠㅠ

답글 달기