Books/도메인 주도 개발 시작하기
Chapter7~8. 도메인서비스 ~ 애그리거트 트랜잭션 관리
YoonJong
2024. 4. 10. 19:54
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..
- 선점 잠금(비관적) - Pessimistic Lock
728x90