추상화란 아래와 같이 정의합니다.
추상화란 하위 시스템의 공통점을 뽑아내서 분리시키는 것을 말한다.
그렇게 하면 하위 시스템이 어떤 것인지 알지 못해도, 또는 하위 시스템이 바뀌더라도 일관된 방법으로 접근 할 수가 있다.
(출처 : 토비의 스프링 p369)
JDBC의 경우 대표적인 서비스 추상화의 예시에 해당합니다.
JDBC에서 사용하는 Connection, PreparedStatement, ResultSet 등은 데이터베이스의 종류에 상관없이 일관되게 사용할 수 있도록 추상화 되어 있습니다.
이러한 추상화를 자바에서는 인터페이스라는 문법을 사용하여 표현하고 있습니다.
인터페이스를 위에 두고, 아래에 구현체(하위 시스템)들을 둬서 추상화하는 겁니다. 그리고 해당 서비스를 호출하는 쪽은 구현체가 아닌 인터페이스를 사용함으로써 일관된 방법으로 접근 가능해지는 것입니다.
이는 제가 스프링 글 쓸때 초반에 언급했었던 디자인 패턴인 전략패턴입니다.
즉, 전략패턴을 이용해서 서비스를 추상화하는 것이죠.
그런데 이것은 그냥 일반적인 프로그래밍 방법론(?)이지, 스프링만의 특징이 아닙니다.
그런데도 스프링에서 대표적인 특징으로 서비스 추상화가 거론되는것은, 스프링에서 이미 많은 서비스들을 대부분
추상화 하여 제공해주기 때문입니다. 앞서 봤던 스프링 예외 계층도 결국 서비스 추상화에 해당합니다.
이 외에도 트랜잭션, 메일 서비스 등등 많이 제공해주고 있습니다.
이번 포스팅에서는 트랜잭션 서비스 추상화에 대해 살펴보겠습니다.
스프링의 트랜잭션 서비스 추상화
앞서 스프링에서 JDBC 사용 시 편리한 트랜잭션 관리를 지원해주는 트랜잭션 동기화를 살펴보았습니다.
해당 게시글에서도 언급했었지만 위의 방식은 한가지 문제점이 있었습니다.
JDBC에 종속적이다. 라는 점이었습니다. 위에서 사용하고 있는 Connection 클래스는 JDBC에서만 사용하는 클래스입니다.
위의 방식이 기술적으로도 조금 한계에 부딪히는게, 만약 두개 이상의 DB를 트랜잭션으로 묶거나 JMS와 같은 메시징 서비스를 같이 트랜잭션으로 묶을 경우입니다.
해당 경우는 JDBC로는 구현이 불가능하므로 JTA와 같은 글로벌 트랜잭션 기술을 써야 하는데, 그럴 경우 위의 JDBC 트랜잭션 동기화 방법을 사용하지 못합니다. ( 트랜잭션 제어에 Connection 객체를 사용하지 않거든요..)
결국 트랜잭션을 제어하는 서비스 계층의 코드가 변경되어야 합니다.
즉, 아주 완벽한 트랜잭션 처리 방식은 아니었던 것입니다.
그리하여 이번엔 스프링에서 제공해주는 트랜잭션 서비스 추상화 계층을 사용해 보겠습니다.
스프링이 제공하는 트랜잭션 경계설정을 위한 추상 인터페이스는 PlatformTransactionManger 입니다.
아래와 같은 계층 구조를 가집니다.
(그림 출처 : https://yangbongsoo.gitbooks.io/study/content/c11c_be44_c2a4_cd94_c0c1_d654.html)
public class AccountService{ @Autowired AccountDAO accountDAO; @Autowired PlatformTransactionManager transactionManager; @Override public void accountTransfer(String sender, String receiver, int money) throws SQLException{ // 트랜잭션 속성 설정 TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크 if(balance >= money){ // 보내는 돈이 잔액보다 많으면 accountDAO.minus(sender, money); accountDAO.plus(receiver, money); } else{ throw new NoMoneyException(); } transactionManager.commit(status); // 성공 } catch (SQLException e) { transactionManager.rollback(status); // 실패 } } }
PlatformTransactionManager 변수가 DI 받는 스프링 빈은 아래와 같습니다.
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
DataSourceTransactionManager는 PalatformTransactionManager의 하위 클래스이며, JDBC용 트랜잭션 클래스입니다.
그러나 아래 commit, rollback 하는 부분을 보시면, 최상위 인터페이스인 PlatformTransactionManager가 소유한 메서드로
commit, rollback을 하고 있음을 보실 수 있습니다.
즉, 자바의 다형성을 이용하므로써 서비스 계층의 코드가 어느 특정기술에 종속되지 않도록 된 것입니다.
여기서 JDBC가 아닌 JTA를 사용하려면 DataSourceTransactionManager 대신 JTATrasncationManager로 바꿔주면 되고,
하이버네이트를 사용하려면 HibernateTransactionManager를 바꿔주면 됩니다.
중요한 것은 이렇게 구현 기술을 바꾸어도 서비스 메서드의 트랜잭션 처리 코드는 변하지 않는다는 것입니다
나중에 시간나실 때 한번 보시면 아시겠지만, 거의 모든 기술이 이미 PlatformTransactionManager 인터페이스의
자식 클래스로 추상화 되어 있습니다. 그리고 위에서도 언급했듯이 트랜잭션 말고도 여러 기술들에 대해 추상화하여
제공해줌으로써 개발자가 개발을 편리하게 하도록 도와줍니다.
단일 책임 원칙
JDBC에 의존적이던 서비스 계층은 PlatformTransactionManager 인터페이스를 사용함으로써 단일 책임 원칙을 지키는
코드가 되었습니다. 그렇다면 여기서 단일 책임 원칙이란 무엇일까요?
단일 책임 원칙이란 하나의 모듈은 한 가지 책임을 가져아 한다라는 의미입니다.
하나의 모듈이 바뀌는 이유는 한가지여야 한다 라는 의미로도 설명할 수 있습니다.
이렇게 놓고 보면 JDBC용 트랜잭션 클래스를 사용할때의 서비스 계층은 바뀔 이유가 두가지 이상이었다는 의미가 되네요.
원래 서비스 계층은 비즈니스 로직만을 위한 계층이고, 해당 계층의 코드가 바뀌는 이유는 비즈니스 로직이 바뀌는 이유 외에
는 변하지 않아야 합니다. 그렇지 않으면 단일 책임 원칙을 위반하는 것입니다.
하지만 JDBC용 트랜잭션 클래스를 사용할때의 서비스 계층은 비즈니스 로직이 변하는 것 외에 트랜잭션 처리 기술이 바뀔 때
(JDBC에서 JTA)도 코드가 변경되게 됩니다. 즉, 모듈이 바뀔 이유가 두가지가 되어버리는 것이죠.
하지만 그 뒤에 우리가 PlatformTransactionManager 클래스를 사용함으로써, 트랜잭션 처리 기술이 바뀌더라도 서비스 계층의 코드는 변할일이 없게 되었습니다.
다형성과 DI 덕분에 단일 책임 원칙에 충실해진 것입니다.
즉, 단일 책임 원칙을 지키게 되면, 변경의 대상이 명확해지는 장점을 가지게 되는 것입니다.
여기까지 서비스 추상화 및 단일책임원칙의 포스팅입니다.
감사합니다
'Spring' 카테고리의 다른 글
스프링 3.1 vol2 - 빈 설정 메타정보 (0) | 2017.02.15 |
---|---|
스프링3.1 vol2 - IoC 컨테이너 : 빈 팩토리와 애플리케이션 컨텍스트 (0) | 2017.02.14 |
JDBC 트랜잭션 동기화 (1) | 2016.07.11 |
예외처리, 스프링 예외처리 (0) | 2016.07.05 |
템플릿 콜백 패턴 (0) | 2016.06.28 |