항해 플러스 6기 4주차를 회고하며 - 끝이 없네 끝이 없어!

1. 문제

4주차의 과제는 다음과 같다.

* 선택한 시나리오 별 비즈니스 로직 개발 및 단위 테스트 작성
* 비즈니스 유즈케이스 개발 및 통합 테스트 작성
  * 여기서의 유즈케이스는 시스템의 동작을 사용자 입장에서 표현한 시나리오로, 시스템에 관련된 요구사항을 알아내는 과정을 의미한다.

다시 한 번 우리 팀이 선택한 E커머스 시나리오의 비즈니스 로직을 살펴보았다.

1. 잔액 충전 / 조회 API
2. 상품 조회 API
3. 주문 / 결제 API
4. 상위 상품 조회 API
5. 장바구니 기능

이번 과제부터 본격적으로 시나리오를 구현하는 과제가 시작되었다. 이전 주차에서 설계했던 시퀀스 다이어그램과 ERD, API 명세서를 기반으로 구현을 하고, 각 요구사항 기능 별로 단위 테스트, 통합 테스트를 진행하는 게 이번 주차 과제의 목표였다.

E커머스 시나리오 구현에서 가장 신경써야 할 부분은 역시 주문/결제 기능이다. 주문할 때 상품 재고 차감에서, 그리고 결제할 때 사용자의 잔액 차감에서 동시성 제어가 이루어져야 한다. 또한 결제 시 외부 데이터 플랫폼에 전송한다는 요구사항도 있는데, 이 때 외부 데이터 플랫폼에 전송 과정에서 실패를 하더라도 결제 비즈니스 로직의 동작에는 영향이 가면 안 된다.

2. 시도

동시성 제어를 위해서 이전 2주차 과제에서 배운 DB 락을 사용하여 구현했다. 여기서 어떤 락을 사용해야 하는지 고민을 하게 되었는데, 다음은 DB 락의 대표적인 종류인 공유락과 베타락에 대해 핵심적인 부분만 알아본 내용이다.

  공유락 베타락
다른 트랜잭션의 공유락 획득 가능 허용 대기
다른 트랜잭션의 베타락 획득 가능 대기 대기
다른 트랜잭션의 읽기 연산 허용 불가
다른 트랜잭션의 쓰기 연산 불가 불가

알아본 바로는 공유락이 걸린 데이터는 다른 트랜잭션도 똑같이 공유락을 획득할 수 있지만 베타락은 획득하지 못한다. 또한 다른 트랜잭션에서도 해당 데이터의 읽기(SELECT) 연산이 가능하다. 반면 베타락이 걸린 데이터는 다른 트랜잭션의 공유락과 베타락 획득에 대해 대기 상태가 되며, 다른 트랜잭션에서 해당 데이터의 읽기와 쓰기 연산 모두 불가능하다.

이번 주차의 과제에서 사용한 락은 베타락을 사용해 구현하긴 했다. 대기가 걸리기 때문에 성능적으로 좋지 않은 락이기는 하지만 동시성 제어 측면에서는 가장 견고하다고 생각했기 때문이다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT b FROM Balance b WHERE b.userId = :userId")
fun findByUserIdWithLock(@Param("userId") userId: Long): Balance?

다음으로 외부 데이터 플랫폼에 전송하는 요청에 대해서는 Spring Event를 사용하여 비동기 이벤트로 요청하도록 했다. 비동기로 요청을 함으로써 비즈니스 로직에 영향을 미치지 못하게 처리하여 해당 요청이 실패하더라도 비즈니스의 트랜잭션은 정상적으로 커밋이 되는 효과를 기대했다.

// 외부 데이터 플랫폼에 전송 요청 이벤트 발행
eventPublisher.publishEvent(DataPlatformEvent(paymentResultInfo))

/**
 * 비동기 이벤트 리스너
 */
@Async
@EventListener
fun listen(event: DataPlatformEvent) {
    // 외부 데이터 플랫폼에 전송하는 동작
    val dataPlatform = ExternalDataPlatform()
    dataPlatform.sendPaymentData(event.paymentResultInfo)
}

3. 해결

다행이 동시성 제어에 대한 병렬 수행 통합 테스트를 수행했을 때 주문과 결제 모두 정상적으로 동작했다. 또한 외부 데이터 플랫폼 전송 이벤트에서 실패가 발생하더라도 트랜잭션은 정상적으로 커밋되는 부분까지 임의로 이벤트 리스너 동작에서 예외를 발생시킴으로써 확인할 수 있었다.

4. 알게된 것

확실히 설계를 앞 주차때 미리 잡고 구현을 하니까 훨씬 수월했던 점이 있었다. 구현할 때는 미리 만든 시퀀스 다이어그램을 보며 흐름을 따라 로직을 구현했고, 테이블 또한 작성한 ERD를 보고 만들다보니 처음 개발을 시작할 때 빠르게 진행할 수 있었다.

더 연습하다보면 요구사항을 구현하는 데 있어서 어떤 순서대로 진행하는 게 베스트인지와, 나만의 구현 노하우가 생길 것 같다는 생각이 들었다.


Keep : 현재 만족하고 계속 유지할 부분

기능의 요구사항을 코드로 옮기는 것에 꽤나 자연스러워졌고, 객체 지향적인 프로그래밍 방식에 나름 익숙해진 게 느껴졌다. 이 감을 앞으로 쭉 가져간다면 앞으로 개발 업무하면서 구현하는 업무에 대해서는 어렵지 않게 진행할 수 있을 것 같다.

Problem : 개선이 필요하다고 생각하는 문제점

가장 고민이 되는 부분은 필요한 데이터를 가공할 때, 어디까지 DB에서 만들어 가져올 것인가에 대한 점이었다. 지금은 JPA에서 엔티티 간 연관관계를 맺는 것을 선호하지 않고, 필요한 데이터는 DB에서 단건 조회로 간단하게 가져온 다음 애플리케이션 (메모리) 상에서 만들어주는 방식으로 하고 있는데, 연관되어 있는 테이블이 많은 애플리케이션에서는 Join 과 같이 DB에서의 조작도 적절하게 사용해야 할 것 같다고 생각했다.

또한 아직까지 공유락과 베타락이 어떤 요구사항일 때 필요할 지 정확하게 이해가 안된 것 같다고 생각했다. “이런 성격의 동작에서는 어떤 락이 필요할까?” 라는 생각을 하면 아직 “이거다!” 라고 확정짓기 어려운 것 같다.

Try : 문제점을 해결하기 위해 시도해야 할 것

데이터를 가공하는 점에 있어서 나름대로 기준을 잡아야할 것 같다. 데이터를 가져오려는 테이블과 도메인적으로 같은 테이블의 경우에는 Join을 사용해 가져오고 (예. 주문 - 주문 아이템) 그렇지 않고 도메인이 다른 테이블의 경우에는 따로 쿼리를 요청하는 식으로 말이다.

또한 DB 락에 대한 점은 앞으로 좀 더 찾아보고 이해할 필요가 있을 것 같다.

감사하게도 4주차까지 모든 과제를 PASS 받았다. 물론 항해플러스의 목적은 과제의 PASS 보단 개인의 개발 역량 향상이다. 이 점을 우선시하고 앞으로 남은 주차에 임할 것이며, PASS는 덤이라고 생각하고자 한다.


🤩 다음 수료생 추천 할인 혜택!

혹시라도 항해 플러스에 합류하고 싶은데 비싼 수강료 때문에 망설여진다면…? 🤔

수료생 추천 할인 혜택으로 20만 원을 할인받으실 수 있다는 사실! 💡

결제페이지 → 할인 코드 → 수료생 할인 코드에 tJQjYK 입력하면 추가로 20만 원을 할인받는 혜택 꼭 챙겨가시길 바란다🚀🚀🌟


#추천인: tJQjYK #항해플러스 #항해99