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.).Transfiriendo la arquitectura típica de 3 niveles a los actores
Éstos son de Typical (presentación), Servicio (lógica de negocio), 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 = { ... }
}
Usted 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 intentando implementar una funcionalidad similar con los actores. Puede tener este aspecto:
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 no es muy importante aquí cómo get() y se implementan Post() extractores. 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 forma elegante de realizar cada paso de procesamiento de forma asíncrona 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 la sensación de que entiendo algo mal. Toda la configuración necesaria parece ser demasiado complicada. ¿Qué piensas sobre esto?
(PS: También sería muy interesante saber cómo las transacciones de base de datos encajan en este cuadro, pero creo que es excesivo para este thread)
+1 - Ambiciosas cosas tuyas, Easy Angel. – duffymo