Как реализовать несколько пулов потоков в приложении Play с использованием IO с эффектом кота

В моем приложении Play я обслуживаю запросы cats-effect"s IO, вместо Future в контроллере, вот так (супер упрощенно):

def handleServiceResult(serviceResult: ServiceResult): Result = ...

def serviceMyRequest(request: Request): IO[ServiceResult] = ...

def myAction = Action { request =>
  handleServiceResult(
    serviceMyRequest(request).unsafeRunSync()
  )
}

Затем запросы обрабатываются (асинхронно) в пуле потоков Play по умолчанию. Теперь я хочу реализовать несколько пулов потоков для обработки запросов разного рода. Я использовал Futures, я мог бы сделать это:

val myCustomExecutionContext: ExecutionContext = ...

def serviceMyRequest(request: Request): Future[ServiceResult] = ...

def myAction = Action.async { request =>
  Future(serviceMyRequest(request))(myCustomExecutionContext)
    .map(handleServiceResult)(defaultExecutionContext)
}

Но я не пользуюсь Futures, я использую IOи я не уверен в правильном способе его реализации. Это выглядит многообещающе, но кажется немного неуклюжим:

def serviceMyRequest(request: Request): IO[ServiceResult] = ...

def myAction = Action { request =>
  val ioServiceResult = for {
    _ <- IO.shift(myCustomExecutionContext)
    serviceResult <- serviceMyRequest(request)
    _ <- IO.shift(defaultExecutionContext)
  } yield {
    serviceResult
  }
  handleServiceResult(ioServiceResult.unsafeRunSync())
}

Это правильный способ реализовать это? Есть ли здесь лучшая практика? Я плохо облажался? Благодарю.

1 ответ

Хорошо, так как это, кажется, не является проторенной площадкой, это то, что я в итоге реализовал:

trait PlayIO { self: BaseControllerHelpers =>

  implicit class IOActionBuilder[A](actionBuilder: ActionBuilder[Request, A]) {

    def io(block: Request[A] => IO[Result]): Action[A] = {
      actionBuilder.apply(block.andThen(_.unsafeRunSync()))
    }

    def io(executionContext: ExecutionContext)(block: Request[A] => IO[Result]): Action[A] = {
      val shiftedBlock = block.andThen(IO.shift(executionContext) *> _ <* IO.shift(defaultExecutionContext))
      actionBuilder.apply(shiftedBlock.andThen(_.unsafeRunSync()))
    }

  }

}

Тогда (используя рамки из вопроса), если я смешиваю PlayIO в контроллер, я могу сделать это,

val myCustomExecutionContext: ExecutionContext = ...

def handleServiceResult(serviceResult: ServiceResult): Result = ...

def serviceMyRequest(request: Request): IO[ServiceResult] = ...

def myAction = Action.io(myCustomExecutionContext) { request =>
  serviceMyRequest(request).map(handleServiceResult)
}

такой, что я выполняю блок кода действия на myCustomExecutionContext и затем, после завершения, вернитесь назад к контексту выполнения Play по умолчанию.

Обновить:

Это немного более гибко:

trait PlayIO { self: BaseControllerHelpers =>

  implicit class IOActionBuilder[R[_], A](actionBuilder: ActionBuilder[R, A]) {

    def io(block: R[A] => IO[Result]): Action[A] = {
      actionBuilder.apply(block.andThen(_.unsafeRunSync()))
    }

    def io(executionContext: ExecutionContext)(block: R[A] => IO[Result]): Action[A] = {
      if (executionContext == defaultExecutionContext) io(block) else {
        val shiftedBlock = block.andThen(IO.shift(executionContext) *> _ <* IO.shift(defaultExecutionContext))
        io(shiftedBlock)
      }
    }

  }

}

Update2:

Согласно приведенному выше комментарию, это гарантирует, что мы всегда вернемся к пулу потоков по умолчанию:

trait PlayIO { self: BaseControllerHelpers =>

  implicit class IOActionBuilder[R[_], A](actionBuilder: ActionBuilder[R, A]) {

    def io(block: R[A] => IO[Result]): Action[A] = {
      actionBuilder.apply(block.andThen(_.unsafeRunSync()))
    }

    def io(executionContext: ExecutionContext)(block: R[A] => IO[Result]): Action[A] = {
      if (executionContext == defaultExecutionContext) io(block) else {
        val shiftedBlock = block.andThen { ioResult =>
          IO.shift(executionContext).bracket(_ => ioResult)(_ => IO.shift(defaultExecutionContext))
        }
        io(shiftedBlock)
      }
    }

  }

}
Другие вопросы по тегам