CEIBO

Domingo 6/enero/2008

Introducción a Smalltalk Web Toolkit (SWT)

Filed under: Programación — Hernan Galante @ 11:42 am

Introducción

Desde la aparición de Internet se han desarrollado gran cantidad de soluciones y tecnologías para el desarrollo en web, con los últimos avances en las mejoras sobre los navegadores a partir de tecnologías como AJAX y controles web más productivos, se acuñó el término web 2.0 para demarcar una nueva era en el desarrollo de aplicaciones. La intención de este articulo es presentar una introducción a Smalltalk Web Toolkit, un framework para la construcción de aplicativos web complejos usando Smalltalk, en particular, está desarrollado sobre Squeak.

Arquitectura

El framework se basa en que una aplicación tiene 2 partes bien definidas, una parte cliente, que corre en los navegadores en Javascript, y una parte servidora, que corre sobre Smalltalk con un web server basado en Comet. La parte cliente, no es programada en Javascript, sino que se desarrollo todo sobre Smalltalk. El framework provee un traductor a Javascript, denominado ST2JS. Este traductor respeta prácticamente toda la semántica Smalltalk y provee un juego de clases base para crear un mini-ambiente. Por lo tanto, al desarrollar se asume que en el cliente cuenta con un Smalltalk corriendo sobre cualquier navegador que soporte a Javascript. La parte servidora, es un servidor web con Comet. Esta es una técnica de programación web que se utiliza para la entrega de datos entre el cliente y el servidor a través del protocolo HTTP, pero la diferencia es que rompe con la asimetría del HTTP tradicional, que solo se limita a contestar las peticiones del cliente, sino que ahora es simétrico, pues el servidor puede entregar contenido al cliente sin que este lo haya solicitado. Esto también se conoce como “HTTP Push”, “Server Push” o “Reversed Ajax” en inglés.

La comunicación entre los dos mundos

La comunicación entre el cliente y el servidor es transparente para el desarrollador, ya que el framework realiza la conexión entre ambos a través del uso de estándares web como RPC, con XML y usando JSON para serializar los objetos que pasan del cliente al servidor y viceversa. No todos los objetos son pasados por copia al cliente y no todo regresa al servidor, como sabemos, es muy costoso pasar tanta información sobre la red, es por ello, que el framework realiza ciertas optimizaciones sobre esta capa de comunicación. Los objetos del modelo de nuestro aplicativo son referencias remotas, por lo tanto, en el cliente tenemos objetos que representan a los objetos que están del lado del servidor, y solo los objetos que pasan por copia son aquellos objetos que son parte del soporte de la comunicación.

ST2JS

En mucho de los aplicativos web que se desarrollan para la era web 2.0 tienen mucho código en Javascript, pero como el SQL, y los mapeos de objetos a relacional, lo que se intenta es no escribir en un lenguaje distinto al que estamos usando para desarrollar las reglas de negocio del aplicativo. Este traductor respeta prácticamente toda la semántica de Smalltalk y la traduce a Javascript. Es decir, en Smalltalk podemos tener clases, instancias, mensajes de instancia, mensajes de clase, bloques, esto el traductor lo traduce a otros recursos, pues Smalltalk y Javascript no son semánticamente simétricos, pero que al final, terminarán haciendo lo que se describe en Smalltalk.

Características del ST2JS

Mapeo de nombres

El traductor sigue una serie de reglas para convertir los nombres Smalltalk (nombre de clase, de métodos, de variables de instancia) a nombre válidos en Javascript.

  • Nombre de clases: Los nombres de clases se traducen a Javascript con un prefijo “ST.” para evitar colisiones con otras clases de Javascript.
  • Nombre de variables de instancia: En Smalltalk es válido tener el mismo nombre de una variable de instancia y un mensaje que lo accede, pero en Javascript no es posible, por lo tanto, el traductor agrega un guión bajo al nombre de la variable de instancia.
  • Nombre de mensajes: Hay casos triviales y complejos a la hora de traducir los mensajes.

    Smalltalk

    Javascript
    Comentario
    self foo^self foo self.foo()return (self.foo()); Es el caso más simple
    self bar: 2 self.bar_(2) El “:” se transforma a “_”
    self bar: ‘a’ foo: true self.bar_foo_(’a’, true) Las partes del mensaje se concatenan
    #+ __add__  
    #- __sub__  
    #class __class__  
    2 asString ST.asString(2)  
    2 >= 1
    (2).__greaterThanOrEquals__(1);
     
    #//
    __integerQuotient__
     
    #@ __at__  
    1 = 2 ST.equals(1,2);  
Mensajes inline

Los mensajes inline, son atajos a operaciones continuas y repetitivas y del cual se conoce su resultado de antemano. En vez de realizar la operación completa, cuando se encuentra una operación inline se retorna su resultado sin el costo computacional de tener que realizarlo una y otra vez, por lo que se gana en performance. En este caso, hay ciertos mensajes inline que se utilizan con una frecuencia muy alta en el código Smalltalk y para no tener que traducir constantemente a código que ya se conoce cual será su resultado o a que funciones o estructuras de control serán traducidas, entonces se hacen inline. Algunas de estas son, como por ejemplo, las bifurcaciones (#ifTrue, ifFalse), las repeticiones (#whileTrue, #whileFalse), las iteraciones (#do:), los chequeos en colecciones (#isEmpty) :

  • #inlineIfTrue:
  • #inlineIfTrue:ifFalse:
  • #inlineIfFalse:
  • #inlineDo:
  • #inlineWithIndexDo:
  • #inlineWhileTrue:
  • #inlineWhileFalse:
  • #inlineIsEmpty
  • #inlineNotEmpty
  • #to:by:do:

Se puede ver la lista completa de mensajes inline en el framework en el mensaje: S2STranslator>>initializeSelectorMapping. Ejemplo de mensaje inline: El #do: en Smalltalk

S2SArrayExtension>>do: elementBlock

“Evaluate aBlock with each of the receiver’s elements as the argument.”


self
inlineDo:[:each | elementBlock value: each. ].

El #do: de Smalltalk en Javascript:

ST.Array.prototype.do_ = function(aBlock) {

var self = this;

var indexLimiT;indexLimiT = self.size();

for (var index = 1; index <= indexLimiT; index++) {aBlock.value_(self.at_(index));}

return (self);

};

El código anterior se obtuvo evaluando:

S2STranslator instance methodSourceFor: Array selector: #do:

Insertando código Javascript en forma directa

El traductor nos permite insertar código Javascript en forma directa desde el código Smalltalk. Esto tiene utilidad no solo para escribir el mismo traductor, sino para operaciones especiales donde es más fácil o donde se desee optimizar o insertar directamente código Javascript. Como también tenemos la posibilidad de escribir SQL directo en un mapeador de Objectos a relacional, lo mismo sucede en ST2JS.

Ejemplo
El mensaje en Smalltalk que inyecta código Javascript
at: anInteger

“Answer my element at index anInteger. at: is used by a knowledgeable client to access an existing element”

^self jsLiteral: ‘this[anInteger - 1]‘

El mensaje traducido a una función en Javascript evaluando:

S2STranslator instance methodSourceFor: S2SArrayExtension selector: #at:

En Javascript:

Array.prototype.at_ = function(anInteger){

var self = this;

return (this[anInteger - 1]);

};

Bloques de Smalltalk a Javascript

Javascript puede tener funciones que contienen funciones, y son válidas en un contexto únicamente, además, contiene funciones anónimas, que son funciones sin nombre que las podemos ejecutar encapsulándolas entre paréntesis. Los bloques Smalltalk son bloques de código que puede ser evaluados en un contexto, entonces los bloques son traducidos a Javascript en funciones anónimas. Este mapeo es directo, excepto en dos casos. El primero es cuando self referencia al objeto receptor del mensaje, y no a la función Javascript, es decir, el self dentro del bloque se refiere al método que lo contiene. Pero el this en javascript se comporta igual, emulando la funcionalidad en Smalltalk. La segunda, es el retorno en un bloque, dado en smalltalk por el caracter ^ (caret), este causa el retorno del método y no solo del bloque.

Ejemplo, en Smalltalk con bloque con un argumento e, y su posterior ejecución con 7.

ClaseSmalltalk>>metodoX

[ :e | self testMethod + e ] value: 7

Se traduce en Javacript a:

ST.ClaseSmalltalk.prototype.metodoX = function(){
var self = this;
(function (e) {return self.testMethod().__add__(e)}).value_(7);
return (self);
};

Si el método incluye un retorno dentro del bloque:

ClaseSmalltalk>>metodoX

[ :e | ^self testMethod + e ] value: 7

Se traduce complejamente a:

ST.ClaseSmalltalk.prototype.metodoX = function() {
var self = this;
var _ret_ = new ST.ReturnValue();
try {(function (e) {throw _ret_.value_(self.testMethod().__add__(e));}).value_(7);
return (self);}
catch (_e_) {if (_e_ === _ret_) return _e_._value;
throw _e_;}
};

Y si retorna el resultado de la ejecución del bloque:

ClaseSmalltalk>>metodoX

^[ :e | self testMethod + e ] value: 7

Se traduce a

ST.ClaseSmalltalk.prototype.metodoX = function() {
var self = this;
return ((function (e) {return self.testMethod().__add__(e)}).value_(7));
};

Semántica de herencia
Mensajes a super

Los mensajes a super se traducen como mensajes a self, pero con un mensaje de nombre super_. En Smalltalk “super” indica que busca en la clase padre la ejecución del método en cuestión, a lo que es traducido a Javascript se dividen en dos casos. El primero, es directo en el caso de que el método exista solo una vez en una clase padre, pero si existiese en varias clases padre dentro de la jerarquía, el más compleja su búsqueda, y por lo tanto más lenta.

Primer caso

Si en la jerarquía de clases hay sólo una superclase con un método de ese nombre, se define la función super_ (en Javascript). Supongamos que tenemos dos clases en Smalltalk: SuperClassExample que tiene una subclase SubClassExample y ambas implementan #testMethod. En Javascript quedaría
La Subclase:

ST.SubClassExample.prototype.testMethod = function() {
var self = this;
self.super_testMethod();return (self);
};

La implementación al super:

ST.SubClassExample.prototype.super_testMethod() = ST.SuperClassExample.prototype.testMethod()

Y el método en la superclase:

ST.SuperClassExample.prototype.testMethod = function() {var self = this;return (self);};

Segundo caso:

Cuando existe más de una superclase con ese método, la implementación de super_ es un poco más complicada:

// super: slow case (more than one super implementation)
ST.SWTFlasher.prototype.super_initialize = function() {
var functions = [ST.SWTPanel.prototype.initialize, ST.Object.prototype.initialize];
if (typeof this.super_initialize_depth == “undefined”)this.super_initialize_depth = 0;
var result = functions[this.super_initialize_depth++].apply(this, arguments); this.super_initialize_depth–; return result;
};

Esta implementación es más lenta que la anterior, pero es compatible con la semántica del Smalltalk.

Ejemplos de uso del traductor desde un workspace en Smalltalk

El mensaje en Smalltalk a ser traducido:

S2SBaseTestCase>>testInspect

| array |

array := {‘foo’. 1@2. true}.

Uso del Traductor ST2JS en un Workspace:

S2STranslator instance methodSourceFor: S2SBaseTestCase selector: #testInspect.

Traducción generada:

ST.BaseTestCase.prototype.testInspect = function() {
var self = this;
var array;array = [''foo'', (1).__at__(2), true]; return (self);
};

Otras expresiones interesantes:

  • Traducir toda una clase
S2STranslator instance sourceForClass: S2SBaseTestCase
  • Generar todo el codigo fuente Javascript (Solo todas las subclases de S2SObjectExtension)

S2STranslator instance allJsSource

La comunicación entre el servidor y el cliente

La comunicación entre el servidor y el cliente, desde el framework, se hace a través de dos mensajes, #serverSide y #clientSide. El primero nos permite “viajar” desde el cliente hacia el servidor, y el segundo, en el caso inverso. Es decir, si estamos en la vista, y requerimos realizar una operación en el servidor a través del envío de un mensaje, se realiza de la siguiente manera

self serverSide miMensajeAlElServidor

y si estamos en el servidor y deseamos enviar un mensaje a el cliente, es:

self clientSide miMensajeAlCliente

Para ilustrarlo,

Arquitectura del SWT

Model-View-Controller Distribuido

El MVC distribuido que provee el SWT, tiene dos propósitos, el primero es poder hacer las interfaces en los navegadores separando el comportamiento de las vistas del modelo, y por el otro lado, tener una comunicación entre el servidor y el cliente optimizado de manera que no haya comunicación innecesaria para evitar el alto costo que tiene la transferencia de datos en la red. En una aplicación MVC, el M(odelo) está en el servidor (Smalltalk), y la V(ista) y el C(ontrolador) están en el cliente (Smalltalk en Javascript corriendo en el navegador). En una aplicación típica de MVC, la vista se conecta a eventos del modelo o a aspectos de este. Recordemos que los aspectos describen una parte o subconjunto de información del modelo que le será visible al usuario. Entonces, cada suscripción implica un envío de mensajes, si el modelo está remoto eso implica hacer una comunicación a través de la red. Un ejemplo simple, en donde una clase Cliente, tiene atributos como #nombre, #apellido, #fechaDeNacimiento y #dni. Para crear una vista con estos aspectos, la vista y el controlador, deberán suscribirse a eventos de cada atributo, lo que implican 4 peticiones al servidor. Si tenemos en cuenta, que antes de suscribirse hay que pedir la referencia remota de la instancia de Cliente, entonces tenemos N + 1 peticiones para crear la vista del objeto con N atributos. El caso empeora cuando cargamos un objetos y descargamos otro, lo que implica las N + 1 peticiones anterior más M cantidad de peticiones para desuscribirse de los eventos del objeto a descargar, siendo M la cantidad de atributos del objeto a descargar. Por lo tanto, esto generaría un alto nivel de transferencia de datos, haciendo muy lenta la velocidad del cliente. La solución adecuada a esto sería reducir a solo dos peticiones toda la operación entre el modelo y la vista. Es decir, en la primer petición se obtiene la referencia del objeto, y en la segunda, en batch todas las suscripciones y desuscripciones necesarias. Cuando se hace una petición al servidor a través del #serverSide, y se espera un valor de respuesta, este envio, se puede hacer asincrónico. El framework permite encolar varios envios asincrónicos en una sola petición, por ejemplo:

self isolatedAsynchronousRPCMethods:

[ "Limpia los widgets viejos, causando que el browser se desconecte de los eventos al modelo anterior."

mainPanel clearWidgets.

"Crea la nueva vista al nuevo modelo, causando la conexion a los eventos del modelo nuevo."

mainPanel addWidget: aRemoteModel defaultView. ].

Eventos extendidos

Otro patrón típico de las aplicaciones MVC es que la vista envía un mensaje (por ejemplo el mensaje #nombre) cuando se entera que el modelo cambió (por estar suscripto al evento de nombre #nombre). Si tenemos en cuenta que el Modelo está en el servidor, y que las Vistas/Controladores están en los browsers, veremos que cuando el modelo cambia (y el evento se propaga a los browsers, usando la conexión Comet) todas las vistas enviarán un mensaje al Server para obtener el nuevo valor del modelo. Para optimizar ese caso, se expandió el mecanismo de eventos suscribiendo a un Aspecto (SWTAspect) al modelo.

self model

when: self eventName

send: #modelChanged:

to: self

withResultsOfSelectors: {self getter}

onConflictSend: #conflict:.

La clave está en la última parte del mensaje: “withResultsOfSelectors: {self getter}”. Cuando ocurre un evento de nombre “self eventName”, el modelo enviará el mensaje “#modelChanged:” a “self”, incluyendo en el envío el resultado de enviar el mensaje “self getter” al modelo. De esta forma, el evento se propaga con la información extra necesaria evitando que los clientes (browsers) tengan que hacer un nuevo pedido para obtener el nuevo valor.

¿Como desarrollamos en este MVC?

El modelo de nuestro aplicativo debe heredar de la clase SWTModel. De esta manera, todas las instancias de las subclases de esta jerarquía que pasen a la vista, se hacen a través de un modelo remoto (SWTRemoteModel), por lo cual, ya se baja la cantidad de transferencia de datos a la vista al solo pasar una referencia y no la copia del objeto. Por cada cambio que se produzca en el modelo, y sea necesario que este cambio deba ser recibido por los suscriptores, enviamos un #triggerEvent: con el nombre del evento. Un ejemplo sería:

Cliente>>nombre: unNuevoNombre

nombre := unNuevoNombre.

self triggerEvent: #nombreClienteCambio

Con este simple ejemplo, por cada vez que cambia el nombre del cliente, hacemos que se dispare un evento informando a todos los suscriptores de este cambio. La siguiente pregunta es, ¿como nos suscribimos al evento? La respuesta es sencilla, al inicializar la vista, suscribimos a los elementos de la vista que queremos que reflejen el cambio. Para esto hacemos:

ClienteVista>>initializeWidgets

self model

when: #nombreClienteCambio

send: #refrescarNombreCliente

to: self.

De esta manera por cada evento que se produce en el modelo, disparamos la acción que necesitemos en su vista.

ClienteVista>>refrescarNombreCliente

clienteTextInput contents: self model nombre

Screencast de ejemplos

Algunos ejemplos, y también pruebas de conceptos, de este framework están en:

  • Ejemplo del Ping/Pong
    • Este ejemplo es bastante simple, es una especie de Hola mundo para SWT, mostrando las cualidades de construcción en SWT.
  • Live Wiki
    • Prueba de concepto de un Wiki, pero donde se edita directamente sobre el texto que se selecciona y todos los usuarios conectados a la misma página pueden ver los cambios apenas son ingresados.
  • Supermercado en linea
    • Prueba de concepto para compras sociales, en donde hacen las compras varios personas a la vez, pudiendo interactuar con el carrito de compras, conversar y ver el estado actual de los precios y el stock del supermercado
  • Planilla de cálculo sobre web
    • Prueba de concepto sobre una planilla de cálculo que puede estar siendo trabajada por varios usuarios al mismo tiempo y viendo los cambios al mismo tiempo

Referencias

About these ads

3 comentarios »

  1. [...] a Smalltalk Web Toolkit (SWT) « CEIBO http://ceibo.wordpress.com/2008/01/06/introduccion-a-smalltalk-web-toolkit-swt/ (with detailed mini examples of Smalltak to Javascript translations, like closures, blocks, [...]

    Pingback por Smalltalk and Javascript « Angel “Java” Lopez on Blog — Jueves 22/septiembre/2011 @ 11:12 am | Responder

  2. [...] a Smalltalk Web Toolkit (SWT) « CEIBO http://ceibo.wordpress.com/2008/01/06/introduccion-a-smalltalk-web-toolkit-swt/ (with detailed mini examples of Smalltak to Javascript translations, like closures, blocks, [...]

    Pingback por Smalltalk y Javascript - Angel "Java" Lopez — Viernes 23/septiembre/2011 @ 12:06 pm | Responder

  3. [...] a Smalltalk Web Toolkit (SWT) « CEIBO http://ceibo.wordpress.com/2008/01/06/introduccion-a-smalltalk-web-toolkit-swt/ Smalltalk and Javascript using [...]

    Pingback por Javascript: Links, News, Resources (2) « Angel “Java” Lopez on Blog — Martes 27/septiembre/2011 @ 12:15 pm | Responder


RSS feed para los comentarios de esta entrada. TrackBack URI

Deja un comentario

Por favor, inicia sesión con uno de estos métodos para publicar tu comentario:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

El tema Rubric. Blog de WordPress.com.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

A %d blogueros les gusta esto: