본문으로 건너뛰기

주문 서비스 (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 트랜잭션 실패