what - scala web framework
Servicio web basado en el actor-¿Cómo hacerlo correctamente? (2)
En lo que respecta a su primera pregunta, sí, está en el camino correcto. (Aunque también me gustaría ver algunas formas alternativas de manejar este tipo de problema).
Una sugerencia que tengo es aislar al actor persister
no sepa nada sobre las solicitudes. Puede pasar la solicitud como Any
tipo. Su emparejador en su código de servicio puede convertir automáticamente la cookie en una 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))
}
En lo que respecta a su segunda pregunta, las clases de actores no son realmente diferentes a las clases regulares. Pero debe asegurarse de llamar al método de receive
la superclase, para que pueda manejar sus propios mensajes. Tenía otras formas de hacer esto en mi respuesta original , pero creo que prefiero encadenar funciones parciales como esta :
class SpecialHttpService extends HttpService {
override def receive = super.receive orElse {
case SpecialMessage(x) =>
// handle special message
}
}
En los últimos meses, mis colegas y yo hemos construido con éxito un sistema del lado del servidor para enviar notificaciones push a dispositivos iPhone. Básicamente, un usuario se registra para estas notificaciones a través de un servicio web RESTful ( Spray-Server , actualizado recientemente para usar Spray-can como capa HTTP), y la lógica programa uno o varios mensajes para enviar en el futuro, utilizando el programador de Akka.
Este sistema, tal como lo construimos, simplemente funciona: puede manejar cientos, tal vez incluso miles de solicitudes HTTP por segundo, y puede enviar notificaciones a una velocidad de 23,000 por segundo, posiblemente incluso más si reducimos la salida del registro, agregamos notificaciones múltiples actores emisores (y, por lo tanto, más conexiones con Apple), y podría haber alguna optimización en la biblioteca de Java que usamos ( java-apns ).
Esta pregunta es sobre cómo hacerlo bien (tm). Mi colega, que tiene mucho más conocimiento sobre Scala y los sistemas basados en actores en general, observó que la aplicación no es un sistema "puro" basado en actores, y tiene razón. Lo que me pregunto ahora es cómo hacerlo bien.
En este momento, tenemos un único actor de HttpService
, no subclasificado, que se inicializa con un conjunto de directivas que describe nuestra lógica de servicio HTTP. Actualmente, muy simplificado, tenemos directivas como esta:
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))
}
}
Ahora, si entiendo esto correctamente, "esperar una respuesta" de un actor es un no-no en la programación basada en actores (más el! Está en desuso). Lo que creo que es la forma "correcta" de hacerlo es pasar el objeto de request
al actor persister
en un mensaje, y hacer que llame a request.complete
tan pronto como reciba una ID generada del back-end.
He reescrito una de las rutas en mi aplicación para hacer precisamente esto; en el mensaje que se envía al actor, también se envía el objeto / referencia de solicitud. Esto parece funcionar como se supone que debe:
content(as[SomeBusinessObject]) { businessObject => request =>
persister ! new PersistSchedule(request, businessObject)
}
Mi principal preocupación aquí es que parece que pasamos el objeto de request
a la ''lógica de negocios'', en este caso el persistente. El perseguidor ahora tiene una responsabilidad adicional, es decir, solicitud de request.complete
, y conocimiento sobre el sistema en el que se ejecuta, es decir, es parte de un servicio web.
¿Cuál sería la forma correcta de manejar una situación como esta, para que el actor perspicaz no se dé cuenta de que es parte de un servicio http y no necesite saber cómo generar la ID generada?
Estoy pensando que la solicitud aún debe pasar al actor persistente, pero en lugar de que el actor persiga llamando a request.complete, envía un mensaje al actor HttpService (un mensaje SchedulePersisted(request, businessObjectId)
), que simplemente llama request.complete("/businessObject/%s".format(businessObjectId))
. Básicamente:
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)
}
}
¿Estoy en el camino correcto con este enfoque?
Una pregunta específica más pequeña sobre el spray-server
secundario, ¿está bien subclasificar HttpService
y anular el método de recepción, o romperé las cosas de esa manera? (No tengo ninguna pista sobre los actores de subclasificación, o cómo pasar mensajes no reconocidos al actor "padre")
Pregunta final: ¿está pasando el objeto / referencia de la request
en los mensajes de actor que pueden pasar a lo largo de toda la aplicación un enfoque correcto, o hay una mejor manera de "recordar" a qué solicitud se le debe enviar una respuesta después de pasar la solicitud a través de la aplicación?
También puede utilizar la directiva de productos. Le permite desacoplar el cálculo real de la finalización de la solicitud:
get {
produce(instanceOf[Person]) { personCompleter =>
databaseActor ! ShowPersonJob(personCompleter)
}
}
La directiva de producción en este ejemplo extrae una función Persona => Unidad que puede utilizar para completar la solicitud de manera transparente en la capa lógica empresarial, que no debe conocer el spray.
https://github.com/spray/spray/wiki/Marshalling-Unmarshalling