В последние несколько месяцев я и мои коллеги успешно создали серверную систему для отправки push-уведомлений на устройства iPhone. В основном пользователь регистрируется для этих уведомлений через веб-сервис RESTful (Spray-Server, недавно обновленный для использования Spray-can в качестве уровня HTTP), а логика планирует одно или несколько сообщений для отправки в будущем с использованием планировщика Akka.
Эта система, как мы ее построили, просто работает: она может обрабатывать сотни, а может быть, и тысячи HTTP-запросов в секунду, и может отправлять уведомления со скоростью 23 000 в секунду - возможно, даже больше, если мы уменьшаем выход журнала, добавьте несколько агентов отправителей уведомлений (и, следовательно, больше связей с Apple), и может быть какая-то оптимизация в используемой библиотеке Java (java-apns).
Этот вопрос касается того, как это сделать Right (tm). Мой коллега, гораздо более осведомленный о Scala и актерских системах в целом, отметил, что приложение не является "чистой" актерской системой - и он прав. Теперь мне интересно, как это сделать правильно.
В настоящий момент у нас есть единственный актер Spray HttpService
, а не подклассы, который инициализируется набором директив, который описывает нашу HTTP-сервисную логику. В настоящее время мы очень упрощены, у нас есть такие директивы:
post {
content(as[SomeBusinessObject]) { businessObject => request =>
// store the business object in a MongoDB back-end and wait for the ID to be
// returned; we want to send this back to the user.
val businessObjectId = persister !! new PersistSchedule(businessObject)
request.complete("/businessObject/%s".format(businessObjectId))
}
}
Теперь, если я получу это правильно, "ожидание ответа" от актера - нет-нет в актерском программировании (плюс!!) устарел). Я верю, что это "правильный" способ сделать это: передать объект request
объекту persister
в сообщении и вызвать его вызов request.complete
, как только он получит сгенерированный идентификатор из back- конец.
Я переписал один из маршрутов в моем приложении, чтобы сделать именно это; в сообщении, которое отправляется актеру, объект запроса/ссылка также отправляется. Кажется, что это работает так, как будто:
content(as[SomeBusinessObject]) { businessObject => request =>
persister ! new PersistSchedule(request, businessObject)
}
Моя главная проблема здесь состоит в том, что мы, кажется, передаем объект request
в "бизнес-логику", в этом случае persister. Теперь persister получает дополнительную ответственность, т.е. Вызывает request.complete
и знание о том, в какой системе он работает, т.е. Что он является частью веб-службы.
Каким будет правильный способ справиться с подобной ситуацией, чтобы актер-персик не осознавал, что он является частью службы http, и не нужно знать, как вывести сгенерированный идентификатор?
Я думаю, что запрос все равно должен быть передан актеру persister, но вместо актера persister, вызывающего request.complete, он отправляет сообщение обратно актеру HttpService (сообщение SchedulePersisted(request, businessObjectId)
), которое просто вызывает request.complete("/businessObject/%s".format(businessObjectId))
. В основном:
def receive = {
case SchedulePersisted(request, businessObjectId) =>
request.complete("/businessObject/%s".format(businessObjectId))
}
val directives = post {
content(as[SomeBusinessObject]) { businessObject => request =>
persister ! new PersistSchedule(request, businessObject)
}
}
Я нахожусь на правильном пути с этим подходом?
Меньший вторичный вопрос spray-server
, хорошо ли подклассом HttpService
и переопределить метод получения, или я буду нарушать такие вещи? (Я не имею понятия о субъектах подкласса или о том, как передавать нераспознанные сообщения "родительскому" актеру)
Заключительный вопрос, передает объект/ссылку request
в сообщениях-актерах, которые могут проходить по всему приложению в порядке, или есть лучший способ "запомнить", какой запрос должен быть отправлен ответ после прохождения запросить через приложение?