이야기박스
Chapter 5. 서비스 추상화 본문
비지니스 로직
단순 CRUD 말고 관리자 도구 만들어 봅시다.
==> 관리용 필드 추가 필요 (아래 예시에서의 Level)
* 사용자 레벨 추가하는 경우
int 필드 사용 ===> enum 필드 사용
// int 사용시, 범위가 넘어서는 에러가 발생할 수 있음
setLevel(1000)
public enum Level {
BASIC(1),
SILVER(2),
GOLD(3);
@Getter
private final int value;
Level(int value) {
this.value = value;
}
public static Level valueOf(int value) {
switch (value) {
case 1:
return BASIC;
case 2:
return SILVER;
case 3:
return GOLD;
default:
throw new AssertionError("Unknown value : " + value);
}
}
}
public class User {
Level level;
int login;
int recommend;
public Level getLevel() {
return level;
}
public void setLevel(Level level) {
this.level = level;
}
}
enum은 DB와 직접적으로 연결된 객체가 아님
- getter/setter를 통한 int value 통하여 DB와 상호작용 할 것
* sql은 컴파일 과정에서 검사가 이루어지지 않음, 런타임에서야 에러를 뱉어낸다.
만약 DB 추가/삭제가 빈번하게 이루어진다면??
수많은 에러 상황이 꽃 피울 것이다.
===> 이러한 경우, 앞 챕터에서 공부했던 테스트의 중요성이 다시 한 번 부각된다.* update() 예시
정말 필요한 row만 업데이트 된건지 확인이 필요
1. jdbc에서 테이블에 영향을 주는 sql (delete, update 등) 같은 경우, 영향받은 row 개수를 반환해 줌
2. 테스트 보강
* upgrade level, add (set basic level)
aop 관점으로 어디에 로직을 넣을지 고민할 것
이번 예제 케이스 같은 경우는 별도의 UserService에 넣자
[ 간단 구조 ]
test/client --> service --> dao
[ 코드 개선 ]
* if / for 문은 너무 많으면 가독성 떨어진다
public void upgradeLevels() {
// dao로 대체할 것
List<User> users = new ArrayList<>();
for (User user : users) {
if(canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
private boolean canUpgradeLevel(User user) {
// check logic
return true;
}
private void upgradeLevel(User user) {
// upgrade level logic
}
* 다음 단계에 대한 정보를 enum에 맡기기
위 코드는 예외상황에 취약함 --> 이런 케이스의 예외는 enum 레벨에서 컨트롤 하기 쉬움
public enum Level {
BASIC(1, Level.SILVER),
SILVER(2, Level.GOLD),
GOLD(3, null);
@Getter
private final int value;
@Getter
private final Level next;
Level(int value, Level next) {
this.value = value;
this.next = next;
}
public static Level valueOf(int value) {
switch (value) {
case 1:
return BASIC;
case 2:
return SILVER;
case 3:
return GOLD;
default:
throw new AssertionError("Unknown value : " + value);
}
}
}
next의 getter만 이용하면 위의 로직이 한번에 정리됨.
(
책에서는 User에서 내부 상태를 다루는 내용이 있어도 된다고 하는데, 난 pojo는 pojo로 남으면 좋겠음,,,
물론 케바케, 책 상황만 보면 굉장히 이상적이긴 함
)
* 객체 지향
다른 오브젝트 데이터를 가져와서 직접 작업하는 대신 '이렇게 저렇게' 해달라고 요청하는 것
( 외주 맡기는 거지 뭐.. )
* 책 내용이 정답은 아니고 항상 케바케를 명심하자
* 테스트도 테스트 로직을 이용합시다. 매번 구현하지 말고..
트랜잭션 서비스 추상화
* TestUserService는 UserService를 상속받아서 구현합시다.
필요한 메서드만 오버라이딩 하면 된다. (원본 클래스의 private --> protected로 변경 필요)
테스트에만 사용될 클래스를 구현하는 것이라면 별도의 파일보다는 스태틱으로 구성하자
* 테스트 코드 작성
* DB는 그 자체로 완벽한 트랜잭션 지원해주고 있음
[ 두 개의 트랜잭션이 하나의 트랜잭션 처럼 움직여야 하는 경우 ]
- A 성공 B 실패시, 트랜잭션 롤백 진행
- A 성공 B 성공시, 트랜잭션 커밋 진행
* 트랜잭션 경계 설정
public void transcationDemarcation() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
connection.setAutoCommit(false);
try {
PreparedStatement preparedStatement = connection.prepareStatement("sql1");
preparedStatement.executeUpdate();
PreparedStatement preparedStatement2 = connection.prepareStatement("sql1");
preparedStatement2.executeUpdate();
connection.commit();
} catch (SQLException e) {
connection.rollback();
}
connection.close();
}
jdbc의 경우 자동 커밋이 기본값으로 설정되어 있는데, 이것을 false로 바꿔주고 컨트롤하면 됨
===> 트랜잭션의 경계설정(Transaction demarcation)
===> 이렇게 구성된 트랜잭션 - "Local Transaction"
* 여러번의 DB 업데이트를 하나의 Local Transaction 으로 묶는 법
public void add(Connection connection, User user);
이런 식으로 비지니스 로직에서 생성한 커넥션을 넘겨주어야 함
* 트랜잭션 경계설정 문제점
* 스프링 트랜잭션 경계설정 문제 해결1 - Connection 파라미터 제거
* 동기화 적용
package org.springframework.transaction.support;
public abstract class TransactionSynchronizationManager
유틸리티를 이용하여 구현
public void transcationComminAndRollbackTest() throws SQLException {
TransactionSynchronizationManager.initSynchronization();
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
try {
PreparedStatement preparedStatement = connection.prepareStatement("sql1");
preparedStatement.executeUpdate();
PreparedStatement preparedStatement2 = connection.prepareStatement("sql1");
preparedStatement2.executeUpdate();
connection.commit();
} catch (SQLException e) {
connection.rollback();
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
TransactionSynchronizationManager.clearSynchronization();
}
}
* 글로벌 트랜잭션이 필요한 경우
* 트랜잭션 API 의존관계 문제 및 해결
서비스 추상화 단일 책임 원칙
* 단일 책임의 원칙
JavaMail 추상화
* 테스트 대역 (Test Double)
* 목 오브젝트를 통한 테스트
목 오브젝트를 DI하고 호출 여부를 저장할 것
'Programming Language > Spring' 카테고리의 다른 글
Chapter 6. AOP(2) (0) | 2019.02.08 |
---|---|
Chapter 6. AOP(1) (0) | 2019.01.23 |
Chapter4 (0) | 2019.01.15 |
Spring Template (Chapter 3) (0) | 2018.12.11 |
Spring IoC & 오브젝트 & DI (1.4~1.8) (0) | 2018.12.04 |