< 코드 개선 >

- 코드에 중복된 부분은 없는가

한가지 변경 이유가 발생했을 때 여러군데를 고치게 만든다면 중복이다.

- 코드가 무엇을 하는 것인지 이해하기 불편하지 않은가

- 코드가 자신이 있어야 할 자리에 있는가

- 앞으로 변경이 일어난다면 어떤 것이 있을 수있고, 그 변화에 쉽게 대응할 수 있게 작성되어 있는가


숫자 타입을 직접 사용할 때는 enum 클래스를 사용하라

ex)

1
2
3
4
5
// 사용자 레벨
pulic enum Level {
    BASIC(1), SIVER(2), GOLD(3);
        ...
}

cs



< 트랜잭션 서비스 추상화 >


트랜잭션

- 더이상 나눌 수 없는 작업단위

- 전체 다 성공하던지, 전체다 실패하든지


트랜잭션 경계설정

- DB는 완벽한 트랜잭션을 지원한다. 

하나의 SQL 명령을 처리할 때 트랜잭션을 보장해준다.

- 그러나 여러개의 SQL을 하나의 트랜잭션으로 취급하는 경우 

하나는 성공하나 다른 하나는 실패했을때 앞의 SQL 작업도 취소되야 한다. ( 트랜잭션 롤백)

- 모두 성공하면 작업확정 ( 트랜잭션 커밋 )


ex) 트랜잭션 경계설정 예

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Connection c = dataSource.getConnection();
c.setAutoCommit(false);     // 트랜잭션 시작
 
try {
    // S : 트랜잭션 경계
    PreparedStatement st1 = c.prepareStatement("update users..");
    st1.executeUpdate();
 
    st1 = c.prepareStatement("update users..");
    st1.executeUpdate();
    // E : 트랜잭션 경계
 
    c.commit();                // 트랜잭션 커밋
 
catch (Exception e) {
    c.rollback();            // 트랜잭션 롤백
 
}
cs



트랜잭션 동기화

- 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 

  이후에 호출되는 DAO 메서드에 저장된 Conneciton을 가져다 사용하게 하는것


트랜잭션 서비스 추상화

- JDBC Connection을 이용한 트랜잭션 방식인 로컬 트랜잭션은 하나의 DB Connection에 종속된다. 

- 여러개에 DB가 참여하는 작업을 하나의 트랜잭션으로 만들수 있는걸 글로벌 트랜잭션방식이라 한다.

- 자바는 JDBC 외에 이런 글로벌 트랜잭션을 지원하는 트랜잭션 매니저를 지원하기 위한 API인

  JTA(Java Transaction API)를 제공한다.


문제점 

1. 로컬 트랜잭션을 사용하면 충분한 곳은 JDBC를 이용한 트랜잭션 관리코드를, 

2. 다중 DB를 위한 글로벌 트랜잭션을 필요로 하는 곳은 JTA를 이용한 트랜잭션 관리코드를,

3. 하이버네이트를 이용하는 곳은 Connection을 직접 사용하지 않고 

    Session을 사용하고 독자적인 트랜잭션 관리 API를 사용



이런 문제를 어떻게 해결하나?

추상화란 하위 시스템의 공톰점을 뽑아내서 분리시키는 것

즉, 위와 같은 문제의 공통점을 뽑아내서 분리시켜야 한다.




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

- 스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있다


ex) 스프링의 트랜잭션 추상화 API를 적용한 소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void upgradeLevel() {
    // JDBC트랜잭션 추상 오브젝트 생성
    PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
 
    // 트랜잭션 시작
    TransactionStatus status= transctionManager.getTransaction(new DefaultTransactionDefinition());

    try {
        ..     //트랜잭션 안에서 진행되는 작업
        transactionManager.commit(status); // 트랜잭션 커밋
    
    } catch (RuntimeException e) {
        transactionManager.rollback(status); // 트랜잭션 롤백
        throw e;
    }
}
cs


PlatformTransactionManager :    스프링이제공하는 트랜잭션 경계설정을 위한 추상 인터페이스

- JDBC의 로컬 트랜잭션을 이용한다면 PlatformTransactionManager 를 구현한

DataSourceTransactionManager를 사용하면 된다. 

- 사용할 DB의 DataSource를 생성자 파라미터로 넣으면서 

DataSourceTransactionManager의 오브젝트를 만든다.

- JDBC를 이용할 경우 먼저 Connection을 생성한 후 트랜잭션을 시작하지만

PlatformTransactionManager 에서는 DB 커넥션을 가져오는 작업도 같이 수행



트랜잭션 기술 설정의 분리

- JTA를 이용하는 글로벌 트랜잭션으로 변경하려면 PlatformTransactionManager 구현 클래스를 

DataSourceTransactionManager에서 JTATransactionManager로 바꿔주면 된다.

ex)

1
2
3
4
5
6
7
8
// JTA
PlatformTransactionManager txManager = new JTATransactionManager();    
 
// 하이버네이트
PlatformTransactionManager txManager = new HibernateTransacitonManager();
 
// JPA
PlatformTransactionManager txManager = new JPATransactionManger();
cs


- 모두 PlatformTransactionManager 인터페이스를 구현한 것이니 

  getTransaction(), commit(), rollback() 메서드는 손댈필요 없다.


- 위 예제코드의 문제는 Servce코드가 어떤 트랜잭션 매니저 구현 클래스를 사용할지 

  알고 있기 때문에 DI 원칙에 위배된다.

  그래서 PlatformTransactionManager는 스프링 빈으로 등록하고 UserService가 DI 받게 하는것이 좋다


- 스프링에서 제공하는 모든 PlatformTransactionManager의 구현 클래스는 싱글톤으로 사용이 가능



< 서비스 추상화와 단일 책임 원칙 >


단일 책임 원칙 (Single Responsibillity Principle)

- 하나의 모듈은 한가지 책임을 가져야한다.

ex) 

UserServive에 JDBC Connection의 메서드를 직접 사용하는 트랜잭션 코드가 들어 있다면, 

해당 서비스는 사용자 레벨 관리와, 트랜잭션 관리라는 두가지 책임을 갖고 있는 것.


- 트랜잭션 서비스 추상화 방식 도입하고, 이를 DI를 통해 외부에서 제어하도록 하고나면 

  UserService가 수정될 이유는 사용자 레벨 관리 뿐이다.


- 수정 대상이 명확해진다.


- 단일책임원칙을 잘 지키는 코드를 만드려면 

1. 인터페이스를 도입하고 이를 DI로 연결해야 하며, 

2. 그 결과로 단일 책임 원칙뿐아니라 개방 패쇄 원칙도 잘 지키고, 

3. 모듈 간에 결합도가 낮아서 서로의 변경이 영향을 주지 않고,

4. 같은 이유로 단일 책임에 집중되는 응집도 높은 코드가 나온다.



< 메일 서비스 추상화 >


JavaMail 메일 발송

자바 메일 발송 표준기술

- javax.mail 패키지에서 제공하는 자바의 이메일 클래스


JavaMail이 포함된 코드의 테스트

문제점

- 테스트서버에 SMTP 프로토콜을 지원하는 메일 서버가 없다면 에러

- 그렇다고 테스트할때마다 메일을 보내면 메일서버에 상당한 부담을 줄수있음


메일서버 없이 테스트

- JavaMail은 자바의 표준기술이고 이미 수많은 시스템에서 검증된 안정적인 모듈

- 따라서 JavaMail API를 통해 요청이 들어간다는 보장만 있으면 

  테스트할 때마다 javaMail을 직접 구동 시킬 필요가 없다

- 즉, JavaMail을 사용할 때와 동일한 인터페이스를 갖는 코드가 동작하도록 만들면 된다.



테스트를 위한 서비스 추상화

javaMail을 이용한 테스트의 문제점

- JavaMail 확장이나 지원이 불가능하도록 만들어진 가장 악명 높은 표준 API중 하나


아 어려워서 나중에 다시 봐야겠다.



< 정리 > 

1. 비지니스 로직을 담은 코드, 데이터 액세스 로직을 담은 코드는 분리되야 함

2. 비지니스 로직을 담은 코드도 내부적으로 책임과 역할에 따라서 메서드로 정리되야 함

3. 이를 위해 DAO 변화에 서비스가 영향받지 않도록 인터페이스, DI를 잘 활용해야 함

4. DAO를 사용하는 비지니스 로직에는 트랜잭션이 필요

5. 트랜잭션 경계설정 : 트랜잭션의 시작과 종료를 지정하는 일

주로 비지니스 로직 안에서 일어난다.

6. 스프링이 제공하는 트랜잭션 동기화 기법을 활용

7. 환경과 서버에 따라 트랜잭션 방법이 변경되면 경계설정 코드도 함께 변경되야 함

8. 트랜잭션 방법에 따라 비지니스 코드도 변경되면 안됨 ( 단일책임원칙 위배 )

9. 영향을 안주려면 트랜잭션 서비스 추상화를 이용

10. 서비스 추상화는 일관된 API를 가진 추상화 계층을 도입

11. 서비스 추상화는 테스트가 어려운 JavaMail같은 기술도 적용 가능

12. 테스트 대역 : 테스트 대상이 사용하는 의존 오브젝트를 대체할 수 있도록 만든 오브젝트

13. 목 오브젝트 :  테스트 대역 중 테스트 대상으로부터 전달받은 정보를 검증할 수 있도록 설계된 것



+ Recent posts