Hola gente,
Estoy terminando una primera versión de un MVC distribuido entre
Smalltalk y los browsers de Internet, y la verdad es que estoy bastante
entusiasmado con los resultados que voy obteniendo.
Como ya dije en post anteriores, el SWT permite convertir cada navegador
web en un mini-ambiente Smalltalk. Y, si tenemos Smalltalk, queremos
tener MVC para hacer las GUIs (por algo el MVC se inventó en Smalltalk).
El reto de diseño que presentó esta parte (el MVC) era lograr mantener
la semántica MVC de Smalltalk, pero con una abstracción que permita
optimizar el flujo de datos entre Smalltalk y los browsers. En un
esquema MVC “normal” (o local), la Vista le puede (y normalmente lo
hace) enviar muchos mensajes al Modelo cuando se entera (la Vista) que
el modelo cambió.
Pero cuando separamos el Modelo de la Vista, y podemos Internet en el
medio, el costo de envío de mensajes es muchísimo más alto que cuando
enviamos mensajes de forma local. Así que si queremos que el MVC
distribuido sea algo usable (y no sólo un experimento friki) hay que
minimizar, por un lado, la transferencia de datos al mínimo; y, por otro
lado, hay que minimizar la cantidad de requests desde el browser.
La arquitectura que estoy utilizando es, a grandes rasgos, la siguiente:
* Cada objeto “Modelo” (herencias de S2SModel) se describe a si
mismo con una colección de Aspectos.
* Los Aspectos (S2SAspect) describen una parte “visible” del
Modelo (visible en el sentido de que el usuario de la aplicación
verá esa parte en alguna vista).
* Los Aspectos están compuestos, básicamente, de lo
siguiente:
* name: Nombre “visible” del aspecto.
* model: El objeto descripto.
* getter: Selector para enviarle al Modelo y
obtener la parte. Si no se especifica un getter,
el name se usa como getter.
* setter: Selector para enviarle al Modelo cuando
queremos cambiar la parte. Si no se especifica
setter, el Aspecto es (por defecto) readOnly. Si
se le pide al Aspect que sea read-write, y no se
especifica el setter, por defecto el setter es
creado con el name.
* eventName: Nombre del evento que el Modelo
disparará cuando cambie la parte. Si no se
especifica un eventName, se utiliza el name.
* Los mensajes más útiles que podemos enviarle a un Aspect
son:
* #value Devuelve el valor representado por el
Aspect.
* #value: Cambia (si el Aspect es ReadWrite) el
valor representado por el Aspect.
* #onChangeSend:to: Cuando el Aspecto cambia
(porque cambió el Modelo) enviar un determinado
mensaje a un determinado receptor.
* #removeActionsWithReceiver: Remover las acciones
con el receptor dado.
* Con este simple protocolo, se puede crear una
vista a cualquier Aspect.
* Por defecto, los objetos herencia de S2SModel, se describen a si
mismo automáticamente de la siguiente forma:
* Por cada variable de instancia, se chequea si existe un
método con el mismo nombre que la variable de instancia.
Si existe, se crea un Aspecto de igual nombre que la
variable, sin setter, Read-Only.
* Si además de existir un método con el mismo nombre que
la variable de instancia existe OTRO método con el
nombre nombreDeVariable: (con un : al final), se crea un
Aspect Read-Write.
* Los aspectos devueltos puede, obviamente, cambiarse
especializando los métodos de S2SModel.
* Para un ejemplo, ver la clase SWTSampleModel.
* Los Aspectos son como “proxies” al modelo.
* Los Aspectos dependen del modelo.
* Las vistas dependen del Aspecto.
* Por defecto, los objetos herencia de S2SModel, pueden crear una
vista por defecto.
* Por defecto, se crea una vista compuesta por una vista
creada por cada Aspecto del Modelo.
* La vista creada por defecto puede, por supuesto,
modificarse especializando los métodos de S2SModel.
Como ven, hasta ahora, la arquitectura no sabe ni asume nada sobre si el
Modelo está local (en el mismo ambiente que la Vista), o remoto (en otro
ambiente, normalmente en el Server).
Lo único que hacen “de más“ los Aspects, y que impacta cuando el modelo
esta remoto, es mantener un cache del valor que representan. Cada vez
que se envía #value al Aspecto, esto no significa que realmente se
enviará un mensaje al modelo. El valor cacheado se mantiene actualizado
por el mecanismo de eventos del Modelo. Esto reduce drásticamente la
cantidad de mensajes enviados al Modelo, y eso es importante cuando el
Modelo está separado de la Vista con una red en el medio.
El Aspecto se comunica con el Modelo usando los siguientes mensajes:
* #perform: Usado para pedir el valor representado por el Aspecto.
El Aspecto hace algo así “self model perform: self getter”
* #perform:with: Usado para cambiar el valor representado por el
Aspecto. El Aspecto hace algo así: “self model perform: self
setter with: newValueObject”
* #when:send:to:withResultsOfSelectors: Usado para que el Aspecto
se mantenga sincronizado con el Modelo. Cuando el modelo cambia,
el Aspecto cachea el nuevo valor, y dispara un evento de cambio
(que se puede usar enviando #onChangeSend:to: al aspecto).
* #removeActionsWithReceiver: Usado para que el Aspecto se
desconecte, del Modelo, cuando el lo considere conveniente.
El mecanismo básico de eventos de Squeak fue expandido “un poquito”
agregando la parte “withResultsOfSelectors:”. Eso quiere decir que,
antes de propagar el evento, envíe los selectores dados contra el modelo
y se incluyan los resultados en la evaluación disparada por el evento.
Esta extensión permite que, cuando el Modelo está en el servidor y el
evento se va a propagar a los navegadores, podamos enviar “algo más” de
datos al Browser. Ese “algo más” es utilizado con picardía por los
Aspects, y se pre-traen el valor cacheado. Es decir que, cuando un
Aspect recibe un evento, recibe junto al Evento el nuevo valor para el
cache. Eso hace que el Aspecto no tenga que enviar NINGUN mensaje al
Modelo cuando este cambia, en el Evento tiene todo lo necesario.
Prácticamente todos los objetos pasan por copia desde Smalltalk al
mini-ambiente que hay en el navegador EXCEPTO los S2SModel. Los S2SModel
pasan por “referencia remota” (ver clase: SWTRemoteModel). El objeto
SWTRemoteModel responde a los 4 mensajes que necesitan los Aspectos
(#perform:, #perform:with:, #when:send:to:withResultsOfSelectors: y
#removeActionsWithReceiver:); es decir: Los SWTRemoteModel son
polimórficos con los S2SModel, al menos desde el punto de vista de los
Aspectos.
Bueno, demasiado verso escrito... les recomiendo que, si les interesa,
sigan la lectura en sus respectivos Squeaks. Miren el ejemplo llamado
"Web 2.0 MVC".
¿Comentarios? ¿Preguntas? ¿Ideas?
Saludos,
-- Diego