В конце концов, единственное, что действительно завершает запрос, — это вызов requestContext.complete
. Таким образом, не имеет значения, из какого потока или контекста Актера сделан этот вызов. Все, что имеет значение, это то, что это происходит в течение настроенного периода «тайм-аута запроса». Вы, конечно, можете инициировать этот вызов самостоятельно тем или иным образом, но спрей дает вам ряд предопределенных конструкций, которые, возможно, лучше подходят для вашей архитектуры, чем передача фактического RequestContext. В основном это:
- Директива
complete
, которая просто добавляет немного сахара поверх «сырого» литерала функции ctx => ctx.complete(…)
.
- Future Marshaller, который вызывает
ctx.complete
из обработчика future.onComplete
.
- Директива
produce
, которая извлекает функцию T => Unit
, которую впоследствии можно использовать для завершения запроса с экземпляром пользовательского типа.
С архитектурной точки зрения в большинстве случаев рекомендуется не допускать «просачивания» уровня API в ядро вашего приложения. т.е. приложение не должно ничего знать об уровне API или HTTP. Он должен иметь дело только с объектами своей собственной модели предметной области. Поэтому передача RequestContext напрямую ядру приложения — не лучшее решение.
Прибегнуть к «спросу» и положиться на Future Marshaller — очевидная, хорошо понятная и довольно простая альтернатива. Это имеет (небольшой) недостаток, заключающийся в том, что запрос поставляется с обязательной проверкой тайм-аута, которая логически не требуется (поскольку слой аэрозольного баллончика уже заботится о тайм-аутах запроса). Тайм-аут запроса требуется по техническим причинам (поэтому базовый PromiseActorRef можно очистить, если ожидаемый ответ так и не придет).
Другой альтернативой передаче RequestContext является директива produce
(например, produce(instanceOf[Foo]) { completer =>
…
). Он извлекает функцию, которую вы можете передать ядру приложения. Когда ваша базовая логика вызывает complete(foo)
, запускается логика завершения, и запрос завершается. Таким образом, ядро приложения остается отделенным от уровня API, а накладные расходы минимальны. Недостатки этого подхода двоякие: во-первых, функция завершения не сериализуема, поэтому вы не можете использовать этот подход за пределами JVM. И, во-вторых, логика завершения теперь работает непосредственно в контексте актора ядра приложения, что может нежелательным образом изменить поведение во время выполнения, если Marshaller[Foo] должен выполнять нетривиальные задачи.
Третий вариант заключается в том, чтобы создать актора для каждого запроса на уровне API и заставить его обрабатывать ответ, возвращаемый ядром приложения. Тогда вам не придется использовать аск. Тем не менее, вы сталкиваетесь с той же проблемой, что и PromiseActorRef, лежащий в основе запроса: как очиститься, если ядро приложения никогда не отвечает? С актером повторного запроса у вас есть полная свобода реализовать решение этого вопроса. Однако, если вы решите положиться на тайм-аут (например, через context.setReceiveTimeout
), преимущества перед «аском» могут отсутствовать.
Какое из описанных решений лучше всего подходит для вашей архитектуры, решать вам самостоятельно. Однако, как я, надеюсь, смог показать, у вас есть несколько альтернатив на выбор.
Чтобы ответить на некоторые из ваших конкретных вопросов: существует только один актер/обработчик, который обслуживает маршрут, поэтому, если вы заблокируете его, Spray заблокирует. Это означает, что вы хотите либо завершить маршрут немедленно, либо отправить работу, используя один из трех вариантов выше.
В Интернете есть много примеров для этих 3 вариантов. Самый простой способ — поместить ваш код в файл Future
. Отметьте также вариант/пример «актер по запросу». В конце концов, ваша архитектура определит наиболее подходящий путь.
Наконец, Spray работает поверх Akka, поэтому все настройки Akka остаются в силе. См. HOCON reference.conf
и application.conf
для настройки многопоточности Актера.