인생을 코딩하다.

[토비의 스프링 5장] 서비스와 추상화 본문

Spring

[토비의 스프링 5장] 서비스와 추상화

Hyung1 2021. 8. 28. 03:22
728x90
반응형

서비스와 추상화

p.343)

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

p.353)

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

p.354)

  • 두 가지 작업이 하나의 트랜잭션이 되려면, 두 번째 SQL이 성공적으로 DB에서 수행되기 전에 문제가 발생할 경우에는 앞에서 처리한 SQL 작업도 취소시켜야 한다. 이런 취소 작업을 트랜잭션 롤백이라고 한다.
  • 반대로 여러 개의 SQL을 하나의 트랜잭션으로 처리하는 경우에 모든 SQL 수행 작업이 다 성공적으로 마무리됐다고 DB에 알려줘서 작업을 확정시켜야 한다. 이것을 트랜잭션 커밋이라고 한다.

JDBC 트랜잭션의 트랜잭션 경계설정

  • 모든 트랜잭션은 시작하는 지점과 끝나는 지점이 있다. 시작하는 방법은 한 가지이지만 끝나느 방법은 두 가지다. 모든 작업을 무효화하는 롤백과 모든 작업을 다 확정하는 커밋이다.
  • 애플리케이션 내에서 트랜잭션이 시작되고 끝나는 위치를 트랜잭션의 경계라고 부른다. 복잡한 로직의 흐름 사이에서 정확하게 트랜잭션 경계를 설정하는 일은 매우 중요한 작업이다.
  • JDBC의 트랜잭션은 하나의 Connection을 가져와 사용하다가 닫는 사이에서 일어난다. 트랜잭션의 시작과 종료는
    Connection 오브젝트를 통해 이뤄지기 때문이다.
  • 트랜잭션이 한 번 시작되면 Commit() 또는 rollback() 메소드가 호출될 때까지의 작업이 하나의 트랜잭셭으로 묶인다.
  • commit() 또는 rollback()이 호출되면 그에 따라 작업 결과가 DB에 반영되거나 취소되고 트랜잭셔이 종료된다.
  • 일반적으로 작업중에 예외가 발생하면 트랜잭션을 롤백한다. 예외가 발생했다는건, 트랜잭션을 구성하는 데이터 엑세스 작업을 마무리할 수 없는 상황이거나 DV에 결과를 반영하면 안 되는 이유가 생겼기 때문이다.
  • 이렇게 setAutoCommit(false)로 트랜잭션의 시작을 선언하고 commit() 또는 rollback()으로 트랜잭션을 종료하는 작업을 트랜잭션의 경계설정이라고 한다. 트랜잭션의 경계는 하나의 Connection이 만들어지고 닫히는 범위 안에 존재한다는 점도 기억해두자. 이렇게하나의 DB 커넥션 안에서 만들어지는 트랜잭션을 로컬 트랜잭션이라고도 한다.

p.357)

  • 여러 번 DB에 업데이트를 해야 하는 작업을 하나의 트랜잭션으로 만들려면 어떻게 해야할까? 어떤 일련의 작업이 하나의 트랜잭션으로 묶이려면 그 작업이 진행되는 동안 DB 커넥션도 하나만 사용돼야 한다.

p.360)

트랜잭션 경계설정의 문제점

  • 멀티스레드 환경에서는 공유하는 인스턴스 변수에 스레드별로 생성하는 정보를 저장하다가는 서로 덮어쓰는 일이 발생할 수 있다.

p.361)

  • 트랜잭션 동기화란 UserService에서 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 이후에 호출되는 DAO의 메소드에서는 Connection을 가져다가 사용하게 하는 것이다.
  • 트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장하고 관리하기 때문에 다중 사용자를 처리하는 서버의 멀티스레드 환경에서도 충돌이 날 염려는 없다.

p.353)

  • 스프링이 제공하는 트랜잭션 동기화 관리 클래스는 TransactionSynchronizationManager다.
  • DataSource에서 Connection을 직접 가져오지 않고, 스프링이 제공하는 유틸리티 메소드를 쓰는 이유는
    DataSourceUtils의 getConnection() 메소드는 Connection 오브젝트를 생성해줄 뿐만 아니라 트랜잭션 동기화에 사용하도록 저장소에 바인딩해주기 떄문이다.

p.365)

  • 한 가지 궁금한 것이 있다. jdbcTemplate의 동작방식이다. 지금까지는 jdbcTemplate은 update()나 query() 같은
    JDBC 작업의 템플릿 메소드를 호출하면 직접 Connection을 생성하고 종료하는 일을 모두 담당한다.

p.366)

한 개 이상의 DB로의 작업을 하나의 트랜잭션으로 만드는건 JDBC의 Connection을 이용한 트랜잭션 방식인 로컬 트랜잭션으로는 불가능하다.

  • 왜냐하면 로컬 트랜잭션은 하나의 DB Connection에 종속되기 떄문이다.
    따라서 각 DB와 독립적으로 만들어지는 Connection을 통해서가 아니라,
  • 별도의 트랜잭션 관리자를 통해 트랜잭션을 관리하는 글로벌 트랜잭션 방식을 사용해야 한다.
  • 자바는 JDBC 외에 이런 글로벌 트랜잭션을 지원하는 트랜잭션 매니저를 지원하기 위한 API인 JTA(Java Transaction API)를 제공하고 있다.
  • 트랜잭션 매니저는 DB와 메시징 서버를 제어하고 관리하는 각각의 리소스 매니저와 XA 프로토콜을 통해 연결된다.
  • 이를 통해 트랜잭션 매니저가 실제 DB와 메시징 서버의 트랜잭션을 종합적으로 제어할 수 있게 되는 것이다.
  • 이렇게 JTA를 이용해 트랜잭션 매니저를 활용하면 여러 개의 DB나 메시징 서버에 대한 작업을 하나의 트랜잭션으로 통합하는분산 트랜잭션 또는 글로벌 트랜잭션이 가능해진다.

p.369)

  • 추상화란 하위 시스템의 공통점 뽑아내서 분리시키는 것을 말한다.
  • 그렇게 하면 하위 시스템이 어떤 것인지 알지 못해도, 또는 하위 시스템이 바뀌더라도 일관된 방법으로 접근할 수가 있다.
  • DB에서 제공하는 DB 클라이언트 라이브러리와 API는 서로 전혀 호환이 되지 않는 독자적인 방식으로 만들어져 있다.
    • 하지만 모두 SQL을 이용하는 방식이라는 공통점이 있다.
  • 이 공통점을 뽑아내 추상화한 것이 JDBC다.
    • JDBC라는 추상화 기술이 있기 때문에 자바의 DB 프로그램 개발자는 DB의 종류에 상관없이 일관된 방법으로 데이터 엑세스 코드를 작성할 수가 있다.

p.371)

  • 스프링이 제공하는 트랜잭션 경계설정을 위한 추상 인터페이스는 PlatformTransactionManager다. JDBC의 로컬 트랜잭션을 이용한다면 PlatformTransactionManager를 구현한 DataSourceTransactionManeger를 사용하면 된다.

p.372)

트랜잭션의 기술 설정의 분리

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

p.376)

  • 결합도가 낮다는건 데이터 엑세스 로직이 바뀌거나, 심지어 데이터 엑세스 기술이 바뀐다고 할지라도 UserSerivce의 코드에는 영향을 주지 않는다는 뜻이다.
  • 애플리케이션 로직의 종류에 따라 수평적인 구분이든, 로직과 기술이라는 수직적인 구분이든 모두 결합도가 낮으며, 서로 영향을 주지 않고 자유롭게 확장될 수 있는 구조를 만들 수 있는 데는 스프링의 DI가 중요한 역할을 하고 있다. DI의 가치는 이렇게 관심, 책임, 성격이 다른 코드를 깔끔하게 분리하는 데 있다.

p.377)

  • 단일 책임 원칙은 하나의 모듈은 한 가지 책임을 가져야 한다는 의미다. 하나의 모듈이 바뀌는 이유는 한 가지여야 한다고 설명할 수 도 있다.
  • 두 가지 책임을 갖고 있다는 건 UserService 코드가 수정되는 이유가 두 가지라는 듰이다.

단일 책임 원칙의 장점

 

이렇게 단일 책임 원칙을 지키는 코드가 되면 어떤 장점이 있을까?

  • 단일 책임 원칙을 잘 지키고 있다면, 어떤 변경이 필요할 때 수정 대상이 명확해진다. 기술이 바뀌면 기술 계층과의 연동을 담당하는 기술 추상화 계층의 설정만 바꿔주면 된다.
  • 그래서 적적하게 책임과 관심이 다른 코드를 분리하고, 서로 영향을 주지 않도록 다야안 추상화 기법을 도입하고, 애플리케이션 로직과 기술/환경을 분리하는 등의 작업은 갈수록 복잡해지는 엔터프라이즈 애플리케이션에는 반드시 필요하다.

p.379)

  • 스프링을 DI 프레임워크라고 부르는 이유는 외부 설정정보를 통한 런타임 오브젝트 DI라는 단순한 기능을 제공하기 때문이 아니다. 오히려 스프링이 DI에 담긴 원칙과 이를 응용하는 프로그래밍 모델을 자바 엔터프라이즈 기술의 많은 문제를 해결하는데 적극적으로 활용하고 있기 때문이다.
  • 또, 스프링과 마찬가지로 스프링을 사용하는 개발자가 만드는 애플리케이션 코드 또한 이런 DI를 활용해서 깔끔하고 유연한 코드와 설계를 만들어낼 수 있도록 지원하고 지지해주기 떄문이다.
  • DI 원리를 잘 활용해서 스프링을 열심히 사용하다 보면, 어느 날 자신이 만든 코드에 객체지향 원칙과 디자인 패턴의 장점이 잘 녹아 있다는 사실을 발견하게 될 것이다. 그것이 스프링을 사용함으로써 얻을 수 있는 가장 큰 장점이다.

p.389)

  • 일반적으로 서비스는 추상화라고 하면 트랜잭션과 같이 기능은 유사하나 사용방법이 다른 로우 레벨의 다양한 기술에 대해 추상 인터페이스와 일관성 있는 접근 방법을 제공해주는 것을 말한다.

p.392)

  • 하나의 오브젝트가 사용하는 오브젝트를 DI에서 의존 오브젝트라고 불러왔다. 의존한다는 말은 종속되거나 기능을 사용한다는 의미다.
  • 의존 오브젝트를 협력 오브젝트라고도 한다. 함께 협력해서 일을 처리하는 대상이기 떄문이다.
  • 운영 중에는 절대 바뀌지 않더라도 테스트 떄는 바꿀 수밖에 없기 때문이다. 그래서 DataSource라는 인터페이스를 사용하고,
    어떤 클래스의 오브젝트를 사용할지 외부에서 주입해주도록 스프링의 DI를 적용해야 한다.

p.393)

  • 이렇게 테스트 환경을 만들어주기 위해, 테스트 대상이 되는 오브젝트 기능에만 충실하게 수행하면서 빠르게 자주 테스트를 실행할 수 있도록 사용하는 이런 오브젝트를 통틀어서 테스트 대역이라고 부른다
  • 대표적인 테스트 대역은 테스트 스텁이다. 테스트 스텁은 테스트 대상 오브젝트의 의존객체로서 존재하면서 테스트 동안에 코드가 정상적으로 수행할 수 있도록 돕는 것을 말한다.
  • 일반적으로 테스트 스텁은 메소드를 통해 전달하는 파라미터와 달리, 테스트 코드 내부에서 간접적으로 사용된다. 따라서 DI 등을 통해 미리 의존 오브젝트를 테스트 스텁으로 변경해야 한다.

p.394)

  • 테스트는 보통 어떤 시스템에 입력을 주었을 때 기대하는 출력이 나오는지를 검증한다. 단위 테스트에서는 보통 입력 값을 테스트 대상 오브젝트의 메소드의 파라미터로 전달하고, 메소드의 리턴 값을 출력 값으로 보고 검증한다.
  • 이런 경우에는 테스트 대상의 간접적인 출력 결과를 검증하고, 테스트 대상 오브젝트와 의존 오브젝트 사이에서 일어나는 일을 검증할 수 있도록 특별히 설계된 목 오브젝트를 사용해야 한다.
  • 목 오브젝트는 스텁처럼 테스트 오브젝트가 정상적으로 실행되도록 도와주면서,테스트 오브젝트와 자신의 사이에서 일어나는 커뮤니케이션 내용을 저장해뒀다가 테스트 결과를 검증하는 데 활용할 수 있게 해준다.
728x90
반응형
Comments