Contenido

Modularizar aplicaciones escalables javascript con POA

13 Feb

+ 11

Hace ya tiempo vimos como construir una aplicación javascript fácilmente escalable basado en una presentación de Nicholas C.Zackas. Una forma de modularizar nuestro código Javascript con la intención de separar competencias haciendo que nuestro código no sea dependiente de otro.

En el código que vimos, además encapsulábamos todos los métodos de los módulos en un gestor de errores básico que se encargaba de evitar que un error en uno de los módulos provocara que la aplicación dejara de funcionar.

...
 for (name in instance){
    method = instance[name];
    if (typeof method == "function") {
      instance[name] = function(name, method) {
      return function(){
          try { return method.apply(this, arguments);}
          catch(ex) { console.log("[Error]" + name + "(): " + ex.message); }
        }
      }(name, method);
    }
  }
...

Programación orientada a aspecto

Por otro lado, hace todavía más tiempo, vimos una implementación de los filtros de WordPress en Javascript. Una forma de acercar la programación orientada a aspectos al lado del cliente.

La programación orientada a aspectos, no es más que un paradigma de programación que nos ayuda modularizar nuestra aplicación de una forma controlada, permitiendo extenderla desde fuera sin alterar el funcionamiento principal de la misma. O lo que es lo mismo, poder desarrollar plugins que modifiquen el funcionamiento principal.

Uniendo conceptos

Uniendo conceptos podríamos disponer de la capacidad de crear aplicaciones escalables gestionadas por un objeto encargado de arrancar y/o parar módulos, con una batería de herramientas disponibles en estos módulos capaces de hacernos disfrutar de las ventajas de la programación orientada a aspectos.

Veamos el código completo:


var debug = false;
var Sandbox = function() {
	var listeners = {};
	return {
		add: function(name, func){
		  if (typeof listeners == 'undefined') return;
		  if (typeof listeners[name] == 'undefined') listeners[name] = new Array();
			listeners[name].push(func);
		},
		remove: function(name, func){
			if (typeof listeners[name] == 'undefined') return;
			var j = 0;
			while (j < listeners[name].length) {
				if (listeners[name][j] == func) { listeners[name].splice(j, 1);}
				else { j++; }
				}
		},
		fire: function(name, args){
			if (typeof listeners == 'undefined' || (!listeners[name]  || typeof listeners[name] == 'undefined')) return;
		    for (var x=0; x<listeners[name].length; x++)
		        listeners[name][x].call(this, args);
		}
	};
};

var Core = function(){
   	var modules = {}, sandbox = new Sandbox(this);
   	function createInstance(moduleID){
   		var instance = modules[moduleID].creator(sandbox),name, method;
    	if (!debug) {
      		for (name in instance){
        		method = instance[name];
        		if (typeof method == "function") {
          			instance[name] = function(name, method) {
						var evname = moduleID + ":" + name;
            			return function(){
              				try {
								sandbox.fire("pre-" + evname, arguments);
								salida = method.apply(this, arguments);
								sandbox.fire("post-" + evname, salida);
								return salida;
							}
              				catch(ex) {
								if (typeof instance["onerror"] == 'function') instance["onerror"].apply(this, [ex]);
								console.log("[Error]" + name + "(): " + ex.message);
							}
            			}
          			}(name, method);
        		}
      		}
    	}
  	return instance;
 	}

 	// Método públicos
 	return {
   		register: function(moduleID, creator) {
     		modules[moduleID] = {
       			creator: creator,
       			instance: null
     		};
   		},
   		start: function(moduleID) {
     		modules[moduleID].instance = createInstance(moduleID);
     		modules[moduleID].instance.init();
   		},
   		stop: function(moduleID){
     		var data = modules[moduleID];
     		if (data.instance) {
       			data.instance.destroy();
       			data.instance = null;
     		}
   		},
   		startAll: function(){
     		for (var moduleID in modules) {
       			if (modules.hasOwnProperty(moduleID)) {
         			this.start(moduleID);
       			}
     		}
   		},
   		stopAll: function() {
     		for (var moduleID in modules) {
       			if (modules.hasOwnProperty(moduleID)) {
         			this.stop(moduleID);
       			}
     		}
   		}
 	};
}();

¿Que nos ofrece?

He modificado el script para añadir 2 puntos de enlace predeterminados a todos los métodos de los módulos, «pre-XXX» y «post-XXX». Lo que nos permite definir funcionalidades que se ejecutarán antes y después de la ejecución del módulo.  Veamos un ejemplo:

Core.register("test", function($s){
	return {
		init: function(){
			console.log("Constructor");
		},
		destroy: function(){
			console.log("Destroy");
		},
		onerror: function(ex){
			alert("Error: " + ex);
		}
	}
});

Core.register("test2", function($s){
	return {
		init: function(){
                        // Añadimos el evento al contructor del módulo "test"
			$s.add("pre-test:init", function(){
				console.log("Bla bla");
			});
			console.log("Constructor 2");
		},
		destroy: function(){
			console.log("Destroy 2");
		},
		onerror: function(ex){
			alert("Error: " + ex);
		}
	}
});

// Cargamos los módulos
Core.start("test2");
Core.start("test");

// Resultado
// --> Constructor 2

// --> Bla bla
// --> Constructo

Eventos personalizados

Al igual que los eventos por defecto de los que disponemos, podemos especificar eventos propios en nuestro módulos mediante el uso de Sandbox.fire(), que se encargará de ejecutar el contenido asociado al evento especificado.


Core.register("test3", function($s){
	return {
		init: function(){
			console.log("Constructor 2");
			// Ejecutamos el evento "mievento"
			$s.fire("mievento", this);
		},
		destroy: function(){
			console.log("Destroy 2");
		},
		onerror: function(ex){
			alert("Error: " + ex);
		}
	}
});

Como podemos ver, nos permitirá crear aplicaciones escalables y fácilmente extensibles mediante módulos que a su vez serán interoperables entre ellos mediante la programación orientada a aspectos.

  • Buenas, Angel.

    Te comento que toda esta arquitectura que estas mostrando, esta disponible en la siguiente url:

    https://www.safecreative.org/work/1005226363662

    La arquitectura desarrollada y basada en esa presentación, pero plenamente funcional

    Como veras la fecha de entrada en el registro es:
    Entry date: May 22, 2010 6:02 PM UTC

    • @Tomás Corral: Aún así te gano, el primer artículo que escribí es de Septiembre de 2009 y lo puedes encontrar aquí 😀

      Solo es una fusión de esta idea de Nicholas C.Zackas con un script que desarrollé en 2008 basado en la implementación de filtros que usa WordPress.

      Vamos, que no es nada nuevo, ni he inventado la rueda, pero me pareció interesante 😀

      Por cierto, no me parece bien que le pongas el copyright a tu nombre al código de otro
      Saludos y gracias por pasar 😀

    • @aNieto2k:
      Una preguntilla inocente:
      Qué coño está registrando Tomás C.? ¿Esa patente o registro es válido en Europa?
      ¿Si está tratando de registrar/patentar/yoquesé el trabajo de otro, será aceptado?

      Sólo por saber, eh? por ponerme en contexto…

    • @santiago:

      No se registra el código sino la implementación.

      La presentación de Zakas, es una aproximación y no es funcional al 100%.

    • @Tomas Corral: Yo saqué el mismo código de Zackas un año antes y te puedo asegurar que era funcional. La implementación no es calcar el código que Zackas publica en su presentación sino desarrollarlo…

    • @aNieto2k:
      Buenas, Angel, cuanto tiempo….

      Si la última vez que escribi en tu foro te rebatí…Recuerdo el post… un chico que se quejaba de que Sizzle no le funcionaba como debía. Tu le pusiste 4 inputs mal puestos y le quisiste demostrar que si que funcionaba. Los tests reales no se pueden hacer con 4 inputs. Por eso te añadi una página real y te demostré que Sizzle fallaba.
      La version 1.2.6 tenía un selector que aunque se produjera un error en una iteración continuaba con el resto de iteraciones, pero Sizzle no, si haces un each sobre 40 elementos y en el segundo te da error, el resto de iteraciones no se realizan.
      No te gusto que te demostrara un par de cosas de jQuery y te APROPIASTE de todos mis posts.

      Ah y para listo alguien que tiene un foro y da por válido contenidos de otros sin realizar las pruebas básicas de rendimiento y usabilidad.

      Como se ha demostrado jQuery es un gran framework pero ha tenido que salir la versión 1.5 para ser algo más extensible. Y no es util para un proyecto de gran embergadura. El mismo P.Bakaus (creador de jQuery UI, reniega de jQuery y sólo lo utiliza para los drag’n drop y la gestión de eventos)

    • @Tomás Corral: No entiendo a que viene este comentario. Yo no me APROPIO del trabajo de nadie, y si lo he hecho seguro que ha sido un error por mi parte que por descontado me gustaría arreglar. Me gustaría que me pasaras el enlace y lo solucionaré.

      Para evitar llegar a esto he procedido a enviarte un email, y que discutieramos esto de una forma más discreta, pero parece que prefieres hablar por aquí…

      Si tienes algún problema con jQuery ponte en contacto con J.Resig y se lo comentas. Creo que cada uno puede usar lo que crea conveniente para la aplicación que quiera.

      Por cierto, me llamo Andrés…

  • que haces aqui Andres leyendo este y post y quejarte en lugar de cotribuir?
    hiciste las pruebas que dice anieto?

    que es eso de «Aún así te gano, el primer artículo …»—-estas en la primaria aún?

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.