@Controller
→ Presentation Layer@Service
→ Business Layer@Repository
→ Persistence LayerOrder
엔티티를 저장하는 애플리케이션 서비스를 작성한다면 아래와 같을 것이다.@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public Order createOrder(String no, BigDecimal total) {
Order order = new Order(no, total);
orderRepository.save(order);
return order;
}
}
TransactionTemplate
을 이용하는 방법이 있다.TransactionTemplate
을 사용하려면 TransactionManager
의존성이 필요JpaTransactionManager
를 사용@Service
public class OrderService {
private final OrderRepository orderRepository;
private final JpaTransactionManager transactionManager;
public OrderService(OrderRepository orderRepository, JpaTransactionManager transactionManager) {
this.orderRepository = orderRepository;
this.transactionManager = transactionManager;
}
public Order createOrder(String no, BigDecimal total) {
Order order = new Order(no, total);
return new TransactoinTemplate(transactionManager)
.execute(status -> {
orderRepository.save(order);
return order;
});
}
}
OrderService
는 지금 JPA에 굉장히 의존적이다.
Repository
클래스에 의존JpaTransactionManager
에 의존OrderService
에게 있어 JPA 기술을 사용한다는 것은 관심사에서 제외되기에 관심사를 분리할 필요가 있다.Order
는 어떤가?
@Entity
로 정의관점에 따라 다르지만,
@Entity
와 같은 어노테이션이 있다고 해서 JPA에 의존하고 강결합이 있다고 보지 않기도 한다. JPA를 사용하지 않는다면 런타임에 JPA 라이브러리를 사용하지 않는다. JPA 말고 다른 기술을 사용한다고 해도 어노테이션을 제거하지 않아도 되고 다른 데이터 기술에 엔티티 클래스를 사용해도 문제 없다.
OrderRepository
에서 JPA 의존성을 분리할 필요가 있다.
OrderService
→ OrderRepository
] ← Infra Layer[JpaOrderRepository
]
OrderRepository
는 순수 인터페이스로 만들고 Infra Layer에서 JPA를 사용하는 구현체를 DI시키면 된다.추상화란 구현의 복잡함과 디테일을 감추고 중요한 것만 남기는 기법이다. 그렇다면 서비스 추상화란? 여러 인프라 서비스 기술의 공통적이고 핵심적인 기능을 인터페이스로 정의하고 이를 구현하는 어댑터를 만들어 일관된 사용이 가능하게 만드는 것이다.
OrderService
는 JpaTransactionManager
에 의존적인데 스프링이 제공하는 트랜잭션 서비스 추상화를 사용해보자.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PlatformTransactionManager transactionManager;
public OrderService(OrderRepository orderRepository, PlatformTransactionManager transactionManager) {
this.orderRepository = orderRepository;
this.transactionManager = transactionManager;
}
// 아래 코드는 전혀 변화가 없다
// ...
}
JdbcClient
JdbcTemplate
의 대체 기술save
기능을 구현하더라도 JPA가 자동으로 해주던 부분을 부가적으로 처리해줘야 하긴 하다.public void save(Order order) {
// id 채번
Long id = jdbcClient.sql("select next value for orders SEO").query(Long.class).single();
order.setId(id);
jdbcClient.sql("insert into orders (no, total, id) values (?, ?, ?)")
.params(order.getNo(), order.getTotal(), order.getId())
.update();
}
OrderService
는 서비스 추상화가 적용되어 있기 때문에 변경될 필요가 없다.TransactionTemplate
, PlatformTransactionManager
와 같은 기술 연관 코드가 여전히 남아 있다.TransactionTemplate
으로 비즈니스 로직을 감싸는 형태도 불편하다.
OrderService
외부로 추출할 수 있다.OrderService
인터페이스가 있고 비즈니스 로직이 담긴 구현체인OrderServiceImpl
이 있다고 했을 때,OrderServiceImpl
을 트랜잭션으로 감싸 호출하는 OrderServiceProxy
를 사용한다면OrderServiceImpl
은 비즈니스 로직에만 집중할 수 있다.public interface OrderService { ... }
public class OrderServiceImpl {
// ...
@Override
public Order createOrder(String no, BigDecimal total) {
Order order = new Order(no, total);
orderRepository.save(order);
return order;
}
}
public class OrderServiceProxy {
private final OrderService target;
// ...
@Override
public Order createOrder(String no, BigDecimal total) {
return new TransactoinTemplate(transactionManager)
.execute(status -> {
return target.createOrder(no, total);
});
}
}
OrderService
타입에 프록시가 주입되게 된다.@Bean
public OrderService orderService(
PlatformTransactionManager transactionManager,
OrderRepository orderRepository
) {
return new OrderServiceProxy(new OrderServiceImpl(orderRepository), transactionManager);
}
@Transactional
을 사용하면 스프링 컨테이너가 자동으로 프록시를 만들어준다. @Transactional
public Order createOrder(String no, BigDecimal total) {
Order order = new Order(no, total);
orderRepository.save(order);
return order;
}