5장 서비스 추상화

Soonwoo Kwon·2022년 3월 14일
0

토비의 스프링

목록 보기
5/11

5.1 사용자 레벨 관리 기능 추가

  • 기존 UserDao는 CRUD 기능만을 포함하였다. 이 UserDao에 간단한 비즈니스 로직을 추가한다.
  • BASIC, SILVER, GOLD 세 가지의 사용자 레벨이 존재하고, 활동에 따라 사용자 레벨이 변경된다.

필드 추가

  • 사용자 레벨을 나타낼 때 BASIC, SILVER, GOLD를 각각 문자열로 표현하면 메모리도 많이 차지하고 사용하기 불편하다.
  • 각 레벨을 코드화하여 숫자로 저장한다면 더욱 좋다.
class User{
	private static final int Basic = 1;
    private static final int SILVER = 2;
    private static final int GOLD = 3;
}
  • 하지만 integer로 저장하게 되면 지정된 범위를 벗어나는 경우 문제가 발생할 수 있기 때문에 이늄(enum)을 사용하는 것이 적합하다.
public enum Level{
	BASIC(1), SILVER(2), GOLD(3);
    
    private final int value;
    
    Level(int value){
    	this.value = value;
    }
    
    public int intValue(){
    	return value;
    }
    
    public static valueOf(int value){
    	switch(value){
        	case 1: return BASIC;
            case 2: return SLIVER;
            case 3: return GOLD;
            default: throw new AssertionError("Unkown value: " + value);
        }
    }
}
  • 이러한 방식을 사요하면 setLevel(1000)과 같이 범위를 벗어나는 값을 넣게 되면 에러를 통해 걸러줄 수 있다.

User 필드 추가

  • 레벨, 로그인 횟수, 추천수를 추가한다.

UserDaoTest 테스트 수정

  • 레벨, 로그인 횟수, 추천수에 대한 assertThat()을 추가한다.

UserDaoJdbc 수정

  • level은 enum 타입이고 DB에 저장이 불가능한 타입이다.
  • level을 DB에 저장할 때는 intValue()를 통해 정수형으로 바꾸어주고, 반대로 DB에서 읽어올 때는 Level의 static method인 valueOf()를 통해 enum으로 바꾸어준다.

UserService.upgradeLevels()

  • upgradeLevels() 기능을 추가할 필요가 있고, 이 기능은 사용자 관리 비즈니스 로직을 담당하므로 기존 UserDao에 추가하는 것은 알맞지 않다. UserService 클래스를 추가하여 User의 비즈니스 로직을 담당하는 기능들을 구현한다.
  • 모든 User의 정보를 가져와 레벨 상승 여부와 변화 레벨을 확이하여 update() 한다.

UserService.add()

  • 유저 생성 시 사용자 레벨을 추가하는 기능이다.
  • 따로 지정되지 않는다면 BASIC을 부여하고, 지정된 레벨이 있다면 그 레벨을 부여한다.

코드 개선

upgradeLevels() 개선

  • 문제점
    • 레벨 업그레이드 조건 확인을 위한 조건문이 레벨의 개수만큼 반복되고, 레벨이 추가되면 조건문이 늘어난다.
    • 현재 레벨과 레벨 업그레이드 조건을 같은 조건문에서 확인한다.
  • 리팩토링
    • canUpgradeLevel() 메소드 추가
    • upgradeLevel() 메소드 추가
    • 레벨업을 위한 login, recommend 횟수 상수화

5.2 트랜잭션 서비스 추상화

  • upgradeLevel() 작업은 모든 User 리스트를 가져와 각각 레벨업을 할 수 있는지 확인하여 레벨을 조정한다.
  • 이 과정에서 서버 장애가 발생하게 되면 모든 작업을 되돌리는 방식이 더 났다.

모 아니면 도

  • 서버가 다운되거나 네트워크 장애가 발생하는 상황을 직접 연출하여 테스트 하는 것은 어렵다.
  • 따라서 예외를 의도적으로 발생시키는 상황을 만든다.

테스트용 UserService의 대역

  • 기존 UserService 클래스를 상속하여 오버라이딩 하여 예외 상황을 발생시키는 클래스를 생성한다.
  • 특정 UserId를 지닌 User 오브젝트가 발생되면 예외를 발생시켜 작업을 중단시킨다.

테스트 실패의 원인

  • 테스트를 위한 클래스를 만들어서 의도적으로 4번째 User 객체에서 예외를 반환시킨다.
  • 하지만 각 User에 대한 upgradeLevels() 작업은 다른 트랜잭션(transaction) 이기 때문에 예외 발생 이전 User의 레벨 변경 사항이 복구되지 않고 유지되게 된다.

트랜잭션 경계설정

  • 트랜잭션은 더 이상 나눌 수 없는 단위 작업이다.
  • 특정 기능 중에 여러개의 SQL이 사용되는 작업이 하나의 트랜잭션으로 취급되어야 하는 경우가 있다.
  • 한 트랜잭션 내에서 첫 번째 SQL이 성공적으로 수행되었지만 두 번째 SQL이 실패하게 된다면 트랜잭션 롤백(transaction rollback)을 통해 모든 작업을 취소해야한다.
  • 모든 SQL 작업이 성공적으로 수행된 경우 트랜잭션 커밋(transaction commit)를 통해 작업이 확정됨을 DB에 알려주어야 한다.

JDBC 트랜잭션의 트랙잭션 경계설정

  • Connection을 생성하고 객체 c에 저장한다.
  • c.setAutoCommit(false) 메소드를 통해 트랜잭션의 시작을 알린다. false 옵션을 주게 되면 자동 커밋 옵션을 false로 변경하는 것이다.
  • 정상적으로 트랜잭션이 수행되었다면 c.commit() 메소드를 통해 트랜잭션을 커밋한다.
  • 예외가 발생하게 되면 catch 내에서 c.rollback() 메소드를 실행하여 트랜잭션을 롤백한다.

UserService와 UserDao의 트랜잭션 문제

  • UserDao의 메소드는 JdbcTemplate을 통해 커넥션을 가져오고 메소드의 작업이 끝나면 커넥션을 닫는다.
  • 각 UserDao의 메소드는 하나의 트랜잭션으로 실행되게 된다.
  • 이러한 이유로 5개의 User에 대한 upgradeLevels()가 각각의 트랜잭션으로 수행된다.
  • 여러번 DB에 업데이트 하는 작업을 하나의 트랜잭션으로 사용하기 위해서는 해당 작업이 진행되는 동안 하나의 DB 커넥션만 사용하도록 해야한다.

비즈니스 로직 내의 트랜잭션 경계설정

  • UserService의 upgradeLevels() 메소드 내에서 DB 커넥션을 생성하고 사용하여야 한다.
  • 이 과정에서 UserDao의 메소드는 UserService에서 생성한 DB 커넥션을 사용해야 하기 때문에 UserDao의 메소드들은 모두 파라미터를 통해 커넥션을 전달 받을 수 있는 형태로 변경되어야 한다.
  • 또한 upgradeLevels() 메소드에서 바로 UserDao의 update를 실행하는 것이 아니고, upgradeLevel() 메소드를 거치기 때문에 upgradeLevel() 메소드도 파라미터를 통해 커넥션을 전달받아야 한다.

UserService 트랜잭션 경계설정의 문제점

  1. JdbcTemplate을 더 이상 사용할 수 없다.
  2. 비즈니스 로직을 담당하는 UserService 클래스에 Connection 파라미터가 추가된다.
  3. Connection 파라미터가 추가되어 UserDao가 기술에 대해 독립적일 수 없다.
  4. Connection 파라미터가 추가되어 Test 코드에도 영향을 미친다.

트랜잭션 동기화

Connection 파라미터 제거

  • 트랜잭션을 시작하기 위해 만든 Connection을 특별한 저장소에 저장하고 이후에 호출된 DAO 메소드가 이 Connection을 사용하도록 한다.
  • 독립적인 트랜잭션 동기화 방식이다.

트랜잭션 동기화 적용

트랜잭션 서비스 추상화

  • 기존 UserService는 UserDao에만 의존하였고 DAO 클래스 구현 기술에 영향을 받지 않았다.
  • 하지만 트랜잭션 동기화를 위해 JDBC에 의존적인 Connection을 이용한 트랜잭션 코드가 추가되어 간접적으로 JDBC에 의존하게 되었다.
  • UserService 내의 트랜잭션 경계설정 코드를 제거할 수 없기 때문에 이 부분을 추상화하는 방법을 생각해 볼 수 있다.

스프링의 트랜잭션 서비스 추상화

  • 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 스프링에서 제공한다.
  • PlatformTransactionManager은 스프링이 제공하는 트랜잭션 경계설정을 위한 추상 인터페이스이다.
  • 사용할 DB의 DataSource를 생성자 파라미터로 넣어 DataSourceTractionManager의 오브젝트인 transactionManager를 얻는다.
  • TransactionStatus는 트랜잭션의 상태를 담는 클래스이다.
  • 트랜잭션 커밋과 트랜잭션 롤백은 PlaformTransactionManagercommit(), rollback() 메소드를 TransactionStatus 파라미터를 넣어 사용한다.

용어

  • @Autowired
    • 필요한 의존 객체의 '타입'에 해당하는 빈을 찾아 주입한다.

0개의 댓글