TIL

섹션 7. 서비스 추상화

서비스란 무엇인가?

‘서비스’의 의미

애플리케이션 서비스

@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;
  }
}
@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;
      });
  }
}

기술에 독립적인 애플리케이션 서비스

관점에 따라 다르지만, @Entity와 같은 어노테이션이 있다고 해서 JPA에 의존하고 강결합이 있다고 보지 않기도 한다. JPA를 사용하지 않는다면 런타임에 JPA 라이브러리를 사용하지 않는다. JPA 말고 다른 기술을 사용한다고 해도 어노테이션을 제거하지 않아도 되고 다른 데이터 기술에 엔티티 클래스를 사용해도 문제 없다.

OrderRepository DIP

트랜잭션 서비스 추상화

추상화란 구현의 복잡함과 디테일을 감추고 중요한 것만 남기는 기법이다. 그렇다면 서비스 추상화란? 여러 인프라 서비스 기술의 공통적이고 핵심적인 기능을 인터페이스로 정의하고 이를 구현하는 어댑터를 만들어 일관된 사용이 가능하게 만드는 것이다.

@Service
public class OrderService {
  private final OrderRepository orderRepository;
  private final PlatformTransactionManager transactionManager; 

  public OrderService(OrderRepository orderRepository, PlatformTransactionManager transactionManager) {
    this.orderRepository = orderRepository;
    this.transactionManager = transactionManager;
  }
  // 아래 코드는 전혀 변화가 없다
  // ...
}

JDBC 데이터 엑세스 기술

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();
}

트랜잭션 테스트

트랜잭션 테스트는 어렵다

트랜잭션 프록시

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);
      });
  }
}
@Bean
public OrderService orderService(
  PlatformTransactionManager transactionManager,
  OrderRepository orderRepository
) {
  return new OrderServiceProxy(new OrderServiceImpl(orderRepository), transactionManager);
}

트랜잭션 프록시

  @Transactional
  public Order createOrder(String no, BigDecimal total) {
    Order order = new Order(no, total);
    
    orderRepository.save(order);
    
    return order;
  }

스프링의 프록시 AOP