반응형
5.2 트랜잭션 서비스 추상화
5.2.3 트랜잭션 동기화
Connection 파라미터 제거
스프링에서 제공해주는 트랜잭션 동기화(transaction synchronization) 은 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 이후에 호출되는 DAO 의 메소드에서는 저장된 Connection 을 가져다가 사용하게 한다. 이는 DAO가 사용하는 JdbcTemplate이 트랜잭션 동기화 방식을 이용하도록 하는 것이다.
트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장하고 관리하기 때문에 다중 사용자를 처리하는 서버의 멀티스레드 환경에서도 충돌날 염려가 없다.
트랜잭션 동기화 적용
- 스프링이 제공하는 트랜잭션 동기화 클래스는 TransactionSynchronizationManager이다. 이 클래스를 이용해 트랜잭션 동기화 작업을 초기화하도록 요청한다.
- 그리고 DataSourceUtils에서 제공하는 getConnection() 메서드를 통해 DB 커넥션을 생성하는데, 이를 사용하면 트랜잭션 동기화에 사용하도록 저장소에 바인딩 해준다.
- 트랜잭션 동기화된 채로 JdbcTemplate 을 사용하면, JdbcTemplate 작업에서 동기화시킨 DB 커넥션을 사용하게 된다. 따서 모든 UserDao 의 JDBC 작업은 동일한 Connection 오브젝트를 사용하고 같은 트랜잭션에 참여하게 된다.
- 트랜잭션이 완료되면 스프링 유틸리티 메소드의 도움을 받아 커넥션을 닫고 트랜잭션 동기화를 마치도록 요청하면 된다.
JdbcTemplate 과 트랜잭션 동기화
- JdbcTemplate은 트랜잭션 동기화 저장소에 등록된 DB 커넥션이나 트랜잭션이 없으면 직접 DB 커넥션을 만들고 트랜잭션을 시작해서 JDBC 작업을 시작한다.
- 그렇지 않다면 이미 시작된 트랜잭션에 참여한다.
5.2.4 트랜잭션 서비스 추상화
기술과 환경에 종속되는 트랜잭션 경계설정 코드
- 한 개 이상의 DB 로의 작업을 하나의 트랜잭션으로 만들어야 하는 상황에서는 JDBC의 Connection 을 이용한 트랜잭션 방식인 로컬 트랜잭션으로는 불가능하다.
- 왜냐하면 로컬 트랜잭션은 하나의 DB Connection 에 종속되기 때문이다.
- 따라서 별도의 글로벌 트랜잭션 방식을 사용해야 한다.
- 자바는 JDBC 이외의 이런 글로벌 트랜잭션을 지원하는 트랜잭션 매니저를 지원하기 위한 API 인 JTA(Java Transaction API)를 제공하고 있다.
- JDBC 로컬 트랜잭션을 JTA 를 이용하는 글로벌 트랜잭션으로 바꾸려면 UserService 코드 수정이 필요하다. 이렇게 되면 UserService는 로직이 바뀌지 않아도 기술 환경에 따라 코드가 바뀌는 코드가 되어버리고 만다.
트랜잭션 API의 의존관계 문제와 해결책
- JDBC 에 종속적인 Connection을 이용한 트랜잭션 코드가 UserService 에 등장하면서 UserService는 UserDaoJdbc에 간접적으로 의존하는 코드가 되어버렸다.
- 만약, UserDao 를 직접 구현하여 Connection 대신 Session 을 사용하고, 독자적인 트랜잭션 API 를 사용하는 모듈 사용이 추가되는 등 트랜잭션 방법에 따라 또 변경이 필요해진다.
- 트랜잭션 처리 코드에 추상화를 도입해보자.
스프링의 트랜잭션 서비스 추상화
- 스프링은 트랜잭션 추상화 기술을 제공하고 있다.
- 이를 이용하면 애플리케이션에서 각 기술의 트랜잭션 API를 이용하지 않고도, 일관된 방식으로 트랜잭션을 제어하는 트랜잭션 경계설정 작업이 가능하다.
- 스프링이 제공하는 추상인터페이스는 PlatformTransactionManager 이다.
- JDBC의 로컬 트랜잭션을 이용한다면 위 인터페이스를 구현한 DataSourceTransactionManager를 사용하면 된다.
트랜잭션 기술 설정의 분리
- 트랜잭션 추상화 API 를 적용한 JTA 를 이용하는 글로벌 트랜잭션으로 변경하려면 JTATrasnactionManager로 바꿔주기만 하면 된다.
- 하지만, 어떤 트랜잭션 매니저 구현 클래스를 사용할지는 UserService 코드가 알게 하면 이것은 DI 원칙에 위배된다.
- 따라서 자신이 사용할 구체클래스를 컨테이너를 통해 외부에서 제공받게 하는 스프링 DI 방식으로 바꾼다.
5.3 서비스 추상화와 단일 책임 원칙
수직, 수평 계층구조와 의존관계
- UserDao, UserService는 각각 담당하는 코드의 기능적인 관심에 따라 분리되고, 서로 독자적으로 확장 가능하도록 만든 것으로 같은 어플리케이션 계층에서 수평적인 분리를 했다고 볼 수 있다.
- 그러나 트랜잭션 추상화는 애플리케이션의 비즈니스 로직과 그 하위 로우레벨의 트랜잭션 기술이라는 다른 계층의 특성을 갖는 코드를 분리한 것이다
- 수평적인 구분이든 수직적인 구분이든, 모두 결합도가 낮으며 서로 영향을 주지 않고 자유롭게 확장될 수 있는 구조를 만들 수 있는 데는 스프링의 DI 가 중요한 역할을 하였다.
- DI 는 관심, 책임, 성격이 다른 코드를 깔끔하게 분리하는데 가치를 있다.
단일 책임 원칙
- 하나의 모듈은 한 가지 책임을 가져야 한다 = 하나의 모듈이 바뀌는 이유는 한 가지여야 한다.
단일 책임 원칙의 장점
- 어떤 변경이 필요할 때 수정 대상이 명확해진다.
5.4 메일 서비스 추상화
JavaMail 메일 발송
- 자바에서 메일을 발송할 때는 표준 기술인 JavaMail 을 사용하면 된다.
5.4.2 JavaMail 이 포함된 코드의 테스트
- 테스트할 때마다 매번 메일이 발송되는 것이 바람직할까?
- 운영 서버 부하를 생각했을 때, 실제 DB 대신 테스트 DB 를 사용하고, 테스트 메일 서버 설정을 다르게 한다면 메일 서버에 부하를 주지 않아서 좋을 것이다.
- 테스트용으로는 JavaMail API 를 통해 요청이 들어간다는 보장만 있다면 굳이 테스트할 때마다 JavaMail 을 굳이 구동시킬 필요가 없다.
5.4.3 테스트를 위한 서비스 추상화
JavaMail 을 이용한 테스트 문제점
- JavaMail 의 핵심 API 는 DataSource 처럼 인터페이스로 만들어져서 구현을 바꿀 수 있는게 없다.
- 예를 들어, JavaMail 에서는 Session 오브젝트를 만들어야만 메일 메시지를 생성할 수 있고, 메일을 전송할 수 있다.
- 이 Session 은 인터페이스가 아니고 클래스이며, 생성자가 모두 private으로 만들어져 직접 생성도 불가능하다. 게다가 이 클래스는 더 이상 상속이 불가능한 final 클래스이다.
메일 발송 기능 추상화
- MailSender라는 스프링이 제공하는 인터페이스는 SimpleMailMessage라는 인터페이스를 구현한 클래스에 담긴 메일 메시지를 전송하는 메소드로만 구성되어 있다.
- JavaMail 을 사용해 메일 발송 기능을 제공하는 JavaMailSenderImpl 클래스를 이용하면 된다.
- UserService에서는 MailSender 인터페이스에 의존하게 하고, MailSender 를 구현하면서 아무것도 하지 않는 DummyMailSender 를 테스트용 코드로 구현한다.
5.4.4 테스트 대역
의존 오브젝트의 변경을 통한 테스트 방법
- 실전에서 사용할 오브젝트를 교체하지 않더라도, 단지 테스트만을 위해서라도 DI 는 유용하다.
테스트 대역의 종류와 특징
- 테스트 환경을 만들어주면, 테스트 대상이 되는 오브젝트 기능에만 충실하게 수행할 수 있다.
- 자주 테스트를 실행할 수 있도록 사용하는 이런 오브젝트를 통틀어서 테스트 대역(test double)이라고 한다.
- 대포적 테스트 대역으로는 test stub이 있다.
- 테스트 대상 오브젝트의 의존관계로 존재하면서, 테스트 동안 코드가 정상적으로 수행할 수 있도록 돕는 것을 말한다.
- 테스트가 수행할 수 있도록 입력 값을 제공해주는 스텁 오브젝트
목 오브젝트를 이용한 테스트
- 테스트 대상과 의존 오브젝트 사이에 주고받는 정보에 관심이 있는 경우 목 오브젝트를 테스트 검증 자료로 삼을 수 있다.
- 간접적 출력 값이 확인 가능한 목 오브젝트
반응형
'도서기록 > 토비의 스프링' 카테고리의 다른 글
5장 서비스 추상화 - 1 (0) | 2025.04.04 |
---|---|
3장 템플릿 (0) | 2025.03.22 |
1장 오브젝트와 의존관계 (0) | 2025.03.08 |