TIL

6.1 트랜잭션 코드의 분리

서비스 추상화를 통해 트랜잭션 기술에 독립적으로 트랜잭션을 적용할 수 있었다. 하지만 트랜잭션 경계 설정을 위한 코드가 여전히 거슬리는 것도 사실이다. 아래는 UserService라는 서비스의 한 비즈니스 로직 메서드이다.

public class UserService {
	private final PlatformTransactionManager transactionmanager;
	// 비즈니스 로직 수행을 위한 의존성도 주입 받음

	public UserService(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	public void upgradeLevels() throws Exception {
		TransactionStatus = status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

		try {
			// 비즈니스 로직
			this.transactionManager.commit(status);
		} catch (Exception e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}

비즈니스 로직 부분을 private 메서드로 분리하는 것도 방법이겠지만 여전히 트랜잭션을 담당하는 코드가 버젓이 비즈니스 코드와 함께 존재한다. 이를 DI를 통해 뽑아낼 수도 있다.

DI 적용을 이용한 트랜잭션 분리

UserService는 어떤 클라이언트에게 DI되어 사용되고 있을 것이다.

public class Client {
	private final UserService userSservice;

	public Client(UserService userService) {
		this.userService = userService;
	}
}

UserService가 인터페이스라면 이 인터페이스이 구현체를 여러개 만들 수도 있다. 비즈니스 로직만을 담당하는 구현체와, 트랜잭션 적용 코드를 적용하는 구현체를 따로 만드는 것이다.

비즈니스 로직만 알고 있는 UserService 구현체

public class UserServiceImpl implements UserService {
	// 비즈니스 로직 수행을 위한 의존성만 주입	

	public void upgradeLevels() throws Exception {
		// 비즈니스 로직만 수행
	}
}

트랜잭션 관련 코드를 전혀 고려하지 않는 모습으로 짜도 무방하다.

트랜잭션 부가 기능을 추가한 UserService 구현체

public class UserServiceTx implements UserService {
	private final PlatformTransactionManager transactionmanager;
	private final UserService userService; // 비즈니스 로직을 알고 있는 구현체

	public UserService(PlatformTransactionManager transactionManager, UserService userService) {
		this.transactionManager = transactionManager;
		this.userService = userService;
	}

	public void upgradeLevels() throws Exception {
		TransactionStatus = status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

		try {
			userService.upgradeLevels();

			this.transactionManager.commit(status);
		} catch (Exception e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}

비즈니스 로직만 담당하는 UserService 구현체와 TransactionManager를 함께 DI 받는다. 그리고 Client 코드에서는 UserServiceTx 구현체를 의존하게 하면 코드 상으로 트랜잭션 기술을 분리하면서 트랜잭션을 적용할 수 있다. 인터페이스를 의존하기에 Client 코드는 무엇 하나 고치지 않아도 된다.

트랜잭션 경계 설정 코드 분리의 장점

  1. 비즈니스 로직을 담당하는 UserServiceImpl 코드를 작성할 때 트랜잭션에 대해 고민하지 않아도 된다.
    1. 트랜잭션의 적용 여부부터 로우레벨 트랜잭션 API는 물론이고 스프링의 트랜잭션 추상화 조차 필요 없다.
    2. DI를 통해 Tx 구현체가 먼저 실행되도록 만들기만 하면 된다.
  2. 비즈니스 로직에 대한 테스트를 손쉽게 만들어낼 수 있다.