서비스 계층
- 애플리케이션 구조
- 여러가지 애플리케이션 구조가 있지만, 가장 단순하면서 많이 사용하는 방법은 역할에 따라 3가지 계층으로 나누는 것이다.
- 프레젠테이션 계층
- UI와 관련된 처리, 웹 요청과 응답, 사용자 요청 검증을 담당
- 주 사용 기술: 서블릿과 HTTP 같은 웹 기술, 스프링 MVC
- 서비스 계층
- 비즈니스 로직을 담당
- 주 사용 기술: 가급적 특정 기술에 의존하지 않고, 순수 자바 코드로 작성
- 데이터 접근 계층
- 실제 데이터베이스에 접근을 담당
- 주 사용 기술: JDBC, JPA, File, Redis, Mongo
- 프레젠테이션 계층
- 여러가지 애플리케이션 구조가 있지만, 가장 단순하면서 많이 사용하는 방법은 역할에 따라 3가지 계층으로 나누는 것이다.
- 서비스 계층 (이전 글 참고)
- 서비스 계층은 가급적 특정 구현 기술에 직접 의존해서는 안된다.
- 서비스 계층에서 트랜잭션을 사용하기 위해 JDBC 기술에 의존하던 문제
-> 트랜잭션 매니저를 통해서 해결했다.
- 서비스 계층에서 트랜잭션을 사용하기 위해 JDBC 기술에 의존하던 문제
- 서비스 계층은 가급적 핵심 비즈니스 로직만 구현해야 한다.
- 아직 서비스 계층에서 비즈니스 로직 뿐만 아니라 트랜잭션을 처리하는 기술 로직이 함께 포함되어 있는 문제가 남아있다.
-> 스프링 AOP를 통해 프록시를 도입하면 해결할 수 있다.
- 아직 서비스 계층에서 비즈니스 로직 뿐만 아니라 트랜잭션을 처리하는 기술 로직이 함께 포함되어 있는 문제가 남아있다.
- 서비스 계층은 가급적 특정 구현 기술에 직접 의존해서는 안된다.
트랜잭션 AOP
- 트랜잭션 프록시
- 프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.
- 트랜잭션 프록시 코드 예시
public class TransactionProxy { private MemberService target; public void logic() { TransactionStatus status = transactionManager.getTransaction(..); try { target.logic(); //실제 대상 호출 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw new IllegalStateException(e); } } }
- 트랜잭션 프록시 적용 후 서비스 코드 예시
public class Service { public void logic() { bizLogic(fromId, toId, money); // 비즈니스 로직 } }
- 스프링이 제공하는 AOP 기능을 사용하면 프록시를 편리하게 적용할 수 있다.
- 스프링 AOP를 사용하려면 어드바이저, 포인트컷, 어드바이스가 필요하다.
- 스프링 AOP를 사용하려면 어드바이저, 포인트컷, 어드바이스가 필요하다.
- 트랜잭션 AOP
- 스프링 AOP를 직접 사용해서 트랜잭션을 처리해도 되지만, 스프링은 트랜잭션 AOP를 처리하기 위한 모든 기능을 제공한다.
- 스프링은 트랜잭션 AOP 처리를 위해 다음 클래스를 제공한다.
- 어드바이저: BeanFactoryTransactionAttributeSourceAdvisor
- 포인트컷: TransactionAttributeSourcePointcut
- 어드바이스: TransactionInterceptor
- 트랜잭션 처리가 필요한 곳에 @Transactional 어노테이션만 붙여주면, 스프링의 트랜잭션 AOP는 이 애노테이션을 인식해서 트랜잭션 프록시를 적용해준다.
- 스프링은 트랜잭션 AOP 처리를 위해 다음 클래스를 제공한다.
- 스프링 AOP를 직접 사용해서 트랜잭션을 처리해도 되지만, 스프링은 트랜잭션 AOP를 처리하기 위한 모든 기능을 제공한다.
트랜잭션 AOP 예시
이전 글 참고
- MemberRepositoryV4 클래스
@Repository public class MemberRepositoryV4 implements MemberRepository { // MemberRepositoryV2와 동일 }
- MemberServiceV4 클래스
@Service public class MemberServiceV4 { private final MemberRepository memberRepository; public MemberServiceV4(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Transactional public void accountTransfer(String fromId, String toId, int money) throws SQLException { bizLogic(fromId, toId, money); // 비즈니스 로직 } private void bizLogic(String fromId, String toId, int money) throws SQLException { Member fromMember = memberRepository.findById(fromId); Member toMember = memberRepository.findById(toId); memberRepository.update(fromId, fromMember.getMoney() - money); memberRepository.update(toId, toMember.getMoney() + money); } }
- 서비스 계층에서 아직 JDBC 기술인 SQLException에 의존하지만, 이 부분은 일단 넘어가자.
- 그래도 간단하게 말하자면, SQLException은 체크 예외인데, 리포지토리 계층에서 체크 예외인 SQLException을 언체크(런타임) 예외로 바꿔서 던지면 된다.
참고 자료
“김영한 스프링 DB 1편” https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html