Early Express 물류 플랫폼의 주문 관리 서비스입니다. 주문 생성부터 배송 완료까지의 전체 주문 생명주기를 관리하며, Saga 패턴을 통해 분산 트랜잭션을 오케스트레이션합니다.
Order Service는 마이크로서비스 아키텍처의 핵심 서비스로, 다음 기능을 제공합니다:
- 주문 생명주기 관리: 생성 → Saga 실행 → 확정 → 배송 → 완료/취소
- Saga 오케스트레이션: 재고 예약, 결제 검증, 배송 생성 등 분산 트랜잭션 조율
- AI 기반 배송 계획: Hub Service + AI Service 연동으로 최적 경로 및 시간 계산
- 실시간 상태 추적: 주문/배송 상태 변경 이벤트 발행
- 보상 트랜잭션: 실패 시 자동 롤백 (재고 복원, 결제 취소 등)
| 구분 | 기술 |
|---|---|
| Framework | Spring Boot 3.5.7 |
| Language | Java 21 |
| Database | PostgreSQL |
| Messaging | Apache Kafka |
| Service Discovery | Netflix Eureka |
| Security | OAuth 2.0 Resource Server (Keycloak) |
| Service Communication | OpenFeign |
| Build Tool | Gradle |
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Order Service │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Presentation Layer │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ CompanyUserOrder │ │ HubManagerOrder │ │ MasterOrder │ │
│ │ Controller │ │ Controller │ │ Controller │ │
│ │ /web/company-user │ │ /web/hub-manager │ │ /web/master │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ OrderCommandService │ │ OrderQueryService │ │
│ │ • createOrder() │ │ • getOrderById() │ │
│ │ • cancelOrder() │ │ • searchOrders() │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ OrderSagaService │ │ OrderCompensationService│ │
│ │ • executeSaga() │ │ • handleRefunded() │ │
│ │ • compensate() │ │ • restoreStock() │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ Order (Aggregate Root) OrderSaga (Aggregate Root) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ CompanyInfo │ │ ProductInfo │ │ SagaStep │ │ │
│ │ │ AmountInfo │ │ ReceiverInfo│ │ StepHistory │ │ │
│ │ │ DeliveryInfo│ │ RequestInfo │ │ Compensation│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ PostgreSQL │ │ Kafka Producer │ │ Kafka Consumer │ │ Feign Client │ │
│ │ Repository │ │ (4 토픽 발행) │ │ (2 토픽 수신) │ │ (5개 서비스) │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ AI Service │
│ (시간 계산) │
└────────▲────────┘
│ Feign
┌─────────────────┐ │
│ Inventory │◀─── Feign ───┐ │
│ Service │ │ │
│ (재고 예약/해제) │ │ │
└─────────────────┘ │ │
┌──────┴───────────┴──────┐
┌─────────────────┐ │ │ ┌─────────────────┐
│ Payment │◀──────│ Order Service │──────▶│ Hub Service │
│ Service │ Kafka │ │ Feign │ (경로 계산) │
│ (결제/환불) │──────▶│ │ └─────────────────┘
└─────────────────┘ │ │
└──────┬───────────┬──────┘
┌─────────────────┐ │ │
│ Hub Delivery │◀─── Feign ───┤ │
│ Service │ │ │
│ (허브 배송) │ │ │
└─────────────────┘ │ │
│ │
┌─────────────────┐ │ │
│ Last Mile │◀─── Feign ───┤ │
│ Delivery Service│ │ │
│ (최종 배송) │ │ │
└─────────────────┘ │ │
│ │
┌─────────────────┐ │ │
│ Track Service │◀─── Kafka ───┘ │
│ (배송 추적) │ │
└─────────────────┘ │
│
┌─────────────────┐ │
│ Notification │◀─── Kafka ────────────────┘
│ Service │
│ (알림 발송) │
└─────────────────┘
public class Order {
// 식별자
private OrderId id; // UUID
private OrderNumber orderNumber; // ORD-YYYYMMDD-XXX
// 업체 정보
private CompanyInfo companyInfo; // 공급/수령 업체 및 허브
private String destinationHubId; // 실제 도착 허브 (Hub Service 결정)
// 상품 정보
private ProductInfo productInfo; // 상품 ID, 허브, 수량
// 배송 정보
private DeliveryInfo deliveryInfo; // 허브/최종 배송 ID
private DeliveryProgressInfo deliveryProgressInfo; // 실제 배송 진행
// 수령자 정보
private ReceiverInfo receiverInfo; // 이름, 연락처, 주소
// 요청사항
private RequestInfo requestInfo; // 납품 희망 일시, 특별 요청
// AI 계산 결과
private AiCalculationResult aiCalculationResult; // 경로, 발송 시한, 예상 도착
// 상태
private OrderStatus status;
// 금액 정보
private AmountInfo amountInfo; // 단가, 총액, Payment ID
// PG 결제 정보
private PgPaymentInfo pgPaymentInfo; // PG사, 결제 ID, 결제 키
// 취소 정보
private String cancelReason;
private LocalDateTime cancelledAt;
// Audit
private String createdBy;
private LocalDateTime createdAt;
}| Value Object | 설명 | 주요 필드 |
|---|---|---|
OrderId |
주문 고유 ID | UUID |
OrderNumber |
주문 번호 | ORD-YYYYMMDD-XXX |
CompanyInfo |
업체 정보 | supplierCompanyId, supplierHubId, receiverCompanyId, receiverHubId |
ProductInfo |
상품 정보 | productId, productHubId, quantity |
AmountInfo |
금액 정보 | unitPrice, totalAmount, paymentId |
ReceiverInfo |
수령자 정보 | name, phone, email, address, postalCode |
RequestInfo |
요청사항 | requestedDeliveryDate, requestedDeliveryTime, specialInstructions |
DeliveryInfo |
배송 정보 | requiresHubDelivery, hubDeliveryId, lastMileDeliveryId |
DeliveryProgressInfo |
배송 진행 | actualDepartureTime, hubArrivalTime, actualDeliveryTime, signature |
AiCalculationResult |
AI 계산 결과 | routeInfoJson, calculatedDepartureDeadline, estimatedDeliveryTime |
PgPaymentInfo |
PG 결제 정보 | pgProvider, pgPaymentId, pgPaymentKey |
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 주문 상태 흐름도 │
└─────────────────────────────────────────────────────────────────────────────────────┘
[주문 생성]
│
▼
┌─────────┐ ┌───────────────┐ ┌────────────────┐ ┌─────────────────┐
│ PENDING │────▶│ STOCK_CHECKING│────▶│ STOCK_RESERVED │────▶│PAYMENT_VERIFYING│
│ 주문생성 │ │ 재고확인중 │ │ 재고예약완료 │ │ 결제검증중 │
└─────────┘ └───────────────┘ └────────────────┘ └─────────────────┘
│
┌─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ PAYMENT_VERIFIED│────▶│ ROUTE_CALCULATING│────▶│DELIVERY_ │
│ 결제검증완료 │ │ 경로계산중 │ │CREATING │
└─────────────────┘ └──────────────────┘ │ 배송생성중 │
└──────┬──────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Saga 완료 - 주문 확정 │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────┐
│ CONFIRMED │
│ 주문확정 │
└──────┬──────┘
│
┌───────────────────────────────────────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ HUB_WAITING │ │HUB_IN_TRANSIT│ │LAST_MILE_ │
│ 허브대기중 │─────────────────────────────────▶│ 허브배송중 │────▶│READY │
└─────────────┘ (허브배송 필요 시) └─────────────┘ │업체배송준비 │
│ └──────┬──────┘
▼ │
┌─────────────┐ │
│ HUB_ARRIVED │────────────┘
│ 수령허브도착 │
└─────────────┘
│
▼
┌─────────────┐
│ IN_DELIVERY │
│ 배송중 │
└──────┬──────┘
│
┌───────────────────────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ COMPLETED │ │ CANCELLED │ │ FAILED │
│ 배송완료 │ │ 취소됨 │ │ 실패 │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ COMPENSATED │
│ 보상완료 │
└─────────────┘
Order Service는 Saga Orchestration 패턴을 사용하여 분산 트랜잭션을 관리합니다.
public class OrderSaga {
private SagaId sagaId;
private OrderId orderId;
private SagaStatus status; // PENDING → IN_PROGRESS → COMPLETED/COMPENSATED
private SagaStep currentStep; // 현재 실행 중인 Step
private CompensationData compensationData; // 보상에 필요한 데이터
private LocalDateTime startedAt;
private LocalDateTime completedAt;
private String failureReason;
private List<SagaStepHistory> stepHistory; // Step 실행 이력
}┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Saga Step 흐름도 │
└─────────────────────────────────────────────────────────────────────────────────────┘
Forward Steps (정방향)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ STOCK_RESERVE│───▶│PAYMENT_VERIFY│───▶│ROUTE_CALCULATE───▶│HUB_DELIVERY_ │
│ 재고 예약 │ │ 결제 검증 │ │ 경로 계산 │ │CREATE │
│ [필수] │ │ [필수] │ │ [조건부] │ │허브배송 생성 │
│ 보상: 복원 │ │ 보상: 취소 │ │ 보상: 없음 │ │ [조건부] │
└──────────────┘ └──────────────┘ └──────────────┘ │ 보상: 취소 │
└───────┬──────┘
│
┌───────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│LAST_MILE_DELIVERY│───▶│NOTIFICATION_SEND │───▶│ TRACKING_START │
│CREATE │ │ 알림 발송 │ │ 추적 시작 │
│업체배송 생성 │ │ [Best Effort] │ │ [Best Effort] │
│ [필수] │ │ 보상: 없음 │ │ 보상: 없음 │
│ 보상: 취소 │ │ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
▼
[Saga 완료]
Compensation Steps (보상 - 역순)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
실패 발생 시 완료된 Step들을 역순으로 보상:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐
│LAST_MILE_DELIVERY│◀───│HUB_DELIVERY_ │◀───│ PAYMENT_CANCEL │◀───│ STOCK_RESTORE│
│CANCEL │ │CANCEL │ │ 결제 취소/환불 │ │ 재고 복원 │
│업체배송 취소 │ │허브배송 취소 │ │ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────┘
| 상태 | 설명 |
|---|---|
PENDING |
시작 대기 |
IN_PROGRESS |
진행 중 |
COMPLETED |
모든 Step 성공 완료 |
COMPENSATING |
보상 트랜잭션 실행 중 |
COMPENSATED |
보상 완료 (롤백됨) |
COMPENSATION_FAILED |
보상 실패 (수동 개입 필요) |
FAILED |
실패 |
| 상태 | 설명 |
|---|---|
PENDING |
실행 대기 |
IN_PROGRESS |
실행 중 |
SUCCESS |
성공 |
FAILED |
실패 |
COMPENSATED |
보상 완료 |
Base Path: /v1/order/web/company-user
| Method | Endpoint | 설명 |
|---|---|---|
POST |
/orders |
주문 생성 |
GET |
/my-orders |
내 주문 목록 조회 |
GET |
/orders/{id} |
주문 상세 조회 |
POST |
/cancel/{id} |
주문 취소 |
POST /v1/order/web/company-user/orders
X-User-Id: user-001
Content-Type: application/jsonRequest
{
"supplierCompanyId": "company-001",
"supplierHubId": "hub-seoul-001",
"receiverCompanyId": "company-002",
"receiverHubId": "hub-busan-001",
"productId": "prod-001",
"quantity": 100,
"unitPrice": 15000.00,
"receiverName": "김수령",
"receiverPhone": "010-1234-5678",
"receiverEmail": "receiver@example.com",
"deliveryAddress": "부산광역시 해운대구 센텀로 123",
"deliveryAddressDetail": "B동 501호",
"deliveryPostalCode": "48058",
"deliveryNote": "경비실에 맡겨주세요",
"requestedDeliveryDate": "2025-01-20",
"requestedDeliveryTime": "14:00:00",
"specialInstructions": "파손 주의",
"pgProvider": "TOSS",
"pgPaymentId": "pg-payment-001"
}Response (200 OK)
{
"success": true,
"data": {
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"status": "PENDING",
"statusDescription": "주문 생성",
"totalAmount": 1500000.00,
"requestedDeliveryDate": "2025-01-20T14:00:00",
"createdAt": "2025-01-15T10:30:00",
"message": "주문이 접수되었습니다. 재고 확인 중입니다."
},
"message": "주문이 생성되었습니다."
}{
"success": true,
"data": {
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"status": "CONFIRMED",
"statusDescription": "주문 확정",
"companyInfo": {
"supplierCompanyId": "company-001",
"supplierHubId": "hub-seoul-001",
"receiverCompanyId": "company-002",
"receiverHubId": "hub-busan-001"
},
"productInfo": {
"productId": "prod-001",
"productHubId": "hub-seoul-001",
"quantity": 100
},
"amountInfo": {
"unitPrice": 15000.00,
"totalAmount": 1500000.00,
"paymentId": "payment-001"
},
"receiverInfo": {
"receiverName": "김수령",
"receiverPhone": "010-1234-5678",
"receiverEmail": "receiver@example.com",
"deliveryAddress": "부산광역시 해운대구 센텀로 123",
"deliveryAddressDetail": "B동 501호",
"deliveryPostalCode": "48058",
"deliveryNote": "경비실에 맡겨주세요"
},
"requestInfo": {
"requestedDeliveryDate": "2025-01-20",
"requestedDeliveryTime": "14:00:00",
"specialInstructions": "파손 주의"
},
"deliveryInfo": {
"requiresHubDelivery": true,
"hubDeliveryId": "hub-delivery-001",
"lastMileDeliveryId": "last-mile-001"
},
"deliveryProgressInfo": {
"actualDepartureTime": null,
"hubArrivalTime": null,
"finalDeliveryStartTime": null,
"actualDeliveryTime": null,
"signature": null,
"actualReceiverName": null
},
"estimatedDeliveryTime": "2025-01-20T13:30:00",
"calculatedDepartureDeadline": "2025-01-19T18:00:00",
"pgProvider": "TOSS",
"pgPaymentId": "pg-payment-001",
"createdAt": "2025-01-15T10:30:00",
"cancelReason": null,
"cancelledAt": null,
"cancellable": true
}
}Base Path: /v1/order/web/hub-manager
| Method | Endpoint | 설명 |
|---|---|---|
GET |
/hub-orders |
허브 주문 목록 조회 (페이징) |
GET |
/hub-orders/search |
허브 주문 검색 (필터링) |
GET |
/orders/{id} |
주문 상세 조회 (허브 관점) |
GET /v1/order/web/hub-manager/hub-orders/search?status=CONFIRMED&startDate=2025-01-01T00:00:00&endDate=2025-01-31T23:59:59
X-Hub-Id: hub-seoul-001Response
{
"success": true,
"data": {
"content": [
{
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"status": "CONFIRMED",
"statusDescription": "주문 확정",
"supplierCompanyId": "company-001",
"receiverCompanyId": "company-002",
"productId": "prod-001",
"quantity": 100,
"totalAmount": 1500000.00,
"receiverName": "김수령",
"requestedDeliveryDate": "2025-01-20T14:00:00",
"createdAt": "2025-01-15T10:30:00"
}
],
"pageInfo": {
"page": 0,
"size": 20,
"totalElements": 1,
"totalPages": 1
}
}
}{
"success": true,
"data": {
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"status": "CONFIRMED",
"statusDescription": "주문 확정",
"companyInfo": { ... },
"destinationHubId": "hub-busan-001",
"productInfo": { ... },
"receiverInfo": { ... },
"requestInfo": { ... },
"deliveryInfo": { ... },
"deliveryProgressInfo": { ... },
"aiCalculationResult": {
"routeInfoJson": "{\"hubs\":[\"hub-seoul-001\",\"hub-daejeon-001\",\"hub-busan-001\"],\"totalDistance\":450,\"segments\":[...]}",
"calculatedDepartureDeadline": "2025-01-19T18:00:00",
"estimatedDeliveryTime": "2025-01-20T13:30:00",
"aiMessage": "서울→대전→부산 경로로 총 450km, 예상 소요시간 19시간 30분"
},
"createdAt": "2025-01-15T10:30:00",
"departureDeadlinePassed": false
}
}Base Path: /v1/order/web/master
| Method | Endpoint | 설명 |
|---|---|---|
GET |
/orders |
전체 주문 검색 (페이징) |
GET |
/orders/all |
전체 주문 검색 (삭제 포함) |
GET |
/orders/{id} |
주문 상세 조회 (전체 정보) |
GET |
/orders/{id}/with-deleted |
주문 상세 조회 (삭제 포함) |
GET |
/orders/upcoming-deadline |
발송 시한 임박 주문 조회 |
GET |
/orders/overdue |
발송 시한 초과 주문 조회 |
GET |
/orders/{id}/saga |
Saga 상태 조회 (TODO) |
{
"success": true,
"data": {
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"status": "CONFIRMED",
"statusDescription": "주문 확정",
"companyInfo": { ... },
"destinationHubId": "hub-busan-001",
"productInfo": { ... },
"amountInfo": { ... },
"receiverInfo": { ... },
"requestInfo": { ... },
"deliveryInfo": { ... },
"deliveryProgressInfo": { ... },
"aiCalculationResult": { ... },
"pgPaymentInfo": {
"pgProvider": "TOSS",
"pgPaymentId": "pg-payment-001",
"pgPaymentKey": "pg-key-001"
},
"sagaInfo": {
"sagaId": "saga-uuid-001",
"orderId": "order-uuid-001",
"status": "COMPLETED",
"statusDescription": "완료",
"currentStep": "TRACKING_START",
"currentStepDescription": "추적 시작",
"startedAt": "2025-01-15T10:30:05",
"completedAt": "2025-01-15T10:30:15",
"failureReason": null,
"stepHistory": [
{
"step": "STOCK_RESERVE",
"stepDescription": "재고 예약",
"status": "SUCCESS",
"statusDescription": "성공",
"errorMessage": null,
"startedAt": "2025-01-15T10:30:05",
"completedAt": "2025-01-15T10:30:06",
"retryCount": 0
},
{
"step": "PAYMENT_VERIFY",
"stepDescription": "결제 검증",
"status": "SUCCESS",
"statusDescription": "성공",
"errorMessage": null,
"startedAt": "2025-01-15T10:30:06",
"completedAt": "2025-01-15T10:30:08",
"retryCount": 0
}
]
},
"createdBy": "user-001",
"createdAt": "2025-01-15T10:30:00",
"cancelReason": null,
"cancelledAt": null,
"cancellable": true,
"departureDeadlinePassed": false,
"completed": false
}
} Order Service
┌──────────────────────────────────────────┐
│ │
Payment Service │ │ Track Service
┌───────────────┐ │ ┌────────────────────────────────┐ │ ┌───────────────┐
│ │ │ │ PaymentRefundedEventConsumer │ │ │ │
│ payment- │─┼───▶│ │ │ │ tracking- │
│ refunded │ │ │ • handlePaymentRefunded() │ │ │ start- │
│ │ │ │ → 재고 복원 처리 │ │◀──┼─requested │
└───────────────┘ │ └────────────────────────────────┘ │ │ │
│ │ └───────────────┘
┌───────────────┐ │ ┌────────────────────────────────┐ │
│ │ │ │PaymentRefundFailedEventConsumer│ │ Notification
│ payment- │─┼───▶│ │ │ Service
│ refund-failed │ │ │ • handlePaymentRefundFailed() │ │ ┌───────────────┐
│ │ │ └────────────────────────────────┘ │ │ │
└───────────────┘ │ │◀──┼─notification- │
│ │ │ events │
│ ┌────────────────────────────────┐ │ │ │
│ │ KafkaPaymentEventPublisher │ │ └───────────────┘
│ │ │ │
│ │ • publishRefundRequested() │───┼──▶ refund-requested
│ └────────────────────────────────┘ │ (→ Payment Service)
│ │
│ ┌────────────────────────────────┐ │
│ │ KafkaTrackingEventPublisher │ │
│ │ │───┼──▶ tracking-start-requested
│ │ • publishTrackingStartRequested│ │ (→ Track Service)
│ └────────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘
주문 취소 시 결제 환불을 요청합니다.
토픽: refund-requested
{
"eventId": "evt-uuid-001",
"eventType": "REFUND_REQUESTED",
"source": "order-service",
"timestamp": "2025-01-15T11:00:00",
"paymentId": "payment-001",
"orderId": "order-uuid-001",
"refundReason": "고객 요청으로 주문 취소",
"requestedAt": "2025-01-15T11:00:00"
}Saga 완료 후 배송 추적 시작을 요청합니다.
토픽: tracking-start-requested
{
"eventId": "evt-uuid-002",
"eventType": "TRACKING_START_REQUESTED",
"source": "order-service",
"timestamp": "2025-01-15T10:30:15",
"orderId": "order-uuid-001",
"orderNumber": "ORD-20250115-001",
"hubDeliveryId": "hub-delivery-001",
"lastMileDeliveryId": "last-mile-001",
"originHubId": "hub-seoul-001",
"destinationHubId": "hub-busan-001",
"routingHub": "{\"hubs\":[\"hub-seoul-001\",\"hub-daejeon-001\",\"hub-busan-001\"]}",
"requiresHubDelivery": true,
"estimatedDeliveryTime": "2025-01-20T13:30:00",
"requestedAt": "2025-01-15T10:30:15"
}주문 상태 변경 시 알림 발송을 요청합니다.
토픽: notification-events
{
"eventId": "evt-uuid-003",
"eventType": "NOTIFICATION_REQUESTED",
"source": "order-service",
"timestamp": "2025-01-15T10:30:15",
"orderId": "order-uuid-001",
"receiverName": "김수령",
"receiverPhone": "010-1234-5678",
"receiverEmail": "receiver@example.com",
"orderNumber": "ORD-20250115-001",
"estimatedDeliveryTime": "2025-01-20T13:30:00",
"deliveryAddress": "부산광역시 해운대구 센텀로 123",
"notificationType": "ORDER_CONFIRMED",
"requestedAt": "2025-01-15T10:30:15"
}환불 완료 시 재고 복원 처리를 수행합니다.
토픽: payment-refunded
{
"eventId": "evt-uuid-004",
"eventType": "PAYMENT_REFUNDED",
"source": "payment-service",
"timestamp": "2025-01-15T11:05:00",
"paymentId": "payment-001",
"orderId": "order-uuid-001",
"refundAmount": 1500000.00,
"totalRefundedAmount": 1500000.00,
"refundReason": "고객 요청으로 주문 취소",
"pgRefundId": "pg-refund-001",
"fullRefund": true,
"refundedAt": "2025-01-15T11:05:00"
}처리 로직:
compensationService.handlePaymentRefunded(event);
// → Inventory Service에 재고 복원 요청환불 실패 시 에러 처리 및 수동 개입 요청을 수행합니다.
토픽: payment-refund-failed
{
"eventId": "evt-uuid-005",
"eventType": "PAYMENT_REFUND_FAILED",
"source": "payment-service",
"timestamp": "2025-01-15T11:05:00",
"paymentId": "payment-001",
"orderId": "order-uuid-001",
"requestedRefundAmount": 1500000.00,
"errorMessage": "PG사 통신 오류",
"failedAt": "2025-01-15T11:05:00"
}처리 로직:
compensationService.handlePaymentRefundFailed(event);
// → Saga 상태를 COMPENSATION_FAILED로 변경, 관리자 알림# 서버 설정
APP_PORT=4010
# 데이터베이스
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/order_db
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=password
# Eureka
EUREKA_DEFAULT_ZONE=http://localhost:8761/eureka/
# Kafka
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
# Keycloak
KEYCLOAK_ISSUER_URI=http://localhost:8080/realms/early-express
# Kafka Topics
KAFKA_TOPIC_REFUND_REQUESTED=refund-requested
KAFKA_TOPIC_PAYMENT_REFUNDED=payment-refunded
KAFKA_TOPIC_PAYMENT_REFUND_FAILED=payment-refund-failed
KAFKA_TOPIC_TRACKING_START_REQUESTED=tracking-start-requested
KAFKA_TOPIC_NOTIFICATION_EVENTS=notification-events
# Feign Client URLs (Eureka 사용 시 서비스명으로 대체)
INVENTORY_SERVICE_URL=http://inventory-service
PAYMENT_SERVICE_URL=http://payment-service
HUB_SERVICE_URL=http://hub-service
HUB_DELIVERY_SERVICE_URL=http://hub-delivery-service
LAST_MILE_DELIVERY_SERVICE_URL=http://last-mile-delivery-service
AI_SERVICE_URL=http://ai-servicespring:
application:
name: order-service
kafka:
consumer:
group-id: order-service-group
enable-auto-commit: false # 수동 ACK
auto-offset-reset: earliest
producer:
acks: all
eureka:
client:
service-url:
defaultZone: ${EUREKA_DEFAULT_ZONE}- Java 21
- PostgreSQL 15+
- Apache Kafka
- Eureka Server
- Keycloak
- 연동 서비스들 (Inventory, Payment, Hub, Delivery, Track, Notification)
# 1. 데이터베이스 생성
createdb order_db
# 2. 환경 변수 설정
export $(cat .env | xargs)
# 3. 애플리케이션 실행
./gradlew bootRundocker-compose up -d order-servicecurl http://localhost:4010/actuator/healthsrc/main/java/com/early_express/order_service/
├── domain/order/
│ ├── application/
│ │ ├── dto/
│ │ │ └── OrderCreateCommand.java
│ │ └── service/
│ │ ├── OrderCommandService.java
│ │ ├── OrderQueryService.java
│ │ ├── OrderSagaService.java
│ │ └── OrderCompensationService.java
│ │
│ ├── domain/
│ │ ├── exception/
│ │ │ ├── OrderErrorCode.java
│ │ │ ├── OrderException.java
│ │ │ └── SagaException.java
│ │ ├── messaging/
│ │ │ ├── notification/
│ │ │ │ ├── NotificationEventPublisher.java
│ │ │ │ ├── NotificationRequestedEventData.java
│ │ │ │ └── event/
│ │ │ │ └── NotificationRequestedEvent.java
│ │ │ ├── payment/
│ │ │ │ ├── PaymentEventPublisher.java
│ │ │ │ └── RefundRequestedEventData.java
│ │ │ └── tracking/
│ │ │ ├── TrackingEventPublisher.java
│ │ │ ├── TrackingStartRequestedEventData.java
│ │ │ └── event/
│ │ │ └── TrackingStartRequestedEvent.java
│ │ ├── model/
│ │ │ ├── Order.java # Aggregate Root
│ │ │ ├── OrderSaga.java # Saga Aggregate
│ │ │ ├── OrderStatus.java
│ │ │ ├── SagaStatus.java
│ │ │ ├── SagaStep.java
│ │ │ ├── SagaStepHistory.java
│ │ │ ├── StepStatus.java
│ │ │ └── vo/
│ │ │ ├── OrderId.java
│ │ │ ├── OrderNumber.java
│ │ │ ├── SagaId.java
│ │ │ ├── CompanyInfo.java
│ │ │ ├── ProductInfo.java
│ │ │ ├── AmountInfo.java
│ │ │ ├── ReceiverInfo.java
│ │ │ ├── RequestInfo.java
│ │ │ ├── DeliveryInfo.java
│ │ │ ├── DeliveryProgressInfo.java
│ │ │ ├── AiCalculationResult.java
│ │ │ ├── PgPaymentInfo.java
│ │ │ └── CompensationData.java
│ │ └── repository/
│ │ ├── OrderRepository.java
│ │ └── OrderSagaRepository.java
│ │
│ ├── infrastructure/
│ │ ├── client/ # Feign Clients
│ │ │ ├── inventory/
│ │ │ ├── payment/
│ │ │ ├── hub/
│ │ │ ├── hubdelivery/
│ │ │ ├── lastmiledelivery/
│ │ │ └── ai/
│ │ ├── messaging/
│ │ │ ├── notification/
│ │ │ │ └── producer/
│ │ │ │ └── KafkaNotificationEventPublisher.java
│ │ │ ├── payment/
│ │ │ │ ├── consumer/
│ │ │ │ │ ├── PaymentRefundedEventConsumer.java
│ │ │ │ │ └── PaymentRefundFailedEventConsumer.java
│ │ │ │ ├── event/
│ │ │ │ │ ├── RefundRequestedEvent.java
│ │ │ │ │ ├── PaymentRefundedEvent.java
│ │ │ │ │ └── PaymentRefundFailedEvent.java
│ │ │ │ └── producer/
│ │ │ │ └── KafkaPaymentEventPublisher.java
│ │ │ └── tracking/
│ │ │ └── producer/
│ │ │ └── KafkaTrackingEventPublisher.java
│ │ └── persistence/
│ │ ├── OrderEntity.java
│ │ ├── OrderSagaEntity.java
│ │ ├── SagaStepHistoryEntity.java
│ │ ├── OrderJpaRepository.java
│ │ └── OrderRepositoryImpl.java
│ │
│ └── presentation/
│ └── web/
│ ├── common/
│ │ └── dto/response/
│ │ ├── OrderSimpleResponse.java
│ │ ├── CompanyInfoDto.java
│ │ ├── ProductInfoDto.java
│ │ ├── AmountInfoDto.java
│ │ ├── ReceiverInfoDto.java
│ │ ├── RequestInfoDto.java
│ │ ├── DeliveryInfoDto.java
│ │ ├── DeliveryProgressInfoDto.java
│ │ └── AiCalculationResultDto.java
│ ├── companyuser/
│ │ ├── CompanyUserOrderController.java
│ │ └── dto/
│ │ ├── request/
│ │ │ ├── OrderCreateRequest.java
│ │ │ └── OrderCancelRequest.java
│ │ └── response/
│ │ ├── OrderCreateResponse.java
│ │ └── OrderDetailResponse.java
│ ├── hubmanager/
│ │ ├── HubManagerOrderController.java
│ │ └── dto/
│ │ ├── request/
│ │ │ ├── HubOrderSearchRequest.java
│ │ │ └── OrderUpdateRequest.java
│ │ └── response/
│ │ └── HubOrderDetailResponse.java
│ └── master/
│ ├── MasterOrderController.java
│ └── dto/
│ ├── request/
│ │ └── OrderSearchRequest.java
│ └── response/
│ ├── MasterOrderDetailResponse.java
│ └── OrderSagaInfoDto.java
│
└── global/
├── common/
│ ├── dto/
│ │ └── PageInfo.java
│ └── utils/
│ └── UuidUtils.java
├── infrastructure/
│ └── event/
│ └── base/
│ └── BaseEvent.java
└── presentation/
└── dto/
├── ApiResponse.java
└── PageResponse.java
- OAuth 2.0 Resource Server: Keycloak JWT 토큰 검증
- 역할별 API 분리:
- Company User:
X-User-Id,X-Company-Id헤더 - Hub Manager:
X-Hub-Id헤더 - Master: 관리자 권한 필요
- Company User:
- 민감 정보 접근 제어:
- PG 결제 키: Master만 조회 가능
- Saga 정보: Master만 조회 가능
- Actuator:
/actuator/health,/actuator/info - Zipkin: 분산 추적 (Saga Step별 추적)
- Loki: 로그 수집
- Prometheus Pushgateway: 메트릭 수집
- Saga 모니터링:
/v1/order/web/master/orders/{id}/saga
| 서비스 | 연동 방식 | 역할 |
|---|---|---|
| Inventory Service | Feign | 재고 예약/해제/확정 |
| Payment Service | Feign + Kafka | 결제 검증, 환불 요청/완료 |
| Hub Service | Feign | 경로 계산, 도착 허브 결정 |
| AI Service | Feign | 배송 시간 계산 |
| Hub Delivery Service | Feign | 허브 배송 생성/취소 |
| Last Mile Delivery Service | Feign | 최종 배송 생성/취소 |
| Track Service | Kafka | 배송 추적 시작 요청 |
| Notification Service | Kafka | 알림 발송 요청 |