Skip to content
Kang Log
Go back

프로젝트 구조에 개념 연결

Updated:

Day 7. 프로젝트 구조에 개념 연결

1. 오늘의 학습 목표

2. 학습 전 생각해보기

질문 1

Product와 Stock은 모두 productId를 사용한다. 그렇다면 두 서비스가 같은 Product 객체나 테이블을 공유해도 될까?

내 답변

     

질문 2

Product가 생성되면 재고 0을 만드는 책임은 Product와 Stock 중 어디에 있어야 할까? 누가 사실을 알리고 누가 행동해야 할까?

내 답변

     

질문 3

프로젝트에 현재 domain이 먼저 구현되어 있고 application, adapter가 이후 추가된다면 어떤 의존성 방향을 의도한 것일까?

내 답변

     

3. 핵심 개념 설명

3.1 네 서비스의 책임과 소유 데이터

Product는 판매할 상품의 이름, 가격, 판매 상태와 그 규칙을 소유한다. Stock은 상품별 수량과 차감·복원 규칙을 소유한다. 두 서비스가 같은 productId를 사용해도 Product 객체나 테이블을 공유하는 것은 아니다.

Order는 회원의 구매 요청, 주문 라인, 주문 당시 상품명·단가 스냅샷, 전체 금액과 주문 상태를 소유한다. 주문 당시 가격을 저장하는 이유는 나중에 상품 가격이 바뀌어도 이미 성립한 주문의 사실을 유지하기 위해서다.

Payment는 주문에 대한 결제 금액과 결제·환불 상태를 소유한다. orderId는 다른 Aggregate를 값으로 참조하는 식별자일 뿐 Order DB에 대한 직접 접근 권한이 아니다.

이 경계 덕분에 각 서비스는 자기 불변식, 즉 항상 지켜야 하는 업무 규칙을 한곳에서 관리한다. 서비스끼리 코드나 DB를 직접 참조하면 상대 내부 변경에 결합되고 규칙을 우회할 수 있으므로 프로젝트는 Kafka 이벤트와 ID 값으로 협력한다.

3.2 ProductCreated 이벤트 흐름

관리자가 Product의 REST API로 상품 생성을 요청하면 Product의 사용 사례가 상품을 등록한다. Product DB의 저장이 성공하면 “상품이 생성되었다”는 사실인 ProductCreated가 발행 대상이 된다.

Stock의 Kafka Inbound Adapter는 이 이벤트를 받고 Stock 생성 사용 사례를 호출한다. Stock Domain은 해당 productId에 대한 초기 재고 0을 만든다. Product는 Stock 테이블을 직접 만들지 않고 Stock의 책임이 실행될 계기만 제공한다.

이 흐름에는 Day 1의 데이터 소유권, Day 2의 Producer/Broker/Consumer, Day 5의 Inbound Adapter와 Application Service가 함께 나타난다.

3.3 주문 생성부터 주문 완료까지

Order는 주문을 CREATED 상태로 저장하고 주문 생성 사실 또는 재고 처리의 계기가 되는 이벤트를 발행한다. Stock은 자기 DB에서 수량을 확인해 차감하고 성공 또는 실패 결과를 이벤트로 알린다.

재고 차감이 성공하면 Order는 STOCK_RESERVED로 전이하고 Payment 처리가 이어진다. Payment는 PENDING에서 결제 성공 시 PAID, 실패 시 FAILED로 바꾸고 결과를 알린다.

결제가 성공하면 Order는 PAID를 거쳐 COMPLETED로 확정된다. 이 과정은 하나의 긴 @Transactional이 아니라 서비스별 로컬 트랜잭션과 이벤트로 이어진다. 각 이벤트 사이에는 잠시 CREATED, STOCK_RESERVED, PAID 같은 중간 상태가 존재한다.

프로젝트에서 정확한 이벤트 이름과 누가 다음 단계를 시작하는지는 구현 주차에 이벤트 계약을 확인해야 한다. 사전 학습 단계에서는 상태와 책임의 이동을 이해하는 데 집중한다.

3.4 결제 실패와 재고 복원

결제 단계에 도달했다면 재고 차감은 이미 Stock DB에 커밋된 상태다. Payment 실패를 Order의 예외로 던져도 Stock의 과거 로컬 트랜잭션은 자동 롤백되지 않는다.

따라서 재고 복원이라는 새로운 Stock 업무가 필요하다. 이것은 단순한 메모리 되돌리기가 아니라 restore 규칙을 거쳐 커밋되는 보상 트랜잭션이다. 보상이 성공한 뒤 Order는 CANCELED로 수렴해야 한다.

이 흐름은 Saga의 부분 성공과 보상을 보여 준다. 이벤트가 중복될 수 있으므로 차감과 복원 모두 같은 이벤트로 효과가 두 번 생기지 않도록 고려해야 한다.

3.5 domain, application, adapter

domain은 Entity, 상태 전이, 불변식을 담는다. 예를 들어 Order의 허용 상태 전이, Stock의 수량이 0 미만이 되지 않는 규칙, Payment의 환불 가능 금액이 여기에 속한다.

application.port.in은 생성·차감·결제 같은 사용 사례의 진입 인터페이스, application.service는 그 흐름의 구현, application.port.out은 저장과 이벤트 발행처럼 외부에 필요한 기능의 인터페이스다.

adapter.in.web은 REST 요청, adapter.in.messaging은 Kafka 이벤트를 Input Port 호출로 바꾼다. adapter.out.persistence는 JPA로 저장 Port를 구현하고, adapter.out.messaging은 Kafka로 발행 Port를 구현한다.

프로젝트 문서 기준 현재는 Domain 계층까지 구현되어 있으며 이후 Application, Adapter 순으로 확장될 예정이다. 그러므로 지금은 없는 클래스를 추측하기보다 앞으로 작성되는 코드가 이 책임과 의존성 방향을 지키는지 확인하면 된다.

3.6 다음 주 구현 전에 연결할 질문

기능을 시작할 때 “이 데이터와 규칙의 소유 서비스는 어디인가?”를 먼저 묻는다. 그다음 외부 입력이 REST인지 Kafka인지, 어떤 Input Port로 들어오며 어떤 Domain 규칙을 실행하는지 연결한다.

DB 저장이나 이벤트 발행이 필요하면 Application이 어떤 Output Port를 요구하는지 확인한다. 구체적인 JPA와 Kafka 클래스가 Domain이나 Application 안으로 새어 들어오지 않는지도 본다.

이벤트 흐름에서는 Key와 순서 범위, Consumer Group, 중복 시 업무 효과, 재시도할 실패와 DLQ로 격리할 실패를 확인한다. DB 변경과 발행이 함께 필요한 지점에서는 Outbox, 후속 단계 실패에는 보상을 연결한다.

이 체크는 구현을 미리 설계 완료하라는 뜻이 아니다. 작은 기능 하나를 만들 때 이전 6일의 개념 중 무엇을 확인해야 하는지 놓치지 않기 위한 지도다.

4. 흐름으로 이해하기

[Product]
  상품 저장
     ↓ ProductCreated
[Kafka]

[Stock]
  초기 재고 0 생성

────────────────────────────────────────

[Order] create → CREATED
     ↓ 재고 처리 이벤트
[Stock] deduct
     ├── 실패 ───────────────→ [Order] CANCELED
     └── 성공

      [Order] STOCK_RESERVED
          ↓ 결제 처리 이벤트
      [Payment] PENDING
          ├── 성공 → PAID → [Order] PAID → COMPLETED
          └── 실패 → FAILED
                         ↓ 보상
                   [Stock] restore

                   [Order] CANCELED

각 상자는 자기 데이터에 대한 로컬 트랜잭션만 수행한다. 아래 화살표는 서비스 내부 메서드 호출이 아니라 Kafka 이벤트를 통한 책임 이동으로 이해한다. 결제 실패 경로의 재고 복원은 이미 성공한 차감을 상쇄하는 보상이다.

5. 직접 채우는 핵심 질문

질문 1. 네 서비스의 책임과 대표 소유 데이터를 한 문장씩 적어라.

내 답변

Product:   Stock:   Order:   Payment:  

답변에 포함해야 할 키워드

질문 2. OrderLine의 productId와 상품명·단가 스냅샷이 Product DB 직접 참조와 다른 이유는 무엇인가?

내 답변

     

답변에 포함해야 할 키워드

질문 3. ProductCreated 흐름에서 Product와 Stock의 책임을 구분하라.

내 답변

     

답변에 포함해야 할 키워드

질문 4. Order가 Stock의 deduct() Domain 메서드를 코드 의존으로 직접 호출하면 프로젝트 규칙을 어기는 이유는 무엇인가?

내 답변

     

답변에 포함해야 할 키워드

질문 5. 주문이 CREATED → STOCK_RESERVED → PAID → COMPLETED로 이동할 때 각 상태 사이의 책임 서비스를 적어라.

내 답변

     

답변에 포함해야 할 키워드

질문 6. 결제 실패 후 재고를 복원하지 않으면 업무적으로 어떤 문제가 생기는가?

내 답변

     

답변에 포함해야 할 키워드

질문 7. Kafka Consumer 클래스와 재고 차감 규칙은 각각 어떤 패키지 역할에 있어야 하는가?

내 답변

     

답변에 포함해야 할 키워드

질문 8. Application Service가 주문 저장과 이벤트 발행을 요청할 때 직접 JPA/Kafka 구현 대신 무엇에 의존해야 하는가?

내 답변

     

답변에 포함해야 할 키워드

질문 9. 주문 이벤트 Consumer 구현 전에 신뢰성 관점에서 확인할 질문을 네 개 적어라.

내 답변

  1.  
  2.  
  3.  
  4.  

답변에 포함해야 할 키워드

질문 10. 업무 데이터 저장과 Kafka 발행이 한 기능에 함께 있다면 어떤 실패 구간과 패턴을 떠올려야 하는가?

내 답변

     

답변에 포함해야 할 키워드

6. 상황 판단 문제

상황 1

Product 가격이 변경되자 과거 OrderLine의 단가까지 Product DB 조인 결과에 따라 바뀌었다.

  1. 어떤 서비스 경계와 주문 사실이 훼손되었는가?
  2. 프로젝트의 스냅샷 방식은 이 문제를 어떻게 피하는가?

내 답변

       

상황 2

Stock Kafka Listener 안에 재고 부족 검증, JPA 저장, 다음 이벤트 발행 코드가 모두 들어 있다.

  1. 어떤 책임들이 Inbound Adapter에 섞였는가?
  2. Domain, Application, Port, Adapter로 어떻게 나누어 생각할 수 있는가?

내 답변

       

상황 3

결제가 실패해 재고 복원 이벤트를 보냈지만 같은 이벤트가 두 번 전달되어 재고가 원래보다 많아졌다.

  1. Day 4의 어떤 문제가 발생했는가?
  2. Day 6의 어떤 보호가 필요했는가?
  3. 최종적으로 어떤 서비스 상태들을 확인해야 하는가?

내 답변

       

7. 오늘의 용어 정리

용어내가 작성하는 정의
서비스 책임
독립 스키마
값 참조
스냅샷
상태 전이
이벤트 흐름
보상
Input Port
Output Port
Adapter

8. 프로젝트에 한 줄 연결

9. 오늘의 자기 점검

10. 이해하지 못한 부분

     

11. 3문장 요약

12. 복습 퀴즈

  1. 상품 생성 후 초기 재고 0을 자기 DB에 만드는 서비스는 무엇인가?
  2. 주문 당시 상품명과 단가를 OrderLine에 복사해 두는 방식을 무엇이라 하는가?
  3. 결제 실패 뒤 이미 차감된 재고를 되돌리는 새 업무 동작은 무엇인가?
  4. Kafka Listener가 위치할 프로젝트 패키지 역할은 무엇인가?
  5. 현재 프로젝트에서 서비스 사이 직접 코드·DB 참조 대신 사용하는 통신 방식은 무엇인가?
복습 퀴즈 정답과 해설 보기
  1. Stock 서비스다. Product는 생성 사실을 알리고 재고 데이터 변경은 소유자인 Stock이 수행한다.
  2. 스냅샷이다. 주문이 성립한 시점의 사실을 보존한다.
  3. 보상 트랜잭션인 재고 복원이다.
  4. Inbound Adapter인 adapter.in.messaging이다.
  5. Kafka 이벤트 통신이다. 각 서비스는 독립 스키마를 소유한다.

Share this post on:

Previous Post
Kafka
Next Post
분산 트랜잭션 실패 처리