본문 바로가기
프로그래밍/Java & Spring

스프링 이벤트 프로그래밍 적용하기

by 방구석개발자 2022. 5. 7.
반응형

안녕하세요.
오늘은 이벤트 기반 프로그래밍에 대해서 알아보고 스프링부트에 적용해보는것까지 확인해보겠습니다.

이벤트 기반 프로그래밍이란?

‘사건’에 따라, 제어 흐름이 결정되어 일을 하도록 하게끔 만들어진 프로그래밍 언어 방식을 뜻한다.  -위키백과-

스프링 이벤트는 언제 사용할까?

  • 서비스간의 결합도를 낮추고 싶을때
  • 서브 로직이 에러가 나더라도 메인 로직은 정상적 완료 하고 싶을때

이커머스 도메인으로 해당 상황을 고민해보자면

사용자가 회원을 가입합니다.

회원 가입할때 웰컴쿠폰을 발행합니다.

 

기존에는

회원가입 서비스에서 쿠폰서비스를 의존하여 사용했습니다.

쿠폰 발행시 에러가 나면 회원가입도 에러가 나서 회원가입이 롤백되었습니다.

 

스프링 이벤트를 적용하면 두가지 문제를 해결 할 수 있습니다.

구현 코드

간단한 게시글을 등록 하는 기능이며 게시글 등록 후에 히스토리에 이력을 쌓는 기능을 구현했습니다.

해당 코드의 문제 상황

  1.  게시글 서비스계층(boardService)에서 이력레포지토리(historyRepository)를 의존합니다.
  2. 이력등록에서 에러가 나면 게시글 등록이 되지 않습니다.

https://github.com/lsm7179/event-study

 

GitHub - lsm7179/event-study

Contribute to lsm7179/event-study development by creating an account on GitHub.

github.com

스프링 이벤트로 문제를 해결해 보자!

기존 코드

BoardService에서 historyRepository를 이용하여 이력을 등록하였습니다.

 @Transactional
    public long createNotEvent(BoardRequest boardRequest){
        Board createBoard = boardRequest.toBoard();
        Board savedBoard = boardRepository.save(createBoard);
        historyRepository.save(new History(createBoard));
        return savedBoard.getId();
    }

스프링이벤트를 이용한 코드

@Service
@Transactional(readOnly = true)
public class BoardService {

    private final BoardRepository boardRepository;
    private final ApplicationEventPublisher eventPublisher;
	/*... 중략 ...*/
    
    @Transactional
    public long createWithEvent(BoardRequest boardRequest) {
        Board createBoard = boardRequest.toBoard();
        Board savedBoard = boardRepository.save(createBoard);
        eventPublisher.publishEvent(new CreatedBoard(savedBoard.getId()));
        return savedBoard.getId();
    }
 }

 

package com.study.event.event;

import lombok.Data;

@Data
public class CreatedBoard {
    private long boardId;

    public CreatedBoard(long boardId) {
        this.boardId = boardId;
    }
}

스프링이벤트는 ApplicationEventPublisher를 이용하여 이벤트를 등록합니다.

등록할때는 CreatedBoard라는 객체를 생성하여 등록하였습니다.

그런 다음 @EventListener와 @TransactionalEventListener를 이용하여 발행된 이벤트를 받아서 실행하는 로직을 만들어 줍니다.

@EventListener는 Event를 사용할 때 publishing 하는 코드 시점에 바로 실행이 된다는점

@TransactionalEventListener는 transaction의 타이밍에 이벤트를 발생시킬지 정할 수 있습니다.  기본값은 커밋 이후에 이벤트를 실행시킵니다.

@TransactionalEventListener을 이용하여 커밋 후 실행하도록 진행했습니다. 커밋 이후에 실행되는것 이므로 @Transactional(propagation = Propagation.REQUIRES_NEW) 속성을 이용하여 별도의 트랜잭션 구간을 만들어 이력을 저장했습니다.

package com.study.event.event;


import com.study.event.domain.Board;
import com.study.event.domain.History;
import com.study.event.repository.BoardRepository;
import com.study.event.repository.HistoryRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Component
public class BoardEventListener {

    private final BoardRepository boardRepository;
    private final HistoryRepository historyRepository;

    public BoardEventListener(BoardRepository boardRepository, HistoryRepository historyRepository) {
        this.boardRepository = boardRepository;
        this.historyRepository = historyRepository;
    }

    @TransactionalEventListener
    public void createBoardEvent(CreatedBoard createdBoard) {
        Board board = boardRepository.findById(createdBoard.getBoardId())
                .orElseThrow(IllegalArgumentException::new);
        historyRepository.save(new History(board));
    }
}

스프링 이벤트를 적용한 후

  1.  게시글 서비스계층(boardService)에서는 이력레포지토리(historyRepository)를 의존하지 않습니다.
  2. 이력등록에서 에러가 나도 이미 게시글 등록이 커밋 되었으므로 등록이 잘 됩니다.

 

실제로 로직이 잘 동작하는 지 확인해 보았습니다.

이벤트를 사용하지 않은 게시글 등록
이벤트를 적용한 게시글 등록

오늘은 스프링 이벤트 프로그래밍 적용하는 방법을 알아 보았는데요.

스프링이벤트를 이용하면 좀 더 안전하게 프로그래밍 할 수 있을거 같습니다.

감사합니다.

참조

스프링공식문서

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/event/TransactionalEventListener.html

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html

반응형

댓글