Я хотел бы разработать сервис со следующим API:
suspend fun getUsers(request: Request): List<User>
Под капотом я бы отправил запрос на сервер (неважно, как, нодопустим, это реактивный WebClient
), но вот хитрость: я могу отправлять запросы только каждые 500 мс, в противном случае я получу сообщение об ошибке.
Может ли кто-нибудь порекомендовать мне, как я мог бы реализовать это, например,Таким образом, когда я вызываю getUsers
из сопрограммы, она приостанавливается, единица работы добавляется в некоторую очередь службы, которая имеет этот метод, затем реализуется в определенный момент времени и возвращает результат?
Я предполагаю, что могу использовать некоторые ReceiveChannel
в качестве очереди, иметь цикл for
для своих элементов с delay
внутри, но я немного растерялся, где разместить эту логику.Должно ли это быть как фоновый метод, который будет работать вечно и вызываться getUsers
?Вероятно, метод close
никогда не будет вызван, поэтому этот метод также можно приостановить, но как мне передать значение из этого метода бесконечного выполнения в getUsers
, для которого нужны результаты?
РЕДАКТИРОВАТЬ
В данный момент я думаю о решении, подобном этому:
private const val REQUEST_INTERVAL = 500
@Service
class DelayedRequestSenderImpl<T> : DelayedRequestSender<T> {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<T>> = Channel()
override suspend fun requestAsync(block: () -> T): Deferred<T> {
val deferred = GlobalScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred
}
@PostConstruct
private fun startRequestProcessing() = GlobalScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < REQUEST_INTERVAL) {
delay(REQUEST_INTERVAL - diff)
lastRequestTime = now
}
request.start()
}
}
}
Проблема, которую я вижу здесь, заключается в том, что мне нужно генерировать класс, чтобы сделать requestChannel
универсальный, так как результатом запроса может быть что угодно.Но это означает, что каждый экземпляр DelayedRequestSender
будет привязан к определенному типу.Любой совет, как этого избежать?
РЕДАКТИРОВАТЬ 2
Вот улучшенная версия.Единственный возможный поток, который я вижу в данный момент, заключается в том, что мы должны сделать метод @PostConstruct
публичным, чтобы писать любые тесты, если мы хотим или используем отражение.
Идея заключалась в том, чтобы не использовать GlobalScope
итакже есть отдельный Job
для метода обработки.Это хороший подход?
interface DelayingSupplier {
suspend fun <T> supply(block: () -> T): T
}
@Service
class DelayingSupplierImpl(@Value("\${vk.request.interval}") private val interval: Int) : DelayingSupplier {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
private val coroutineScope = CoroutineScope(EmptyCoroutineContext)
override suspend fun <T> supply(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
@PostConstruct
fun startProcessing() = coroutineScope.launch(context = Job(coroutineScope.coroutineContext[Job])) {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}