토비의 스프링 5장을 읽으며

김다혜·2020년 4월 5일
1

토비의 스프링

목록 보기
5/5
post-thumbnail

토비의 스프링을 읽으며 나중에 또 찾아볼 것 같은 내용을 정리하고자 한다. (개념 위주로 ~ 😀) 5장은 '서비스 추상화'에 관련 내용이다.😀 해당 챕터에서는 트랜잭션을 적용해보면서 추상화하고 이를 일관된 방법으로 사용할 수 있도록 지원한다.

💡 작성된 코드를 살펴볼 때는 다음과 같은 질문을 해보자

  • 코드에 중복된 부분은 없는가 ?
  • 코드가 무엇을 하는 것인지 이해하기 불편하지 않은가 ?
  • 코드가 자신이 있어야 할 자리에 있는가 ?
  • 앞으로 변경이 일어난다면 어떤 것이 있을 수 있고, 그 변화에 쉽게 대응할 수 있게 작성되어 있는가 ?

아래의 코드를 위와 같은 질문을 통해 확인해보자.

// 현재 grade은 무엇 ? && 업그레이드 조건
if(user.getGrade() == Grade.BASIC && user.getLogin() >= 50) {
	user.setGrade(Grade.SILVER); // 다음 단계의 Grade는 무엇 ?
    changed = true; // 임시 플래그
}
...
  • if 조건이 Grade 개수 만큼 반복될 것이다.
  • 새로운 grade가 추가된다면 enum도 추가해야하며 업그레이드 로직을 수정해야한다.
    따라서 점점 더 복잡해질 것이다.

👉 위의 코드를 개선해보자.
개선 1 - 좀 더 추상적으로
개선 2 - 각 오브젝트가 해야할 책임을 깔끔하게 분리하자
개선한 코드를 살펴보면 각 오브젝트와 메소드가 각각 자기 몫의 책임을 맡아 일을 하는 구조로 만들어졌음을 알 수 있을 것이다. 각자 자기 책임에 충실한 작업만 하고 있으니 이해하기도 쉽다. 또한, 변경이 필요할 때 어디를 수정해야 할 지 명확해졌다.

객체지향적인 코드는 다른 오브젝트의 데이터를 가져와서 작업하는 대신 데이터를 갖고 있는 다른 오브젝트에게 작업을 해달라고 요청한다. 오브젝트에게 데이터를 요구하지 말고 작업을 요청하라는 것이 객체지향 프로그래밍의 가장 기본이 되는 원리이기도 하다.

🤔 테스트와 어플리케이션 코드에 나타난 숫자의 중복도 제거해줘야 할까?

한 가지 변경 이유가 발생했을 때, 여러 군데를 고치게 만든다면 중복이기 때문이다. 가장 좋은 방법은 정수형 상수로 변경하는 것이다.
이렇게 함으로 써, 숫자의 의미도 명확해진다.(무슨 의도로 어떤 값을 넣었는지 이해하기도 쉬워진다.)

참고로! 테스트 시에는 가능한 경계 값을 사용하는 것이 좋다.
(기준 값 - 1)

트랜잭션

중간에 예외가 발생해서 작업을 완료할 수 없을 때, 아예 작업이 시작하지 않은 것처럼 초기 상태로 돌려놓는 것
트랜잭션이란 더 이상 나눌 수 없는 단위 작업을 말한다. 작업을 쪼개서 작은 단위로 만들 수 없다는 것은 트랜잭션의 핵심 속성의 원자성을 의미한다.

트랜잭션 경계 설정

트랜잭션이 존재하는 범위. 즉 트랜잭션이 시작하는 곳과 끝나는 곳을 지정하는 것

  • 트랜잭션 롤백 : 취소 작업
  • 트랜잭션 커밋 : 성공적으로 마무리되어 작업을 확정

👉 트랜잭션의 시작을 선언하고 commit() 또는 rollback()으로 트랜잭션을 종료하는 작업

트랜잭션 경계 설정 구조

트랜잭션은 보통 아래와 같은 구조로 동작한다.

public void method() throws Exception {
	(1) DB connection 생성
    	(2) 트랜잭션 시작
    try {
    	(3) insert 나 update.. 등 DB 관련 작업
        (4) 트랜잭션 커밋
    } catch(Exception e) {
    	(5) 트랜잭션 롤백
    } finally {
    	(6) DB connection 종료
    }
}

위의 구조에서는 하나의 Connection으로 진행되어야 한다.
하지만, Connection객체를 직접적으로 사용하는 코드를 구현하게 된다면 DB 커넥션을 비롯한 리소스의 깔끔한 처리를 가능하게 했던 JdbcTemplate를 더이상 사용할 수 없게된다.

트랜잭션 동기화

스프링이 제공하는 방법이다. 어떤 서비스 로직에서 트랜잭션을 시작하기 위해 만든 Connection오브젝트를 특별한 저장소에 보관해두고, 이후에 호출되는 로직에서 저장된 Connection을 가져다가 사용하게 하는 것이다.
🤔 특별한 저장소란 ?
ThreadLocalConnection이 저장된다. 즉, 트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection오브젝트를 저장하고 관리하기 때문에 다중 사용자를 처리하는 멀티스레드 환경에서도 충돌이 날 염려는 없다 😀
즉, Connection변수를 물고 다니면서 전달할 필요도 없어진다.

스프링에서는 DataSourceUtils.getConnection(dataSource)라는 DB 커넥션 생성과 동기화를 함께 해주는 유틸리티 메서드를 제공한다.

트랜잭션 서비스 추상화

위에서 말했던 DataSourceUtils.getConnection(dataSource) 의 코드를 사용하게 된다면 JDBC API에 의존적인 코드가 되어버리고 만다. 여러 기술의 사용 방법에 공통점(connection을 가져와 성공하면 commit, 실패하면 rollback을 할 것이다!)이 있고, 특정 기술에 독립적으로 구현하고 싶다면 추상화를 생각해볼 수 있다. 추상화란 하위 시스템의 공통점을 뽑아내서 분리시키는 것을 말한다.

스프링이 제공하는 트랜잭션 경계 설정을 위한 추상 인터페이스는 PlatformTransactionManager이다.
🤔 추상적인 인터페이스를 사용하고, DI를 적용함으로써 얻을 수 있는 장점은 ?
특정 기술환경(JPA, 하이버네이트, JDBC 등)에 종속되지 않는 포터블한 코드를 만들 수 있다.

JDBC는 DB 연동/sql 사용을 추상화 한 것이라면,
PlatformTransactionManager는 데이터 엑세스 기술(JPA,, 등)의 트랜잭션 경계설정을 추상화한 것이다. (= 인터페이스를 통한 추상화 계층)

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

단일 책임 원칙

단일 책임 원칙은 하나의 모듈은 한 가지 책임을 가져야 한다는 것을 의미한다. 하나의 모듈이 바뀌는 이유는 한 가지여야 한다고 설명할 수 있다.

단일 책임 원칙을 잘 지키는 코드를 만들려면 인터페이스를 도입하고 이를 DI로 연결해야 한다.

👍 단일 책임 원칙의 장점

단일 책임 원칙을 잘 지키고 있다면,

  • 어떤 변경이 필요할 때 수정 대상이 명확해진다.
  • 결합도가 낮아져 서로의 변경이 영향을 주지 않는다.
  • 테스트하기도 편해진다. (테스트 대상이 되는 코드를 수정하지 않고, 유연하게 변경이 가능하여 테스트에도 편리!)

기승전, DI

DI의 가치는 관심, 책임, 성격이 다른 코드를 깔끔하게 분리하는 데 있다.

🤔 만약, DI가 없었다면 ?
우리가 추상화 기법을 도입하여 책임과 관심이 다른 코드를 분리하고, 서로 영향을 주지 않도록 적용하더라도 (PlatformTransactionManager을 통한 트랜잭션 프로세스 분리) DI를 적용하지 않는다면, 결국 코드 사이의 결합이 남아 있게 된다. (PlatformTransactionManager mgr = new DataSourceTransactionManager() 처럼 구체적인 의존 클래스 정보가 코드에 드러나는 경우)

DI를 주입할 때 주의해야할 점!

어떤 클래스든 스프링의 빈으로 등록할 때 먼저 검토해야 할 것은 싱글톤으로 만들어져 여러 스레드에서 동시에 사용해도 괜찮은가 하는 점이다. 상태를 갖고 있고, 멀티스레드 환경에서 안전하지 않은 클래스를 빈으로 무작정 등록하면 심각한 문제가 발생한다.

2개의 댓글

comment-user-thumbnail
2021년 4월 6일

잘 보고있어요!
토비의 스프링 오래전에 읽었는데 기억이 가물가물해요
블로그 내용 보면서 리뷰하고 있네요 ㅎㅎ

1개의 답글