diff --git a/pom.xml b/pom.xml index 5724b2568..79d3f6656 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ ${project.basedir}/src/main/kotlin - src/test/kotlin + src/test/jvmTarget org.springframework.boot diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index 6f23fa18d..d122cd164 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -3,14 +3,19 @@ package ru.quipy.apigateway import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import ru.quipy.common.utils.RateLimiter import ru.quipy.orders.repository.OrderRepository import ru.quipy.payments.logic.OrderPayer -import java.util.* +import java.util.UUID +import kotlin.random.Random @RestController -class APIController { - +class APIController(val rateLimiter: RateLimiter) { val logger: Logger = LoggerFactory.getLogger(APIController::class.java) @Autowired @@ -49,26 +54,27 @@ class APIController { ) enum class OrderStatus { - COLLECTING, - PAYMENT_IN_PROGRESS, - PAID, + COLLECTING, PAYMENT_IN_PROGRESS, PAID, } @PostMapping("/orders/{orderId}/payment") fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { - val paymentId = UUID.randomUUID() - val order = orderRepository.findById(orderId)?.let { - orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) - it - } ?: throw IllegalArgumentException("No such order $orderId") - - - val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) - return PaymentSubmissionDto(createdAt, paymentId) + while (true) { + if (rateLimiter.tick()) { + val paymentId = UUID.randomUUID() + val order = orderRepository.findById(orderId)?.let { + orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) + it + } ?: throw IllegalArgumentException("No such order $orderId") + val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) + return PaymentSubmissionDto(createdAt, paymentId) + } + } } + private fun waitProgress() = Thread.sleep(Random.nextLong(1000, 2000)) + class PaymentSubmissionDto( - val timestamp: Long, - val transactionId: UUID + val timestamp: Long, val transactionId: UUID ) } \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/common/utils/FixedWindowRateLimiter.kt b/src/main/kotlin/ru/quipy/common/utils/FixedWindowRateLimiter.kt index 15920bd9c..85f4b2686 100644 --- a/src/main/kotlin/ru/quipy/common/utils/FixedWindowRateLimiter.kt +++ b/src/main/kotlin/ru/quipy/common/utils/FixedWindowRateLimiter.kt @@ -2,7 +2,11 @@ package ru.quipy.common.utils import io.github.resilience4j.ratelimiter.RateLimiterConfig import io.github.resilience4j.ratelimiter.RateLimiterRegistry -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.slf4j.Logger import org.slf4j.LoggerFactory import java.time.Duration diff --git a/src/main/kotlin/ru/quipy/config/ApiConfig.kt b/src/main/kotlin/ru/quipy/config/ApiConfig.kt new file mode 100644 index 000000000..729b81927 --- /dev/null +++ b/src/main/kotlin/ru/quipy/config/ApiConfig.kt @@ -0,0 +1,13 @@ +package ru.quipy.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import ru.quipy.common.utils.RateLimiter +import ru.quipy.common.utils.SlidingWindowRateLimiter +import java.time.Duration + +@Configuration +class ApiConfig { + @Bean + fun getDefaultRateLimiter(): RateLimiter = SlidingWindowRateLimiter(8, Duration.ofSeconds(1)) +} \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/config/EventSourcingLibConfiguration.kt b/src/main/kotlin/ru/quipy/config/EventSourcingLibConfiguration.kt index 9bcb80d07..8f0b9f452 100644 --- a/src/main/kotlin/ru/quipy/config/EventSourcingLibConfiguration.kt +++ b/src/main/kotlin/ru/quipy/config/EventSourcingLibConfiguration.kt @@ -12,7 +12,7 @@ import ru.quipy.core.EventSourcingServiceFactory import ru.quipy.payments.api.PaymentAggregate import ru.quipy.payments.logic.PaymentAggregateState import ru.quipy.streams.AggregateEventStreamManager -import java.util.* +import java.util.UUID /** diff --git a/src/main/kotlin/ru/quipy/orders/repository/OrderRepository.kt b/src/main/kotlin/ru/quipy/orders/repository/OrderRepository.kt index 4a3efa9a2..9a9242625 100644 --- a/src/main/kotlin/ru/quipy/orders/repository/OrderRepository.kt +++ b/src/main/kotlin/ru/quipy/orders/repository/OrderRepository.kt @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service import ru.quipy.apigateway.APIController.Order import ru.quipy.streams.AggregateSubscriptionsManager import java.time.Duration -import java.util.* +import java.util.UUID @Service class OrderRepository { diff --git a/src/main/kotlin/ru/quipy/payments/api/PaymentAggregateDomainEvents.kt b/src/main/kotlin/ru/quipy/payments/api/PaymentAggregateDomainEvents.kt index d4067f1b1..47fb8f043 100644 --- a/src/main/kotlin/ru/quipy/payments/api/PaymentAggregateDomainEvents.kt +++ b/src/main/kotlin/ru/quipy/payments/api/PaymentAggregateDomainEvents.kt @@ -3,7 +3,7 @@ package ru.quipy.payments.api import ru.quipy.core.annotations.DomainEvent import ru.quipy.domain.Event import java.time.Duration -import java.util.* +import java.util.UUID const val PAYMENT_CREATED_EVENT = "PAYMENT_CREATED_EVENT" const val PAYMENT_SUBMITTED_EVENT = "PAYMENT_SUBMITTED_EVENT" diff --git a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt index eceb90cff..cdc0a87b6 100644 --- a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt +++ b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt @@ -8,7 +8,10 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate -import ru.quipy.payments.logic.* +import ru.quipy.payments.logic.PaymentAccountProperties +import ru.quipy.payments.logic.PaymentAggregateState +import ru.quipy.payments.logic.PaymentExternalSystemAdapter +import ru.quipy.payments.logic.PaymentExternalSystemAdapterImpl import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateCommands.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateCommands.kt index 2389e3cb3..8e94e5ef7 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateCommands.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateCommands.kt @@ -4,7 +4,7 @@ import ru.quipy.payments.api.PaymentCreatedEvent import ru.quipy.payments.api.PaymentProcessedEvent import ru.quipy.payments.api.PaymentSubmittedEvent import java.time.Duration -import java.util.* +import java.util.UUID fun PaymentAggregateState.create(id: UUID, orderId: UUID, amount: Int): PaymentCreatedEvent { diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateState.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateState.kt index ec0dd3709..c76a734b0 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateState.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentAggregateState.kt @@ -7,7 +7,7 @@ import ru.quipy.payments.api.PaymentCreatedEvent import ru.quipy.payments.api.PaymentProcessedEvent import ru.quipy.payments.api.PaymentSubmittedEvent import java.time.Duration -import java.util.* +import java.util.UUID import java.util.concurrent.ConcurrentHashMap class PaymentAggregateState : AggregateState { diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 5cb12106a..7d62c7fde 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -10,7 +10,7 @@ import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import java.net.SocketTimeoutException import java.time.Duration -import java.util.* +import java.util.UUID // Advice: always treat time as a Duration diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt index 255db77dd..6d8fe2bc8 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt @@ -1,7 +1,7 @@ package ru.quipy.payments.logic import java.time.Duration -import java.util.* +import java.util.UUID interface PaymentService { /** diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt index 1c24e5a72..b60e9d7d9 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt @@ -1,16 +1,8 @@ package ru.quipy.payments.logic import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import ru.quipy.common.utils.NamedThreadFactory -import ru.quipy.core.EventSourcingService -import ru.quipy.payments.api.PaymentAggregate -import java.time.Duration -import java.util.* -import java.util.concurrent.Executors -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock +import java.util.UUID @Service diff --git a/src/main/kotlin/ru/quipy/payments/subscribers/PaymentTransactionsSubscriber.kt b/src/main/kotlin/ru/quipy/payments/subscribers/PaymentTransactionsSubscriber.kt index 033b8a7b3..148d6a17c 100644 --- a/src/main/kotlin/ru/quipy/payments/subscribers/PaymentTransactionsSubscriber.kt +++ b/src/main/kotlin/ru/quipy/payments/subscribers/PaymentTransactionsSubscriber.kt @@ -1,5 +1,6 @@ package ru.quipy.payments.subscribers +import jakarta.annotation.PostConstruct import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -9,10 +10,9 @@ import ru.quipy.payments.api.PaymentProcessedEvent import ru.quipy.streams.AggregateSubscriptionsManager import ru.quipy.streams.annotation.RetryConf import ru.quipy.streams.annotation.RetryFailedStrategy -import java.util.* +import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList -import jakarta.annotation.PostConstruct @Service class PaymentTransactionsSubscriber { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..719cb8de8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,5 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +payment.accounts=${PAYMENT_ACCOUNTS:acc-3} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file