@Override
@Transactional
public void deletePost(Member member, Long postId) {
Post post = findPost(postId); // repository에서 Post 찾기
checkPostWriter(member, post); // Post의 작성자가 Member가 일치하는 지
s3ImageServiceImplV1.deleteImageUrlList(post.getImageUrl()); // S3에서 이미지 객체 삭제
post.deletePost(); // Post isDeleted 컬럼 true로 바꾸기
}
내가 작성했던 코드다
해당 코드를 보면 Transactional 이 달려있고 S3에서 이미지를 삭제하는 메서드가 있다.
문제
- Entity 를 다루는 중 예외가 발생해서 롤백이 되는 상황
- DeletePost를 하고 커밋을 날리는 중 예외가 발생
- Post는 트랜잭션이 걸려 롤백되지만 S3에서 삭제한 이미지는 롤백이 안됨
- S3는 별개의 외부 리소스이기 때문에 트랜잭션이랑 연관이 없음
- 회원 입장에서 이미지를 찾을 수 없는 상황이 온다.
- Post는 트랜잭션이 걸려 롤백되지만 S3에서 삭제한 이미지는 롤백이 안됨
- DeletePost를 하고 커밋을 날리는 중 예외가 발생
- 이미지라는 자원이 중요한 서비스라면?
- 현재 진행중인 프로젝트는 물물교환 서비스이다.
- 물물교환 서비스는 물건을 온라인 상으로 교환하기 때문에 물건의 상태를 파악하기 위해서 이미지라는 자원은 필수 조건
- 근데 이미지에 관해서 오류가 나서 게시글의 이미지가 보이지 않는다면 문제다.
- 게시글은 무조건 삭제 되어야한다
- 오류가 난 이미지를 사용자에게 보여선 안된다.
- 현재 진행중인 프로젝트는 물물교환 서비스이다.
위의 문제들로 해결방안 모색
PostController에서 PostService랑 S3Service를 따로 주입 받는다
같은 Post를 공유하여 DB 조회 한번으로 끝내는 방법
// PostController
@DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(@PathVarible Long postId, @AuthMember member) {
Post post = postService.deletePost(postId, member)
s3Service.deletePostImage(post)
return ResponseEntity.ok.build();
}
// PostService
@Transactional
public Post deletePost(Long postId, Member member {
Post post = findPost(postId);
checkPostWriter(member, post);
post.deletePost();
return post;
}
// s3Service는 생략하겠습니다.
문제점
1. Entity가 Controller 단 까지 내려오게 됐다.
2. deletePost를 하는데 Post를 반환? 메서드를 딱 봤을 때 이해가 안되고 이상하다
PostId로 두번 조회해서 끝내는 방법
// PostController
@DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(@PathVarible Long postId, @AuthMember member) {
postService.deletePost(postId, member)
s3Service.deletePostImage(postId)
return ResponseEntity.ok.build();
}
// PostService
@Transactional
public void deletePost(Long postId, Member member {
Post post = findPost(postId);
checkPostWriter(member, post);
post.deletePost();
}
// s3Service
public void deletePostImage(Long postId) {
Post post = findPost(postId);
// 이후 생략
}
문제점
1. DB조회를 두번 하게 된다. -> 쓸데없는 성능 저하가 될 수 있음
2. S3에서 Post를 알아야되고, 기존의 메서드 (Map 형식의 매개변수를 받고 있었음)을 다른 기능에서 사진 업로드가 필요할 때 재활용을 못한다.
트랜잭션을 유지하지 말자
애초에 트랜잭션 환경을 메서드내에서 유지 시키지 않는 방법이있다.
// PostService
public void deletePost(Member member, Long postId) {
Post post = findPost(postId);
checkPostWriter(member, post);
post.deletePost(); // Post delete상태로 변환
postRepository.saveAndFlush(post); // SaveAndFlush 레포 반영
s3ImageServiceImplV1.deleteImageUrlList(post.getImageUrl()); // S3 이미지 객체 삭제
}
문제점
1. 한 메소드 안에서 계속 다른 트랜잭션이 만들어진다.
2. 데이터 무결성 - 중간에 오류가 날 경우 롤백 할 수 없다.
2-1 - 위의 메소드의 경우 현재는 롤백이 중요한 메서드가 아니긴하다.
2-2 - 추후에 데이터 무결성이 중요한 코드 변경 요구가 있다면?
일반적으로는 데이터의 무결성을 위해 트랜잭션을 사용하는데 위의 코드의 같은 경우에는 트랜잭션을 거의 사용하지 않는다.
그래서 추후에 추가적인 요구 사항이 생겼을 때, 코드 변경이 쉽지 않을 수도 있다.
비동기 메서드로 트랜잭션 환경 분리 및 트랜잭션 이벤트 리스너 활용
이게 내가 하고 싶은 거다
동기 방식과 비동기 방식의 차이?
- 동기 방식
- 요청이 들어오면 순차적으로 작업을 수행하고, 해당 작업이 수행중이면 다음 작업은 대기한다.
- 비동기 방식
- 요청이 들어오면 해당 요청에 의한 작업이 끝나지 않더라도 계속 요청을 받는다.
- 작업이 끝났다는 이벤트가 오면 해당 요청을 처리한다.
트랜잭션 환경에서 동기 방식과 비동기 방식
- 동기 방식
- 트랜잭션 전파가 된다.
- 비동기 방식
- 다른 쓰레드로 옮겨가서 트랜잭션 전파가 되지 않는다.
애초에 S3는 트랜잭션이 필요 없는 환경이라서 전파를 받을 필요가 없다
맨처음 보여줬던 코드의 문제점은 이거다
애초에 S3는 내 서비스가 아닌 거다 S3는 별개라고 생각하고 나는 Post가 잘 삭제 된건지만 보면 된다.
S3는 외부서비스다
트랜잭션이 아무 문제 없이 Commit 까지 성공하면 사용자에게 응답을 하고 S3는 따로 돌아가면 되는 것이다.
사용자 입장에선 S3에 사용자가 올린 이미지가 삭제된게 중요한게 아니라 게시글이 삭제 됐다는 게 중요한 것이다.
이렇게 하면
1. DB 두번 조회 안해도 됨
2. deletePost 에서 Post를 반환하는 일이 없음
3. Entity Controller 까지 내려올 일이 없음
4. 사용자에게 좀 더 빠른 응답 가능
해결 방법
스프링에서 지원하는 ApplicationEventPublisher 를 사용해서 해결 할 수 있다.
트랜잭션 커밋 까지 완료되면 EventPublisher 호출
Listener는 이걸 듣고 알맞는 메소드를 호출
하는 방법이다.
일반적으로 회원 가입시 이메일을 보내는데, 이때 메일 보내는 시간이 좀 걸린다.
근데 그렇다고 유저가 로딩창을 계속 보고 있으면 불만이 쌓일 것
그래서 일단 회원가입 이메일 보냈다고 하고, 이메일 보내는 외부 서비스를 따로 호출해서 처리할 때 위와 같은 방식을 많이 쓰는데,
나도 외부 서비스를 쓰고 위에서 생각했던 방법들이 다 마음에 안 들었고, 이게 제일 마음에 들어서 이 방법으로 구현할 예정이다.
쓰다보니 참 길어졌는데 자세한 사용법은 내일..? 오늘..?
'TIL' 카테고리의 다른 글
TIL 2024-01-16 커서 기반 페이지네이션 구현 (0) | 2024.01.16 |
---|---|
TIL 2024-01-15 프록시 서버 (0) | 2024.01.16 |
TIL 2024-01-11 NoSQL RDBMS 차이 (0) | 2024.01.12 |
TIL 2024-01-10 AWS S3 객체 삭제~~ (0) | 2024.01.11 |
TIL 2024-01-09 쿼리최적화란 (0) | 2024.01.10 |