FlashSaleResultHandler.java
package com.ecommerce.marketing.application.service;
import com.ecommerce.marketing.application.exception.MarketingError;
import com.ecommerce.marketing.application.exception.MarketingException;
import com.ecommerce.marketing.application.model.FlashSaleModels.FlashSaleOrderResultCommand;
import com.ecommerce.marketing.domain.FlashSaleAdmissionStatus;
import com.ecommerce.marketing.infrastructure.persistence.entity.FlashSaleAdmissionEntity;
import com.ecommerce.marketing.infrastructure.persistence.mapper.ConsumedEventMapper;
import com.ecommerce.marketing.infrastructure.persistence.mapper.FlashSaleAdmissionMapper;
import com.ecommerce.marketing.infrastructure.messaging.FlashSaleResultConsumerProperties;
import com.ecommerce.platform.common.idempotency.PayloadFingerprint;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class FlashSaleResultHandler {
public static final String CONSUMER_GROUP = "marketing-flash-sale-result-v1";
private final ConsumedEventMapper consumedEventMapper;
private final FlashSaleAdmissionMapper admissionMapper;
private final String consumerGroup;
public FlashSaleResultHandler(
ConsumedEventMapper consumedEventMapper,
FlashSaleAdmissionMapper admissionMapper,
FlashSaleResultConsumerProperties properties) {
this.consumedEventMapper = consumedEventMapper;
this.admissionMapper = admissionMapper;
this.consumerGroup = properties.consumerGroup();
}
@Transactional
public void handle(FlashSaleOrderResultCommand command) {
String payloadFingerprint = PayloadFingerprint.of(command);
if (consumedEventMapper.insertIfAbsent(
command.eventId(), consumerGroup, payloadFingerprint,
admissionMapper.currentTime()) != 1) {
String storedFingerprint = consumedEventMapper.selectPayloadFingerprint(
command.eventId(), consumerGroup);
if (!PayloadFingerprint.matches(storedFingerprint, payloadFingerprint)) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
return;
}
FlashSaleAdmissionEntity admission = admissionMapper.selectByTokenForUpdate(command.requestToken());
if (admission == null) {
throw new MarketingException(MarketingError.RESOURCE_NOT_FOUND);
}
if (!admission.getActivityNo().equals(command.activityNo())
|| !admission.getUserId().equals(command.userId())) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
if (FlashSaleAdmissionStatus.ORDER_CREATED.name().equals(admission.getStatus())
|| FlashSaleAdmissionStatus.FAILED.name().equals(admission.getStatus())) {
requireSameTerminalResult(admission, command);
return;
}
if (!FlashSaleAdmissionStatus.QUEUED.name().equals(admission.getStatus())
&& !FlashSaleAdmissionStatus.RESULT_UNKNOWN.name().equals(admission.getStatus())) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
switch (command.eventType()) {
case "FlashSaleOrderSucceeded" -> {
if (command.orderNo() == null || command.orderNo().isBlank()) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
admission.setStatus(FlashSaleAdmissionStatus.ORDER_CREATED.name());
admission.setOrderNo(command.orderNo());
admission.setFailureCode(null);
}
case "FlashSaleOrderFailed" -> {
if (command.failureCode() == null || command.failureCode().isBlank()) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
admission.setStatus(FlashSaleAdmissionStatus.FAILED.name());
admission.setFailureCode(command.failureCode());
}
default -> throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
admission.setCompletedAt(command.completedAt());
admission.setUpdatedAt(admissionMapper.currentTime());
requireUpdated(admissionMapper.updateById(admission));
}
private void requireSameTerminalResult(
FlashSaleAdmissionEntity admission,
FlashSaleOrderResultCommand command) {
boolean sameSuccess = FlashSaleAdmissionStatus.ORDER_CREATED.name().equals(admission.getStatus())
&& "FlashSaleOrderSucceeded".equals(command.eventType())
&& admission.getOrderNo().equals(command.orderNo());
boolean sameFailure = FlashSaleAdmissionStatus.FAILED.name().equals(admission.getStatus())
&& "FlashSaleOrderFailed".equals(command.eventType())
&& admission.getFailureCode().equals(command.failureCode());
if (!sameSuccess && !sameFailure) {
throw new MarketingException(MarketingError.IDEMPOTENCY_CONFLICT);
}
}
private void requireUpdated(int rows) {
if (rows != 1) {
throw new MarketingException(MarketingError.CONCURRENT_MODIFICATION);
}
}
}