@Transactional 구현 - 목차
2021.12.02 - [Java/@Transaction 구현] - 1. 배경설명, 구현 동기 및 목표 (현재 글)
2021.12.02 - [Java/@Transaction 구현] - 2. Aspect Oriented Programming
2021.12.02 - [Java/@Transaction 구현] - 3. Proxy 패턴
2021.12.02 - [Java/@Transaction 구현] - 4. JDK Dynamic Proxy
2021.12.02 - [Java/@Transaction 구현] - 5. 애너테이션 적용
배경설명
Eclipse UI 에 등록된 값을 입력/수정할 때는 두가지 과정을 거친다. 첫 번째로 인가(Authorization)이고, 두 번째는 쓰기 트랜잭션에 진입해야 한다.
Authorization
인가 과정은 외부 라이브러리를 사용한다. 값을 입력하겠다는 메소드가 실행되면
외부 라이브러리를 호출해 이 동작을 수행해도 되는지 확인한다. 동작을 수행할 수 없으면 적절한 Error 메세지를 띄우고 툴을 종료한다.
WorkspaceTransactionUtil.executeInWriteTransaction
Eclpise UI 에 등록된 객체의 값을 입력/수정할 때는 쓰기 트랜잭션 안에서 수행해야한다.
public static void executeInWriteTransaction(
TransactionalEditingDomain editingDomain,
final Runnable runnable,
final String operationLabel,
IOperationHistory operationHistory,
final Map<String, Object> transactionOptions)
throws OperationCanceledException, ExecutionException {}
지금까지 트랜잭션 안에서 수행해야하는 코드를 함수형 인터페이스인 Runnable 을 구현해서 사용했다. 위 코드를 개발자가 사용하기 편하도록 축약할 수 있다.
public class Transaction {
public static void executeInWriteTransaction(EObject target, Runnable runnable){
//Transaction 안에서 runnable 을 수행한다.
}
}
실제로 setValue() 에 설정할 땐, 아래와 같이 코드를 작성한다.
class Cell implements ICell {
private final EObject target;
@Override
void setValue(Object value){
Transaction.executeInWriteTransaction(target, new Runnable() {
@Override
public void run() {
// Transaction 안에서 수행할 코드를 작성한다.
}
});
}
}
함수형 인터페이스인 Runnable 인터페이스를 람다식으로 작성하면 더 간단히 사용할 수 있다.
class Cell implements ICell {
private final EObject target;
@Override
public void setValue(Object value){
Transaction.executeInWriteTransaction(target, () -> {
// Transaction 안에서 수행할 코드를 작성한다.
}
}
}
지금까지 이렇게 사용했었다. Transaction.executeInWriteTransaction() 안에 인가 과정을 넣어 인가와 트랜잭션 진입을 동시에 하도록 작성하도록 한 것 외에는 보일러 플레이크 코드를 추가했다.
구현 동기
현재 방법으로는 테스트 코드를 작성할 때 문제가 발생한다. 테스트 코드는 Eclipse UI 와 관계없이 비즈니스 로직만 수행한다. 그러나 setValue() 코드 내부에는 UI 관련된 코드가 들어있기 때문에, setValue() 메소드를 직접 사용하는 테스트를 작성할 수 없었다
class Cell implements ICell {
private final EObject target;
@Override
public void setValue(Object value){
Transaction.executeInWriteTransaction(target, () -> {
setCellValue(value);
})
}
protected void setCellValue(Object value){
// 값을 변경하는 로직을 작성
}
}
그래서 Transaction 안에서 동작하는 메소드를 따로 분리하고, 테스트 코드 안에서는 setCellValue() 를 호출해서 비즈니스 로직을 테스트 한다.
여기서 다른 문제가 발생한다. 다른 클래스에 불필요하게 많은 정보를 제공하게 된다. setCellValue() 메소드의 공개범위는 protected 이다. protected로 설정하면, 같은 패키지 내에 존재하는 다른 클래스에서 setCellValue() 메소드에 접근할 수 있게 된다. 본래 목적인 "테스트 코드에 작성하기위해 만든 메소드" 를 넘어서게 된다.
목표
다음과 같은 목표를 달성하기위해 리팩토링을 진행한다.
- Proxy 패턴을 이용해 핵심기능과 부가기능을 분리한다.
- 애너테이션을 활용해 부가기능을 끼워넣는다.

구현 후 setValue() 사용 방법
프록시 패턴과 애너테이션을 이용해서 관점을 분리했을때 setValue() 메소드는 아래와 같다.
public RealCell implements Cell {
private final Object target;
public RealCell (Object target){
this.target = target;
}
@Transactional
@Override
public void setValue(Object value){
//핵심 로직 구현
}
}
'Java > @Transaction 구현' 카테고리의 다른 글
5. 애너테이션 적용 (0) | 2021.12.02 |
---|---|
4. JDK Dynamic Proxy (0) | 2021.12.02 |
3. Proxy 패턴 (0) | 2021.12.02 |
2. Aspect Oriented Programming (0) | 2021.12.02 |