공부기록/Spring

토비 스프링 5장 - 서비스 추상화

DGL 2021. 10. 27. 14:53

트랜잭션을 적용해보면서 스프링이 어떻게 성격이 비슷한여러 종류의 기술을 추상화하고 이를 일관된 방법으로 사용할 수 있도록 지원하는지를 살펴볼 것이다.

5.1 사용자 레벨 관리 기능 추가

의식의 흐름으로 사용자 레벨 관리기능을 추가함. 수정해야할 레거시 코드의 경우는 한번에 작성해도 되지않았을까 싶다. 한번에 도메인을 비교검사하는 checkSameUser같은 것을 활용. jdbc → rowMapper(이것도 jdbc를 사용하는한 자주사용하게 될듯하다.)

ResultSet의 rs.get~함수에서 parameter로 오타를 주게되면, 실패한다. 어디서 실패했는지 알기 어려울수 있다.

Level에 관한 설계 → 이게 중요한 것일까? 아직은 모르겠다. 다음 설명을 위한 빌드업일까? → 스프링의 기능을 적용하기 적절한 구조로 만들어둔 것?

 

트랜잭션 서비스 추상화

→ 묶어서 발생해야하는 일 같은 경우, 하나가 실패하면 같이 실패해야함 = 트랜잭션 롤백

→ 성공적으로 마무리 = 트랜잭션 커밋

 

시작되고 끝나는 위치를 트랜잭션 경계

→ 시작: setAutoCommit(false)

→끝: commit, 예외가 발생하면 rollback

 

하나의 DB 커넥션 안에서 만들어지는 트랜잭션을 로컬 트랜잭션이라고도 한다.

 

jdbcTempalte → 하나의 문장을(update) 실행할 때마다 트랜잭션을 생성함...

 

여러문장을 트랜잭션화 시키려면 어떻게 해야할까?

 

어떤함수() throws Exception{ 
(1) DB 커넥션 생성 
(2) 트랜잭션 시작 
try{ 
(3) DAO 메소드 호출 
(4) 트랜잭션 커밋 
}catch(Exception e){ 
(5) 트랜잭션 롤백 
throw e; 
} finally{ 
(6) DB 커넥션 종료 
} }

 

jdbcTemplate으로 Connection을 받아오게 하는 것이 맞을까?

→ 스프링에서는 트랜잭션 동기화 방식을 사용한다.

→ 옛날버전 스프링이어서 그런건지 요즘에는 Transactional 애노테이션으로 이 역할을 해주는 것 같은데..

→ Transactionl이 나오기 이전의 방식에 대해 말해주는 것 같다.

 

동기화 저장소 → 여기에다가 conenction을 저장하여, connection이 종료되기 전까지 여기서 connection을 받아와서, sql을 실행시킴

작업 스레드마다 독립적으로 Conenction 오브젝트를 저장하고 관리함

 

TransactionSynchronizationManager.initiSynchronization();

Connection c= DataSourceUtils.getConenction(dataSource);

c.setAutoCommit(false);

 

이렇게 초기화하고,

나중에 manger로 동기화를 종료한다.

 

5.2.4 트랜잭션 서비스 추상화

여러 종류의 DB에서도 트랜잭션이라는 기능을 사용하고 싶다?

→ 글로벌 트랜잭션 ( 트랜잭션 매니저를 위한 API인 JTA)

 

문제는 트랜잭션 API를 어떤것을 사용하느냐에 따라 기존의 코드를 수정하게 된다는 것

이런 문제를 어떻게 해결할 것인가?

→ 트랜잭션 서비스를 추상화하자

 

애플리케이션 계층 - 추상화 계층 - 트랜잭션 계층

PlatformTransactionManager

 

트랜잭션 기술 설정의 분리

JTATransactionManager → JTA정보를 JDNI를 통해 자동으로 인식하는 기능

 

UserService{ private PlatformTransactionManager transactionManager; public void setTransactionManager(PlatformTransactionManager transactionmanager){ this.transactionManager = transactionManager; } public void upgradeLevels(){ TransactionStatus status = this.transactionManager.getRansaction(new DefaultTransactionDefinition)); try{ 작업 this.transactionManager.commit(status); }catch(Exception e){ this.transactionManger.rollback(status); throw e; }} }

 

트랜잭션 작업을 추상화시켜서, DB의 종류가달라져도 트랜잭션 기능을 사용할 수 있게 되었음

 

설정에서 JPA를 쓸지, JDBC를 쓸지 등등만 바꿔주면 됨

 

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

설정을 고치는 것만으로도 DB연결 기술, 데이터 액세스 기술, 트랜잭션 기술을 자유롭게 바꿔서 사용할 수 있다.

수직, 수평 계층구조와 의존관계:

 

애플리케이션 계층 → 서비스 추상화 계층 → 기술 서비스 계층

(개발자가 개발하는 부분) → (사용하는 추상화 인터페이스 라이브러리) → (설정에서 선택한 자세한 라이브러리 종류)

 

구체적인 트랜잭션 기술에 독립적인 코드가 되었음. 서버가 바뀌고 로우레벨의 트랜잭션 기술이 변경된다고 할지라도 Userservice는 영향을 받지 않는다.

 

DI가 중요한 역할을 하고 있다. 관심, 책임, 성격이 다른 코드를 깔끔하게 분리하는 데 있다.

단일 책임 원칙

하나의 모듈이 바뀌는 이유는 한 가지여야 한다

UserService → 어떻게 사용자 레벨을 관리할 것인가, 어떻게 트랜잭션을 관리할 것인가라는 두 가지 책임

 

→ 하지만 트랜잭션 추상화를 도입한다면? →UserService가 바뀔 이유는 한 가지뿐이다.

장점 →어떤 변경이 필요할 때 수정 대상이 명확해진다. User하나만 있어서 큰 장점이 아닌 것처럼 보일 수 있지만, 서비스가 커쳐서 DAO가 각각 수백 개가 되고, 서비스 클래스도 그만큼 많다고 생각해보면, 서비스 하나가 여러개의 DAO를 사용하는 경우도 많아질테고, DAO하나를 수정할 경우 그에 의존하고 있는 서비스 클래스도 같이 수정해야한다?

 

애플리케이션 계층의 코드가 특정 기술에 종속돼서 기술이 바뀔때마다 코드의 수정이 필요하다면? 그나마 트랜잭션 동기화 기법을 사용하여 간략화 시켰다고 하더라도,

기술이 바뀌게 되면 (서비스 클래수) (트랜잭션을 사용하는 메소드 수) 만큼의 엄청난 양의 코드를 수정해야 할 것이다.

작업량의 문제가 아니라, 많은 코드를 수정하는 작업에선 그만큼 실수가 일어날 확률이 높다.

 

적절하게 책임과 관심이 다른 코드를 분리하고, 서로 영향을 주지 않도록 다양한 추상화 기법을 도입하고, 애플리케이션 로직과 기술/환경을 분리하는 등의 작업은 갈수록 복잡해지는 엔터프라이즈 애플리케이션에는 반드시 필요하다.

 

단일 책임 원칙을 잘 지키는 코드를 만들려면 인터페이스를 도입하고 이를 DI로 연결해야 하며, 그 결과로 단일 책임 원칙뿐 아니라 개방 폐쇄 원칙도 잘 지키고, 모듈 간에 결합도가 낮아서 서로의 변경이 영향을 주지 않고, 같은 이유로 변경이 단일 책임에 집중되는 응집도 높은 코드가 나온다.

 

이런 과정에서 전략 패턴, 어댑터 패턴, 브리지 패턴, 미디에이터 패턴 등 많은 디자인 패턴이 자연스럽게 적용되기도 한다.

 

그저 기능이 동작한다고 해서 코드에 쉽게 만족하지 말고 계속 다듬고 개선하려는 자세도 필요하다.

스프링의 의존관계 주입 기술인 DI는 모든 스프링 기술의 기반이 되는 핵심엔진이자 원리. 스프링의 DI를 잘 활용해서 열시히 사용하다보면, 어느 날 자신이 만든 코드에 객체지향 원칙과 디자인 패턴의 장점이 잘 녹아 있다는 사실을 발견하게 될 것이다. 그것이 스프링을 사용함으로써 얻을 수 있는 가장 큰 장점이다.

5.4 메일 서비스 추상화

레벨이 업그레이드 되는 사용자에게는 안내 메이을 발송해달라.

  1. 사용자의 이메일 정보를 관리해야 한다.
  2. 업그레이드 작업을 담은 UserService의 upgradeLevel() 메소드에 메일 발송 기능을 추가하는 것

JavaMail을 사용하여 메일기능을 추가했음

그런데 테스트코드에서도 매번 실행마다 메일을 보내는 것이 올바를까? ( 올바르지 못하다)

 

테스트용으로 준비된 메일 서버를 이용하는 방법은 어떨까?

테스트용 메일 서버란?→ 테스트에서 메일을 보내지않고, 메일서버를 마치게 해주는 것

JavaMail은 자바 표준기술이고, 이미 수많은 시스템에 사용돼서 검증된 안정적인 모듈로, JavaMail APi를 통해 요청이 들어간다는 보장만 있ㅇ다면 굳이 테스트할 때마다 JavaMail을 직접 구동시킬 필요가 없다.

 

테스트를 위한 추상화

 

<mail 서비스를 들고 있는 추상적인 인터페이스를 만들어서

실제서비스에서는 메일을 보내라고 하면 Javamail을 사용하여 보내고

테스트에서는 메일을 보내라고 하면 다른 것을 사용하여 메일을 보낸다.> 내생각

메일서비스 추상화의 장점

  1. 테스트에 자원을 절약
  2. JavaMail말고 다른 메일서비스를 사용하려고할때, 쉽게 바꿀수 있음

실전이었다면?

  1. 메일 서비스에 트랜잭션을 적용해야함

(방법

→대상자목록을 만들어 놓고, 트랜잭션 작업이 끝나고 한번에 전송

→ 또는 메일전송에 트랜잭션 개념을 적용하는 것, 실제로 전송하고 있지 않다가, send가 완료되면 트랜잭션 기능을 가진 send가 실제로 보내게 된다.

전자 → 비즈니스 로직과 메일 발솔에 트랜잭션 개념을 적용하는 기술적인 부분이 한데 섞이게 한다면,

메일 서버의 구현 클래스를 이용하는 방법은 서로 다른 종류의 작업을 분리해 처리한다는 면에서 장점이 있다.

 

5.4.4 테스트 대역

테스트용 xml설정파일 → 테스트용 db를 위해서, 테스트할 대상이 의존하고 있는 오브젝트를 DI를 통해 바꿔치기

 

UserServiceTest의 관심사는 구현해놓은 사용자 정보를 가공하는 비즈니스 로직이지, 메일이 어떻게 전송이 될 것인가가 아니다.

 

테스트 대역의 종류와 특징

테스트 스텁, 목 오브젝트(테스트 오브젝트가 정상적으로 실행되도록 도와주면서, 테스트 오브젝트와 자신의 사시에서 일어나는 커뮤니케이션 내용을 저장해뒀다가 테스트 결과를 검증하는 데 활용할 수 있게 해준다.)

 

목 오브젝트를 이용한 테스트

MockMailSender ( 메일을 전송받게될 주소들을 저장해둬서, 나중에 확인할 수 있게함)

작성하기는 간단하면서도 기능은 상당히 막강하다