서비스 추상화를 통해 트랜잭션 기술에 독립적으로 트랜잭션을 적용할 수 있었다. 하지만 트랜잭션 경계 설정을 위한 코드가 여전히 거슬리는 것도 사실이다. 아래는 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를 통해 뽑아낼 수도 있다.
UserService
는 어떤 클라이언트에게 DI되어 사용되고 있을 것이다.
public class Client {
private final UserService userSservice;
public Client(UserService userService) {
this.userService = userService;
}
}
UserService
가 인터페이스라면 이 인터페이스이 구현체를 여러개 만들 수도 있다. 비즈니스 로직만을 담당하는 구현체와, 트랜잭션 적용 코드를 적용하는 구현체를 따로 만드는 것이다.
public class UserServiceImpl implements UserService {
// 비즈니스 로직 수행을 위한 의존성만 주입
public void upgradeLevels() throws Exception {
// 비즈니스 로직만 수행
}
}
트랜잭션 관련 코드를 전혀 고려하지 않는 모습으로 짜도 무방하다.
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
코드는 무엇 하나 고치지 않아도 된다.
UserServiceImpl
코드를 작성할 때 트랜잭션에 대해 고민하지 않아도 된다.