[Spring] 이벤트 적용기

YoungHo-Cha·2022년 8월 29일
2

Catch Bug Project

목록 보기
11/12
post-thumbnail

OverView

이 글은 로직이 쌓여감에 따라 도메인 의존성이 늘어나 해결하기 위하여 "이벤트"를 적용한 내용을 기록한 글입니다.

이벤트란?

이벤트에 대해서는 정리를 하였으며, 다음의 링크로 대체하겠습니다.

✅ 이벤트란?

이벤트를 고민하게된 계기

다음의 로직을 살펴보겠습니다.

/**
     * 고용정보 생성 메서드
     * @param employeeId : 요청자 id(pk)
     * @param boardId 배치하려는 게시 글 id(pk)
     * @return 고용된 글의 정보 dto
     */
    public DtoOfApplyEmploy apply(Long employeeId, Long boardId){
        //중략(기타 비즈니스 로직 실행)

        employRepository.save(createdEmployEntity);
        

        boardEntity.updateStatus(Status.MATCHED);         // 문제가 된다고 판단한 코드

        return DtoOfApplyEmploy.builder()
                .employeeNickname(employeeEntity.getNickname())
                .employerNickname(employerEntity.getNickname())
                .boardId(boardEntity.getId())
                .build();

    }

    /**
     * 고용자가 고용을 취소하는 메서드
     * @param memberId : 요청자 id(pk)
     * @param boardId : 요청 대상 게시 글 id(pk)
     * @return : 요청 취소에 대한 응답 dto
     */
    public DtoOfCancelByEmployer cancelEmployByEmployer(Long memberId, Long boardId){
        // 중략 ... 필요 엔티티 find 및 필요 로직 실행
        employRepository.delete(employEntity);

        boardEntity.updateStatus(Status.WAITING);         // 문제가 된다고 판단한 코드
        return DtoOfCancelByEmployer.builder()
                // 중략
                .build();
    }

위의 코드를 보면 boardEntity의 상태를 나타내는 "Status"를 update하는 것을 볼 수 있습니다.

해당 클래스는 EmployService.java 클래스이며, boardEntity과 의존성을 우려하였습니다. 그리고 추가적인 기능을 구현함에 따라 해당 "문제가 된다고 판단한 코드"를 이어서 많은 로직이 추가될 것을 우려하였습니다.

그래서 이벤트를 고민하게되었습니다.

변경 전 코드 살펴보기

변경 전의 로직은 다음과 같습니다.

@Transactional(isolation = Isolation.SERIALIZABLE)
    public DtoOfApplyEmploy apply(Long employeeId, Long boardId){
        
        // 관련 엔티티 조회 로직
        
        // 고용 정보 생성 여부 판단 로직
        
        // 고용 정보 생성

        try {
            employRepository.save(createdEmployEntity);
        }catch (Exception e){
            throw new TransactionException("이미 배치되었습니다.");
        }

        boardEntity.updateStatus(Status.MATCHED); // 수정될 코드

        return //중략

    }

위의 코드에서 로직이 수행되고, boardEntity의 코드를 직접적으로 수정하는 메서드를 호출하게됩니다.

해당 로직에서 의존성과 결합도가 생긴다고 판단이 되어, 이벤트를 적용했습니다.

수정 후 코드 살펴보기

이벤트기반으로 처리된 코드를 살펴보겠습니다.

Event 객체 생성

MatchedEvent.java 생성

@Getter
public class MatchedEvent{

    private final Board board;
    private final Status status;

    @Builder
    public MatchedEvent(Board board, Status status){
        this.board = board;
        this.status = status;
    }
}

Status를 업데이트하는 것이 목표이므로, Status와 Board 객체를 가지고 있도록 만들어줍니다.

Event publish 추가

@Transactional(isolation = Isolation.SERIALIZABLE)
    public DtoOfApplyEmploy apply(Long employeeId, Long boardId){
        
        // 관련 엔티티 조회 로직
        
        // 고용 정보 생성 여부 판단 로직
        
        // 고용 정보 생성

        try {
            employRepository.save(createdEmployEntity);
        }catch (Exception e){
            throw new TransactionException("이미 배치되었습니다.");
        }

        // boardEntity.updateStatus(Status.MATCHED); // 수정될 코드
        System.out.println("Publish Event");
        eventPublisher.publishEvent(MatchedEvent.builder().board(boardEntity).status(Status.MATCHED).build()); //수정 후의 코드
        return //중략

    }

publisher로 publish를 하였습니다.

Event Handler 작성

BoardEmployEventHandler.java 생성

@RequiredArgsConstructor
@Component
public class BoardEmployEventHandler{
    private final BoardService boardService;

    @Async
    @TransactionalEventListener
    public void updateStatus(MatchedEvent matchedEvent) throws InterruptedException {
        Thread.sleep(5000L); // 5초 thread sleep
        System.out.println("Update Board Status Event 실행");
        Board boardEntity = matchedEvent.getBoard();
        boardEntity.updateStatus(matchedEvent.getStatus());
        boardService.saveEntity(boardEntity);
    }
}
  • @Async : 비동기로 처리할 계획입니다.
  • @TransactionalEventListener : "AFTER_COMMIT"을 이용하기 위한 어노테이션입니다. 트랜잭션이 성공적으로 마무리되었을 때 새로운 트랜잭션에서 처리되도록 하기 위해서 적용하였습니다. 또한 기존 트랜잭션에서 에러 또는 예외가 발생할 경우 비동기 트랜잭션도 Rollback을 시켜주기 위하여 적용하였습니다.

테스트

위의 로직대로 Employ 요청을 해보았습니다.

게시 글 생성(매칭되지 않은 상태)

image

Status가 "WAITING" 상태인 것을 볼 수 있습니다.

고용 정보 생성(매칭된 상태)

image

Status가 "MATCHED" 상태로 변경된 것을 볼 수 있습니다.

로그 살펴보기

사전에 삽입한 print 문을 통해서 로그를 살펴보겠습니다.

image
  1. Publish Event 실행
  2. 정상적으로 응답됨
  3. 5초 경과
  4. Event 실행

정상적인 흐름으로 수행된 것을 볼 수 있습니다.

끝으로

이벤트를 이용하여 의존성과 결합도를 줄여보았습니다.

하지만 다음과 같은 의문이 생겼습니다.

이벤트로 엔티티의 수정, 삭제, 생성 등등의 로직을 수행해도되는가?

이에 대한 답은 아직 찾지못하였습니다.

추가로 다음과 같은 의문이 또 생겼습니다.

꼭 핸들러에서 메서드를 구현하여야 하는가? BoardService의 메서드에 "TransactionalEventListener"을 설정하고 사용하면 안되는가?

이에 대한 답으로는 이벤트는 이벤트 핸들러에서 관리되어야 하고, 수많은 이벤트가 생겼을 때 관리하기 힘들기 때문에 이벤트 핸들러를 반드시 작성해야한다고 판단이 됩니다.

profile
관심많은 영호입니다. 궁금한 거 있으시면 다음 익명 카톡으로 말씀해주시면 가능한 도와드리겠습니다! https://open.kakao.com/o/sE6T84kf

0개의 댓글