환경설정이 끝났다면, 이제 본격적으로 우리가 생성한 userSchema.avsc 를 통해 User.java를 생성해봅시다.
UserSchema.avsc
(참고로 avsc 파일의 namespace는 경로와도 연관이 있기 때문에, 본인 프로젝트에 알맞게 경로설정을 해야합니다.)
{"namespace": "com.example.my_rest_api.model",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int", "null"]},
{"name": "favorite_color", "type": ["string", "null"]}
]
}
UserSchema.avsc 파일을 생성한 후 `mvn clean install` 명령어를 입력하면 자동으로 User.java 파일이 pom.xml에 지정한 경로에 생성된다.
참고로, avro-tools를 이용하여도 Java Object 생성은 가능합니다.
java -jar /path/to/avro-tools-1.11.1.jar compile schema <schema file> <destination>
아무튼 생성된 파일은 다음과 같습니다.
User.java
(이 파일은 웬만하면 수정하지 마숑.)
/**
* Autogenerated by Avro
*
* DO NOT EDIT DIRECTLY
*/
package com.example.my_rest_api.model;
import org.apache.avro.generic.GenericArray;
import org.apache.avro.specific.SpecificData;
import org.apache.avro.util.Utf8;
import org.apache.avro.message.BinaryMessageEncoder;
import org.apache.avro.message.BinaryMessageDecoder;
import org.apache.avro.message.SchemaStore;
@org.apache.avro.specific.AvroGenerated
public class User extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
private static final long serialVersionUID = 6150300143547297666L;
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example.my_rest_api.model\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"favorite_number\",\"type\":[\"int\",\"null\"]},{\"name\":\"favorite_color\",\"type\":[\"string\",\"null\"]}]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
private static final SpecificData MODEL$ = new SpecificData();
private static final BinaryMessageEncoder<User> ENCODER =
new BinaryMessageEncoder<>(MODEL$, SCHEMA$);
private static final BinaryMessageDecoder<User> DECODER =
new BinaryMessageDecoder<>(MODEL$, SCHEMA$);
/**
* Return the BinaryMessageEncoder instance used by this class.
* @return the message encoder used by this class
*/
public static BinaryMessageEncoder<User> getEncoder() {
return ENCODER;
}
/**
* Return the BinaryMessageDecoder instance used by this class.
* @return the message decoder used by this class
*/
public static BinaryMessageDecoder<User> getDecoder() {
return DECODER;
}
/**
* Create a new BinaryMessageDecoder instance for this class that uses the specified {@link SchemaStore}.
* @param resolver a {@link SchemaStore} used to find schemas by fingerprint
* @return a BinaryMessageDecoder instance for this class backed by the given SchemaStore
*/
public static BinaryMessageDecoder<User> createDecoder(SchemaStore resolver) {
return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver);
}
/**
* Serializes this User to a ByteBuffer.
* @return a buffer holding the serialized data for this instance
* @throws java.io.IOException if this instance could not be serialized
*/
public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
return ENCODER.encode(this);
}
/**
* Deserializes a User from a ByteBuffer.
* @param b a byte buffer holding serialized data for an instance of this class
* @return a User instance decoded from the given buffer
* @throws java.io.IOException if the given bytes could not be deserialized into an instance of this class
*/
public static User fromByteBuffer(
java.nio.ByteBuffer b) throws java.io.IOException {
return DECODER.decode(b);
}
private java.lang.CharSequence name;
private java.lang.Integer favorite_number;
private java.lang.CharSequence favorite_color;
/**
* Default constructor. Note that this does not initialize fields
* to their default values from the schema. If that is desired then
* one should use <code>newBuilder()</code>.
*/
public User() {}
/**
* All-args constructor.
* @param name The new value for name
* @param favorite_number The new value for favorite_number
* @param favorite_color The new value for favorite_color
*/
public User(java.lang.CharSequence name, java.lang.Integer favorite_number, java.lang.CharSequence favorite_color) {
this.name = name;
this.favorite_number = favorite_number;
this.favorite_color = favorite_color;
}
@Override
public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; }
@Override
public org.apache.avro.Schema getSchema() { return SCHEMA$; }
// Used by DatumWriter. Applications should not call.
@Override
public java.lang.Object get(int field$) {
switch (field$) {
case 0: return name;
case 1: return favorite_number;
case 2: return favorite_color;
default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
}
}
// Used by DatumReader. Applications should not call.
@Override
@SuppressWarnings(value="unchecked")
public void put(int field$, java.lang.Object value$) {
switch (field$) {
case 0: name = (java.lang.CharSequence)value$; break;
case 1: favorite_number = (java.lang.Integer)value$; break;
case 2: favorite_color = (java.lang.CharSequence)value$; break;
default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
}
}
/**
* Gets the value of the 'name' field.
* @return The value of the 'name' field.
*/
public java.lang.CharSequence getName() {
return name;
}
/**
* Sets the value of the 'name' field.
* @param value the value to set.
*/
public void setName(java.lang.CharSequence value) {
this.name = value;
}
/**
* Gets the value of the 'favorite_number' field.
* @return The value of the 'favorite_number' field.
*/
public java.lang.Integer getFavoriteNumber() {
return favorite_number;
}
/**
* Sets the value of the 'favorite_number' field.
* @param value the value to set.
*/
public void setFavoriteNumber(java.lang.Integer value) {
this.favorite_number = value;
}
/**
* Gets the value of the 'favorite_color' field.
* @return The value of the 'favorite_color' field.
*/
public java.lang.CharSequence getFavoriteColor() {
return favorite_color;
}
/**
* Sets the value of the 'favorite_color' field.
* @param value the value to set.
*/
public void setFavoriteColor(java.lang.CharSequence value) {
this.favorite_color = value;
}
/**
* Creates a new User RecordBuilder.
* @return A new User RecordBuilder
*/
public static com.example.my_rest_api.model.User.Builder newBuilder() {
return new com.example.my_rest_api.model.User.Builder();
}
/**
* Creates a new User RecordBuilder by copying an existing Builder.
* @param other The existing builder to copy.
* @return A new User RecordBuilder
*/
public static com.example.my_rest_api.model.User.Builder newBuilder(com.example.my_rest_api.model.User.Builder other) {
if (other == null) {
return new com.example.my_rest_api.model.User.Builder();
} else {
return new com.example.my_rest_api.model.User.Builder(other);
}
}
/**
* Creates a new User RecordBuilder by copying an existing User instance.
* @param other The existing instance to copy.
* @return A new User RecordBuilder
*/
public static com.example.my_rest_api.model.User.Builder newBuilder(com.example.my_rest_api.model.User other) {
if (other == null) {
return new com.example.my_rest_api.model.User.Builder();
} else {
return new com.example.my_rest_api.model.User.Builder(other);
}
}
/**
* RecordBuilder for User instances.
*/
@org.apache.avro.specific.AvroGenerated
public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<User>
implements org.apache.avro.data.RecordBuilder<User> {
private java.lang.CharSequence name;
private java.lang.Integer favorite_number;
private java.lang.CharSequence favorite_color;
/** Creates a new Builder */
private Builder() {
super(SCHEMA$, MODEL$);
}
/**
* Creates a Builder by copying an existing Builder.
* @param other The existing Builder to copy.
*/
private Builder(com.example.my_rest_api.model.User.Builder other) {
super(other);
if (isValidValue(fields()[0], other.name)) {
this.name = data().deepCopy(fields()[0].schema(), other.name);
fieldSetFlags()[0] = other.fieldSetFlags()[0];
}
if (isValidValue(fields()[1], other.favorite_number)) {
this.favorite_number = data().deepCopy(fields()[1].schema(), other.favorite_number);
fieldSetFlags()[1] = other.fieldSetFlags()[1];
}
if (isValidValue(fields()[2], other.favorite_color)) {
this.favorite_color = data().deepCopy(fields()[2].schema(), other.favorite_color);
fieldSetFlags()[2] = other.fieldSetFlags()[2];
}
}
/**
* Creates a Builder by copying an existing User instance
* @param other The existing instance to copy.
*/
private Builder(com.example.my_rest_api.model.User other) {
super(SCHEMA$, MODEL$);
if (isValidValue(fields()[0], other.name)) {
this.name = data().deepCopy(fields()[0].schema(), other.name);
fieldSetFlags()[0] = true;
}
if (isValidValue(fields()[1], other.favorite_number)) {
this.favorite_number = data().deepCopy(fields()[1].schema(), other.favorite_number);
fieldSetFlags()[1] = true;
}
if (isValidValue(fields()[2], other.favorite_color)) {
this.favorite_color = data().deepCopy(fields()[2].schema(), other.favorite_color);
fieldSetFlags()[2] = true;
}
}
/**
* Gets the value of the 'name' field.
* @return The value.
*/
public java.lang.CharSequence getName() {
return name;
}
/**
* Sets the value of the 'name' field.
* @param value The value of 'name'.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder setName(java.lang.CharSequence value) {
validate(fields()[0], value);
this.name = value;
fieldSetFlags()[0] = true;
return this;
}
/**
* Checks whether the 'name' field has been set.
* @return True if the 'name' field has been set, false otherwise.
*/
public boolean hasName() {
return fieldSetFlags()[0];
}
/**
* Clears the value of the 'name' field.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder clearName() {
name = null;
fieldSetFlags()[0] = false;
return this;
}
/**
* Gets the value of the 'favorite_number' field.
* @return The value.
*/
public java.lang.Integer getFavoriteNumber() {
return favorite_number;
}
/**
* Sets the value of the 'favorite_number' field.
* @param value The value of 'favorite_number'.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder setFavoriteNumber(java.lang.Integer value) {
validate(fields()[1], value);
this.favorite_number = value;
fieldSetFlags()[1] = true;
return this;
}
/**
* Checks whether the 'favorite_number' field has been set.
* @return True if the 'favorite_number' field has been set, false otherwise.
*/
public boolean hasFavoriteNumber() {
return fieldSetFlags()[1];
}
/**
* Clears the value of the 'favorite_number' field.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder clearFavoriteNumber() {
favorite_number = null;
fieldSetFlags()[1] = false;
return this;
}
/**
* Gets the value of the 'favorite_color' field.
* @return The value.
*/
public java.lang.CharSequence getFavoriteColor() {
return favorite_color;
}
/**
* Sets the value of the 'favorite_color' field.
* @param value The value of 'favorite_color'.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder setFavoriteColor(java.lang.CharSequence value) {
validate(fields()[2], value);
this.favorite_color = value;
fieldSetFlags()[2] = true;
return this;
}
/**
* Checks whether the 'favorite_color' field has been set.
* @return True if the 'favorite_color' field has been set, false otherwise.
*/
public boolean hasFavoriteColor() {
return fieldSetFlags()[2];
}
/**
* Clears the value of the 'favorite_color' field.
* @return This builder.
*/
public com.example.my_rest_api.model.User.Builder clearFavoriteColor() {
favorite_color = null;
fieldSetFlags()[2] = false;
return this;
}
@Override
@SuppressWarnings("unchecked")
public User build() {
try {
User record = new User();
record.name = fieldSetFlags()[0] ? this.name : (java.lang.CharSequence) defaultValue(fields()[0]);
record.favorite_number = fieldSetFlags()[1] ? this.favorite_number : (java.lang.Integer) defaultValue(fields()[1]);
record.favorite_color = fieldSetFlags()[2] ? this.favorite_color : (java.lang.CharSequence) defaultValue(fields()[2]);
return record;
} catch (org.apache.avro.AvroMissingFieldException e) {
throw e;
} catch (java.lang.Exception e) {
throw new org.apache.avro.AvroRuntimeException(e);
}
}
}
@SuppressWarnings("unchecked")
private static final org.apache.avro.io.DatumWriter<User>
WRITER$ = (org.apache.avro.io.DatumWriter<User>)MODEL$.createDatumWriter(SCHEMA$);
@Override public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException {
WRITER$.write(this, SpecificData.getEncoder(out));
}
@SuppressWarnings("unchecked")
private static final org.apache.avro.io.DatumReader<User>
READER$ = (org.apache.avro.io.DatumReader<User>)MODEL$.createDatumReader(SCHEMA$);
@Override public void readExternal(java.io.ObjectInput in)
throws java.io.IOException {
READER$.read(this, SpecificData.getDecoder(in));
}
@Override protected boolean hasCustomCoders() { return true; }
@Override public void customEncode(org.apache.avro.io.Encoder out)
throws java.io.IOException
{
out.writeString(this.name);
if (this.favorite_number == null) {
out.writeIndex(1);
out.writeNull();
} else {
out.writeIndex(0);
out.writeInt(this.favorite_number);
}
if (this.favorite_color == null) {
out.writeIndex(1);
out.writeNull();
} else {
out.writeIndex(0);
out.writeString(this.favorite_color);
}
}
@Override public void customDecode(org.apache.avro.io.ResolvingDecoder in)
throws java.io.IOException
{
org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff();
if (fieldOrder == null) {
this.name = in.readString(this.name instanceof Utf8 ? (Utf8)this.name : null);
if (in.readIndex() != 0) {
in.readNull();
this.favorite_number = null;
} else {
this.favorite_number = in.readInt();
}
if (in.readIndex() != 0) {
in.readNull();
this.favorite_color = null;
} else {
this.favorite_color = in.readString(this.favorite_color instanceof Utf8 ? (Utf8)this.favorite_color : null);
}
} else {
for (int i = 0; i < 3; i++) {
switch (fieldOrder[i].pos()) {
case 0:
this.name = in.readString(this.name instanceof Utf8 ? (Utf8)this.name : null);
break;
case 1:
if (in.readIndex() != 0) {
in.readNull();
this.favorite_number = null;
} else {
this.favorite_number = in.readInt();
}
break;
case 2:
if (in.readIndex() != 0) {
in.readNull();
this.favorite_color = null;
} else {
this.favorite_color = in.readString(this.favorite_color instanceof Utf8 ? (Utf8)this.favorite_color : null);
}
break;
default:
throw new java.io.IOException("Corrupt ResolvingDecoder.");
}
}
}
}
}
생성된 model 파일을 토대로 이제부터 본격적으로 avro 예제를 활용한 service 및 controller 파일을 생성합니다.
(어차피 궁극적으로 REST API를 기반으로 생성한 프로젝트이기에, MVC 패턴을 사용합니다.)
RestService.java
package com.example.my_rest_api.service;
import com.example.my_rest_api.model.User;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumWriter;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
@Service
public class RestService {
public void writeUserByAvroPlugins() throws IOException {
User user1 = new User();
user1.setName("Alyssa");
user1.setFavoriteNumber(256);
User user2 = new User("Ben", 7, "red");
User user3 = User.newBuilder()
.setName("Charlie")
.setFavoriteColor("blue")
.setFavoriteNumber(null)
.build();
// Serialize user1, user2 and user3 to disk
DatumWriter<User> userDatumWriter = new SpecificDatumWriter<>(User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
dataFileWriter.create(user1.getSchema(), new File("C:\\workspace\\intelliJ\\my_rest_api\\src\\main\\resources\\avro\\users.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.append(user3);
dataFileWriter.close();
}
}
!여기서 잠깐!
DatumWriter란? avro type의 파일을 직렬화 하는 직렬화 인터페이스 입니다.
- DatumWriter<T>는 일반적인 직렬화 인터페이스로, 데이터를 특정 형식으로 변환하는 데 사용됩니다.
- 이 인터페이스는 Avro 데이터 구조를 파일에 쓰거나, 네트워크로 전송할 때 사용합니다.
- DatumWriter는 데이터를 Avro의 이진 형식으로 직렬화하기 위한 구체적인 클래스를 통해 구현됩니다.
다시 본론으로 넘어가서, controller에 POST method를 추가합니다.
avro 예제에 맞춰 하다보니, 현재는 매개변수는 넣지 않았지만 최종본엔 넣어져 있겠죠?
(만약 저처럼 org.springbootframeowkr.web 관련 dependency를 사용하고자 한다면, 다음 내용들을 pom.xml에 추가해주세요.)
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
AvroController.java
package com.example.my_rest_api.controller;
import com.example.my_rest_api.service.RestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
@org.springframework.web.bind.annotation.RestController
@RequestMapping("/avro")
public class AvroController {
@Autowired
private RestService avroService;
@PostMapping("/write")
public void writeAvroFile() throws IOException {
avroService.writeUserByAvroPlugins();
}
}
application.properties 은 그대로 두고, application.yml 파일을 새로 생성하여 address와 port를 설정합니다.
application.yml
server:
address: localhost
port: 8081
compile을 한 뒤, postman 툴을 이용하여 POST method request를 던져봅니다.
던짐과 동시에 다음 파일이 생성되는 것을 확인할 수 있습니다.
하지만 avro 파일을 열어보니 다음과 같이 깨져서 나오는군요.
avro type의 파일을 읽을 수 있는 viewer plugin이 필요하겠습니다.
인텔리제이는 역시 똑똑해서 바로 install plugin이 상단에 뜹니다.
설치 후 다음의 값들이 정상적으로 출력됨을 확인 할 수 있었습니다.
users.avro
avro 예제 출처
https://avro.apache.org/docs/1.11.1/getting-started-java/
Getting Started (Java)
This is a short guide for getting started with Apache Avro™ using Java. This guide only covers using Avro for data serialization; see Patrick Hunt’s Avro RPC Quick Start for a good introduction to using Avro for RPC. Download Avro implementations for C
avro.apache.org
다음편에선 write한 데이터들을 read 하는 예제를 작성해보겠어욤.
'Apache Avro' 카테고리의 다른 글
Spring Boot REST API 및 DB 연동 실습 - 1 (환경설정) (3) | 2024.09.28 |
---|---|
avro plugin을 이용한 Spring boot 실습 - 3 (avro read) (1) | 2024.09.24 |
avro dependency를 이용한 Spring boot 실습 (without plugins) (1) | 2024.09.24 |
avro plugin을 이용한 Spring boot 실습 - 1 (환경설정) (0) | 2024.09.21 |
직렬화와 역직렬화 (0) | 2024.09.21 |
댓글