Outbox Message and Scheduler class bug fixed.
This commit is contained in:
@@ -34,7 +34,7 @@ public class RestaurantEntity {
|
|||||||
|
|
||||||
private BigDecimal productPrice;
|
private BigDecimal productPrice;
|
||||||
|
|
||||||
private Boolean productActive;
|
private Boolean productAvailable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
SCHEMA_REGISTRY_HOST_NAME: schema-registry
|
SCHEMA_REGISTRY_HOST_NAME: schema-registry
|
||||||
SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
|
SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
|
||||||
SCHEMA_REGISTRY_LISTENERS: http://schema-registry:8081
|
SCHEMA_REGISTRY_LISTENERS: http://schema-registry:8081
|
||||||
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka-broker-2:9092,LISTENER_LOCAL://localhost:29092
|
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka-broker-1:9092,LISTENER_LOCAL://localhost:19092,PLAINTEXT://kafka-broker-2:9092,LISTENER_LOCAL://localhost:29092,PLAINTEXT://kafka-broker-3:9092,LISTENER_LOCAL://localhost:39092
|
||||||
SCHEMA_REGISTRY_DEBUG: 'true'
|
SCHEMA_REGISTRY_DEBUG: 'true'
|
||||||
networks:
|
networks:
|
||||||
- ${GLOBAL_NETWORK:-kafka}
|
- ${GLOBAL_NETWORK:-kafka}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package com.food.order.system.outbox.scheduler.approval;
|
package com.food.order.system.outbox.scheduler.approval;
|
||||||
|
|
||||||
import com.food.order.system.domain.exception.OrderDomainException;
|
|
||||||
import com.food.order.system.outbox.OutboxScheduler;
|
import com.food.order.system.outbox.OutboxScheduler;
|
||||||
import com.food.order.system.outbox.OutboxStatus;
|
import com.food.order.system.outbox.OutboxStatus;
|
||||||
import com.food.order.system.outbox.model.approval.OrderApprovalOutboxMessage;
|
import com.food.order.system.outbox.model.approval.OrderApprovalOutboxMessage;
|
||||||
import com.food.order.system.saga.SagaStatus;
|
|
||||||
import com.food.order.system.ports.output.message.publisher.restaurantapproval.RestaurantApprovalRequestMessagePublisher;
|
import com.food.order.system.ports.output.message.publisher.restaurantapproval.RestaurantApprovalRequestMessagePublisher;
|
||||||
|
import com.food.order.system.saga.SagaStatus;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -28,28 +28,20 @@ public class RestaurantApprovalOutboxScheduler implements OutboxScheduler {
|
|||||||
@Scheduled(fixedDelayString = "${order-service.outbox-scheduler-fixed-rate}",
|
@Scheduled(fixedDelayString = "${order-service.outbox-scheduler-fixed-rate}",
|
||||||
initialDelayString = "${order-service.outbox-scheduler-initial-delay}")
|
initialDelayString = "${order-service.outbox-scheduler-initial-delay}")
|
||||||
public void processOutboxMessage() {
|
public void processOutboxMessage() {
|
||||||
|
Optional<List<OrderApprovalOutboxMessage>> outboxMessagesResponse =
|
||||||
log.info("Processing outbox message STARTED !");
|
|
||||||
|
|
||||||
var outboxMessageResponse =
|
|
||||||
approvalOutboxHelper.getApprovalOutboxMessageByOutboxStatusAndSagaStatus(
|
approvalOutboxHelper.getApprovalOutboxMessageByOutboxStatusAndSagaStatus(
|
||||||
OutboxStatus.STARTED,
|
OutboxStatus.STARTED,
|
||||||
SagaStatus.STARTED,
|
SagaStatus.PROCESSING);
|
||||||
SagaStatus.COMPENSATING)
|
if (outboxMessagesResponse.isPresent() && outboxMessagesResponse.get().size() > 0) {
|
||||||
.orElseThrow(
|
List<OrderApprovalOutboxMessage> outboxMessages = outboxMessagesResponse.get();
|
||||||
() -> new OrderDomainException("No outbox message found for processing"));
|
log.info("Received {} OrderApprovalOutboxMessage with ids: {}, sending to message bus!",
|
||||||
|
outboxMessages.size(),
|
||||||
|
outboxMessages.stream().map(outboxMessage ->
|
||||||
|
outboxMessage.getId().toString()).collect(Collectors.joining(",")));
|
||||||
|
outboxMessages.forEach(outboxMessage ->
|
||||||
|
restaurantApprovalRequestMessagePublisher.publish(outboxMessage, this::updateOutboxStatus));
|
||||||
|
log.info("{} OrderApprovalOutboxMessage sent to message bus!", outboxMessages.size());
|
||||||
|
|
||||||
if (Objects.nonNull(outboxMessageResponse) && outboxMessageResponse.size() > 0) {
|
|
||||||
|
|
||||||
log.info("Received {} OrderPaymentOutboxMessage with ids : {} , sending message bus !" ,
|
|
||||||
outboxMessageResponse.size(),
|
|
||||||
outboxMessageResponse.stream().map(orderPaymentOutboxMessage -> orderPaymentOutboxMessage.getId().toString())
|
|
||||||
.collect(Collectors.joining(",")));
|
|
||||||
outboxMessageResponse.forEach(orderPaymentOutboxMessage -> {
|
|
||||||
restaurantApprovalRequestMessagePublisher.publish
|
|
||||||
(orderPaymentOutboxMessage,this::updateOutboxStatus);
|
|
||||||
});
|
|
||||||
log.info("Processing outbox message completed ! ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.food.order.system.messaging.listener.kafka;
|
package com.food.order.system.messaging.listener.kafka;
|
||||||
|
|
||||||
|
import com.food.order.system.domain.exception.OrderNotFoundException;
|
||||||
import com.food.order.system.kafka.consumer.KafkaConsumer;
|
import com.food.order.system.kafka.consumer.KafkaConsumer;
|
||||||
import com.food.order.system.kafka.order.avro.model.PaymentResponseAvroModel;
|
import com.food.order.system.kafka.order.avro.model.PaymentResponseAvroModel;
|
||||||
import com.food.order.system.kafka.order.avro.model.PaymentStatus;
|
import com.food.order.system.kafka.order.avro.model.PaymentStatus;
|
||||||
@@ -7,6 +8,7 @@ import com.food.order.system.messaging.mapper.OrderMessagingDataMapper;
|
|||||||
import com.food.order.system.ports.input.message.listener.payment.PaymentResponseMessageListener;
|
import com.food.order.system.ports.input.message.listener.payment.PaymentResponseMessageListener;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.kafka.annotation.KafkaListener;
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
import org.springframework.kafka.support.KafkaHeaders;
|
import org.springframework.kafka.support.KafkaHeaders;
|
||||||
import org.springframework.messaging.handler.annotation.Header;
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
@@ -33,20 +35,31 @@ public class PaymentResponseKafkaListener implements KafkaConsumer<PaymentRespon
|
|||||||
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions,
|
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions,
|
||||||
@Header(KafkaHeaders.OFFSET) List<Long> offSets) {
|
@Header(KafkaHeaders.OFFSET) List<Long> offSets) {
|
||||||
|
|
||||||
log.info("{} number of payment responses received with keys : {} , partitions : {} , offsets : {}",
|
log.info("{} number of payment responses received with keys:{}, partitions:{} and offsets: {}",
|
||||||
messages.size(), keys, partitions, offSets);
|
messages.size(),
|
||||||
|
keys.toString(),
|
||||||
|
partitions.toString(),
|
||||||
|
offSets.toString());
|
||||||
|
|
||||||
messages.forEach(message -> {
|
messages.forEach(paymentResponseAvroModel -> {
|
||||||
if (PaymentStatus.COMPLETED.equals(message.getPaymentStatus())) {
|
try {
|
||||||
log.info("Processing successful payment response for order id: {}", message.getOrderId());
|
if (PaymentStatus.COMPLETED == paymentResponseAvroModel.getPaymentStatus()) {
|
||||||
paymentResponseMessageListener.paymentCompleted(orderMessagingDataMapper.
|
log.info("Processing successful payment for order id: {}", paymentResponseAvroModel.getOrderId());
|
||||||
paymentResponseAvroModelToPaymentResponse(message));
|
paymentResponseMessageListener.paymentCompleted(orderMessagingDataMapper
|
||||||
}
|
.paymentResponseAvroModelToPaymentResponse(paymentResponseAvroModel));
|
||||||
else if (PaymentStatus.FAILED.equals(message.getPaymentStatus()) ||
|
} else if (PaymentStatus.CANCELLED == paymentResponseAvroModel.getPaymentStatus() ||
|
||||||
PaymentStatus.CANCELLED.equals(message.getPaymentStatus())) {
|
PaymentStatus.FAILED == paymentResponseAvroModel.getPaymentStatus()) {
|
||||||
log.info("Processing failed payment response for order id: {}", message.getOrderId());
|
log.info("Processing unsuccessful payment for order id: {}", paymentResponseAvroModel.getOrderId());
|
||||||
paymentResponseMessageListener.paymentCancelled(orderMessagingDataMapper.
|
paymentResponseMessageListener.paymentCancelled(orderMessagingDataMapper
|
||||||
paymentResponseAvroModelToPaymentResponse(message));
|
.paymentResponseAvroModelToPaymentResponse(paymentResponseAvroModel));
|
||||||
|
}
|
||||||
|
} catch (OptimisticLockingFailureException e) {
|
||||||
|
//NO-OP for optimistic lock. This means another thread finished the work, do not throw error to prevent reading the data from kafka again!
|
||||||
|
log.error("Caught optimistic locking exception in PaymentResponseKafkaListener for order id: {}",
|
||||||
|
paymentResponseAvroModel.getOrderId());
|
||||||
|
} catch (OrderNotFoundException e) {
|
||||||
|
//NO-OP for OrderNotFoundException
|
||||||
|
log.error("No order found for order id: {}", paymentResponseAvroModel.getOrderId());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package com.food.order.system.messaging.mapper;
|
package com.food.order.system.messaging.mapper;
|
||||||
|
|
||||||
import com.food.order.system.domain.event.OrderCancelledEvent;
|
|
||||||
import com.food.order.system.domain.event.OrderCreatedEvent;
|
|
||||||
import com.food.order.system.domain.event.OrderPaidEvent;
|
|
||||||
import com.food.order.system.kafka.order.avro.model.*;
|
|
||||||
import com.food.order.system.dto.message.PaymentResponse;
|
import com.food.order.system.dto.message.PaymentResponse;
|
||||||
import com.food.order.system.dto.message.RestaurantApprovalResponse;
|
import com.food.order.system.dto.message.RestaurantApprovalResponse;
|
||||||
|
import com.food.order.system.kafka.order.avro.model.*;
|
||||||
import com.food.order.system.outbox.model.approval.OrderApprovalEventPayload;
|
import com.food.order.system.outbox.model.approval.OrderApprovalEventPayload;
|
||||||
import com.food.order.system.outbox.model.payment.OrderPaymentEventPayload;
|
import com.food.order.system.outbox.model.payment.OrderPaymentEventPayload;
|
||||||
import com.food.order.system.valueobject.OrderApprovalStatus;
|
import com.food.order.system.valueobject.OrderApprovalStatus;
|
||||||
@@ -17,54 +14,9 @@ import java.util.UUID;
|
|||||||
@Component
|
@Component
|
||||||
public class OrderMessagingDataMapper {
|
public class OrderMessagingDataMapper {
|
||||||
|
|
||||||
public PaymentRequestAvroModel orderCreatedEventToPaymentRequestAvroModel(OrderCreatedEvent orderCreatedEvent) {
|
|
||||||
var order = orderCreatedEvent.getOrder();
|
|
||||||
return PaymentRequestAvroModel.newBuilder()
|
|
||||||
.setId(UUID.randomUUID().toString())
|
|
||||||
.setSagaId("")
|
|
||||||
.setCustomerId(order.getCustomerId().getValue().toString())
|
|
||||||
.setOrderId(order.getId().getValue().toString())
|
|
||||||
.setPrice(order.getPrice().getAmount())
|
|
||||||
.setCreatedAt(orderCreatedEvent.getCreatedAt().toInstant())
|
|
||||||
.setPaymentOrderStatus(PaymentOrderStatus.PENDING)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentRequestAvroModel orderCancelledEventToPaymentRequestAvroModel(OrderCancelledEvent orderCancelledEvent) {
|
|
||||||
var order = orderCancelledEvent.getOrder();
|
|
||||||
return PaymentRequestAvroModel.newBuilder()
|
|
||||||
.setOrderId(order.getId().getValue().toString())
|
|
||||||
.setSagaId("")
|
|
||||||
.setCustomerId(order.getCustomerId().getValue().toString())
|
|
||||||
.setId(UUID.randomUUID().toString())
|
|
||||||
.setPrice(order.getPrice().getAmount())
|
|
||||||
.setCreatedAt(orderCancelledEvent.getCreatedAt().toInstant())
|
|
||||||
.setPaymentOrderStatus(PaymentOrderStatus.CANCELLED)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public RestaurantApprovalRequestAvroModel orderPaidEventToRestaurantApprovalRequestAvroModel(OrderPaidEvent event) {
|
|
||||||
var order = event.getOrder();
|
|
||||||
return RestaurantApprovalRequestAvroModel.newBuilder()
|
|
||||||
.setOrderId(order.getId().getValue().toString())
|
|
||||||
.setRestaurantId(order.getRestaurantId().getValue().toString())
|
|
||||||
.setProducts(order.getItems().stream()
|
|
||||||
.map(item -> Product.newBuilder()
|
|
||||||
.setId(item.getProduct().getId().getValue().toString())
|
|
||||||
.setQuantity(item.getQuantity())
|
|
||||||
.build())
|
|
||||||
.toList())
|
|
||||||
.setId(UUID.randomUUID().toString())
|
|
||||||
.setSagaId("")
|
|
||||||
.setPrice(order.getPrice().getAmount())
|
|
||||||
.setCreatedAt(event.getCreatedAt().toInstant())
|
|
||||||
.setRestaurantOrderStatus(RestaurantOrderStatus.PAID)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentResponse paymentResponseAvroModelToPaymentResponse(PaymentResponseAvroModel message) {
|
public PaymentResponse paymentResponseAvroModelToPaymentResponse(PaymentResponseAvroModel message) {
|
||||||
return PaymentResponse.builder()
|
return PaymentResponse.builder()
|
||||||
|
.id(message.getId())
|
||||||
.orderId(message.getOrderId())
|
.orderId(message.getOrderId())
|
||||||
.sagaId(message.getSagaId())
|
.sagaId(message.getSagaId())
|
||||||
.paymentId(message.getPaymentId())
|
.paymentId(message.getPaymentId())
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<version>2.7.1</version>
|
<version>2.6.7</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class RestaurantDataAccessMapper {
|
|||||||
.productId(new ProductId(entity.getProductId()))
|
.productId(new ProductId(entity.getProductId()))
|
||||||
.name(entity.getProductName())
|
.name(entity.getProductName())
|
||||||
.price(new Money(entity.getProductPrice()))
|
.price(new Money(entity.getProductPrice()))
|
||||||
.available(entity.getProductActive())
|
.available(entity.getProductAvailable())
|
||||||
.build())
|
.build())
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user