Skip to content

Commit

Permalink
initial saga
Browse files Browse the repository at this point in the history
  • Loading branch information
BlenoClaus authored and blenohclaus committed Jul 12, 2024
1 parent 53f972c commit adae578
Show file tree
Hide file tree
Showing 20 changed files with 215 additions and 25 deletions.
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.awspring.cloud/spring-cloud-aws-starter-sns -->
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sns</artifactId>
<version>3.2.0-M1</version>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sqs</artifactId>
<version>3.2.0-M1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class OrderController(

override fun create(orderRequest: OrderRequest): ResponseEntity<PendingOrderResponse> =
ResponseEntity.ok(
createOrderUseCase.create(
createOrderUseCase.requestCreate(
customerId = getAuthenticatedCustomerId(),
items = orderRequest.toOrderItemsDomain()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface PaymentGateway {
fun findByPaymentId(paymentId: String): PaymentResponse?

fun requestPayment(order: Order): PaymentResponse

fun notifyRequestPayment(order: Order)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package com.fiap.order.adapter.gateway.impl

import com.fiap.order.adapter.client.PaymentsApiClient
import com.fiap.order.adapter.gateway.PaymentGateway
import com.fiap.order.adapter.messaging.sender.PaymentSender
import com.fiap.order.domain.entities.Order
import com.fiap.order.driver.web.request.PaymentOrderInfo
import com.fiap.order.driver.web.request.PaymentOrderInfoLine
import com.fiap.order.driver.web.request.PaymentRequest
import com.fiap.order.driver.web.response.PaymentResponse

class PaymentGatewayImpl(
private val paymentsApiClient: PaymentsApiClient
private val paymentsApiClient: PaymentsApiClient,
private val paymentSender: PaymentSender
) : PaymentGateway {

override fun findByPaymentId(paymentId: String): PaymentResponse {
Expand Down Expand Up @@ -37,6 +39,27 @@ class PaymentGatewayImpl(
return paymentsApiClient.create(paymentHTTPRequest)
}

override fun notifyRequestPayment(order: Order) {
paymentSender.requestPayment(
paymentRequest = PaymentRequest(
orderInfo = PaymentOrderInfo(
number = order.number!!,
orderedAt = order.orderedAt,
orderedBy = order.customer?.name ?: ANONYMOUS,
total = order.total,
lines = order.lines.map { orderLine ->
PaymentOrderInfoLine(
name = orderLine.name,
quantity = orderLine.quantity,
unitPrice = orderLine.unitPrice,
total = orderLine.total
)
}
)
)
)
}

companion object {
const val ANONYMOUS = "Anonymous"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.fiap.order.adapter.messaging.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fiap.order.adapter.messaging.sender.PaymentSender
import com.fiap.order.adapter.messaging.sender.impl.PaymentSenderImpl
import io.awspring.cloud.sns.core.SnsTemplate
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class MessagingConfig {

@Bean("PaymentSender")
fun createPaymentSender(snsTemplate: SnsTemplate,
@Value("\${topic.request-payment}") topicArn: String,
objectMapper: ObjectMapper) : PaymentSender {
return PaymentSenderImpl(snsTemplate, topicArn, objectMapper)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.fiap.order.adapter.messaging.sender

import com.fiap.order.driver.web.request.PaymentRequest

interface PaymentSender {
fun requestPayment(paymentRequest:PaymentRequest)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fiap.order.adapter.messaging.sender.impl

import com.fasterxml.jackson.databind.ObjectMapper
import com.fiap.order.adapter.messaging.sender.PaymentSender
import com.fiap.order.driver.web.request.PaymentRequest
import io.awspring.cloud.sns.core.SnsTemplate
import org.springframework.messaging.support.GenericMessage


class PaymentSenderImpl(private val snsTemplate: SnsTemplate, private val topicName: String, private val mapper: ObjectMapper) : PaymentSender {

override fun requestPayment(paymentRequest: PaymentRequest) {
snsTemplate.send(topicName, GenericMessage(mapper.writeValueAsString(paymentRequest)))
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/com/fiap/order/domain/entities/Payment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fiap.order.domain.entities

import com.fiap.order.domain.valueobjects.PaymentStatus
import java.time.LocalDateTime

data class Payment(
val id: String,
val orderNumber: Long,
val externalOrderId: String,
val externalOrderGlobalId: String?,
val paymentInfo: String,
val createdAt: LocalDateTime,
val status: PaymentStatus,
val statusChangedAt: LocalDateTime,
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.fiap.order.adapter.gateway.impl.PaymentGatewayImpl
import com.fiap.order.adapter.gateway.impl.ProductGatewayImpl
import com.fiap.order.adapter.gateway.impl.StockGatewayImpl
import com.fiap.order.adapter.gateway.impl.TransactionalGatewayImpl
import com.fiap.order.adapter.messaging.sender.PaymentSender
import com.fiap.order.driver.database.persistence.jpa.CustomerJpaRepository
import com.fiap.order.driver.database.persistence.jpa.OrderJpaRepository
import org.springframework.context.annotation.Bean
Expand Down Expand Up @@ -51,7 +52,7 @@ class GatewayConfig {
}

@Bean("PaymentGateway")
fun createPaymentGateway(paymentsApiClient: PaymentsApiClient): PaymentGateway {
return PaymentGatewayImpl(paymentsApiClient)
fun createPaymentGateway(paymentsApiClient: PaymentsApiClient, paymentSender: PaymentSender): PaymentGateway {
return PaymentGatewayImpl(paymentsApiClient, paymentSender)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.fiap.order.driver.messaging.consumer

import com.fiap.order.domain.valueobjects.PaymentStatus
import com.fiap.order.driver.messaging.event.PaymentEvent
import com.fiap.order.driver.messaging.event.toDomain
import com.fiap.order.usecases.CreateOrderUseCase
import com.fiap.order.usecases.services.OrderService
import io.awspring.cloud.sqs.annotation.SqsListener
import org.slf4j.LoggerFactory
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.handler.annotation.Headers
import org.springframework.stereotype.Component

@Component
class OrderConsumer(
private val createOrderUseCase: CreateOrderUseCase,
) {

private val log = LoggerFactory.getLogger(javaClass)


@SqsListener("\${sqs.queues.request-payment-response}")
fun onMessage(message: PaymentEvent, @Headers headers: MessageHeaders) {
when (message.status) {
PaymentStatus.PENDING -> createOrderUseCase.acceptPending(message.toDomain())
PaymentStatus.EXPIRED -> TODO()
PaymentStatus.FAILED -> TODO()
PaymentStatus.CONFIRMED -> TODO()
}
log.info(message.toString())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.fiap.order.driver.messaging.event

import com.fiap.order.domain.entities.Payment
import com.fiap.order.domain.valueobjects.PaymentStatus
import java.time.LocalDateTime

data class PaymentEvent(
val id: String,
val orderNumber: Long,
val externalOrderId: String,
val externalOrderGlobalId: String?,
val paymentInfo: String,
val createdAt: LocalDateTime,
val status: PaymentStatus,
val statusChangedAt: LocalDateTime,
)

fun PaymentEvent.toDomain() = Payment(
id = id,
orderNumber = orderNumber,
externalOrderId = externalOrderId,
externalOrderGlobalId = externalOrderGlobalId,
paymentInfo = paymentInfo,
createdAt = createdAt,
status = status,
statusChangedAt = statusChangedAt,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.fiap.order.driver.web.response

import com.fiap.order.domain.entities.Order
import com.fiap.order.domain.entities.Payment

data class PendingOrderResponse(
val order: Order,
val payment: PaymentResponse,
val payment: Payment?,
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.fiap.order.usecases

import com.fiap.order.domain.entities.Order
import com.fiap.order.domain.entities.OrderItem
import com.fiap.order.domain.entities.Payment
import com.fiap.order.domain.valueobjects.PaymentStatus
import com.fiap.order.driver.messaging.event.PaymentEvent
import com.fiap.order.driver.web.response.PendingOrderResponse
import java.util.*

interface CreateOrderUseCase {
fun create(customerId: UUID?, items: List<OrderItem>): PendingOrderResponse
fun requestCreate(customerId: UUID?, items: List<OrderItem>): PendingOrderResponse
fun acceptPending(payment: Payment) : PendingOrderResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import com.fiap.order.domain.entities.Order
import com.fiap.order.driver.web.response.PaymentResponse

interface RequestPaymentUseCase {
fun requestPayment(order: Order): PaymentResponse
fun requestPayment(order: Order)
}
35 changes: 24 additions & 11 deletions src/main/kotlin/com/fiap/order/usecases/services/OrderService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fiap.order.adapter.gateway.TransactionalGateway
import com.fiap.order.domain.entities.Order
import com.fiap.order.domain.entities.OrderItem
import com.fiap.order.domain.entities.OrderLine
import com.fiap.order.domain.entities.Payment
import com.fiap.order.domain.errors.ErrorType
import com.fiap.order.domain.errors.SelfOrderManagementException
import com.fiap.order.domain.valueobjects.OrderStatus
Expand Down Expand Up @@ -56,7 +57,7 @@ open class OrderService(
override fun findByStatusAndCustomerId(status: OrderStatus, customerId: UUID): List<Order> =
orderGateway.findByStatusAndCustomerId(status, customerId)

override fun create(
override fun requestCreate(
customerId: UUID?,
items: List<OrderItem>,
): PendingOrderResponse {
Expand Down Expand Up @@ -87,7 +88,7 @@ open class OrderService(
)
}

var order = orderGateway.upsert(
val order = orderGateway.upsert(
Order(
number = null,
orderedAt = LocalDateTime.now(),
Expand All @@ -98,24 +99,36 @@ open class OrderService(
)
)

order = orderGateway.upsert(
order.copy(
status = OrderStatus.PENDING,
lines = order.lines.map { i -> i.copy(orderNumber = order.number) },
)
)

val payment = providePaymentRequestUseCase.requestPayment(order)
providePaymentRequestUseCase.requestPayment(order)

log.info("Created order $order and respective payment $payment")
log.info("Created order $order ")

PendingOrderResponse(
order = order,
payment = payment,
payment = null,
)
}
}

override fun acceptPending(payment: Payment): PendingOrderResponse {
return orderGateway.findByOrderNumber(payment.orderNumber)?.let {
orderGateway.upsert(
it.copy(
status = OrderStatus.PENDING,
lines = it.lines.map { i -> i.copy(orderNumber = it.number) },
)
)
}?.let { orderUpdate ->
PendingOrderResponse(order = orderUpdate, payment = payment,).also {
log.info("Accepted order $orderUpdate")
}
} ?: throw SelfOrderManagementException(
errorType = ErrorType.ORDER_NOT_FOUND,
message = "Order ${payment.orderNumber} should exist in the database",
)
}

override fun confirmOrder(orderNumber: Long): Order {
return transactionalRepository.transaction {
val order = getByOrderNumber(orderNumber)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class PaymentService(
{
private val log = LoggerFactory.getLogger(javaClass)

override fun requestPayment(order: Order): PaymentResponse {
override fun requestPayment(order: Order) {
log.info("Requesting payment request for order $order")
return paymentGateway.requestPayment(order)
paymentGateway.notifyRequestPayment(order)
}
}
16 changes: 16 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
spring:
cloud:
aws:
endpoint: http://localhost:4566
credentials:
access-key: localstack
secret-key: localstack
region:
static: ${AWS_REGION}
application:
name: orders-api
datasource:
Expand Down Expand Up @@ -32,3 +40,11 @@ clients:
url: ${STOCK_SERVICE_URL}
payments-api:
url: ${PAYMENTS_SERVICE_URL}


topic:
request-payment: arn:aws:sns:us-east-2:000000000000:request-payment_topic

sqs:
queues:
request-payment-response: payment-response_queue
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ class OrderControllerTest {
val items = orderRequest.items.map { it.toOrderItem() }
val pendingOrderResponse = createPendingOrderResponse()

every { createOrderUseCase.create(null, items) } returns pendingOrderResponse
every { createOrderUseCase.requestCreate(null, items) } returns pendingOrderResponse

val result = orderController.create(orderRequest)

assertThat(result.statusCode).isEqualTo(HttpStatus.OK)
assertThat(result.body).isEqualTo(pendingOrderResponse)
verify(exactly = 1) { createOrderUseCase.create(null, items) }
verify(exactly = 1) { createOrderUseCase.requestCreate(null, items) }
}

@Test
Expand Down
Loading

0 comments on commit adae578

Please sign in to comment.