SmartOrder — 파트 4: 인벤토리 서비스 내부
I’m happy to translate the article for you, but I need the full text of the post (the content you’d like translated). Could you please paste the article’s body here? Once I have the text, I’ll provide a Korean translation while preserving the source link, formatting, markdown, and any code blocks or URLs exactly as they appear.
소유권 및 경계
Inventory Service는 각 아이템에 대한 재고 상태 — available (사용 가능), reserved (예약됨), out of stock (품절), discontinued (단종) — 를 소유합니다. 주문 라이프사이클이나 가격 책정은 관리하지 않습니다. 경계는 엄격합니다:
- No cross‑service joins
- No shared schemas
- No synchronous calls
OrderCreated 이벤트를 소비하고 반응함으로써 분리된, 결국 일관성을 갖는 마이크로서비스를 가능하게 합니다 — SmartOrder 아키텍처의 핵심 요소입니다.
도메인 모델 및 commons 통합
@Entity
@Table
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Jacksonized
@EntityListeners(AuditingEntityListener.class)
public class Inventory implements it.portus84.business.commons.model.Entity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String description;
@NotNull
@Builder.Default
@Enumerated(EnumType.STRING)
private InventoryStatus status = InventoryStatus.PENDING;
@CreatedDate
@Setter(AccessLevel.NONE)
@Column(nullable = false, updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Setter(AccessLevel.NONE)
@Column(nullable = false)
private LocalDateTime lastModifiedDate;
}
InventoryStatus는 saga를 구동합니다.@CreatedDate/@LastModifiedDate는 commons‑business auditing을 활용합니다.
리포지토리는 의도적으로 얇게 설계되었습니다:
public interface InventoryRepository extends JpaRepository {}
다중 데이터베이스 영속성
- Inventory Service는 Spring Data JPA와 H2/Postgres를 사용합니다.
- Order Service는 MongoDB를 사용합니다.
왜 혼합했나요?
| Requirement | Choice |
|---|---|
| 원자적인 재고 수준 업데이트 | JPA 트랜잭션 의미론 |
| 예약 (reserve → commit) | 트랜잭션 보장 |
| 감사 및 타임스탬프 | JPA 감사 (commons 통합) |
Commons 모듈은 기본 엔티티, 매퍼 인터페이스 및 헬퍼를 제공하여 서비스 전반에 일관된 패턴을 보장합니다.
이벤트‑드리븐 소비
@Bean
public Consumer orderCreatedConsumer() {
return event -> orderConfirmationPublisher.send(event);
}
spring:
cloud:
function:
definition: orderCreatedConsumer
stream:
function:
bindings:
orderCreatedConsumer-in-0: consumeOrderCreated
bindings:
consumeOrderCreated:
destination: pending-orders
group: inventory-group
inventory-group은 인스턴스 간에 이벤트당 단일 처리를 보장합니다.- 소비자는 무상태이며 완전히 이벤트 기반입니다.
Commons는 공유 DTO 및 바인딩 상수를 제공합니다:
@UtilityClass
public class BindingNames {
public static final String PUBLISH_ORDER_CONFIRMED = "publishOrderConfirmed";
public static final String PUBLISH_ORDER_OUT_OF_STOCK = "publishOrderOutOfStock";
}
가용성 확인 및 조건부 이벤트 발행
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderConfirmationPublisher {
private final StreamBridge streamBridge;
private final InventoryService inventoryService;
public void send(OrderCreatedEvent event) {
boolean available = inventoryService.checkAvailability(event.getOrderId());
if (available) {
streamBridge.send(BindingNames.PUBLISH_ORDER_CONFIRMED,
new OrderConfirmedEvent(event.getOrderId()));
} else {
streamBridge.send(BindingNames.PUBLISH_ORDER_OUT_OF_STOCK,
new OrderOutOfStockEvent(event.getOrderId(),
"Insufficient stock"));
}
}
}
- 결정 로직은 명령형이지만 이벤트를 통해 완전히 분리됩니다.
- 공통에서 공유되는 events‑model은 일관된 직렬화를 보장합니다.
StreamBridge는 출력 바인딩의 동적 선택을 가능하게 합니다.
REST 응답에서 HATEOAS
@Override
public ResponseEntity> getInventoryById(UUID id) {
return inventoryService.findById(id)
.map(inventoryMapper::toDTO)
.map(hateoasHelper::toEntityModel)
.map(ResponseEntity::ok)
.orElseThrow(() -> new InventoryNotFoundException(id));
}
hateoasHelper를 통해 생성된 HATEOAS 링크가 클라이언트를 동적으로 안내합니다.- URI를 하드코딩하지 않고 PATCH / UPDATE 작업을 지원합니다.
Commons는 공유 매퍼 인터페이스, 페이지 매퍼 및 HATEOAS 헬퍼를 제공합니다.
이벤트 기반 워크플로우: 해결된 과제
- Order Service 가
OrderCreated를 발행합니다. - Inventory Service 가 이를 소비합니다.
OrderConfirmationPublisher가 재고에 따라 결정합니다.OrderConfirmedEvent또는OrderOutOfStockEvent가 발행됩니다.- Order Service 가 그에 따라 상태를 업데이트합니다.
해결된 과제
| 과제 | 해결책 |
|---|---|
| 이벤트 기반 재고 예약 | 분산 트랜잭션 없음; 이벤트를 통한 최종 일관성 |
| 원자적 가용성 검사 | Inventory Service 의 JPA 트랜잭션 의미론 |
| 보일러플레이트 감소 | 공통 인프라 공유 (엔티티, 매퍼, DTO, 헬퍼) |
TODO: checkAvailability
@Override
public boolean checkAvailability(String orderId) {
// TODO: 각각 검증
}
구현에서는 주문 라인을 가져오고, 필요한 수량을 합산한 뒤 현재 재고와 비교하며, 필요에 따라 단일 트랜잭션 내에서 항목을 예약해야 합니다.
실제 재고 대비 제품 항목
return true;
플레이스홀더는 항상
true를 반환합니다.
개발자를 위한 다음 단계
OrderCreatedEvent에서 제품 라인 항목을 통합하고 재고 레코드별로 수량을 검증합니다.
Note: 이 TODO는 이 기사 범위에 포함되지 않지만, 전체 재고 정확성을 위해 필수적입니다.
부트스트랩 및 테스트 데이터 (개발자 힌트)
@AutoConfiguration
@ConditionalOnClass(Instancio.class)
public class TestDataLoaderConfiguration {
@Bean
CommandLineRunner loadTestData(InventoryService service) {
return args -> service.saveAll(
Instancio.ofList(Inventory.class)
.size(5)
.ignoreFields()
.create()
);
}
}
- Instancio는 로컬 개발을 위한 시드 데이터를 생성합니다.
- 프로덕션에 영향을 주지 않고 일관된 설정을 보장합니다.
다음 단계: commons 모듈
Focus will be on services/commons:
Shared business logic, DTOs, event definitions, and helpers used across order-service and inventory-service.
Includes
- Entity base classes
- MapStruct mappers
- HATEOAS helpers
- Test data loaders
Commons are key to scaling SmartOrder without duplicating boilerplate.
Repository: →