AWS s3 에서는 다음과같이 java에서 비동기적으로 객체를 업로드하는 기능을 제공한다.
default CompletableFuture<PutObjectResponse> putObject(PutObjectRequest putObjectRequest, AsyncRequestBody requestBody) {
throw new UnsupportedOperationException();
}
나는 다음과 같이 이용할 수 있었다.
fun putObject(key: String, multipartFile: MultipartFile): CompletableFuture<PutObjectResponse> {
val file = convertMultipartToFile(multipartFile) // 요청으로 들어온 multipartFile을 File로 변환한다.
val objectRequest = PutObjectRequest
.builder().bucket(bucketName).key(key).build() // PutObject 메서드에 넣어줄 Request 객체를 만든다.
return s3AsyncClient.putObject(objectRequest, AsyncRequestBody.fromFile(Path.of(file.path)))
.whenComplete { _, _ -> removeNewFile(file) }
}
게시글을 쓸 때 이미지 업로드를 하는 경우 (Request DTO에 이미지 리스트가 비어있지 않은 경우)를 분기처리하여 이 때만 Async Uploader를 사용하게 했다.
if (!postErrandReqDto.images.isNullOrEmpty()) {
val imageWithKeyList = postErrandReqDto.images.map { image ->
val fileName = "${errand.id}-${user.id}-${LocalDateTime.now()}"
val key = s3AsyncUploader.generateKey(fileName) // 키 지어주기. (이전에 올라갔던 같은 키의 객체가 있을 경우 이 파일로 덮어쓰여지기 때문에 유니크하게!)
imageRepository.save(Image(s3AsyncUploader.generateObjectUrl(key), errand)) // 이미지를 업로드 하기 전에 키를 생성하면서(이미지 url을 얻을 수 있으므로) 같이 이미지 엔티티를 데이터베이스에 생성해주었다.
ImageFileWithKey(image, key) // map으로 들어가는 요소들의 형태.
}
val futures = imageWithKeyList.map { imgWithKey ->
s3AsyncUploader.putObject(imgWithKey.key, imgWithKey.image)
}
CompletableFuture.allOf(*futures.toTypedArray()).handle { _, err -> throw err } // TODO: 확인
}
return errand
글을 올리고 나서 바로 사진을 볼 수 없는 문제 외에도,
다른 이유로 iOS에서는 아예 사진 업로드를 실패했기 때문에
Presigned url 발급으로 클라단에서 사진을 업로드 하고 서버에서는 이미지 url만 받아 글을 생성하는 방법으로 바꾸게 되었다.
그 이야기는 다음 포스팅에서 해야겠당
비동기 업로드를 공부하면서 추가적으로 알게된 것
s3에 같은 객체를 업로드하는 경우 마지막으로 업로드한 애가 덮어쓰인다.
s3에 업로드를 하는 것 자체가 분산 컴퓨팅으로 이뤄지기 때문이라고 한다.
그래서 우리가 lock을 걸고 싶을 경우에는 애플리케이션 단에서 기능을 추가해줘야한다.
괜찮아요. 흔히들 실패는 성공의 어머니라고 한답니다 ^^