Как реализовать несколько пулов потоков в приложении Play с использованием IO с эффектом кота - PullRequest
0 голосов
/ 19 октября 2018

В моем приложении 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 по умолчанию.Теперь я хочу реализовать несколько пулов потоков для обработки запросов разного рода.Если бы я использовал Future s, я мог бы сделать это:

val myCustomExecutionContext: ExecutionContext = ...

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

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

Но я не использую Future s, я использую 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 Ответ

0 голосов
/ 20 октября 2018

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

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)
      }
    }

  }

}

Обновление2:

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

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)
      }
    }

  }

}
...