tutorial sirve requestmapping que para mvc modelandview example español ejemplo spring scala java-ee actor 3-tier

spring - sirve - Transferencia de la arquitectura típica de 3 niveles a los actores



spring mvc que es (5)

Esta pregunta me molesta desde hace un tiempo (espero no ser el único). Quiero tomar una aplicación típica Java EE de 3 niveles y ver cómo se ve cómo se implementa con los actores. Me gustaría saber si realmente tiene sentido hacer tal transición y cómo puedo sacar provecho si tiene sentido (tal vez el rendimiento, una mejor arquitectura, extensibilidad, capacidad de mantenimiento, etc.).

Aquí están el controlador típico (presentación), el servicio (lógica comercial), DAO (datos):

trait UserDao { def getUsers(): List[User] def getUser(id: Int): User def addUser(user: User) } trait UserService { def getUsers(): List[User] def getUser(id: Int): User def addUser(user: User): Unit @Transactional def makeSomethingWithUsers(): Unit } @Controller class UserController { @Get def getUsers(): NodeSeq = ... @Get def getUser(id: Int): NodeSeq = ... @Post def addUser(user: User): Unit = { ... } }

Puede encontrar algo como esto en muchas aplicaciones de primavera. Podemos tomar una implementación sencilla que no tenga ningún estado compartido y eso se debe a que no tiene bloques sincronizados ... por lo que todo el estado está en la base de datos y la aplicación depende de las transacciones. Service, controller y dao tienen solo una instancia. Por lo tanto, para cada solicitud, el servidor de aplicaciones usará un hilo separado, pero los hilos no se bloquearán entre sí (pero serán bloqueados por DB IO).

Supongamos que estamos tratando de implementar funcionalidades similares con los actores. Puede verse así:

sealed trait UserActions case class GetUsers extends UserActions case class GetUser(id: Int) extends UserActions case class AddUser(user: User) extends UserActions case class MakeSomethingWithUsers extends UserActions val dao = actor { case GetUsers() => ... case GetUser(userId) => ... case AddUser(user) => ... } val service = actor { case GetUsers() => ... case GetUser(userId) => ... case AddUser(user) => ... case MakeSomethingWithUsers() => ... } val controller = actor { case Get("/users") => ... case Get("/user", userId) => ... case Post("/add-user", user) => ... }

Creo que aquí no es muy importante cómo se implementan los extractores Get () y Post (). Supongamos que escribo un marco para implementar esto. Puedo enviar un mensaje al controlador de esta manera:

controller !! Get("/users")

Lo mismo se haría con el controlador y el servicio. En este caso, todo el flujo de trabajo sería sincrónico. Lo que es peor, puedo procesar solo una solicitud a la vez (mientras tanto, todas las demás solicitudes caen en el buzón del controlador). Entonces necesito hacer que todo sea asincrónico.

¿Hay alguna manera elegante de realizar cada paso de procesamiento de forma asincrónica en esta configuración?

Por lo que yo entiendo, cada nivel debe de alguna manera guardar el contexto del mensaje que recibe y luego enviar el mensaje al nivel que se encuentra debajo. Cuando el nivel debajo de las respuestas con algún mensaje de resultado debería ser capaz de restaurar el contexto inicial y responder con este resultado al remitente original. ¿Es esto correcto?

Además, en este momento solo tengo una instancia de actor para cada nivel. Incluso si funcionan de forma asíncrona, aún puedo procesar en paralelo solo un controlador, servicio y mensaje de dao. Esto significa que necesito más actores del mismo tipo. Lo que me lleva a LoadBalancer para cada nivel. Esto también significa que si tengo UserService y ItemService, debería LoadBalace ambos por separado.

Tengo sentimientos, que entiendo algo mal. Toda la configuración necesaria parece ser demasiado complicada. ¿Qué piensas sobre esto?

(PD: Sería también muy interesante saber cómo las transacciones DB encajan en esta imagen, pero creo que es excesivo para este hilo)


Como dijiste, !! = bloqueo = malo para la escalabilidad y el rendimiento, vea esto: Rendimiento entre! y !!

La necesidad de transacciones generalmente ocurre cuando está persistiendo el estado en lugar de los eventos. Eche un vistazo a CQRS y DDDD (Diseño impulsado por dominios distribuidos) y a la fuente de eventos , porque, como usted dice, todavía no tenemos un STM distribuido.


Evite el procesamiento asincrónico a menos y hasta que tenga una razón clara para hacerlo. Los actores son abstracciones encantadoras, pero incluso ellos no eliminan la complejidad inherente del procesamiento asincrónico.

Descubrí esa verdad de la manera difícil. Quería aislar la mayor parte de mi aplicación del único punto de inestabilidad potencial: la base de datos. Actores al rescate! Actores Akka en particular. Y fue increíble.

Martillo en mano, me puse a golpear cada clavo a la vista. Sesiones de usuario? Sí, podrían ser actores también. Um ... ¿qué tal ese control de acceso? ¡Seguro Por qué no! Con una creciente sensación de incomodidad, convertí mi arquitectura hasta ahora simple en un monstruo: múltiples capas de actores, paso de mensajes asíncrono, mecanismos elaborados para tratar las condiciones de error y un caso grave de los feos.

Retrocedí, principalmente.

Retuve a los actores que me daban lo que necesitaba, tolerancia a fallas para mi código de persistencia, y convertí a todos los demás en clases ordinarias.

¿Puedo sugerir que lea detenidamente el caso de buen uso para las preguntas / respuestas de Akka ? Eso puede darle una mejor comprensión de cuándo y cómo valdrán los actores. En caso de que decida utilizar Akka, le agradecería ver mi respuesta a una pregunta anterior sobre la escritura de actores con equilibrio de carga .


Las grandes transacciones atómicas intensivas en cómputo son difíciles de llevar a cabo, que es una de las razones por las que las bases de datos son tan populares. Entonces, si está preguntando si puede usar actores de forma transparente y fácil para reemplazar todas las características transaccionales y altamente escalables de una base de datos (cuya potencia está muy inclinada en el modelo Java EE), la respuesta es no.

Pero hay algunos trucos que puedes jugar. Por ejemplo, si un actor parece estar causando un cuello de botella, pero no desea realizar el esfuerzo de crear una estructura de granja de operador / despachador, puede mover el trabajo intensivo a futuros:

val service = actor { ... case m: MakeSomethingWithUsers() => Futures.future { sender ! myExpensiveOperation(m) } }

De esta manera, las tareas realmente costosas se generan en nuevos hilos (suponiendo que no necesita preocuparse por la atomicidad y los interbloqueos, etc., lo que puede hacer, pero una vez más, la solución de estos problemas no es fácil en general) y los mensajes ser enviados a donde sea que deberían ir, independientemente.


Para las transacciones con actores, debe echar un vistazo a los "Transcators" de Akka, que combinan actores con STM (memoria transaccional de software): http://doc.akka.io/transactors-scala

Es una gran cosa.


Solo riffing, pero ...

Creo que si quieres usar actores, debes descartar todos los patrones anteriores y soñar con algo nuevo, luego tal vez re-incorporar los viejos patrones (controlador, dao, etc.) según sea necesario para llenar los vacíos.

Por ejemplo, ¿qué pasa si cada Usuario es un actor individual sentado en la JVM, o mediante actores remotos, en muchas otras JVM? Cada usuario es responsable de recibir mensajes de actualización, publicar datos sobre sí mismo y guardarse en el disco (o un DB o Mongo o algo así).

Supongo que lo que quiero decir es que todos los objetos con estado pueden ser actores que esperan mensajes para actualizarse.

(Para HTTP (si desea implementarlo usted mismo), cada solicitud engendra a un actor que bloquea hasta que obtiene una respuesta (usando!? O un futuro), que luego se formatea en una respuesta. Puede engendrar MUCHOS actores que camino, creo.)

Cuando llega una solicitud para cambiar la contraseña del usuario "[email protected]", envía un mensaje a ''[email protected]''. ChangePassword ("nuevo secreto").

O tiene un proceso de directorio que realiza un seguimiento de las ubicaciones de todos los actores del usuario. El actor de User Directory puede ser un actor en sí mismo (uno por JVM) que recibe mensajes sobre qué actores de usuario se están ejecutando actualmente y cuáles son sus nombres, luego les transmite mensajes desde los actores de Request, delegados a otros actores de Directory federados. Le preguntaría a UserDirectory dónde está un Usuario, y luego enviará ese mensaje directamente. El actor de User Directory es responsable de iniciar un actor de usuario si no se está ejecutando uno. El actor de Usuario recupera su estado, luego exceptúa las actualizaciones.

Etc, y así sucesivamente.

Es divertido pensar en eso. Cada actor de usuario, por ejemplo, puede persistir en el disco, agotar el tiempo de espera después de un cierto tiempo e incluso enviar mensajes a los agentes de agregación. Por ejemplo, un actor de usuario podría enviar un mensaje a un actor de LastAccess. O un PasswordTimeoutActor podría enviar mensajes a todos los actores del usuario, diciéndoles que soliciten un cambio de contraseña si su contraseña es anterior a cierta fecha. Los usuarios pueden incluso clonarse en otros servidores o guardarse en múltiples bases de datos.

¡Divertido!