├── README.md └── saga-choreography-pattern ├── common-dtos ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── javatechie │ └── saga │ └── commons │ ├── CommonsDtoApplication.java │ ├── dto │ ├── OrderRequestDto.java │ ├── OrderResponseDto.java │ └── PaymentRequestDto.java │ └── event │ ├── Event.java │ ├── OrderEvent.java │ ├── OrderStatus.java │ ├── PaymentEvent.java │ └── PaymentStatus.java ├── order-service ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── javatechie │ │ └── saga │ │ └── order │ │ ├── OrderServiceApplication.java │ │ ├── config │ │ ├── EventConsumerConfig.java │ │ ├── OrderPublisherConfig.java │ │ └── OrderStatusUpdateHandler.java │ │ ├── controller │ │ └── OrderController.java │ │ ├── entity │ │ └── PurchaseOrder.java │ │ ├── repository │ │ └── OrderRepository.java │ │ └── service │ │ ├── OrderService.java │ │ └── OrderStatusPublisher.java │ └── resources │ ├── application.properties │ └── application.yaml ├── payment-service ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── javatechie │ │ └── saga │ │ └── payment │ │ ├── PaymentServiceApplication.java │ │ ├── config │ │ └── PaymentConsumerConfig.java │ │ ├── entity │ │ ├── UserBalance.java │ │ └── UserTransaction.java │ │ ├── repository │ │ ├── UserBalanceRepository.java │ │ └── UserTransactionRepository.java │ │ └── service │ │ └── PaymentService.java │ └── resources │ ├── application.properties │ └── application.yaml ├── pom.xml └── saga-choreography-pattern.iml /README.md: -------------------------------------------------------------------------------- 1 | # saga-choreography-example 2 | 3 | 4 | ## request 5 | ```bash 6 | curl --location --request POST 'http://localhost:8081/order/create' \ 7 | --header 'Content-Type: application/json' \ 8 | --data-raw '{ 9 | "userId": 103, 10 | "productId": 33, 11 | "amount": 4000 12 | }' 13 | ``` 14 | ## Kafka payload 15 | 16 | ### Happy scenario 17 | ```bash 18 | {"eventId":"b0e47448-eeb8-4cf4-bd29-b3a4315fc592","date":"2021-09-03T17:26:46.777+00:00","orderRequestDto":{"userId":103,"productId":33,"amount":4000,"orderId":1},"orderStatus":"ORDER_CREATED"} 19 | ``` 20 | 21 | ```bash 22 | {"eventId":"c48c5593-9f81-4ab4-9de8-b9fca2d2bef2","date":"2021-09-03T17:26:51.989+00:00","paymentRequestDto":{"orderId":1,"userId":103,"amount":4000},"paymentStatus":"PAYMENT_COMPLETED"} 23 | ``` 24 | 25 | ## Request 26 | ```bash 27 | curl --location --request POST 'http://localhost:8081/orders' \ 28 | --header 'Content-Type: application/json' \ 29 | --data-raw '{ 30 | "userId": 103, 31 | "productId": 12, 32 | "amount": 800 33 | }' 34 | ``` 35 | 36 | ### insufficent amount 37 | ```bash 38 | {"eventId":"fecacc77-017d-49cd-bdfa-58e47170da49","date":"2021-09-03T17:28:23.126+00:00","orderRequestDto":{"userId":103,"productId":12,"amount":800,"orderId":2},"orderStatus":"ORDER_CANCELLED"} 39 | ``` 40 | 41 | ```bash 42 | {"eventId":"46378bbc-5d15-4436-bed1-c6f3ddb1dc31","date":"2021-09-03T17:28:15.940+00:00","paymentRequestDto":{"orderId":2,"userId":103,"amount":800},"paymentStatus":"PAYMENT_FAILED"} 43 | ``` 44 | 45 | ```bash 46 | curl --location --request GET 'http://localhost:8081/orders' \ 47 | --header 'Content-Type: application/json' \ 48 | --data-raw '' 49 | ``` 50 | 51 | ## Response 52 | 53 | ```bash 54 | [ 55 | { 56 | "id": 1, 57 | "userId": 103, 58 | "productId": 33, 59 | "price": 4000, 60 | "orderStatus": "ORDER_COMPLETED", 61 | "paymentStatus": "PAYMENT_COMPLETED" 62 | }, 63 | { 64 | "id": 2, 65 | "userId": 103, 66 | "productId": 12, 67 | "price": 800, 68 | "orderStatus": "ORDER_CANCELLED", 69 | "paymentStatus": "PAYMENT_FAILED" 70 | } 71 | ] 72 | ``` 73 | 74 | ## user Balance 75 | ![Screen Shot 1400-06-12 at 23 01 03](https://user-images.githubusercontent.com/25712816/132045620-183ecf3e-80e4-447d-b07c-a875d027bf01.png) 76 | 77 | ## Order repo 78 | ![Screen Shot 1400-06-12 at 23 01 19](https://user-images.githubusercontent.com/25712816/132045727-092ba09b-e6ea-42ac-9dba-9aa6aebb050a.png) 79 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | saga-choreography-pattern 7 | com.javatechie 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | common-dtos 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/CommonsDtoApplication.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons; 2 | 3 | public class CommonsDtoApplication { 4 | 5 | public static void main(String[] args) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/dto/OrderRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class OrderRequestDto { 11 | 12 | private Integer userId; 13 | private Integer productId; 14 | private Integer amount; 15 | private Integer orderId; 16 | } 17 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/dto/OrderResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.dto; 2 | 3 | import com.javatechie.saga.commons.event.OrderStatus; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class OrderResponseDto { 12 | 13 | private Integer userId; 14 | private Integer productId; 15 | private Integer amount; 16 | private Integer orderId; 17 | private OrderStatus orderStatus; 18 | } 19 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/dto/PaymentRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class PaymentRequestDto { 11 | 12 | private Integer orderId; 13 | private Integer userId; 14 | private Integer amount; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/event/Event.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.event; 2 | 3 | import java.util.Date; 4 | import java.util.UUID; 5 | 6 | public interface Event { 7 | 8 | UUID getEventId(); 9 | 10 | Date getDate(); 11 | } 12 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/event/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.event; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | import java.util.UUID; 9 | @NoArgsConstructor 10 | @Data 11 | public class OrderEvent implements Event{ 12 | 13 | private UUID eventId=UUID.randomUUID(); 14 | private Date eventDate=new Date(); 15 | private OrderRequestDto orderRequestDto; 16 | private OrderStatus orderStatus; 17 | 18 | @Override 19 | public UUID getEventId() { 20 | return eventId; 21 | } 22 | 23 | @Override 24 | public Date getDate() { 25 | return eventDate; 26 | } 27 | 28 | public OrderEvent(OrderRequestDto orderRequestDto, OrderStatus orderStatus) { 29 | this.orderRequestDto = orderRequestDto; 30 | this.orderStatus = orderStatus; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/event/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.event; 2 | 3 | public enum OrderStatus { 4 | 5 | ORDER_CREATED,ORDER_COMPLETED,ORDER_CANCELLED 6 | } 7 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/event/PaymentEvent.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.event; 2 | 3 | import com.javatechie.saga.commons.dto.PaymentRequestDto; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | import java.util.UUID; 9 | @NoArgsConstructor 10 | @Data 11 | public class PaymentEvent implements Event{ 12 | 13 | private UUID eventId=UUID.randomUUID(); 14 | private Date eventDate=new Date(); 15 | private PaymentRequestDto paymentRequestDto; 16 | private PaymentStatus paymentStatus; 17 | 18 | @Override 19 | public UUID getEventId() { 20 | return eventId; 21 | } 22 | 23 | @Override 24 | public Date getDate() { 25 | return eventDate; 26 | } 27 | 28 | public PaymentEvent(PaymentRequestDto paymentRequestDto, PaymentStatus paymentStatus) { 29 | this.paymentRequestDto = paymentRequestDto; 30 | this.paymentStatus = paymentStatus; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /saga-choreography-pattern/common-dtos/src/main/java/com/javatechie/saga/commons/event/PaymentStatus.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.commons.event; 2 | 3 | public enum PaymentStatus { 4 | 5 | PAYMENT_COMPLETED,PAYMENT_FAILED 6 | } 7 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | saga-choreography-pattern 7 | com.javatechie 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | order-service 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-data-jpa 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-webflux 22 | 23 | 24 | org.springframework.cloud 25 | spring-cloud-stream 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-stream-binder-kafka 30 | 31 | 32 | org.springframework.kafka 33 | spring-kafka 34 | 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | runtime 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | true 45 | 46 | 47 | com.javatechie 48 | common-dtos 49 | 0.0.1-SNAPSHOT 50 | 51 | 52 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class OrderServiceApplication { 8 | 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(OrderServiceApplication.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/config/EventConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.config; 2 | 3 | import com.javatechie.saga.commons.event.PaymentEvent; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import java.util.function.Consumer; 9 | 10 | @Configuration 11 | public class EventConsumerConfig { 12 | 13 | @Autowired 14 | private OrderStatusUpdateHandler handler; 15 | 16 | 17 | @Bean 18 | public Consumer paymentEventConsumer(){ 19 | //listen payment-event-topic 20 | //will check payment status 21 | //if payment status completed -> complete the order 22 | //if payment status failed -> cancel the order 23 | return (payment)-> handler.updateOrder(payment.getPaymentRequestDto().getOrderId(),po->{ 24 | po.setPaymentStatus(payment.getPaymentStatus()); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/config/OrderPublisherConfig.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.config; 2 | 3 | import com.javatechie.saga.commons.event.OrderEvent; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Sinks; 8 | 9 | import java.util.function.Supplier; 10 | 11 | @Configuration 12 | public class OrderPublisherConfig { 13 | 14 | @Bean 15 | public Sinks.Many orderSinks(){ 16 | return Sinks.many().multicast().onBackpressureBuffer(); 17 | } 18 | 19 | @Bean 20 | public Supplier> orderSupplier(Sinks.Many sinks){ 21 | return sinks::asFlux; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/config/OrderStatusUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.config; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import com.javatechie.saga.commons.event.OrderStatus; 5 | import com.javatechie.saga.commons.event.PaymentStatus; 6 | import com.javatechie.saga.order.entity.PurchaseOrder; 7 | import com.javatechie.saga.order.repository.OrderRepository; 8 | import com.javatechie.saga.order.service.OrderStatusPublisher; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.function.Consumer; 14 | 15 | @Configuration 16 | public class OrderStatusUpdateHandler { 17 | 18 | @Autowired 19 | private OrderRepository repository; 20 | 21 | @Autowired 22 | private OrderStatusPublisher publisher; 23 | 24 | @Transactional 25 | public void updateOrder(int id, Consumer consumer) { 26 | repository.findById(id).ifPresent(consumer.andThen(this::updateOrder)); 27 | } 28 | 29 | private void updateOrder(PurchaseOrder purchaseOrder) { 30 | boolean isPaymentComplete = PaymentStatus.PAYMENT_COMPLETED.equals(purchaseOrder.getPaymentStatus()); 31 | OrderStatus orderStatus = isPaymentComplete ? OrderStatus.ORDER_COMPLETED : OrderStatus.ORDER_CANCELLED; 32 | purchaseOrder.setOrderStatus(orderStatus); 33 | if (!isPaymentComplete) { 34 | publisher.publishOrderEvent(convertEntityToDto(purchaseOrder), orderStatus); 35 | } 36 | } 37 | 38 | public OrderRequestDto convertEntityToDto(PurchaseOrder purchaseOrder) { 39 | OrderRequestDto orderRequestDto = new OrderRequestDto(); 40 | orderRequestDto.setOrderId(purchaseOrder.getId()); 41 | orderRequestDto.setUserId(purchaseOrder.getUserId()); 42 | orderRequestDto.setAmount(purchaseOrder.getPrice()); 43 | orderRequestDto.setProductId(purchaseOrder.getProductId()); 44 | return orderRequestDto; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.controller; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import com.javatechie.saga.order.entity.PurchaseOrder; 5 | import com.javatechie.saga.order.service.OrderService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequestMapping("/order") 13 | public class OrderController { 14 | 15 | @Autowired 16 | private OrderService orderService; 17 | 18 | 19 | @PostMapping("/create") 20 | public PurchaseOrder createOrder(@RequestBody OrderRequestDto orderRequestDto){ 21 | return orderService.createOrder(orderRequestDto); 22 | } 23 | 24 | @GetMapping 25 | public List getOrders(){ 26 | return orderService.getAllOrders(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/entity/PurchaseOrder.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.entity; 2 | 3 | import com.javatechie.saga.commons.event.OrderStatus; 4 | import com.javatechie.saga.commons.event.PaymentStatus; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.*; 10 | 11 | @Entity 12 | @Table(name = "PURCHASE_ORDER_TBL") 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class PurchaseOrder { 17 | @Id 18 | @GeneratedValue 19 | private Integer id; 20 | private Integer userId; 21 | private Integer productId; 22 | private Integer price; 23 | @Enumerated(EnumType.STRING) 24 | private OrderStatus orderStatus; 25 | @Enumerated(EnumType.STRING) 26 | private PaymentStatus paymentStatus; 27 | } 28 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.repository; 2 | 3 | import com.javatechie.saga.order.entity.PurchaseOrder; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface OrderRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.service; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import com.javatechie.saga.commons.event.OrderStatus; 5 | import com.javatechie.saga.order.entity.PurchaseOrder; 6 | import com.javatechie.saga.order.repository.OrderRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | @Service 13 | public class OrderService { 14 | 15 | @Autowired 16 | private OrderRepository orderRepository; 17 | 18 | @Autowired 19 | private OrderStatusPublisher orderStatusPublisher; 20 | 21 | @Transactional 22 | public PurchaseOrder createOrder(OrderRequestDto orderRequestDto) { 23 | PurchaseOrder order = orderRepository.save(convertDtoToEntity(orderRequestDto)); 24 | orderRequestDto.setOrderId(order.getId()); 25 | //produce kafka event with status ORDER_CREATED 26 | orderStatusPublisher.publishOrderEvent(orderRequestDto, OrderStatus.ORDER_CREATED); 27 | return order; 28 | } 29 | 30 | public List getAllOrders(){ 31 | return orderRepository.findAll(); 32 | } 33 | 34 | 35 | private PurchaseOrder convertDtoToEntity(OrderRequestDto dto) { 36 | PurchaseOrder purchaseOrder = new PurchaseOrder(); 37 | purchaseOrder.setProductId(dto.getProductId()); 38 | purchaseOrder.setUserId(dto.getUserId()); 39 | purchaseOrder.setOrderStatus(OrderStatus.ORDER_CREATED); 40 | purchaseOrder.setPrice(dto.getAmount()); 41 | return purchaseOrder; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/java/com/javatechie/saga/order/service/OrderStatusPublisher.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.order.service; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import com.javatechie.saga.commons.event.OrderEvent; 5 | import com.javatechie.saga.commons.event.OrderStatus; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Sinks; 9 | 10 | @Service 11 | public class OrderStatusPublisher { 12 | 13 | @Autowired 14 | private Sinks.Many orderSinks; 15 | 16 | 17 | public void publishOrderEvent(OrderRequestDto orderRequestDto, OrderStatus orderStatus){ 18 | OrderEvent orderEvent=new OrderEvent(orderRequestDto,orderStatus); 19 | orderSinks.tryEmitNext(orderEvent); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 2 | spring.datasource.url = jdbc:mysql://localhost:3306/javatechie 3 | spring.datasource.username = root 4 | spring.datasource.password = Password 5 | spring.jpa.show-sql = true 6 | spring.jpa.hibernate.ddl-auto = update 7 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect 8 | spring.jpa.properties.hibernate.format_sql=true -------------------------------------------------------------------------------- /saga-choreography-pattern/order-service/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | stream: 4 | function: 5 | definition : orderSupplier;paymentEventConsumer 6 | bindings: 7 | orderSupplier-out-0: 8 | destination: order-event 9 | paymentEventConsumer-in-0 : 10 | destination: payment-event 11 | 12 | 13 | 14 | server: 15 | port: 8081 -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | saga-choreography-pattern 7 | com.javatechie 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | payment-service 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-data-jpa 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-webflux 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-stream 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-stream-binder-kafka 29 | 30 | 31 | org.springframework.kafka 32 | spring-kafka 33 | 34 | 35 | 36 | mysql 37 | mysql-connector-java 38 | runtime 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | true 44 | 45 | 46 | com.javatechie 47 | common-dtos 48 | 0.0.1-SNAPSHOT 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/PaymentServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class PaymentServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(PaymentServiceApplication.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/config/PaymentConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.config; 2 | 3 | import com.javatechie.saga.commons.event.OrderEvent; 4 | import com.javatechie.saga.commons.event.OrderStatus; 5 | import com.javatechie.saga.commons.event.PaymentEvent; 6 | import com.javatechie.saga.payment.service.PaymentService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | import java.util.function.Function; 14 | 15 | @Configuration 16 | public class PaymentConsumerConfig { 17 | 18 | @Autowired 19 | private PaymentService paymentService; 20 | 21 | @Bean 22 | public Function, Flux> paymentProcessor() { 23 | return orderEventFlux -> orderEventFlux.flatMap(this::processPayment); 24 | } 25 | 26 | private Mono processPayment(OrderEvent orderEvent) { 27 | // get the user id 28 | // check the balance availability 29 | // if balance sufficient -> Payment completed and deduct amount from DB 30 | // if payment not sufficient -> cancel order event and update the amount in DB 31 | if(OrderStatus.ORDER_CREATED.equals(orderEvent.getOrderStatus())){ 32 | return Mono.fromSupplier(()->this.paymentService.newOrderEvent(orderEvent)); 33 | }else{ 34 | return Mono.fromRunnable(()->this.paymentService.cancelOrderEvent(orderEvent)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/entity/UserBalance.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | 10 | @Entity 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserBalance { 15 | @Id 16 | private int userId; 17 | private int price; 18 | } 19 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/entity/UserTransaction.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | 10 | @Entity 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserTransaction { 15 | @Id 16 | private Integer orderId; 17 | private int userId; 18 | private int amount; 19 | } 20 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/repository/UserBalanceRepository.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.repository; 2 | 3 | import com.javatechie.saga.payment.entity.UserBalance; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserBalanceRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/repository/UserTransactionRepository.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.repository; 2 | 3 | import com.javatechie.saga.payment.entity.UserTransaction; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserTransactionRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/java/com/javatechie/saga/payment/service/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.javatechie.saga.payment.service; 2 | 3 | import com.javatechie.saga.commons.dto.OrderRequestDto; 4 | import com.javatechie.saga.commons.dto.PaymentRequestDto; 5 | import com.javatechie.saga.commons.event.OrderEvent; 6 | import com.javatechie.saga.commons.event.PaymentEvent; 7 | import com.javatechie.saga.commons.event.PaymentStatus; 8 | import com.javatechie.saga.payment.entity.UserBalance; 9 | import com.javatechie.saga.payment.entity.UserTransaction; 10 | import com.javatechie.saga.payment.repository.UserBalanceRepository; 11 | import com.javatechie.saga.payment.repository.UserTransactionRepository; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import javax.annotation.PostConstruct; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.Stream; 19 | 20 | @Service 21 | public class PaymentService { 22 | 23 | @Autowired 24 | private UserBalanceRepository userBalanceRepository; 25 | @Autowired 26 | private UserTransactionRepository userTransactionRepository; 27 | 28 | @PostConstruct 29 | public void initUserBalanceInDB() { 30 | userBalanceRepository.saveAll(Stream.of(new UserBalance(101, 5000), 31 | new UserBalance(102, 3000), 32 | new UserBalance(103, 4200), 33 | new UserBalance(104, 20000), 34 | new UserBalance(105, 999)).collect(Collectors.toList())); 35 | } 36 | 37 | /** 38 | * // get the user id 39 | * // check the balance availability 40 | * // if balance sufficient -> Payment completed and deduct amount from DB 41 | * // if payment not sufficient -> cancel order event and update the amount in DB 42 | **/ 43 | @Transactional 44 | public PaymentEvent newOrderEvent(OrderEvent orderEvent) { 45 | OrderRequestDto orderRequestDto = orderEvent.getOrderRequestDto(); 46 | 47 | PaymentRequestDto paymentRequestDto = new PaymentRequestDto(orderRequestDto.getOrderId(), 48 | orderRequestDto.getUserId(), orderRequestDto.getAmount()); 49 | 50 | return userBalanceRepository.findById(orderRequestDto.getUserId()) 51 | .filter(ub -> ub.getPrice() > orderRequestDto.getAmount()) 52 | .map(ub -> { 53 | ub.setPrice(ub.getPrice() - orderRequestDto.getAmount()); 54 | userTransactionRepository.save(new UserTransaction(orderRequestDto.getOrderId(), orderRequestDto.getUserId(), orderRequestDto.getAmount())); 55 | return new PaymentEvent(paymentRequestDto, PaymentStatus.PAYMENT_COMPLETED); 56 | }).orElse(new PaymentEvent(paymentRequestDto, PaymentStatus.PAYMENT_FAILED)); 57 | 58 | } 59 | 60 | @Transactional 61 | public void cancelOrderEvent(OrderEvent orderEvent) { 62 | 63 | userTransactionRepository.findById(orderEvent.getOrderRequestDto().getOrderId()) 64 | .ifPresent(ut->{ 65 | userTransactionRepository.delete(ut); 66 | userTransactionRepository.findById(ut.getUserId()) 67 | .ifPresent(ub->ub.setAmount(ub.getAmount()+ut.getAmount())); 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 2 | spring.datasource.url = jdbc:mysql://localhost:3306/javatechie 3 | spring.datasource.username = root 4 | spring.datasource.password = Password 5 | spring.jpa.show-sql = true 6 | spring.jpa.hibernate.ddl-auto = update 7 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect 8 | spring.jpa.properties.hibernate.format_sql=true -------------------------------------------------------------------------------- /saga-choreography-pattern/payment-service/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | stream: 4 | function: 5 | definition : paymentProcessor 6 | bindings: 7 | paymentProcessor-in-0 : 8 | destination: order-event 9 | paymentProcessor-out-0: 10 | destination: payment-event 11 | 12 | 13 | server: 14 | port: 8082 -------------------------------------------------------------------------------- /saga-choreography-pattern/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | pom 6 | 7 | order-service 8 | payment-service 9 | common-dtos 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 2.5.4 15 | 16 | 17 | com.javatechie 18 | saga-choreography-pattern 19 | 0.0.1-SNAPSHOT 20 | saga-choreography-pattern 21 | Demo project for Spring Boot 22 | 23 | 1.8 24 | 2020.0.3 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-test 31 | test 32 | 33 | 34 | io.projectreactor 35 | reactor-test 36 | test 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-stream 41 | test 42 | test-binder 43 | test-jar 44 | 45 | 46 | org.springframework.kafka 47 | spring-kafka-test 48 | test 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.cloud 55 | spring-cloud-dependencies 56 | ${spring-cloud.version} 57 | pom 58 | import 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 69 | 70 | 71 | org.projectlombok 72 | lombok 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /saga-choreography-pattern/saga-choreography-pattern.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | --------------------------------------------------------------------------------