본문 바로가기
Books/도메인 주도 개발 시작하기

Chapter7~8. 도메인서비스 ~ 애그리거트 트랜잭션 관리

by YoonJong 2024. 4. 10.
728x90

 

  • 여러 애그리거트가 필요한 기능 ex) 결제 금액 계산 로직 → 상품, 주문, 할인, 회원 애그리거트 등 필요.
    • 하나의 애그리거트에 넣기 애매한 도메인 기능을 억지로 넣어 구현하면 안된다.
    • 도메인 기능을 별도 서비스로 구현하는 방법을 사용한다.
    • 도메인 서비스는 도메인 로직을 표현하므로 도메인 서비스의 위치는 다른 도메인 구성요소와 동일한 패키지에 위치
@Service
@Transactional
public class PaymentCalculationService {
    
    private final ProductRepository productRepository;
    private final DiscountRepository discountRepository;
    private final MemberRepository memberRepository;
    
    //...   
}

// 일부 기능을 위해 굳이 도메인 서비스 객체를 애그리거트에 의존 주입을 할 이유는 없다.

 


  • 트랜잭션 처리 방식
    • 선점 잠금(비관적) - Pessimistic Lock
      • 먼저 애그리거트를 선점한 스레드가 사용이 끝날 때 까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 하는 방식
      • 선점한 스레드가 커밋하면 잠금을 해제한다.
      public interface ProductRepository extends JpaRepository<Product, Long> {
      
          @Lock(LockModeType.PESSIMISTIC_WRITE)
          @Query("SELECT p FROM Product p WHERE p.id = :productId")
          Product findProductForWrite(@Param("productId") Long productId);
      }
      
      • 잠금 순서에 따른 교착 상태가 발생하지 않도록 주의
      1. 스레드1: A 애그리거트에 대한 선점 잠금 구함
      2. 스레드2: B 애그리거트에 대한 선점 잠금 구함
      3. 스레드1: B 애그리거트에 대한 선점 잠금 시도
      4. 스레드2: A 애그리거트에 대한 선점 잠금 시도
      
      • 선점 잠금을 시도할 때 문제가 발생하지 않게 하려면, 잠금을 구할 때 최대 대기 시간을 설정
        private Map<String, Object> buildLockHints(int maxWaitTimeSeconds) {
              Map<String, Object> hints = new HashMap<>();
              hints.put("javax.persistence.lock.timeout", maxWaitTimeSeconds * 1000); // milliseconds
              return hints;
          }
      
    • 비선점 잠금(낙관적) - Optimistic Lock
      • 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식 → Version 체크
      • 애그리거트에 버전으로 사용할 숫자 타입 속성을 추가해야 한다.
      • 애그리거트를 수정할 때마다 버저능로 사용할 속성 값이 1씩 증가한다 .
      @Entity
      public class Product {
      
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
      
          private String name;
      
          private double price;
      
          @Version
          private int version; // 버전 속성 추가
      }
      
      // 서비스에서 예외가 발생했을 때 로직을 구현해야 한다.
      @Service
      @Transactional
      public class ProductService {
      
          private final ProductRepository productRepository;
      
          public ProductService(ProductRepository productRepository) {
              this.productRepository = productRepository;
          }
      
          public void updateProduct(Product product) {
              try {
                  productRepository.save(product);
                  // 상품 업데이트 로직 추가
              } catch (OptimisticLockException e) {
                  // 낙관적 락 오류 처리
                  handleOptimisticLockException(product, e);
              }
          }
      
          private void handleOptimisticLockException(Product product, OptimisticLockException e) {
              // 낙관적 락 오류 발생 시 처리할 로직 작성
              // 예를 들어, 사용자에게 오류 메시지를 전달하거나 로깅할 수 있음
              System.out.println("상품 '" + product.getName() + "'을(를) 수정하는 중에 다른 사용자에 의해 변경되었습니다.");
              System.out.println("다시 시도하거나 최신 정보를 확인하세요.");
          }
      }
      
      • OPTIMISTIC_FORCE_INCREMENT : 엔티티 버전 속성을 강제로 증가
        • 엔티티를 업데이트 할때마다 버전 속성이 갱신
        • 다른 트랜잭션이 동시에 같은 엔티티를 수정하려고 시도할 경우 버전 충돌을 감지
      entityManager.find(Product.class, product.getId(), LockModeType.OPTIMISTIC_FORCE_INCREMENT);
      

      오프라인 선점 잠금 .. TODO..
728x90

댓글