22 Stimmen

Akteursbasierter Webservice - Wie macht man es richtig?

In den letzten Monaten haben meine Kollegen und ich erfolgreich ein serverseitiges System zur Versendung von Push-Benachrichtigungen an iPhone-Geräte entwickelt. Im Grunde registriert sich ein Benutzer für diese Benachrichtigungen über einen RESTful-Webservice ( Spray-Server , kürzlich aktualisiert, um die Sprühdose wie die HTTP-Schicht), und die Logik plant eine oder mehrere Nachrichten für den Versand in der Zukunft, indem sie den Scheduler von Akka verwendet.

Dieses System, so wie wir es aufgebaut haben, funktioniert einfach: Es kann Hunderte, vielleicht sogar Tausende von HTTP-Anfragen pro Sekunde verarbeiten und Benachrichtigungen mit einer Rate von 23.000 pro Sekunde versenden - möglicherweise sogar noch mehr, wenn wir die Protokollausgabe reduzieren, mehrere Benachrichtigungssender-Akteure hinzufügen (und damit mehr Verbindungen mit Apple), und es könnten einige Optimierungen in der von uns verwendeten Java-Bibliothek vorgenommen werden ( java-apns ).

In dieser Frage geht es darum, wie man es richtig(tm) macht. Mein Kollege, der sich mit Scala und akteurbasierten Systemen im Allgemeinen viel besser auskennt, bemerkte, dass die Anwendung kein "reines" akteurbasiertes System ist - und er hat Recht. Was ich mich jetzt frage, ist, wie man es richtig macht.

Zurzeit haben wir ein einziges Spray HttpService Akteur, der nicht unterklassifiziert ist und mit einer Reihe von Direktiven initialisiert wird, die unsere HTTP-Dienstlogik umreißen. Derzeit haben wir, stark vereinfacht, Direktiven wie diese:

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

Wenn ich das richtig verstehe, ist das Warten auf eine Antwort von einem Akteur in der akteurbasierten Programmierung ein Tabu (und das !! ist veraltet). Was ich glaube, ist der "richtige" Weg, um es zu tun ist, um die request Objekt in den Bereich persister Akteur in einer Nachricht, und lassen Sie ihn request.complete sobald es eine generierte ID vom Back-End erhalten hat.

Ich habe eine der Routen in meiner Anwendung umgeschrieben, um genau dies zu tun; in der Nachricht, die an den Akteur gesendet wird, wird auch das Anforderungsobjekt / die Referenz gesendet. Dies scheint zu funktionieren, wie es sein soll:

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

Mein Hauptanliegen ist, dass wir an der request Objekts an die "Geschäftslogik", in diesem Fall den Persister. Der Persister erhält nun zusätzliche Verantwortung, d.h. er ruft request.complete und das Wissen darüber, in welchem System er läuft, d. h. dass er Teil eines Webservice ist.

Was wäre der richtige Weg, um eine Situation wie diese zu behandeln, so dass der Persister-Akteur wird nicht bewusst, dass es Teil eines http-Dienstes ist, und muss nicht wissen, wie die generierte ID auszugeben?

Ich denke, dass die Anforderung noch an den Persister-Akteur übergeben werden sollte, aber anstelle der Persister-Akteur Aufruf request.complete, sendet es eine Nachricht zurück an den HttpService-Akteur (ein SchedulePersisted(request, businessObjectId) Nachricht), die einfach aufruft request.complete("/businessObject/%s".format(businessObjectId)) . Im Grunde genommen:

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

Bin ich mit diesem Ansatz auf dem richtigen Weg?

Eine kleinere sekundäre spray-server spezielle Frage: Ist es in Ordnung, die Unterklasse HttpService und die Empfangsmethode außer Kraft setzen, oder mache ich die Sache auf diese Weise kaputt? (Ich habe keine Ahnung von der Unterklassifizierung von Akteuren oder wie man nicht erkannte Nachrichten an den "übergeordneten" Akteur weitergibt)

Letzte Frage: Ist das Bestehen der request Objekt/Referenz in Akteursnachrichten, die die gesamte Anwendung durchlaufen können, ein guter Ansatz, oder gibt es eine bessere Möglichkeit, sich zu "erinnern", welche Anfrage als Antwort gesendet werden sollte, nachdem die Anfrage durch die Anwendung geleitet wurde?

3voto

leedm777 Punkte 22404

Zu Ihrer ersten Frage: Ja, Sie sind auf dem richtigen Weg. (Obwohl ich auch gerne einige alternative Möglichkeiten sehen würde, diese Art von Problem zu behandeln).

Ein Vorschlag, den ich habe, ist die Isolierung der persister Akteur von Anfragen überhaupt nichts wissen. Sie können die Anfrage als eine Any Typ. Ihr Matcher in Ihrem Dienstcode kann den Cookie automatisch in einen 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))
}

Was Ihre zweite Frage betrifft, so unterscheidet sich der Unterricht für Schauspieler nicht vom normalen Unterricht. Sie müssen jedoch sicherstellen, dass Sie die Superclass receive Methode, so dass sie ihre eigenen Nachrichten bearbeiten kann. Ich hatte einige andere Möglichkeiten, dies zu tun in meiner ursprünglichen Antwort aber ich glaube, ich bevorzuge Verkettung von Teilfunktionen wie diese :

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

0voto

krishnen Punkte 172

Sie könnten auch die Richtlinie produce verwenden. Sie ermöglicht es Ihnen, das eigentliche Marshalling von der Vervollständigung der Anfrage zu entkoppeln:

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

Die produce-Direktive in diesem Beispiel extrahiert eine Funktion Person => Unit, die Sie verwenden können, um die Anfrage transparent tief in der Geschäftslogikschicht zu vervollständigen, die von spray nichts wissen sollte.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X