3/25(수) 기능 요구사항 정리 및 API 명세 작성 준비

dev_joo·2026년 3월 25일

아직 생성되지 않은 엔티티의 Id가 필요한 엔티티의 생성


발제문에 이렇게 적혀있어서 주문이 당연히 배송ID를 가져야한다고 생각했다.

구현 방법 생각해보기

  1. 주문이 생성될 땐 배송 Id를 우선 비워두고, 배송 서비스에서 배송 정보가 생성되면 이벤트 처리를 통해 주문의 배송 Id를 채워줄 수 있다.

  2. 주문이 생성될 때 배송 Id를 주문에서 생성해 미리 채워두고 배송에게 넘겨주는 방식을 사용할 수도 있다. 이 때는 정합성을 위해 배송 생성이 실패하면 재시도를 하는 처리가 추가로 필요해진다.

결론


3. 주문 서비스는 배송 정보를 참조할 일이 없다. 그래서 주문에는 배송 Id를 두지 않는다.
주문에 대한 배송 정보를 조회하기 위해서는
배송이 주문Id를 가지고 있고, 배송 서비스에 요청하면 된다. (단일 책임 원리)


주문 도메인

수정할 때 이미 완료된 주문이면 안된다.

  • 검증을 위해 상태 Enum 필요한가? 현재 상황에선 결제를 생략한 프로젝트니까 그냥
  • Audit 플래그를 보면 되지 않을까?
  • Audit는 비즈니스 로직이 아닌 관리, 추적용으로 만 사용되는것이 좋다.
    -> 상태 Enum 필요하다.
Order (Aggregate Root)
 ├── id (UUID)
 ├── supplierId      ← Company Context 참조 (ID만 보관)
 ├── receiverId      ← Company Context 참조 (ID만 보관)
 ├── productId       ← Product Context 참조 (ID만 보관) // 주문 하나당 상품 한 종류만
 ├── quantity  // 상품의 수량
 ├── deliveryId      ← Delivery Context 참조 (ID만 보관)
 ├── OrderStatus (Value Object)
 │    └── | PAID | ORDER_CONFIRMED | CANCELLED  
 └── requestDetails

슬랙 알림 서비스의 책임은 어디까지?

슬랙 알림 서비스의 책임을 어디까지 둘 지 결정할 때 다음 두 가지를 생각할 수 있다.


1. 다른 서비스가 메시지 정보를 만들고 알림 서비스에 요청을 한다.
이 경우 알림서비스의 역할은 메시지 전송밖에 안해서 알림서비스의 책임이 너무 적어질 수 있다.


2. 메시지 서비스의 슬랙 API가 다른 플랫폼 API로 확장될 때를 생각해보자.
다양한 메시지 API 중에서는 메시지 템플릿 기능을 같이 가지고있는 경우가 있다.
메시지 조합을 메시지 서비스가 할 땐 알림 서비스의 부담이 커질 수 있다.

결국 책임을 어느 쪽으로 몰아줄지의 문제이고, 두 방법중 확실히 옳거나 더 효율적인 방법이 있는 문제는 아니었다.

그래서 알림 서비스를 맡게 되는 사람은 다른 서비스도 같이 맡게 될것 같아 알림 서비스를 맡은 사람이 팀에서 너무 적게 역할을 맡게 되는 것이 아니고,

알림 서비스가 다른 플랫폼으로도 확장가능할 것을 고려해 알림 서비스는 메시지에 대한 정보를 받아서 전송만 하는 것으로 결정했다.


주문,배송 알림 구현을 위한 설계

우리는 앞서서 주문이 배송을 바라보지 않게 했다.
배송주문 정보를 가져와서 메시지를 만들어 알림 서비스에 전송을 요청한다

도메인의 책임 명확하고
흐름 자연스럽고
유지보수가 쉽다.

주문 서비스에서 배송정보를 만들게 될 때,
수시로 데이터가 바뀌는 실시간 주문 정보와
배송이 시작될 수 있는 확정된 주문 정보를 구분해야한다.

  • 왜냐하면 주문이 취소되면 배송도 시작되지 않아야 하기 때문이다.

  • 우리는 앞서 00시를 기준으로 최종적으로 주문을 마감을 해 배송을 하기로 정했다.

  • 00시에 상태값을 보고 확정된 주문에 대한 배송을 만들어준다면, 사용자 관점에서 배송상태가 바로 적용되지 않는 스케줄링 소요시간 문제가 생긴다.

  • 00시 주문 확정 이전에
    오늘 간 주문 확정된 테이블을 따로 만들어서 배송과 메시지 관리를 해나가는 게 좋을것이라 생각되었다.

Order (Aggregate Root)
 ├── id (UUID)
 ├── supplierId      ← Company Context 참조 (ID만 보관)
 ├── receiverId      ← Company Context 참조 (ID만 보관)
 ├── productId       ← Product Context 참조 (ID만 보관) // 주문 하나당 상품 한 종류만
 ├── quantity || // 상품의 수량
 ├── deliveryId      ← Delivery Context 참조 (ID만 보관)
 ├── OrderStatus (Value Object)
 │    └── | PAID | ORDER_CONFIRMED | CANCELLED  
 └── requestDetails
 
 FixOrder
 ├── id (UUID)
 ├── supplierId      ← Company Context 참조 (ID만 보관)
 ├── receiverId      ← Company Context 참조 (ID만 보관)
 ├── productId       ← Product Context 참조 (ID만 보관) // 주문 하나당 상품 한 종류만
 ├── quantity
 ├── deliveryId      ← Delivery Context 참조 (ID만 보관)
 └── requestDetails

MSA 의 한계 :

MSA에서는 하나의 비즈니스 로직이 여러 서비스에 걸쳐 수행되기 때문에,
단일 트랜잭션으로 처리하던 모놀리식 구조와 달리 분산 트랜잭션 문제가 발생한다.

겉보기에는 주문과 배송이 동시에 생성되는 것처럼 보이지만,

주문 임시 생성 → 배송 생성 → 주문 확정

실제로는 위와 같은 순차적인 처리 과정을 거친다.

데이터 불일치 1: 서비스에서 문제발생

이 과정에서 중간 단계(예: 배송 생성 실패)에서 문제가 발생할 경우,
데이터 불일치가 발생할 수 있다.

이러한 문제를 해결하기 위해 SAGA 패턴을 사용한다.

각 서비스는 자신의 로컬 트랜잭션만 처리한다.
문제가 발생하면 보상 트랜잭션(Compensation Transaction) 을 통해 이전 작업을 되돌린다.

👉 분산 환경에서 최종 일관성(Eventual Consistency) 을 보장한다.

데이터 불일치 2: 네트워크 문제

서비스 내부에서는 문제가 없더라도,
서비스 간 통신 과정에서 네트워크 장애가 발생할 수 있다.

  • 이벤트는 발행했지만 Kafka에 전달되지 않는 경우
  • 메시지는 전달됐지만 DB에는 반영되지 않은 경우

이러한 문제를 해결하기 위해 Inbox/Outbox 패턴을 사용한다.

✔ Outbox 패턴
DB에 이벤트를 함께 저장
이후 메시지 브로커(Kafka)로 안전하게 발행

✔ Inbox 패턴
이미 처리한 이벤트를 기록
중복 처리 방지


서비스 간 통신 방식

“데이터를 가져오는 것”과 “데이터를 바꾸는 것”은 실패했을 때 영향이 완전히 다르기 때문에 서비스 간 통신은 목적에 따라 다음과 같이 구분한다.

1. 조회(Read)
Feign Client (동기 호출) 사용
실시간 데이터 조회에 적합


2. 상태 변경 / 이벤트 전달

Kafka 기반 비동기 이벤트 처리
서비스 간 결합도를 낮추고 확장성 확보
이벤트 카프카 처리 정보가 필요할 때


DDD - 애그리거트 개념 다시 확인

상품의 재고 필드 -> 재고 객체

애그리거트는 도메인, 컨트롤러, 서비스 레포지토리 4계층 구조를 가진다.

그래서 이 경우 주문 확정 테이블을 애그리거트로 나눌 정도로 4계층 구조를 나눌 필요가 있는지 (인프라를 개별적으로 가질 수 있는지)
생각해봐야한다.

Order (Aggregate Root)
 ├── id (UUID)
 ├── supplierId      ← Company Context 참조 (ID만 보관)
 ├── receiverId      ← Company Context 참조 (ID만 보관)
 ├── productId       ← Product Context 참조 (ID만 보관) // 주문 하나당 상품 한 종류만
 ├── quantity || // 상품의 수량
 ├── deliveryId      ← Delivery Context 참조 (ID만 보관)
 ├── OrderStatus (Value Object)
 │    └── | PAID | ORDER_CONFIRMED | CANCELLED  
 └── requestDetails
 
 FixOrder (Aggregate)
 ├── id (UUID)
 ├── supplierId      ← Company Context 참조 (ID만 보관)
 ├── receiverId      ← Company Context 참조 (ID만 보관)
 ├── productId       ← Product Context 참조 (ID만 보관) // 주문 하나당 상품 한 종류만
 ├── quantity
 ├── deliveryId      ← Delivery Context 참조 (ID만 보관)
 └── requestDetails

+ 컨텍스트 분리

profile
풀스택 연습생. 끈기있는 삽질로 무대에서 화려하게 데뷔할 예정 ❤️🔥

0개의 댓글