Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions 오수진/docs/토비/1장.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
1장 오브젝트와 의존 관계
=

## 전략 패턴

- 자신의 기능 맥락(Context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴
- 컨텍스트 : 자신의 기능을 수행하는데 필요한 기능 중에서 변경 가능한 알고리즘을 인터페이스로 정의하고, 이를 구현한 클래스(전략)을 바꿔가면서 사용한다.
- 이때, 컨텍스트를 사용하는 클라이언트가 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해서 제공해주면서 DI 구현

191 changes: 191 additions & 0 deletions 오수진/docs/토비/6장_AOP/6.1 트랜잭션 코드의 분리.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
6.1 트랜잭션 코드의 분리
=
```java
public void upgradeLevels() throws Exception {
TransactionStatus status = this.transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e;
}
}
```

- 비즈니스 로직 코드와 트랜잭션 경계설정 코드는 완벽하게 독립적인 코드
- 비즈니스 로직 코드를 사이에 두고 트랜잭션 경계설정 코드가 앞뒤에 위치(분리되어 있음)
- 트랜잭션 경계설정 코드와 비즈니스 로직 코드 사이에 주고 받는 정보가 없음.
<br>&rarr; 두 코드를 분리할 수 있을 것

---

### 1. **비즈니스 로직 담당 코드를 메소드로 추출**
```java
public void upgradeLevels() throws Exception {
TransactionStatus status = this.transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
upgradeLevelsInternal();
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e;
}
}

private void upgradeLevelsInternal() {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
```
- 가독성 향상, 수정 용이

---

## 2. 트랜잭션 코드를 클래스 밖으로 뽑아내기

### 인터페이스를 통해 간접으로 접근
- Client &rarr; UserService
- Client &rarr; UserService(Interface) &larr; UserServiceImpl

### 인터페이스 사용 이유
- 일반적으로 구현 클래스를 바꿔가면서 사용하기 위함
- 이번 예제에서는 한 번에 두 개의 UserService 인터페이스 구현 클래스를 동시에 이용하기 위함

### 코드
#### UserService 인터페이스
```java
public interface UserService {
void add(User user);
void upgradeLevels();
}
```

#### 트랜잭션 코드를 제거한 UserServiceImpl
```java
public class UserServiceImpl implements UserService {
UserDao userDao;
MailSender mailSender;

public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
...
}
```
- 기존의 UserService 코드에서 트랜잭션 경계설정 코드 제거
- 비즈니스 로직에 충실한 코드

#### 분리된 트랜잭션 코드
```java
public class UserServiceTx implements UserService {
UserService userService;
PlatformTransactionManager transactionManager;

public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}

// UserService 타입의 오브젝트를 DI
public void setUserService(UserService userService) {
this.userService = userService;
}

// DI 받은 오브젝트에 비즈니스 로직을 위임
public void add(User user) {
userService.add(user);
}

public void upgradeLevels() {
TransactionStatus status = this.transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
userService.upgradeLevels();
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e;
}

}
}
```
- 비즈니스 로직을 구현한 다른 구현 객체를 DI 받는다.
- 비즈니스 로직은 해당 구현 객체에 모두 위임한다.

### 트랜잭션 적용을 위한 DI 설정
#### 스프링 설정 파일
```java
@Configuration
public class UserServiceConfig {
@Bean
public UserService userServiceImpl(UserDao userDao, MailSender mailSender) {
return new UserServiceImpl(userDao, mailSender);
}

@Bean
public UserService userServiceTx(UserService userServiceImpl, PlatformTransactionManager transactionManager) {
return new UserServiceTx(userServiceImpl, transactionManager);
}
}
```
- 기존 테스트에서 @Autowired 를 사용해 DI를 구현했는데, UserService가 인터페이스로 바뀌었다.
- @Autowired는 타입이 일치하는 빈을 찾아서 주입해준다.
- 만약 같은 타입의 빈이 여러 개 있으면, 필드 이름을 사용해 빈을 찾는다.
- 단, 위의 Configuration의 경우 UserService 이름을 직접 사용하는 빈이 없기 때문에 의존성 받으려 하면 오류 남

#### 해결1 @Primary

```java
@Configuration
public class UserServiceConfig {

@Bean
public UserService userServiceImpl(UserDao userDao, MailSender mailSender) {
return new UserServiceImpl(userDao, mailSender);
}

@Bean
@Primary
public UserService userServiceTx(UserService userServiceImpl, PlatformTransactionManager transactionManager) {
return new UserServiceTx(userServiceImpl, transactionManager);
}
}
```
#### 해결 2 @Qualifier
```java
@Autowired
@Qualifier("userServiceTx")
private UserService userService;
```

```java
private UserService userService;

public UserServiceTest(@Qualifier("userServiceTx") UserService userService) {
this.userService = userService;
}
```

### 트랜잭션 경계설정 코드 분리의 장점
- 관심사의 분리
- 비즈니스 로직을 담당하는 구현 객체의 코드를 작성할 때에는 트랜잭션과 같은 기술적인 내용에는 신경 쓰지 않아도 된다.
- DI를 통해 트랜잭션 기능을 가진 객체가 먼저 실행되도록 만들면 된다.
- 테스트 용이성
- 비즈니스 로직에 대한 테스트를 손쉽게 만들 수 있다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
6.2 고립된 단위 테스트
=
### 작은 단위의 테스트가 좋은 이유
- 테스트가 실패했을 때, 원인을 찾기 쉬움
- 테스트의 의도나 내용이 분명해짐
- 만들기 쉬워짐
Loading