일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- HttpServletResponse
- 테스트코드
- 싱글톤
- 코드트리조별과제
- fielderror
- JSON
- 오블완
- 의존관계
- DI
- objecterror
- java
- 다형성
- 김영한
- 티스토리챌린지
- 인터페이스
- html form
- @configuration
- 스프링
- 코드트리
- equals()
- 백준
- 오버라이딩
- 코딩테스트
- 프록시
- 추상클래스
- 서블릿
- ocp
- 참조변수
- http 메시지 컨버터
- 스프링컨테이너
- Today
- Total
minOS
스프링과 문제 해결 - 문제점들 본문
순수한 서비스 계층
애플리케이션 구조
- 프레젠테이션 계층 (Presentation Layer) - @Controller
- 사용자 인터페이스를 담당
- 웹 페이지, 모바일 앱, 데스크톱 애플리케이션 등이 포함
- 사용자 입력을 받고 결과를 표시
- 서비스 계층 (Service Layer) - @Service
- 비즈니스 로직을 처리
- 데이터 유효성 검사, 트랜잭션 관리 등을 수행
- 프레젠테이션 계층과 데이터 접근 계층 사이의 중간 매개체 역할
- 데이터 접근 계층 (Data Access Layer) - @Repository
- 데이터베이스와 직접 상호작용
- SQL 쿼리 실행, ORM(Object-Relational Mapping) 등을 담당
- 데이터베이스로부터 데이터를 가져오거나 저장
순수한 서비스 계층
- 여기서 가장 중요한 곳은 어디일까? 바로 핵심 비즈니스 로직이 들어있는 서비스 계층이다. 시간이 흘러서 UI(웹)와 관련된 부분이 변하고, 데이터 저장 기술을 다른 기술로 변경해도, 비즈니스 로직은 최대한 변경없이 유지되어
야 한다
- 이렇게 하려면 서비스 계층을 특정 기술에 종속적이지 않게 개발해야 한다 !
- 이렇게 계층을 나눈 이유도 서비스 계층을 최대한 순수하게 유지하기 위한 목적이 크다. 기술에 종속적인 부분은 프레젠 테이션 계층, 데이터 접근 계층에서 가지고 간다.
- 프레젠테이션 계층은 클라이언트가 접근하는 UI와 관련된 기술인 웹, 서블릿, HTTP와 관련된 부분을 담당해준다.
그래서 서비스 계층을 이런 UI와 관련된 기술로부터 보호해준다. 예를 들어서 HTTP API를 사용하다가 GRPC 같은 기 술로 변경해도 프레젠테이션 계층의 코드만 변경하고, 서비스 계층은 변경하지 않아도 된다.
- 데이터 접근 계층은 데이터를 저장하고 관리하는 기술을 담당해준다. 그래서 JDBC, JPA와 같은 구체적인데이터 접근 기술로부터 서비스 계층을 보호해준다. 예를 들어서 JDBC를 사용하다가 JPA로 변경해도 서비스 계층은 변경하지 않아 도 된다. 물론 서비스 계층에서 데이터 접근 계층을 직접 접근하는 것이 아니라,인터페이스를 제공하고 서비스 계층은 이 인터페이스에 의존하는 것이 좋다. 그래야 서비스 코드의 변경 없이 `JdbcRepository` 를 `JpaRepository`로 변경 할 수 있다.
- 서비스 계층이 특정 기술에 종속되지 않기 때문에 비즈니스 로직을 유지보수 하기도 쉽고, 테스트 하기도 쉽다.
- 정리하자면 서비스 계층은 가급적 비즈니스 로직만 구현하고 특정 구현 기술에 직접 의존해서는 안된다. 이렇게 하면 향후 구현 기술이 변경될 때 변경의 영향 범위를 최소화 할 수 있다.
문제점들
MemberServiceV1
@RequiredArgsConstructor public class MemberServiceV1 { private final MemberRepositoryV1 memberRepository; public void accountTransfer(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); } }
문제- `SQLException` 이라는 JDBC 기술에 의존한다는 점이다.
- 이 부분은 `memberRepository` 에서 올라오는 예외이기 때문에 `memberRepository` 에서 해결해야 한다.
이 부분은 뒤에서 예외를 다룰 때 알아보자.
- MemberRepositoryV1` 이라는 구체 클래스에 직접 의존하고 있다. `MemberRepository` 인터페이스를 도
입하면 향후 `MemberService` 의 코드의 변경 없이 다른 구현 기술로 손쉽게 변경할 수 있다.
트랜잭션을 적용한 `MemberServiceV2` 코드
@Slf4j @RequiredArgsConstructor public class MemberServiceV2 { private final DataSource dataSource; private final MemberRepositoryV2 memberRepository; public void accountTransfer(String fromId, String toId, int money) throws SQLException { Connection con = dataSource.getConnection(); try { con.setAutoCommit(false); bizLogic(con, fromId, toId, money); con.commit(); } catch (Exception e) { con.rollback(); throw new IllegalStateException(e); } finally { release(con); } } private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException { Member fromMember = memberRepository.findById(con, fromId); Member toMember = memberRepository.findById(con, toId); memberRepository.update(con, fromId, fromMember.getMoney() - money); memberRepository.update(con, toId, toMember.getMoney() + money); } private void release(Connection con) { if (con != null) { try { con.setAutoCommit(true); con.close(); } catch (SQLException e) { log.error("Error releasing connection", e); } } } }
- 트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작하는 것이 좋다.
- 그런데 문제는 트랜잭션을 사용하기 위해서 `javax.sql.DataSource` , `java.sql.Connection` ,`java.sql.SQLException` 같은 JDBC 기술에 의존해야 한다는 점이다.
- 트랜잭션을 사용하기 위해 JDBC 기술에 의존한다. 결과적으로 비즈니스 로직보다 JDBC를 사용해서 트랜잭션을 처리하는 코드 가 더 많다.
- 향후 JDBC에서 JPA 같은 다른 기술로 바꾸어 사용하게 되면 서비스 코드도 모두 함께 변경해야 한다. (JPA는 트랜잭션을 사용 하는 코드가 JDBC와 다르다.)
- 핵심 비즈니스 로직과 JDBC 기술이 섞여 있어서 유지보수 하기 어렵다.
문제 정리
지금까지 개발한 애플리케이션의 문제점은 크게 3가지이다.
- 트랜잭션 문제
- 예외 누수 문제
- JDBC 반복 문제
트랜잭션 문제
1) JDBC 기술 누수와 서비스 계층 순수성 문제
핵심 문제점:
1. 서비스 계층은 특정 기술에 종속되지 않아야 함
2. JDBC 구현 기술이 서비스 계층에 누수되면 안 됨
3. 데이터 접근 계층에 기술 특화 코드 집중
4. 변경에 유연하게 대응할 수 있는 구조 필요
해결 방향:
- 인터페이스 기반 설계
- 트랜잭션 처리 추상화
- 서비스 계층의 기술 독립성 확보
서비스 계층 (순수한 비즈니스 로직) ↓ 데이터 접근 추상화 인터페이스 ↓ 구체적인 데이터 접근 구현체 (JDBC, JPA 등)
2) 트랜잭션 동기화와 메서드 분리 문제
핵심 문제점:
1. 커넥션을 파라미터로 전달해야 하는 구조적 한계
2. 트랜잭션 유지/미유지에 따른 중복 메서드 발생
3. 관심사 분리의 어려움
해결 방향:
- 트랜잭션 전용 메서드와 일반 메서드 분리
- 커넥션 파라미터 전달 최소화
- 트랜잭션 관리의 추상화
3) 트랜잭션 적용 반복 문제
트랜잭션 적용 코드를 보면 반복이 많다. ex) `try` , `catch` , `finally` ...
예외 누수
- 데이터 접근 계층의 JDBC 구현 기술 예외가 서비스 계층으로 전파된다.
- `SQLException` 은 체크 예외이기 때문에 데이터 접근 계층을 호출한 서비스 계층에서 해당 예외를 잡아서 처리하거나 명시적 으로 `throws` 를 통해서 다시 밖으로 던져야한다.
- `SQLException` 은 JDBC 전용 기술이다. 향후 JPA나 다른 데이터 접근 기술을 사용하면, 그에 맞는 다른 예외로 변경해야 하 고, 결국 서비스 코드도 수정해야 한다.
JDBC 반복 문제
- 지금까지 작성한 `MemberRepository` 코드는 순수한 JDBC를 사용했다.
- 이 코드들은 유사한 코드의 반복이 너무 많다.
ㄴ`try` , `catch` , `finally` ...
ㄴ 커넥션을 열고, `PreparedStatement` 를 사용하고, 결과를 매핑하고... 실행하고, 커넥션과 리소스를 정리한다.
스프링과 문제 해결
스프링은 서비스 계층을 순수하게 유지하면서, 지금까지 이야기한 문제들을 해결할 수 있는 다양한 방법과 기술들을 제
공한다. 지금부터 스프링을 사용해서 우리 애플리케이션이 가진 문제들을 하나씩 해결하자.
'TIL > 김영한의 스프링 DB 1편' 카테고리의 다른 글
스프링과 문제 해결 - 트랜잭션 동기화 (0) | 2024.11.29 |
---|---|
스프링과 문제 해결 - 트랜잭션 추상화 (0) | 2024.11.28 |
트랜잭션 - 적용 (0) | 2024.11.23 |
트랜잭션 - 개념 이해 (0) | 2024.11.20 |
DataSource 이해 (1) | 2024.11.20 |