PlaceOrderService.java
package com.dmasone.identity.orders.application;
import com.dmasone.identity.catalog.application.StockReservationService;
import com.dmasone.identity.orders.application.events.OrderPlacedEvent;
import com.dmasone.identity.orders.domain.CustomerOrder;
import com.dmasone.identity.orders.domain.IdempotencyKeyConflictException;
import com.dmasone.identity.orders.domain.OrderRepository;
import com.dmasone.identity.sharedkernel.domain.EventPublisher;
import java.time.Clock;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Primary order placement use case. It coordinates module boundaries by asking
* catalog to reserve stock through an application service, saving the order,
* and then publishing an internal event for payment. When clients provide an
* idempotency key, retries return the original order without reserving stock or
* publishing payment-triggering events again.
*/
@Service
public class PlaceOrderService {
private final StockReservationService stockReservationService;
private final OrderRepository orderRepository;
private final EventPublisher eventPublisher;
private final Clock clock;
public PlaceOrderService(
StockReservationService stockReservationService,
OrderRepository orderRepository,
EventPublisher eventPublisher,
Clock clock
) {
this.stockReservationService = stockReservationService;
this.orderRepository = orderRepository;
this.eventPublisher = eventPublisher;
this.clock = clock;
}
@Transactional
public PlaceOrderResult placeOrder(PlaceOrderCommand command) {
Optional<CustomerOrder> existingOrder = existingOrder(command);
if (existingOrder.isPresent()) {
return replay(command, existingOrder.get());
}
CustomerOrder order = CustomerOrder.place(
UUID.randomUUID(),
command.productId(),
command.quantity(),
clock.instant(),
command.idempotencyKey()
);
stockReservationService.reserveStock(order.productId(), order.quantity());
CustomerOrder saved = orderRepository.save(order);
eventPublisher.publish(new OrderPlacedEvent(saved.id(), saved.productId(), saved.quantity()));
return PlaceOrderResult.placed(saved);
}
private Optional<CustomerOrder> existingOrder(PlaceOrderCommand command) {
if (command.idempotencyKey() == null) {
return Optional.empty();
}
return orderRepository.findByIdempotencyKey(command.idempotencyKey());
}
private PlaceOrderResult replay(PlaceOrderCommand command, CustomerOrder existingOrder) {
if (!Objects.equals(existingOrder.productId(), command.productId())
|| existingOrder.quantity() != command.quantity()) {
throw new IdempotencyKeyConflictException(command.idempotencyKey());
}
return PlaceOrderResult.replayed(existingOrder);
}
}