Early Express 물류 플랫폼의 허브 간 배송 담당자 관리 서비스입니다. 허브와 허브 사이의 대량 물류 운송을 담당하는 드라이버를 관리하며, 전사 단위의 자동 배정 알고리즘을 통해 효율적인 허브 간 물류 운영을 지원합니다.
Hub Driver Service는 허브 간 배송(Hub-to-Hub Delivery)을 담당하는 드라이버를 관리하는 서비스입니다.
- 드라이버 관리: 허브 간 배송 담당자 등록, 상태 관리, 근무 관리
- 전사 자동 배정: 우선순위 기반 드라이버 자동 배정 (Round-Robin)
- 배송 통계: 총 배송 건수, 평균 배송 시간 등 성과 지표 관리
- 마스터 관리: 전사 드라이버 현황 모니터링
| 구분 | Hub Driver | Last Mile Driver |
|---|---|---|
| 담당 구간 | 허브 ↔ 허브 | 허브 → 최종 목적지 |
| 소속 | 전사 소속 (hubId 없음) | 특정 허브 소속 |
| 배정 범위 | 전국 허브 간 이동 가능 | 소속 허브 내 배송만 |
| 배정 방식 | 전사 우선순위 기반 | 허브별 우선순위 기반 |
| 차량 유형 | 대형 화물차 | 소형 배송 차량 |
| 구분 | 기술 |
|---|---|
| Framework | Spring Boot 3.x |
| Language | Java 21 |
| Database | PostgreSQL |
| Service Discovery | Netflix Eureka |
| Security | OAuth 2.0 Resource Server (Keycloak) |
| Build Tool | Gradle |
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Hub Driver Service │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Presentation Layer │
│ ┌───────────────────────────────────┐ ┌───────────────────────────────────┐ │
│ │ MasterController │ │ InternalController │ │
│ │ /web/master │ │ /internal │ │
│ │ │ │ │ │
│ │ • 전체 드라이버 목록 조회 │ │ • 드라이버 생성 │ │
│ │ • 드라이버 상세 조회 │ │ • 자동 배정 │ │
│ │ • 상태별 필터링 │ │ • 배송 완료/취소 통지 │ │
│ └───────────────────────────────────┘ └───────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │
│ │ HubDriverCommandService │ │ HubDriverQueryService │ │
│ │ • create() │ │ • findAll() │ │
│ │ • assignDelivery() │ │ • findById() │ │
│ │ • completeDelivery() │ │ • findByStatus() │ │
│ │ • cancelDelivery() │ │ │ │
│ └──────────────────────────────┘ └──────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ HubDriver (Aggregate Root) │ │
│ │ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ HubDriverId │ │ HubDriverStatus │ │ │
│ │ │ (Value Object) │ │ (Enum) │ │ │
│ │ └─────────────────┘ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ ┌────────────────────────────┐ │
│ │ PostgreSQL │ │
│ │ Repository │ │
│ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌─────────────────────────────┐
│ User Service │ │ Hub Driver Service │
│ │───── Feign ───────▶│ │
│ (드라이버 등록) │ POST /internal/ │ • 드라이버 생성 │
│ │ drivers │ • 상태: AVAILABLE │
└─────────────────┘ └─────────────────────────────┘
▲
│
│ Feign
│
┌─────────────────────────────────────────────────────────────────────┐
│ Hub Delivery Service │
│ │
│ 허브 배송 생성 시: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ POST /internal/drivers/assign │ │
│ │ → 전사 AVAILABLE 드라이버 중 우선순위가 가장 낮은 드라이버 선택│ │
│ │ → 드라이버 상태: AVAILABLE → ON_DELIVERY │ │
│ │ → currentDeliveryId에 hubDeliveryId 저장 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 허브 배송 완료 시: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PUT /internal/drivers/{driverId}/complete │ │
│ │ → 통계 업데이트 (totalDeliveries, averageDeliveryTimeMin) │ │
│ │ → 드라이버 상태: ON_DELIVERY → AVAILABLE │ │
│ │ → assignmentPriority 초기화 (0) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 허브 배송 취소 시: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PUT /internal/drivers/{driverId}/cancel │ │
│ │ → 배정 해제, 드라이버 상태: ON_DELIVERY → AVAILABLE │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Early Express 물류 흐름 │
└─────────────────────────────────────────────────────────────────────────────────────┘
[공급업체] [수령업체]
│ ▲
│ 상품 출고 │ 상품 수령
▼ │
┌─────────┐ ┌─────────┐
│ 출발 │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 도착 │
│ 허브 │───▶│ 경유 │───▶│ 경유 │───▶│ 경유 │────────────▶│ 허브 │
│ (서울) │ │ 허브 │ │ 허브 │ │ 허브 │ │ (부산) │
└─────────┘ │ (대전) │ │ (대구) │ │ (울산) │ └─────────┘
└─────────┘ └─────────┘ └─────────┘ │
│
◀──────────────────────────────────────────────────────────▶ │
Hub Driver 담당 구간 │
(허브 간 대량 물류 운송) │
▼
[최종 목적지]
▲
│
Last Mile Driver 담당│
(허브 → 최종 배송) │
public class HubDriver {
// 식별자
private HubDriverId id; // UUID
// 기본 정보
private String userId; // 사용자 ID (User Service 연동)
private String name; // 드라이버 이름
// ※ hubId 없음 - 전사 소속
// 상태 정보
private HubDriverStatus status; // 드라이버 상태
private String currentDeliveryId; // 현재 배송 ID (배송 중일 때)
// 배정 관련
private Integer assignmentPriority; // 배정 우선순위 (낮을수록 우선 배정)
private LocalDateTime availableFrom; // 배정 가능 시점
// 통계
private Long totalDeliveries; // 총 배송 건수
private Long totalDeliveryTimeMin; // 총 배송 시간 (분)
private Long averageDeliveryTimeMin; // 평균 배송 시간 (분)
private LocalDateTime lastDeliveryCompletedAt; // 마지막 배송 완료 시간
// Audit
private LocalDateTime createdAt;
private String createdBy;
private LocalDateTime updatedAt;
private String updatedBy;
private LocalDateTime deletedAt;
private String deletedBy;
private boolean isDeleted;
}| Value Object | 설명 | 주요 필드 |
|---|---|---|
HubDriverId |
드라이버 고유 ID | UUID 기반 value |
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 드라이버 상태 흐름도 │
└─────────────────────────────────────────────────────────────────────────────────────┘
[드라이버 생성]
│
▼
┌─────────────┐ assignDelivery() ┌─────────────┐
│ AVAILABLE │───────────────────────▶│ ON_DELIVERY │
│ 배정 가능 │◀───────────────────────│ 배송 중 │
└─────────────┘ completeDelivery() └─────────────┘
│ cancelDelivery() │
│ │
│ endWork() │ (배송 중에는 불가)
▼ │
┌─────────────┐ │
│ OFF_DUTY │ │
│ 비활성 │ │
└─────────────┘ │
│ │
│ deactivate() │
▼ │
┌─────────────┐ │
│ INACTIVE │◀──────────────────────────────┘
│ 휴직 │ (배송 중에는 휴직 불가)
└─────────────┘
│
│ activate()
▼
┌─────────────┐
│ AVAILABLE │
└─────────────┘
| 상태 | 설명 | 배정 가능 | 근무 중 |
|---|---|---|---|
AVAILABLE |
배정 가능 | ✅ | ✅ |
ON_DELIVERY |
배송 중 | ❌ | ✅ |
OFF_DUTY |
비활성 (퇴근) | ❌ | ❌ |
INACTIVE |
휴직 | ❌ | ❌ |
- 배송 배정:
AVAILABLE상태에서만 배정 가능 - 배송 완료/취소:
ON_DELIVERY상태에서만 가능 - 근무 종료:
ON_DELIVERY상태에서는 근무 종료 불가 - 휴직 처리:
ON_DELIVERY상태에서는 휴직 불가 - 삭제:
ON_DELIVERY상태에서는 삭제 불가 - 우선순위: 배정 시
assignmentPriority증가, 완료 시 0으로 초기화
Base Path: /v1/hub-driver/internal
⚠️ 이 API는 내부 서비스 간 통신용입니다. Gateway를 통해 외부에 노출되지 않습니다.
| Method | Endpoint | 설명 | 호출 서비스 |
|---|---|---|---|
POST |
/drivers |
드라이버 생성 | User Service |
POST |
/drivers/assign |
드라이버 자동 배정 | HubDelivery Service |
PUT |
/drivers/{driverId}/complete |
배송 완료 통지 | HubDelivery Service |
PUT |
/drivers/{driverId}/cancel |
배송 취소 통지 | HubDelivery Service |
User Service에서 허브 간 배송 담당자 등록 시 호출됩니다.
POST /v1/hub-driver/internal/drivers
X-User-Id: admin-001
Content-Type: application/jsonRequest
{
"userId": "user-hub-driver-001",
"name": "박허브"
}Response (200 OK)
{
"driverId": "hub-driver-uuid-001",
"userId": "user-hub-driver-001",
"name": "박허브",
"status": "AVAILABLE"
}💡 LastMileDriver와의 차이:
hubId필드가 없습니다. Hub Driver는 전사 소속이므로 특정 허브에 종속되지 않습니다.
HubDelivery 생성 시 전사의 가용 드라이버 중 우선순위가 가장 낮은 드라이버를 자동 배정합니다.
POST /v1/hub-driver/internal/drivers/assign
Content-Type: application/jsonRequest
{
"hubDeliveryId": "hub-delivery-001"
}Response (200 OK)
{
"driverId": "hub-driver-uuid-001",
"userId": "user-hub-driver-001",
"driverName": "박허브",
"status": "ON_DELIVERY",
"assignedAt": "2025-01-15T10:30:00"
}배정 알고리즘:
- 전사 드라이버 중
AVAILABLE상태인 드라이버 조회 assignmentPriority가 가장 낮은 드라이버 선택- 선택된 드라이버의
assignmentPriority증가 (Round-Robin 효과) - 드라이버 상태를
ON_DELIVERY로 변경 currentDeliveryId에 hubDeliveryId 저장
💡 LastMileDriver와의 차이:
hubId로 필터링하지 않고 전사 범위에서 배정합니다.
허브 배송 완료 시 드라이버 통계를 업데이트하고 상태를 변경합니다.
PUT /v1/hub-driver/internal/drivers/{driverId}/complete
Content-Type: application/jsonRequest
{
"deliveryTimeMin": 180
}Response (200 OK)
{
"driverId": "hub-driver-uuid-001",
"status": "AVAILABLE",
"message": "배송이 완료되었습니다.",
"timestamp": 1705312200000
}처리 내용:
totalDeliveries증가totalDeliveryTimeMin에 배송 시간 추가averageDeliveryTimeMin재계산lastDeliveryCompletedAt업데이트currentDeliveryId초기화 (null)assignmentPriority0으로 리셋- 상태를
AVAILABLE로 변경
허브 배송 취소 시 드라이버 배정을 해제합니다.
PUT /v1/hub-driver/internal/drivers/{driverId}/cancel
Content-Type: application/jsonRequest (Optional)
{
"reason": "경로 변경으로 인한 취소"
}Response (200 OK)
{
"driverId": "hub-driver-uuid-001",
"status": "AVAILABLE",
"message": "배송이 취소되었습니다.",
"timestamp": 1705312200000
}Base Path: /v1/hub-driver/web/master
| Method | Endpoint | 설명 |
|---|---|---|
GET |
/drivers |
전체 드라이버 목록 조회 |
GET |
/drivers/{driverId} |
드라이버 상세 조회 |
GET /v1/hub-driver/web/master/drivers?status=AVAILABLE
X-User-Id: master-001
X-User-Roles: MASTERQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
status |
Enum | ❌ | 드라이버 상태 필터 |
page |
Integer | ❌ | 페이지 번호 (기본값: 0) |
size |
Integer | ❌ | 페이지 크기 (기본값: 20) |
sort |
String | ❌ | 정렬 기준 (기본값: createdAt,DESC) |
Response (200 OK)
{
"success": true,
"data": {
"content": [
{
"driverId": "hub-driver-uuid-001",
"userId": "user-hub-driver-001",
"name": "박허브",
"status": "AVAILABLE",
"statusDescription": "배정 가능",
"currentDeliveryId": null,
"assignmentPriority": 0,
"totalDeliveries": 500,
"totalDeliveryTimeMin": 90000,
"averageDeliveryTimeMin": 180,
"lastDeliveryCompletedAt": "2025-01-15T14:30:00",
"availableFrom": "2025-01-15T14:30:00",
"createdAt": "2024-01-15T10:00:00"
},
{
"driverId": "hub-driver-uuid-002",
"userId": "user-hub-driver-002",
"name": "김간선",
"status": "ON_DELIVERY",
"statusDescription": "배송 중",
"currentDeliveryId": "hub-delivery-003",
"assignmentPriority": 2,
"totalDeliveries": 450,
"totalDeliveryTimeMin": 81000,
"averageDeliveryTimeMin": 180,
"lastDeliveryCompletedAt": "2025-01-15T10:00:00",
"availableFrom": "2025-01-15T09:00:00",
"createdAt": "2024-02-01T10:00:00"
}
],
"pageInfo": {
"page": 0,
"size": 20,
"totalElements": 50,
"totalPages": 3
}
}
}GET /v1/hub-driver/web/master/drivers/{driverId}
X-User-Id: master-001
X-User-Roles: MASTERResponse (200 OK)
{
"success": true,
"data": {
"driverId": "hub-driver-uuid-001",
"userId": "user-hub-driver-001",
"name": "박허브",
"status": "AVAILABLE",
"statusDescription": "배정 가능",
"currentDeliveryId": null,
"assignmentPriority": 0,
"totalDeliveries": 500,
"totalDeliveryTimeMin": 90000,
"averageDeliveryTimeMin": 180,
"lastDeliveryCompletedAt": "2025-01-15T14:30:00",
"availableFrom": "2025-01-15T14:30:00",
"isAvailable": true,
"isOnDelivery": false,
"createdAt": "2024-01-15T10:00:00",
"createdBy": "admin-001"
}
}| 서비스 | 연동 방식 | 역할 |
|---|---|---|
| User Service | Feign (호출받음) | 드라이버 생성 요청 |
| HubDelivery Service | Feign (호출받음) | 배정/완료/취소 통지 |
User Service Hub Driver Service
│ │
│ POST /internal/drivers │
│ {userId, name} │
│─────────────────────────────▶│
│ │ create HubDriver
│ │ status: AVAILABLE
│ │ assignmentPriority: 0
│ {driverId, status} │
│◀─────────────────────────────│
│ │
HubDelivery Service Hub Driver Service
│ │
│ POST /internal/drivers/ │
│ assign │
│ {hubDeliveryId} │
│─────────────────────────────▶│
│ │ 1. 전사 AVAILABLE 드라이버 조회
│ │ 2. assignmentPriority 오름차순 정렬
│ │ 3. 첫 번째 드라이버 선택
│ │ 4. assignDelivery() 호출
│ │ - status: ON_DELIVERY
│ │ - currentDeliveryId 설정
│ │ - assignmentPriority++
│ {driverId, driverName, │
│ status, assignedAt} │
│◀─────────────────────────────│
│ │
HubDelivery Service Hub Driver Service
│ │
│ PUT /internal/drivers/ │
│ {driverId}/complete │
│ {deliveryTimeMin: 180} │
│─────────────────────────────▶│
│ │ completeDelivery() 호출
│ │ - totalDeliveries++
│ │ - totalDeliveryTimeMin += 180
│ │ - averageDeliveryTimeMin 재계산
│ │ - status: AVAILABLE
│ │ - currentDeliveryId: null
│ │ - assignmentPriority: 0
│ {driverId, status, │
│ message: "완료"} │
│◀─────────────────────────────│
│ │
# 서버 설정
APP_PORT=4020
# 데이터베이스
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/hub_driver_db
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=password
# Eureka
EUREKA_DEFAULT_ZONE=http://localhost:8761/eureka/
# Keycloak
KEYCLOAK_ISSUER_URI=http://localhost:8080/realms/early-expressspring:
application:
name: hub-driver-service
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
default_schema: hub_driver
eureka:
client:
service-url:
defaultZone: ${EUREKA_DEFAULT_ZONE}
instance:
prefer-ip-address: true- Java 21
- PostgreSQL 15+
- Eureka Server
- Keycloak
# 1. 데이터베이스 생성
createdb hub_driver_db
# 2. 환경 변수 설정
export $(cat .env | xargs)
# 3. 애플리케이션 실행
./gradlew bootRundocker-compose up -d hub-driver-servicecurl http://localhost:4020/actuator/healthsrc/main/java/com/early_express/hub_driver_service/
├── domain/hub_driver/
│ ├── application/
│ │ ├── command/
│ │ │ ├── HubDriverCommandService.java
│ │ │ └── dto/
│ │ │ └── HubDriverCommandDto.java
│ │ │ ├── CreateCommand
│ │ │ ├── CreateResult
│ │ │ ├── AssignDeliveryCommand
│ │ │ ├── DriverAssignResult
│ │ │ ├── CompleteDeliveryCommand
│ │ │ └── CancelDeliveryCommand
│ │ └── query/
│ │ ├── HubDriverQueryService.java
│ │ └── dto/
│ │ └── HubDriverQueryDto.java
│ │ ├── HubDriverResponse
│ │ └── HubDriverDetailResponse
│ │
│ ├── domain/
│ │ ├── exception/
│ │ │ ├── HubDriverErrorCode.java
│ │ │ └── HubDriverException.java
│ │ ├── model/
│ │ │ ├── HubDriver.java # Aggregate Root
│ │ │ └── vo/
│ │ │ ├── HubDriverId.java
│ │ │ └── HubDriverStatus.java
│ │ └── repository/
│ │ └── HubDriverRepository.java
│ │
│ ├── infrastructure/
│ │ └── persistence/
│ │ ├── HubDriverEntity.java
│ │ ├── HubDriverJpaRepository.java
│ │ └── HubDriverRepositoryImpl.java
│ │
│ └── presentation/
│ ├── internal/
│ │ ├── HubDriverInternalController.java
│ │ └── dto/
│ │ ├── request/
│ │ │ ├── HubDriverCreateRequest.java
│ │ │ ├── DriverAssignRequest.java
│ │ │ ├── DriverCompleteRequest.java
│ │ │ └── DriverCancelRequest.java
│ │ └── response/
│ │ ├── HubDriverCreateResponse.java
│ │ ├── DriverAssignResponse.java
│ │ └── DriverOperationResponse.java
│ └── web/
│ └── master/
│ ├── HubDriverMasterController.java
│ └── dto/response/
│ ├── MasterHubDriverResponse.java
│ └── MasterHubDriverDetailResponse.java
│
└── global/
├── common/dto/
│ └── PageInfo.java
└── presentation/dto/
├── ApiResponse.java
└── PageResponse.java
| 에러 코드 | HTTP Status | 설명 |
|---|---|---|
DRIVER_NOT_FOUND |
404 | 드라이버를 찾을 수 없음 |
DRIVER_NOT_AVAILABLE |
400 | 드라이버가 배정 가능한 상태가 아님 |
DRIVER_NOT_ON_DELIVERY |
400 | 드라이버가 배송 중이 아님 |
DRIVER_ALREADY_ON_DELIVERY |
400 | 드라이버가 이미 배송 중 |
NO_AVAILABLE_DRIVER |
400 | 배정 가능한 드라이버가 없음 |
INVALID_HUB_ID |
400 | 유효하지 않은 허브 ID |
- OAuth 2.0 Resource Server: Keycloak JWT 토큰 검증
- 접근 제어:
- Internal API: 서비스 간 통신만 허용 (Gateway 미노출)
- Master API:
MASTER역할 필요, 전사 드라이버 관리
- Hub Manager API 미제공: 전사 소속이므로 허브 관리자 권한 불필요
- Actuator:
/actuator/health,/actuator/info - 주요 메트릭:
- 전사 가용 드라이버 수
- 평균 허브 간 배송 시간
- 일별 허브 배송 완료 건수
| 서비스 | 연동 방식 | 역할 |
|---|---|---|
| User Service | Feign | 드라이버 등록 시 생성 요청 |
| HubDelivery Service | Feign | 허브 배송 생성/완료/취소 시 드라이버 상태 관리 |
| 항목 | Hub Driver | Last Mile Driver |
|---|---|---|
| 소속 | 전사 | 특정 허브 |
| hubId | ❌ 없음 | ✅ 있음 |
| 배정 범위 | 전국 | 허브 내 |
| 배정 요청 | {hubDeliveryId} |
{hubId, deliveryId} |
| API 구성 | Internal + Master | Internal + HubManager + Master |
| 평균 배송 시간 | 수 시간 (장거리) | 수십 분 (단거리) |