콘텐츠로 이동

2021 06 14

2021-06-14

토비의 스프링 5장. 서비스 추상화

  • [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를 도입하여 테스트 행위 자체를 검토 - 보통 테스트 방법이라면 검토하기 까다로울 텐데 이를 도입하면 내부 정보까지 검증 가능