22

過去数か月で、私と同僚は、プッシュ通知を iPhone デバイスにディスパッチするためのサーバー側システムの構築に成功しました。基本的に、ユーザーは RESTful Web サービス ( Spray-Server、最近HTTP レイヤーとしてSpray-canを使用するように更新されました) を介してこれらの通知を登録し、ロジックは Akka のスケジューラーを使用して、将来のディスパッチのために 1 つまたは複数のメッセージをスケジュールします。

私たちが構築したこのシステムは、単純に機能します。1 秒間に数百、場合によっては数千の HTTP リクエストを処理でき、1 秒あたり 23,000 の速度で通知を送信できます。送信側のアクター (したがって、Apple とのより多くの接続) と、使用する Java ライブラリ ( java-apns ) で実行する最適化が行われる可能性があります。

この質問は、Right(tm) を行う方法に関するものです。私の同僚は、Scala とアクター ベースのシステム全般に精通しており、アプリケーションが「純粋な」アクター ベースのシステムではないことを指摘しており、その通りです。私が今疑問に思っているのは、それを正しく行う方法です。

現時点では、HttpServiceサブクラス化されていない単一の Spray アクターがあり、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、バックエンドから生成された ID を受信したらすぐに呼び出すことだと私は信じています。

これを行うために、アプリケーションのルートの 1 つを書き直しました。アクターに送信されるメッセージでは、リクエスト オブジェクト/参照も送信されます。これは、次のように機能するようです。

  content(as[SomeBusinessObject]) { businessObject => request =>
    persister ! new PersistSchedule(request, businessObject)
  }

ここでの私の主な懸念は、requestオブジェクトを「ビジネス ロジック」、この場合は永続化プログラムに渡しているように見えることです。パーシスタは、 call などの追加の責任と、request.completeそれが実行されるシステムに関する知識 (つまり、それが Web サービスの一部である) を取得するようになりました。

このような状況を処理する正しい方法は何でしょうか?永続化アクターは、それが http サービスの一部であることを認識せず、生成された ID を出力する方法を知る必要がありません。

リクエストは引き続き永続化アクターに渡す必要があると考えていますが、永続化アクターが request.complete を呼び出す代わりに、SchedulePersisted(request, businessObjectId)単に を呼び出す HttpService アクター (メッセージ) にメッセージを送り返します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化して receive メソッドをオーバーライドしてもいいですか、それともそのように壊れますか? (アクターのサブクラス化や、認識されていないメッセージを「親」アクターに渡す方法についてはわかりません)

最後の質問は、requestアプリケーション全体を通過する可能性のあるアクター メッセージでオブジェクト/参照を渡すことです。

4

2 に答える 2

3

最初の質問に関しては、はい、あなたは正しい道を進んでいます。(ただし、この種の問題を処理するための代替方法もいくつか見たいと思います)。

私が持っている 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))
}

2 番目の質問についてですが、アクター クラスは通常のクラスと何ら変わりはありません。ただし、スーパークラスのreceiveメソッドを呼び出して、独自のメッセージを処理できるようにする必要があります。元の回答ではこれを行う他の方法がいくつかありましたが、次のように部分関数をチェーンすることを好むと思います:

class SpecialHttpService extends HttpService {
  override def receive = super.receive orElse {
    case SpecialMessage(x) =>
      // handle special message
  }
}
于 2012-02-10T14:41:19.503 に答える
0

また、プロデュース ディレクティブを使用することもできます。これにより、実際のマーシャリングをリクエストの完了から切り離すことができます。

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

この例の Produce ディレクティブは、関数 Person => Unit を抽出します。これを使用して、スプレーを認識してはならないビジネス ロジック レイヤーの奥深くで透過的にリクエストを完了することができます。

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

于 2012-03-14T17:07:22.423 に答える