TIL 2024-01-12 TransactionlEventListener 와 비동기 메서드

2024. 1. 12. 16:53TIL
목차
  1. 문제
  2.  
  3. PostController에서 PostService랑 S3Service를 따로 주입 받는다
  4. 트랜잭션을 유지하지 말자
  5. 비동기 메서드로 트랜잭션 환경 분리 및 트랜잭션 이벤트 리스너 활용
  6. 해결 방법

오늘 날린 PR에 코멘트가 남겨진 부분이다.

 


      
@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는 롤백이 되었지만 이미지는 롤백이 안되기 때문에 문제 발생

  • 이미지라는 자원이 중요한 서비스라면?
    • 현재 진행중인 프로젝트는 물물교환 서비스이다.
      • 물물교환 서비스는 물건을 온라인 상으로 교환하기 때문에 물건의 상태를 파악하기 위해서 이미지라는 자원은 필수 조건
      • 근데 이미지에 관해서 오류가 나서 게시글의 이미지가 보이지 않는다면 문제다.
      • 게시글은 무조건 삭제 되어야한다
      • 오류가 난 이미지를 사용자에게 보여선 안된다.

위의 문제들로 해결방안 모색

 

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
  1. 문제
  2.  
  3. PostController에서 PostService랑 S3Service를 따로 주입 받는다
  4. 트랜잭션을 유지하지 말자
  5. 비동기 메서드로 트랜잭션 환경 분리 및 트랜잭션 이벤트 리스너 활용
  6. 해결 방법
'TIL' 카테고리의 다른 글
  • TIL 2024-01-16 커서 기반 페이지네이션 구현
  • TIL 2024-01-15 프록시 서버
  • TIL 2024-01-11 NoSQL RDBMS 차이
  • TIL 2024-01-10 AWS S3 객체 삭제~~
wonow_
wonow_
꾸준히 성장하는 개발자 WONOW 입니다. 🤗
wonow_
wonow_
wonow_
전체
오늘
어제
  • 분류 전체보기
    • Language
      • JAVA
    • TIL
    • 코딩테스트
      • 프로그래머스
      • 백준
    • 프로젝트
      • JAVA
      • Spring
    • 기타
    • ERROR
      • Spring
    • CS
      • 알고리즘
      • DB
      • Kafka
    • 잡담
    • 활동
      • 척척박사

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 공부열심히하자
  • 오블완
  • 나는바봉가봉가
  • 문자열 붙여서 출력하기
  • 프로그래머스
  • 티스토리챌린지
  • 티스토리 꾸미기
  • 문자열 돌리기
  • 홀짝 구분하기

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.1
wonow_
TIL 2024-01-12 TransactionlEventListener 와 비동기 메서드
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.