[UserService의 도입]
- Enum을 통해 잘못된 코드를 컴파일 타임에 바로잡자!
- 빠르게 실행 가능한 포괄적인 테스트를 통해 기능의 추가/수정을 용이하게 하자
- UserService는 UserDao의 구현 클래스가 변경되더라도 영향 받지 않도록 할 것
- Data Access Logic의 변경으로 인해 비즈니스 로직 코드를 수정하면 안 됨
- 객체지향적인 코드는 데이터 가져와서 작업하지 말고, 데이터 갖고있는 객체에게 처리를 요청
[트랜잭션 서비스 추상화]
- 트랜잭션: 더 이상 나눌 수 없는 단위 작업. "원자성"
- 테스트용 UserService의 서브클래스를 정의해 특정 시점에서 강제로 예외를 발생시켜보자
- 해당 테스트에서는 앞선 DB 저장 로직이 반영이 되었음
- 모든 사용자의 레벨을 업그레이드하는 작업인 upgradeLevels() 메서드가 하나의 트랜잭션 안에서 동작하지 않았음
- 전체가 다 성공/실패해야 바람직
- DB는 그 자체로 완벽한 트랜잭션을 제공하나, 여러개의 SQL을 하나의 메서드에서 사용시 문제 발생
- 트랜잭션의 경계
- 애플리케이션 내에서 트랜잭션이 시작되고 끝나는 위치
- 하나의 Connection이 만들어지고 닫히는 범위 안에서 존재
- JDBC의 트랜잭션은 하나의 Connection을 가져와 사용한 후 닫는 사이에 일어남
- 트랜잭션이 한 번 시작되면 commit() or rollback() 메서드가 호출될 때까지 작업이 하나의 트랜잭션으로 묶임
- DAO로 분리해 놓은 데이터 액세스 코드는 메서드 호출마다 새로운 트랜잭션이 만들어지는 구조가 됨
- 작업이 진행되는 동안 하나의 DB 커넥션을 만들어 놓을 필요가 있음!
- upgradeLevels()를 하나의 DB 커넥션으로 만드는 방법
1. Connection 오브젝트를 파라미터로 전달
- JdbcTemplate 구현 파토남
- 코드가 지저분해짐
- Jdbc에 의존적여짐
2. Spring의 트랜잭션 동기화
- 트랜잭션 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 저장
- 이후 호출되는 Dao의 메서드에서는 Connection 가져다가 사용
- 문제상황 발생! 하나의 트랜잭션 안에 여러가지 DB 데이터 접근 로직은 불가능 (로컬 트랜잿견은 하나의 DB Connection에 종속)
- "글로벌 트랜잭션"을 활용해야 여러개의 DB가 참여하는 작업을 트랜잭션 처리 가능
- JTA를 활용해야 함
- 그런데 하이버네이트의 트랜잭션 관리를 JTA 안써...
- 또 서비스가 JDBC에 의존적이게 되버림
- Service와 트랜잭션 API간의 의존을 제거하자
- Spring에서는 PlatformTransactionManager를 통해 해당 작업이 가능
- 외부에서 어떤 TransactionManager를 쓸지 Bean을 통해 주입해주자
[서비스 추상화와 단일 책임 원칙]
- 애플리케이션의 비즈니스 로직과 하위에서 동작하는 로우레벨의 트랜잭션 기술을 분리함
- UserService와 UserDao, UserService와 PlatformTransactionManager, UserDao와 DataSource 모두 느슨한 결합
- 이는 DI 덕분에 가능한 구현
- 단일 책임 원칙을 준수하였기에, 변경 시 대상이 명확해짐
[메일 서비스 추상화]
- 실제 Mail 전송 로직을 테스트하는 것은 비용이 너무 큼
- 테스트용 DB 설정한 것 마냥 테스트를 해주는 것이 좋아보임
- JavaMail 같은 자바 표준 기술은 검증이 된 것으로 간주
- 직접 JavaMail의 구현을 테스트용으로 바꿔치기 하는 것은 내부 구현상 불가능
- 따라서 테스트 하기 힘든 구조에 "서비스 추상화"를 해줌으로써 테스트해보자
- LEVEL1 때 배웠던 전략 패턴을 통한 테스트 가능한 구조로 만들기가 생각남
- JavaMailSender라는 스프링이 제공하는 인터페이스로 관련 클래스를 수정해줌
- 이후 콘크리트 클래스를 만들어 메일 발송 기능이 잘 동작하는지 확인하면 됨
- 확장이 불가능한 API를 테스트해야 하는 경우라면, 추상화 계층 도입을 검토할 것!
- 테스트 환경에서 DI를 다르게 주입하여 테스트를 할 수 있음
- 테스트 대역
- 테스트용으로 사용되는 오브젝트
- 테스트 스텁이 대표적
- 테스트 대상 오브젝트의 의존객체로 존재하면서 테스트 동안 코드가 정상적으로 수행할 수 있도록 도움
- Mock Object를 도입하여 테스트 행위 자체를 검토
- 보통 테스트 방법이라면 검토하기 까다로울 텐데 이를 도입하면 내부 정보까지 검증 가능