Подтвердить что ты не робот

Актерский вебсервис - Как это сделать правильно?

В последние несколько месяцев я и мои коллеги успешно создали серверную систему для отправки 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 в сообщениях-актерах, которые могут проходить по всему приложению в порядке, или есть лучший способ "запомнить", какой запрос должен быть отправлен ответ после прохождения запросить через приложение?

4b9b3361

Ответ 1

Что касается вашего первого вопроса, да, вы на правильном пути. (Хотя я также хотел бы увидеть некоторые альтернативные способы решения этой проблемы).

Одно из моих предложений - изолировать актера persister от знания о запросах вообще. Вы можете передать запрос как тип Any. Ваш помощник в вашем сервисном коде может автоматически перевести файл cookie в Request.

case class SchedulePersisted(businessObjectId: String, cookie: Any)

// in your actor
override def receive = super.receive orElse {
  case SchedulePersisted(businessObjectId, request: Request) =>
    request.complete("/businessObject/%s".format(businessObjectId))
}

Что касается вашего второго вопроса, классы актеров действительно не отличаются от обычных классов. Но вам нужно убедиться, что вы вызываете метод superclass receive, чтобы он мог обрабатывать свои собственные сообщения. У меня были другие способы сделать это в моем первоначальном ответе, но я думаю, что предпочитаю цепочки частичных выполняет следующие функции:

class SpecialHttpService extends HttpService {
  override def receive = super.receive orElse {
    case SpecialMessage(x) =>
      // handle special message
  }
}

Ответ 2

Вы также можете использовать директиву продукта. Это позволяет отделить фактическую сортировку от завершения запроса:

get {
  produce(instanceOf[Person]) { personCompleter =>
    databaseActor ! ShowPersonJob(personCompleter)
  }
}

Директива product в этом примере извлекает функцию Person = > Unit, которую вы можете использовать, чтобы выполнить запрос прозрачно глубоко внутри уровня бизнес-логики, который не должен знать о спрее.

https://github.com/spray/spray/wiki/Marshalling-Unmarshalling