이야기박스

Chapter 5. 서비스 추상화 본문

Programming Language/Spring

Chapter 5. 서비스 추상화

박스님 2019. 1. 15. 15:35
반응형

비지니스 로직

단순 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)

* TestUserService는 UserService를 상속받아서 구현합시다.

필요한 메서드만 오버라이딩 하면 된다. (원본 클래스의 private --> protected로 변경 필요)

테스트에만 사용될 클래스를 구현하는 것이라면 별도의 파일보다는 스태틱으로 구성하자

* 테스트 코드 작성

DI는 번거롭게 빈으로 하지말고 수동 DI 할 것
트랜잭션[각주:1]으로 동작하게 구현하여, 테스트가 실패하면 원래의 상태로 돌아가는지 확인할 것

* 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 으로 묶는 법

기존의 AOP 분리를 망칠수는 없는 노릇.. DAO 메소드 안으로 로직을 넣는다는 한심한 생각은 하지도 말라
Business Logic 안으로 트랜잭션 경계설정 작업을 가져와야 함

1. 커넥션 생성
2. 트랜잭션 시작
[ try ]
3. dao 메소드 호출
4. 트랜잭션 커밋
[ catch ]
5. 트랜잭션 롤백
[ finally ]
6. 커넥션 클로즈

주의할 점 !!!
dao에서 별개의 Connection 오브젝트를 만들면 별도의 트랜잭션이 동작해버림
public void add(Connection connection, User user);

이런 식으로 비지니스 로직에서 생성한 커넥션을 넘겨주어야 함

* 트랜잭션 경계설정 문제점

1. JdbcTemplate을 더 이상 활요하지 못하게 됨
2. Connection 파라미터를 추가해야 하는 번거로움
3. Dao 인터페이스가 비독립적으로 변환됨
4. 테스트 코드에서도 Connection 만들어서 해줘야 함

* 스프링 트랜잭션 경계설정 문제 해결1 - Connection 파라미터 제거

트랜잭션 동기화(Transaction Synchronization) : 트랜잭션을 시작하기 위해 Connection을 만들어 별도의 공간에 보관해두고 이후 호출되는 dao의 메소드에서 저장된 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();
}
}

* 글로벌 트랜잭션이 필요한 경우

별도의 트랜잭션 매니저를 통하여 여러 DB가 참여하는하나의 트랜잭션으로 구성
JTA(Java Transaction API) 제공

글로벌/분산 트랜잭션 관리에 대한 이미지 검색결과


* 트랜잭션 API 의존관계 문제 및 해결

비지니스 로직이 JDBC/Session/Transaction API 등에 의존적이게 됨
---> 유사한 구조를 찾고 공통점을 추린 후, 추상화를 하자
===> 위의 트랜잭션 매니저를 추상화
트랜잭션 추상화 계층에 대한 이미지 검색결과
이후는 Chapter1과 유사하게 진행
DI는 알아서 진행하자

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

트랜잭션 추상화 계층에 대한 이미지 검색결과
비즈니스 로직 & 하위 동작하는 트랜잭션 기술을 별도의 계층으로 분리
수평/수직 구분은 모두 결합도가 낮으며, 서로 영향주지 않고 자유 확장이 가능한 구조 
--> 스프링 DI의 공헌
==> 관심, 책임, 성격이 다른 코드를 깔끔하게 분리해줌

* 단일 책임의 원칙

하나의 모듈은 한 가지 책임을 가져야 한다

[장점]
- 변경이 필요할 때 수정이 쉬움
- 기술 바뀌면 연동하는 추상화 계층의 설정만 바꿔주면 됨
- 등등...

단일 책임 원칙 & 개방 폐쇄 원칙 잘 지키면 객체 지향 설계/프로그래밍이 되고 수많은 장점이 따라온다. (디자인 패턴, 테스트 등등)

스프링 - DI 프레임워크라고도 불림


JavaMail 추상화

DataSource처럼 인터페이스로 구성된 것이 아님.
확장이나 지원이 아주 불편한 API임

MailSender Intreface를 이용. (스프링 라이브러리임)
DI ...


마찬가지로 계층을 나누어 준다.
- 애플리케이션 계층
- 추상화 계층
- 메일 서비스 계층

트랜잭션 개념 적용하기
- 별도의 큐에 전송대기 목록을 보관한다
- MailSender를 확장하여 전송에 트랜잭션 개념을 추가한다

두 방법이 유사하지만, 후자가 좀 더 AOP 관점으로 좋다.

* 테스트 대역 (Test Double)

빠르고 자주 테스트를 실행할 수 있도록 사용하는 오브젝트
DataSource... MailSender...

테스트 오브젝트가 간접적으로 의존 오브젝트에 넘기는 값, 행위 자체에 대해서도 검증하고 싶다면?
--> 목 오브젝트[각주:2] 사용할 것
목 오브젝트를 이용한 테스트 동작방식에 대한 이미지 검색결과

* 목 오브젝트를 통한 테스트

목 오브젝트를 DI하고 호출 여부를 저장할 것


  1. 더 이상 나눌 수 없는 단위 작업 [본문으로]
  2. 실제와 비슷하게 보이도록 만든 가짜 객체 (비슷한 행동 + 상태 저장 요소가 필요) [본문으로]
반응형

'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