Protocol Buffers
는 프로그래밍 언어와 플랫폼에 구애받지 않으면서 구조가 있는 데이터를 직렬화 역직렬화하는 매커니즘이다. Google이 만들어냈다. XML
과 JSON
과 같은 것들보다 빠르다.
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
한 중첩된 자바 클래스들로 컴파일될 수 있다. 위의 예제에서는 Course
와 Student
메세지들이 Course
와 Student
자바 클래스들로 각각 변환된다. 동시에, 메세지들의 필드는 생성된 타입들 내부에 JavaBeans
스타일의 getters와 setters를 갖게 된다. 메세지들에서 =
기호 옆에 있던 숫자는 관련된 필드를 2진 형태로 인코딩하는데 사용되는 고유한 태그이다.
메세지의 타입을 가진 필드들이 어떻게 접근자 메소드로 변환되는지 한번 살펴보자.
Course
메세지부터 시작해보자. 이 메세지엔 두 개의 Simple 필드가 있다. id
와 course_name
이다. 이들의 protocol buffer type은 int32
와 string
이다. 이것들은 자바의 int
타입과 String
타입으로 변환될 것이다. 다음은 컴파일 이후에 그들의 getter
가 어떻게 생겼는지에 대한 간결한 코드이다.
public int getId();
public java.lang.String getCourseName();
처음에 .proto
파일에서는 snake_case
로 작성했던 것들이 자바 컨벤션에 따라 camelCase
로 변환된 것을 주의깊게 보자.
Course
의 마지막 필드인 student
는 Complex 타입이다. 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();
마지막 필드인 phone
은 Complex PhoneNumber
타입이다. Course
의 studnet
필드와 비슷하다. 이 필드는 반복적이고 몇가지 관련된 메소드들을 갖는다.
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();
PhoneType
은 PhoneNumber
메세지의 complex 필드 type
이다. 메세지에서도 enum
타입이며, 컴파일 후에도 자바에서의 enum
타입과 똑같다.
public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
MOBILE(0),
LANDLINE(1),
UNRECOGNIZED(-1),
;
}
CourseRepository
빈은 API를 위한 테스트 데이터들을 갖고 있다.
여기서 중요한 것은 우리가 POJOs가 아닌 Protocol Buffer에 대한 데이터를 갖고 운영하고 있다는 것이다.
ProtobufHttpMessageConverter
빈은 @RequestMapping
어노테이션 메소드에 의해 반환되는 응답을 protocol buffer 메세지로 바꾸는데 사용된다.
여기서 중요한 건, 컨트롤러가 반환하는게 standard POJO가 아니라는 것이다. 그래서 컨트롤러에서 클라이언트에게 반환되기 전에, protocol buffer 메세지로 변환되어 갈 것이다. 실제로 해당 라우트 주소를 크롬에서 접속하면 binary 파일이 다운로드 된다.
여기까지 간단한 API 구현에 대해서 알아보았고, 클라이언트 사이드에서 프로토콜 버퍼 메세지들을 역직렬화 해보자. 두가지 메소드를 쓸 건데,
첫번째 메소드는 자동적으로 메세지를 변환하기 위해 미리 설정해둔 ProtocolbufHttpMessageConverter
빈을 RestTemplate
API에 넣어서 사용한다.
두번째는 수동으로 Protocol Buffer 응답을 JSON 문서 형태로 변환하기 위해 protobuf-java-format
을 사용한다.
먼저, 스프링부트에 Application
클래스에 있는 설정 정보들을 알려주고 통합 테스트를 구축해야 한다. 그러기 위해서 다음과 같이 테스트 코드를 작성해보자.
자동으로 수신된 프로토콜 버퍼 메세지를 변환시키기 위해서 ProtobufHttpMessageConverter
타입이 필요하다. 이 빈은 이전에 client
와 server
가 공유했던 그 객체와 같은 것이다.
우리는 RestTemplate
빈을 선언하고 미리 선언해두었던 위 빈을 그 안에 넣어 재사용한 것이다.
HttpClient
API를 사용하고 수동으로 프로토콜 버퍼 메세지를 사용하기 위한 첫 단계는 두 개의 의존성을 먼저 추가해주는 것이다.
protobuf-java-format
과httpclient
라이브러리 중 최신버전을 깔아주자.
클라이언트에서 GET
요청을 실행하고, 응답을 InputStream
인스턴스로 바꾸어주자.
{"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
여기까지 작성한 소스코드는 이 깃허브 링크에서 볼 수 있다.
혹시 깃허브 링크 지우셨는지 ㅠㅠ