최근 나는 회사 서비스에 사용할 이미지같은 정적인 콘텐츠를 S3에 올리고, 글로벌한 서비스로 CDN을 이용했다. 그리고 DB는 이미지에 대한 정보와 함께 원본 저장소인 S3 URL과 CDN URL을 저장을 하도록 구성을 했다.
나는 이미지에 대한 정보를 CSV 파일에 넣고 한번에 DB에 import 하려 했으나, 이미지 URL을 하나씩 S3에서 가져와 CSV 파일에 데이터를 넣는 불편함을 느꼈다. API를 따로 만들고 화면을 구성해서 DB에 데이터를 넣도록 해결을 할까 고민도 했으나, 화면 구성부터 시작해서 API 개발까지 - 불필요한 시간을 줄이고자 이 프로젝트를 진행하게 되었다.
위에서 언급했듯, 나는 불필요한 시간을 줄이고자 CSV 파일에 있는 데이터를 이용해 URL 문자열을 만들고, 필드에 추가하여 새로운 CSV 파일을 다운받아 DB에 반영하는 방법을 선택했다.
우선, 어떤 구조로 동작을 할지에 대해 감을 잡고자 직접 구현(Custom)을 해보기로 했고, 그 다음으로는 더 편리하게 사용할 수 있는 라이브러리(OpenCSV)를 사용해보고자 했다.
사실 이 부분에서 가장 많은 시간을 소비했다. 기본적으로 Spring에서 읽어들이는 application.yml
파일 외에도 별도의 .yml
설정 파일을 자동으로 읽어들여 값을 가져오도록 설정하는 부분에서 삽질을 했다.
@EnableConfigurationProperties
어노테이션에 설정 파일을 객체화(?)에 사용할 파일들을 지정해주고, SpringApplicationBuilder
클래스에 설정 파일들을 넣어주기만 하면 간단한 일이였는데, SpringApplicationBuilder
의 .properties
가 아닌 .profiles
로 설정하는 바람에 문제를 찾는데 시간이 오래걸렸다.
@Slf4j
@EnableConfigurationProperties(value = {
TestProperties.class,
MyProperties.class
})
@SpringBootApplication
public class Application {
public static final String APPLICATION_LOCATIONS = "" +
"spring.config.location=" +
"classpath:application.yml," +
"classpath:my-test.yml," +
"classpath:my-settings.yml";
public static void main(String[] args) {
// SpringApplication.run(Application.class, args);
new SpringApplicationBuilder(Application.class)
.properties(APPLICATION_LOCATIONS)
.run(args);
// 메모리 사용량 출력
long heapSize = Runtime.getRuntime().totalMemory();
log.info("Heap Size(M) : " + heapSize / (1024 * 1024) + " MB");
}
}
하지만 의미있는 삽질이였다고 생각한다. 원인을 찾아보면서 Spring Boot 2.2 버전에서부터는 Immutable하게 프로퍼티 설정을 할 수 있다는 점을 알아낼 수 있었기 때문이다. 기존 방식은 @Setter
기반으로 바인딩하기 때문에 언제든지 설정 정보가 변경될 가능성이 있었지만, @ConstructorBinding
어노테이션 - 생상자 바인딩을 통해 불변하게 만들 수 있었다.
@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties("my")
public final class MyProperties {
private final Paths paths;
private final Uris uris;
@Getter
@RequiredArgsConstructor
public static final class Paths {
private final String read;
private final String write;
private final String download;
}
@Getter
@RequiredArgsConstructor
public static final class Uris {
private final String s3;
private final String cdn;
}
}
(이렇게 구성하는 것도 방법이지만, PropertySourceFactory
구현체를 만들어서 사용하는 방법도 있다!)
직접 구현에 사용한 클래스로는 File, BufferedReader, BufferedWriter, StringBuilder
를 이용했다.
처리 순서는 간단하다. 앞서 설정한 외부 설정 파일로 부터 파일 경로를 읽어 - 파일을 File
객체로 만들어주고, 이 객체를 BufferedReader
로 읽어들인 뒤, BufferedWriter
로 CSV 파일을 생성해주는 방식이다.
코드를 보면 알겠지만, CSV 파일로 만들어주기 위해 구분자(",")를 직접 추가해주도록 구현을 해놓았는데 이 부분이 직접 구현하는데 있어서 굉장히 비효율적임을 알 수 있었다. 🤬
(코드는 간단하니 생략...)
OpenCSV 라이브러리의 경우, realrain님의 사이트에 사용방법이 자세히 나와있어서 도움을 많이 받았다.
OpenCSV 라이브러리에서 가장 인상 깊었던(?) 부분은, CSV 데이터와 필드의 바인딩이였다. 직접 구현했을 때처럼 불필요하게 매핑처리하지 않아도 된다는 점이 좋았다. (매핑 전략도 StatefulBeanToCsv
객체를 이용하면, 직접 만들어서 사용할 수 있다!)
// 이름 바인딩
@CsvBindByName(column = "title")
private String id;
// 위치 바인딩
@CsvBindByPosition(position = 0)
private String id;
(코드는 간단하니 생략...)
화면 개발에 필요한 언어와 문법들은 어느정도 알지만 능숙하게 다루지 못해, 기본적인 화면 구성에도 어려움을 느끼고 있어 - 이 프로젝트만을 위한 화면을 개발하지 못했다는 점이 아쉬웠다.
(+오늘 내가 포스팅한 내용 외에도 Spring Batch를 이용한 방법도 있을 것 같다. 간단하게 이용은 해보았지만, 제대로 다루지 못하기에 Spring Batch에 대해서는 찾아본 링크만 첨부해놓고 나중에 자세히 정리해보기로 하자!)