본문으로 건너뛰기

주문 서비스 (Order Service)

개요

주문 서비스는 쇼핑몰의 핵심 주문 처리를 담당하며, SAGA 패턴을 활용한 분산 트랜잭션 오케스트레이터 역할을 수행합니다.

항목내용
언어Java 17
프레임워크Spring Boot 3.2
데이터베이스Aurora PostgreSQL (Global Database)
네임스페이스mall-order
포트8080
헬스체크/actuator/health

아키텍처

API 엔드포인트

메서드경로설명
POST/api/v1/orders주문 생성
GET/api/v1/orders/{id}주문 조회
GET/api/v1/orders?userId={userId}사용자별 주문 목록 조회
PUT/api/v1/orders/{id}/cancel주문 취소

주문 생성

POST /api/v1/orders

요청:

{
"userId": "user-123",
"items": [
{
"productId": "prod-001",
"sku": "SKU-ELECTRONICS-001",
"quantity": 2,
"price": 299000.00
},
{
"productId": "prod-002",
"sku": "SKU-FASHION-001",
"quantity": 1,
"price": 89000.00
}
]
}

응답 (201 Created):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user-123",
"status": "PENDING",
"totalAmount": 687000.00,
"items": [
{
"id": "item-uuid-1",
"productId": "prod-001",
"sku": "SKU-ELECTRONICS-001",
"quantity": 2,
"price": 299000.00
},
{
"id": "item-uuid-2",
"productId": "prod-002",
"sku": "SKU-FASHION-001",
"quantity": 1,
"price": 89000.00
}
],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T10:30:00"
}

주문 조회

GET /api/v1/orders/{id}

응답 (200 OK):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user-123",
"status": "CONFIRMED",
"totalAmount": 687000.00,
"items": [...],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T10:35:00"
}

주문 취소

PUT /api/v1/orders/{id}/cancel

응답 (200 OK):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user-123",
"status": "CANCELLED",
"totalAmount": 687000.00,
"items": [...],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T11:00:00"
}

데이터 모델

Order 엔티티

@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

@Column(name = "user_id", nullable = false)
private String userId;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private OrderStatus status = OrderStatus.PENDING;

@Column(name = "total_amount", precision = 12, scale = 2)
private BigDecimal totalAmount = BigDecimal.ZERO;

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();

@Column(name = "created_at")
private LocalDateTime createdAt;

@Column(name = "updated_at")
private LocalDateTime updatedAt;
}

OrderItem 엔티티

@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;

@Column(name = "product_id", nullable = false)
private String productId;

@Column(nullable = false)
private String sku;

@Column(nullable = false)
private Integer quantity;

@Column(precision = 12, scale = 2, nullable = false)
private BigDecimal price;
}

OrderStatus 열거형

public enum OrderStatus {
PENDING, // 대기 중
CONFIRMED, // 확정됨
CANCELLED, // 취소됨
FAILED // 실패
}

데이터베이스 스키마

CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
total_amount DECIMAL(12, 2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID REFERENCES orders(id),
product_id VARCHAR(255) NOT NULL,
sku VARCHAR(255) NOT NULL,
quantity INTEGER NOT NULL,
price DECIMAL(12, 2) NOT NULL
);

CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);

이벤트 (Kafka)

발행 토픽

토픽명이벤트설명
orders.createdorder.created주문 생성 시 발행
orders.confirmedorder.confirmed결제 완료 후 주문 확정 시 발행
orders.cancelledorder.cancelled주문 취소 시 발행

orders.created 페이로드

{
"event": "order.created",
"order": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user-123",
"status": "PENDING",
"totalAmount": 687000.00,
"items": [...],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T10:30:00"
}
}

orders.confirmed 페이로드

{
"event": "order.confirmed",
"order": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user-123",
"status": "CONFIRMED",
"totalAmount": 687000.00,
"items": [...],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T10:35:00"
}
}

구독 토픽

토픽명설명
payments.completed결제 완료 이벤트 수신하여 주문 확정 처리

환경 변수

변수명설명기본값
SPRING_DATASOURCE_URLAurora PostgreSQL 연결 URL-
SPRING_DATASOURCE_USERNAMEDB 사용자명-
SPRING_DATASOURCE_PASSWORDDB 비밀번호-
SPRING_KAFKA_BOOTSTRAP_SERVERSKafka 브로커 주소-
SERVER_PORT서비스 포트8080

서비스 의존성

SAGA 패턴

주문 서비스는 분산 트랜잭션을 위해 SAGA 오케스트레이터 패턴을 사용합니다:

  1. 주문 생성: PENDING 상태로 주문 생성
  2. 재고 확인: Inventory 서비스에 재고 확보 요청
  3. 결제 처리: Payment 서비스에 결제 요청
  4. 주문 확정: 모든 단계 성공 시 CONFIRMED 상태로 변경
  5. 보상 트랜잭션: 실패 시 이전 단계 롤백

에러 처리

HTTP 상태 코드에러설명
404OrderNotFoundException주문을 찾을 수 없음
400IllegalStateException잘못된 상태 전이 (예: 이미 취소된 주문 취소)
500SagaExceptionSAGA 트랜잭션 실패