- 서비스 계층이 특정 기술에 종속되지 않기 때문에 비즈니스 로직을 유지보수 하기도 쉽고, 테스트 하기도 쉽다. - 정리하자면 서비스 계층은 가급적 비즈니스 로직만 구현하고 특정 구현 기술에 직접 의존해서는 안된다. 이렇게 하면 향후 구현 기술이 변경될 때 변경의 영향 범위를 최소화 할 수 있다.
문제점들
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);
}
}
@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` 를 사용하고, 결과를 매핑하고... 실행하고, 커넥션과 리소스를 정리한다.