스프링이 제공하는 트랜잭션 서비스 추상화 PlatformTransactionManager 클래스를 이용하여 Data Access 기술에 의존하지 않고도 UserService의 트랜잭션 처리를 구현할 수 있었습니다.
public class UserService {
...
private UserDao userDao;
private PlatformTransactionManager transactionManager;
public UserService(UserDao userDao, PlatformTransactionManager transactionManager) {
this.userDao = userDao;
this.transactionManager = transactionManager;
}
...
public void upgradeLevels() {
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.findAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
transactionManager.commit(status); //트랜잭션 커밋
} catch (RuntimeException e) {
transactionManager.rollback(status); //트랜잭션 롤백
throw e;
}
}
}
UserService 의 upgradeLevels 메서드를 살펴보면, 순수한 비즈니스 로직(유저 레벨 업그레이드)뿐만 아니라 트랜잭션 경계설정 하는 로직까지 있어서 복잡해보입니다.
어떻게 보면 UserService 가 User 도메인 관련 비즈니스 로직 처리 뿐만 아니라 트랜잭션 경계설정까지 2가지의 책임을 가지고 있으므로 이를 분리하도록 하겠습니다.
그런데, 만약 단순히 UserService 클래스로부터 트랜잭션 경계설정 로직을 분리하여 다른 클래스로 만들면 UserService를 참조하고 있던 UserServiceTest 에서의 트랜잭션 테스트들이 정상적으로 동작하지 않을 것입니다.
따라서 UserService를 인터페이스로 선언하고 User 도메인 관련 비즈니스 로직을 처리하는 UserServiceImpl 클래스, 트랜잭션 경계 설정을 담당하는 UserServiceTx 클래스를 만들고 두 클래스 모두 UserService 를 구현하게 구성하겠습니다.
그리고 UserServiceTx 는 UserService 를 주입받을건데 트랜잭션 경계 설정만 책임질 뿐 실질적인 비즈니스 로직 처리는 주입 받은 UserService 구현체에 그대로 위임할 것입니다.
다음과 같이 UserService 인터페이스를 생성해줍니다.
public interface UserService {
void add(User user);
void upgradeLevels();
}
기존 UserService 클래스는 UserServiceImpl 로 이름을 변경하고 UserService를 구현해줍니다. 또한, 트랜잭션 관련 로직을 제거하겠습니다.
public class UserServiceImpl implements UserService {
...
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void add(User user) {
if (Objects.isNull(user.getLevel())) {
user.setLevel(Level.BRONZE);
}
userDao.add(user);
}
@Override
public void upgradeLevels() {
List<User> users = userDao.findAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
protected void upgradeLevel(User user) {
user.upgradeLevel();
userDao.update(user);
}
private boolean canUpgradeLevel(User user) {
...
}
}
UserServiceImpl 클래스는 앞으로 User 도메인의 비즈니스 로직만 책임 질 것입니다.
트랜잭션 경계 설정 로직은 다음과 같이 UserServiceTx 클래스에 구현합니다.