SmartOrder — 파트 4: 인벤토리 서비스 내부

발행: (2026년 3월 9일 PM 06:54 GMT+9)
7 분 소요
원문: Dev.to

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;
}
  • InventoryStatussaga를 구동합니다.
  • @CreatedDate / @LastModifiedDatecommons‑business auditing을 활용합니다.

리포지토리는 의도적으로 얇게 설계되었습니다:

public interface InventoryRepository extends JpaRepository {}

다중 데이터베이스 영속성

  • Inventory Service는 Spring Data JPA와 H2/Postgres를 사용합니다.
  • Order Service는 MongoDB를 사용합니다.

왜 혼합했나요?

RequirementChoice
원자적인 재고 수준 업데이트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 헬퍼를 제공합니다.

이벤트 기반 워크플로우: 해결된 과제

  1. Order ServiceOrderCreated 를 발행합니다.
  2. Inventory Service 가 이를 소비합니다.
  3. OrderConfirmationPublisher 가 재고에 따라 결정합니다.
  4. OrderConfirmedEvent 또는 OrderOutOfStockEvent 가 발행됩니다.
  5. 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:

0 조회
Back to Blog

관련 글

더 보기 »