스프링

TIL 20240215 - 대용량 프로젝트(GIGABOX) (2)

개발자 백구 블로그 2024. 2. 15. 22:25

crud를 하는 와중에는 크게 어려움은 없었다.

게시글 정도의 crud는 그동안 해왔던 거랑 크게 차이가 없으니까

그래서 간단하게 코드를 작성해두었다.

 

근데 나는 왜 매번 삭제 로직에서 바보 같은 실수를 하는 것일까?

이전에는 테스트를 하던 중 삭제가 안되는 것이다,, 그래서? 왜 안되지 하고 찾아본 결과,,

내가 삭제로직을 작성해두지 않은 상태에서 했기때문에 그랬던 것이다....후

더 누누히 살펴봐야 한다는 반성을 했ㄴ는데!!!!!!

 

 

이번에도 삭제 로직에 실수를 하고 만것이다,,, ㅠㅠ 으 바보

 

fun deletePost(
    @PathVariable postId: Long,
): ResponseEntity<Unit> {
    postService.deletePost(postId)
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

 

이런식으로 작성을 해줬어야 했는데

 

fun deletePost(
    @PathVariable postId: Long,
): ResponseEntity<Unit> {
    postService.deletePost(postId)
    return ResponseEntity.status(HttpStatus.NO_CONTENT).body(postService.updatePost(postId))
}

 

이렇게 삭제 로직을 두번이나 작성해주고,, ㅋㅋㅋㅋ 디버깅 모드를 했을 때 왜 삭제 로직이 두번이나 실행하는지,, 의아했닼ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

으 바보 

왜 이렇게 유달리 삭제로직을 할때마다 이런 실수를 하는건지,, 

진짜 반복하지 않게 꼼꼼히 봐야겠다고 다짐을 했다...

 

 


아 이제 기본적인 crud 작성은 끝났고!

 

이제 이미지를 업로드를 하는 시간을 가져보겠다.

supabase와 s3 두 가지 중에 고르는 시간을 가졌는데

 

어떤 것을 고를까,,?

 

음..s3은 아무래도 supabase 보다 용량도 크고 나중에 확장성에 더 용이하다는 장점을 가지고 있다.

게다가 매번 supabase에서 db를 사용하고 했으니 안해본 S3을 사용해서 하는 경험을 통해 새로운 것을 학습을 하고 싶어서 S3을 선택하게 되었다! 

 

 

우선 S3 사이트에 들어가서 버킷과 IAM 사용자를 생성해주었다.

 

 

버킷 생성

 

IAM 사용자

 

 

그리고 AWS S3의 빈을 생성해두고 

@Configuration
class AwsS3Config {
    @Value("\${cloud.aws.credentials.access-key}")
    val accessKey: String? = null

    @Value("\${cloud.aws.credentials.secret-key}")
    val secretKey: String? = null

    @Value("\${cloud.aws.region.static}")
    val region: String? = null

    @Bean
    fun AmazonS3Client(): AmazonS3Client {
        val awsCred: AWSCredentials = BasicAWSCredentials(accessKey, secretKey)
        return AmazonS3ClientBuilder.standard()
            .withRegion(region)
            .withCredentials(AWSStaticCredentialsProvider(awsCred))
            .build() as AmazonS3Client
    }
}

 

 

이어서 서비스 로직도 작성해두었다.. 큼큼

아니 근데!! 다들

MultipartFile

 

했던 거 보니까 이걸로 하던데,, 흐음,, 근데 사진을 여러 개 한 번에 넣을 수 있지 않나??

싶어서 찾아보니까 LIST로 해서 넣더라@! 그래서 나도 이번에 LIST 형식으로 해주었다... ㅎㅎ

 

service 부분 로직까지 작성하고 

이미 작성해둔 게시글 crud에 덧붙이는 일만 남았는데,,

스웨거로 테스틀 하는데 계속 에러가 뜨는 것이다.. 알고보니 파일을 첨부하는 ? 그런게 있는데 내가 안해서 그런거였다 ㅋㅋㅋㅋ

 

@PostMapping("/posts", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])

 

이를 붙여주니까 파일을 첨부하는 ? 게 나와서 쉽게 첨부할 수 있었고

@RequestBody @RequestPart 여러가지 어노테이션이 있었는데,,

null 값이 계속 나오는 버그가 생겨서

 

 

 

찾아보다가 

@ModelAttribute

 

이라는 어노테이션으로 작성해두었다.

 

이게 뭐 대표적으로 원인이 된다! 이건 아니지만 안써본 어노테이션이고 또 값이 안받아지니까 어디서부터 손을 대봐야할지 모르겠어서 이것저것 찾아보다가 찾게되었다.

 

이 @ModelAttribute는 클라이언트로부터 일반 HTTP 요청 파라미터나 multipart/form-data 형태의 파라미터를 받아 객체로 사용하고 싶을 때 이용되고 "가장 적절한" 생성자를 찾아 객체를 생성 및 초기화한다.
객체 생성 및 초기화 > Data Binding > Validation 순서로 진행되며 Data Binding은 getter/setter가 존재하는 변수에 한해서 이루어진다.

 

아 근데 이 null 값 나온 이유가 양방향매핑(a-b)을 해준 상태라 다른(b) 로직에 a로직이 없으면 null 값이 나올 수도 있다고 하셨다

참고해서 다음에 응용해봐야지

 

 


그리고 S3 에 저장되는 파일명과 주소는 다르므로 

https://을 덧붙여주는 작업을 했고!

그래서 url를 저장할 때는 인터넷 창 주소가 나오도록 해주었다!

 

자자자 이제 근데 생성 로직을 다시 한번 작성해볼까?

근데 문제가 하나 발생햇다! 나중에 수정이나 삭제를 하게될 때 어떻게 해야할지,, 염두를 차마 못했던 것이다.

List로 하였기 때문에 한번에 수정 했어야 했기 때문이다.

분명 하나만 수정하거나 아예 수정을 안하는 경우도 있을 테니까 말이다...

그래서 storage 테이블을 추가적으로 생성하여 하기로 했다.

사실 erd 작성해둘때 storage 테이블이 있었으나 postId를 받으면서 하는 것이 아니였기 때문에 따로 만들어주었다!

 

그래서 아래의 코드를 작성해주었다!

 

먼저 작성해둔 imgUrl이 null 값이 아니라면 post 엔티티에 title과 content가 저장하도록 하고

그 다음에 S3에 업로드 한 다음에 

따로 만든 post storage에 id와 url을 저장하게끔 로직을 짜주었다.

@Transactional
override fun createPost(formData: PostRequest): PostResponse {

    if (formData.imgUrl != null) {
        val post = postRepository.save(
            Post(
                title = formData.title!!,
                content = formData.content!!
            )
        )
        val uploadData = awsS3Service.uploadImage(formData.imgUrl)

        uploadData.forEach { url ->
            storageRepository.save(
                Storage(
                    post = post,
                    imageUrl = url
                )
            )
        }
        return post.toResponse()

    } else {
        val post = postRepository.save(
            Post(
                title = formData.title!!,
                content = formData.content!!
            )
        )
        return post.toResponse()
    }
}

 

이외에도 사진이 없을 경우에 생성할 수 있도록 로직을 하기 위해서 이런식으로 해주었다!

다만 스웨거에서 테스트를 해보려고 했으나! 스웨그 문제상으로 되지 않아 포스트맨을 사용해서 하게 됐더니 다행이

imgUrl이 없어도 생성이 되는 것을 확인 할 수 있었다.

 

아 그런데! 사실 이 코드는 뭔가 가독성이 떨어지고 반복되는 코드가 존재하므로 확장함수를 이용해 수정을 하였다!

 

음 이렇게 이미지 파일을 생성하는 것까지는 성공을 했으나!

문제는 업로드를 했으면? 삭제를 하거나 수정하는 일도 발생할텐데 이런 로직을 어떻게 짜야 할지에 대해 구상을 해보는 시간을 가졌다.

처음에는 업로드 하는 로직을 짜는데만 하루 정도 걸려서 내가 할 수 있을까라는 자신감도 떨어진 상태였지만

그래도 이왕 필요한 기능을 구현하는 것이니 포기하지 않고 시도해보려고 한다.

 


 

고려해야 할점

1.파일을 저장할 때 url 을 덧붙여주었기 때문에 파일을 삭제할때는 이 상태로 aws S3에서 삭제가 되지 않을테니 변경을 해주어야 한다.

2. 수정해야 할때는 디비에 나온 내용을 삭제해준 다음에 새로운 데이터를 넣어주는 방식을 사용해야하며 aws s3도 같이 수정되어야 하는 로직을 짜야한다.

3. 논리적 삭제를 한 만큼 당장은 삭제가 되지 않으므로 스케줄러를 통해 시간을 지난 후에 저절로 삭제되는 로직을 짜야한다.

4. 그리고 게시글이 삭제될때는 모든 url이 같이 삭제되어야 한다.

5. 수정할때도 생성할때처럼 imgUrl이 없어도 수정이 가능하게끔 바꿔야 한다. 그리고 하나의 사진씩 수정을 할 수 있으므로 storageId도 필요하다.

 

이런 생각을 하고 오늘 하루를 끝맺쳤다..

 

 

 

후,, 업로드 하는 기능을 난생 처음해보니 너무 어려웠지만

그래도 해냈으니 뿌듯한 마음이 가득하다.

이번을 통해 s3을 사용했으니 다음에도 더 응용해서 사용하거나 다른 기능을 더 구현해야겠다는 생각을 했다.