사용자 레벨 조정 기능 추가

  • 필드 타입이 int일 때
      public class User {
          private int level;
    	
          public int getLevel() {
              return level;
          }
    	
          public void setLevel(int level) {
              this.level = level;
          }
      }
    
      public class Main {
          public static void main(String[] args) {
              User user = new User();
              user.setLevel(1000);
          }
      }
    
    
  • 문제점 : 잘못된 값이나 범위를 넘는 값이 들어올 수 있다

  • enum(이늄) : 특정한 값만을 가지게 하는 열거 타입
  • enum 사용
      public enum Level {
          BASIC(1), SILVER(2), GOLD(3);
    	
          private final int value;
    	
          Level(int value) {
              this.value = value;
          }
    	
          public int intValue() { // DB에 값을 저장할 때 
              return value;
          }
    
          public static Level valueOf(int value){ // DB에서 값을 가져올 때
              switch(value) {
                  case 1: return BASIC;
                  case 2: return SILVER;
                  case 3: return GOLD;
                  default: throw new AssrtionError("Unknown value: " + value);
              }	
          }
      }
    

사용자 레벨을 올리는 작업을 하던 중 문제가 발생해 중단되면 지금까지 작업한 내용은 어떻게 될까?

트랜잭션 경계 설정

  • 트랜잭션 : 더 이상 나눌 수 없는 단위 작업
    • 특성
      1. 원자성(Automicity) : 트랜잭션에서 정의된 연산들은 모두 성공적으로 실행되던지 아니면 전혀 실행되지 않은 상태로 남아 있어야 한다 (All or Nothing)
      2. 일관성(Consistency) : 트랜잭션이 실행 되기 전의 데이터베이스 내용이 잘못 되어 있지 않다면 트랜잭션이 실행된 이후에도 데이터베이스의 내용에 잘못이 있으면 안된다
      3. 고립성(Isolation) : 트랜잭션이 실행되는 도중에 다른 트랜잭션의 영향을 받아 잘못된 결과를 만들어서는 안된다
      4. 지속성(Durability) : 트랜잭션이 성공적으로 수행되면 그 트랜잭션이 갱신한 데이터베이스의 내용은 영구적으로 저장된다
  • 트랜잭션 롤백(transaction rollback) : 작업 중 문제가 발생했을 때, 트랜잭션 처리 과정에서 발생한 변경 사항을 모두 취소하고 트랜잭션을 종료
  • 트랜잭션 커밋(transaction commit) : 모든작업을 정상적으로 처리하겠다고 확정하는 명령어

  • 트랜잭션의 경계 : 트랜잭션이 시작되고 끝나는 위치
  • 트랜잭션 경게설정 : 트랜잭션을 시작하고 종료시키는 작업

  • 트랜잭션 경계설정 구조
      public void upgradeLevels() throws Exception {
          //(1) DB Connection 생성
          //(2)트랜잭션 시작
          try {
              //(3)DAO 메소드 호출
              //(4)트랜잭션 커밋
          }
          catchException e{
              //(5)트랜잭션 롤백
              throw e;
          }
          finally {
              // (6)DB Connection 종료
          }
      }
    
  • 한 트랜잭션에서 동작하게 하려면 해당 DB 커넥션을 사용해야함
  • 파라미터로 넘겨주면 문제가 있음(기술에 독립적이지 않음)

트랜잭션 동기화

Connection 오브젝트를 저장소에 보관해두고, 필요할 때 가져다 쓰자

스피링이 제공하는 TransactionSynchronizationManager 사용

  • 하나의 트랜잭션 안에서 여러 개의 DB에 데이터를 넣는 작업을 할 수 있을까?
    • JDBC의 Connection을 이용한 방식은 로컬 트랜잭션이다
      • 하나의 DB Connection에 종속된다
    • 글로벌 트랜잭션 방식 사용
  • JTA(Java Transaction API) : XA 리소스 (분산 리소스) 간의 트랜잭션을 처리하는 API 업로드한 이미지
  • JTA호출 -> 트랜잭션 시작 -> 트랜잭션 ID 생성 -> 자원 접근 -> 트랜잭션에 참여 요청 -> XA 프로토콜을 통한 동기화 -> 커밋 or 롤백 명령 -> 최종 상태 확인 후 트랜잭션 종료

  • 여러 데이터, 자원을 일관되게 관리

**하이버네이트를 이용한 트랜잭션 관리 코드는 JDBC나 JTA와 달리 독자적인 트랜잭션 관리 API를 사용한다

특정 기술환경에 따라 코드가 바뀔 수 있다

트랜잭션 서비스 추상화

  • PlatformTransactionManager : 스프링이 제공하는 트랜잭션 경계설정을 위한 추상 인터페이스
  • 사용할 DB의 DataSource를 생성자 파라미터로 넣어DataSourceTransactionManger 객체 생성
  • JTA 이용 시 DataSourceTransactionManger -> JTATransactionManger 하이버네이트 HibernateTransactionManger JPA JPATransactionManger

단일 책임 원칙

DI는수평적인 분리든 수직적인 구분이든 결합도를 낮추는 것에 있어 핵심적인 역할을 한다

  • 단일 책임 원칙(Single Responsibility Principle) : 하나의 모듈은 한 가지 책임을 가져야 한다
  • 장점
    • 유지보수가 용이해짐
    • 자연스러운 디자인 패턴 적용
    • 테스트가 쉬움

JavaMail 서비스 추상화

메일 서버에 부담도 주지 않고 매번 검증을 하지 않도록 테스트용 JavaMail을 사용하자

  • 운영 메일 서버를 통한 테스트는 서버에 큰 부담
  • 테스트 서버를 사용하는 방법을 UserService와 JavaMail 사이에도 적용하자

방법 1

  • JavaMail과 동일한 인터페이스를 갖는 코드가 동작하도록 해보자
    • JavaMail에서는 Session 오브젝트를 만들어야 메일 메시지를 생성할 수 있는데 Session은 더 이상 상속이 불가능한 final 클래스이고 생성자가 모두 private이여서 직접 생성도 불가능하다

방법 2

  • 서비스 추상화를 적용하자
    • 스프링에서 제공하는 추상화 기능을 사용
  • JavaMail의 서비스 추상화 인터페이스
      package org.springframework.mail;
    
      public interface MailSender {
    	
          void send(SimpleMailMessage simpleMessage) throws MailException;
    		
          void send(SimpleMailMessage[] simpleMessages) throws MailException;
    		
      }
    

테스트 대역

테스트할 대상이 의존하는 오브젝트를 DI를 통해 바꾸는 기법이 많이 사용됨

  • 테스트 대역 : 테스트 대상과 연관된 오브젝트를 대신하여 그 기능만 충실히 수행하는 오브젝트(가짜 의존성)
  • 종류
    • Dummy
      • 객체가 인스턴스화 되기는 하지만 실제로 테스트에 사용되지 않는 객체
      • 보통 매개변수를 채우기 위한 용도로 사용
    • Fake
      • 프로덕션에는 적합하지 않지만, 실제 동작하는 구현을 제공
    • Stub
      • 테스트 대상 오브젝트의 의존객체로 존재
      • 테스트에 맞게 단순히 원하는 동작을 수행
      • 메소드를 통해 전달하는 파라미터와 달리 코드 내부에서 간접적 사용
    • Spy
      • 호출된 내역을 기록, 기록한 내용은 테스트 결과를 검증할 때 사용
    • Mock
      • 테스트 대상의 간접적인 출력 결과를 검증하고, 테스트 대상 오브젝트와 의존 오브젝트 사이에서 일어나는 일을 검증
      • 기대한 대로 상호작용하는 행위를 검증, 기대한 대로 동작하지 않으면 Exception을 발생할 수 있다
      • Mock은 Stub이자 Spy도 된다

카테고리:

업데이트:

댓글남기기