jueves, 25 de julio de 2013

Iniciar un proyecto con Grunt

Introducción

En esta ocasión voy a explicar brevemente los pasos a seguir para realizar un proyecto HTML, CSS, Javascript desde cero. Más concretamente, voy a explicar cómo lo hago yo, que no es ni la mejor ni la peor manera de hacerlo. Simplemente a la que estoy acostumbrado y me va bien. Espero que os ayude a crear vuestra propia metodología de trabajo.

He dejado los archivos en un repositorio de BitBucket: base-grunt. Espero que os sirva!

Voy a suponer que tenemos instalado en nuestro sistema:

  • Bower: gestor de librerías, con el que podemos descargar automáticamente las que necesitemos para el proyecto (bootstrap, jquery, etc.)
  • Grunt; automatizador de tareas
  • Growl: notificaciones de nuestros watchers (para MacOS) (para Windows)
  • Node.js. Sin él, nada sería posible.

Si todo esto te suena a chino, te recomiendo entonces que leas antes el artículo donde explico más en detalle cada una de estas herramientas: Node.js, Grunt.js, Bower y Yeoman.

No comento nada acerca de Yeoman (generador de estructuras de directorios), porque no he sido capaz de hacerlo funcionar correctamente bajo Windows 7, con un generator personalizado.

Creamos el árbol de directorios

Supongamos que tenemos dos entornos: dev (desarrollo) y pro (producción). En desarrollo, guardaremos nuestros archivos ya compilados (CSS, HTML, imágenes y javascript), pero sin minificar, de tal modo que al ejecutarlos, podamos ver fácilmente en qué linea del código se producen los errores. Y también en desarrollo será donde guardemos nuestros test. En producción, guardamos los compilados pero en este caso, con un formato minificado, y a ser posible, concatenando ficheros en uno sólo (todos los CSS en un único archivo), para minificar el número de peticiones al servidor. En dicho caso, la estructura sería:

/proyecto/
|-- app/ <- fuentes
|   |-- coffee/
|   |   `-- commons.coffee
|   |
|   |-- jade/
|   |   |-- inc/
|   |   |   |-- footer.jade
|   |   |   `-- header.jade
|   |   |
|   |   |-- layouts/
|   |   |   `-- main.jade
|   |   |
|   |   `-- index.jade
|   |
|   |-- js/ <- Custom JS sin preprocesar
|   |   `-- app.js
|   |
|   |-- less/
|   |   `-- *.less
|   |
|   `-- test/
|       |-- js/
|       |   `-- test.js
|       |
|       `-- index.jade
|
|-- dev/ <- compilados para desarrollo (sin minificar ni ofuscar)
|
`-- pro/ <- compilados para producción (minificados)

Configuramos las librerías a usar con Bower

Para ello, nos creamos dos ficheros: .bowerrc y component.json. Con el primero, especificamos el directorio donde se desarcarán los repositorios de cada librería, y con el segundo, especificamos qué librerías descargamos, y qué versión de cada una.

En el siguiente ejemplo, indico que vamos a necesitar la última versión de jquery, y la última de QUnit:

.bowerrc

{
  "directory": "bower",
  "json": "bower.json"
}

bower.json

{
  "name": "base-grunt",
  "version": "0.1.0",
  "dependencies": {
    "qunit": "latest",
    "jquery": "latest"
  },
  "ignore": [
    "**/.*",
    ".jshintrc",
    "node_modules",
    "components",
    "bower",
    "test",
    "tests"
  ]
}

Configuramos nuestro package.json y gruntfile.js

El siguiente paso, es añadir nuestro gruntfile.js al proyecto, con la configuración y tareas que deseemos automatizar:

  • Copy: para copiar carpetas y archivos de un lugar a otro
  • JSHint: para revisar nuestro código javascript en busca de errores y malas prácticas
  • Uglify: para minificar código javascript con UglifyJS
  • Notify: para lanzar avisos a Grow
  • Coffeescript: preprocesador javascript
  • Coffeelint: lo mismo que jshint, pero para coffescript: nos ayudará a evitar errores en nuestros archivos coffeescript.
  • Sass: preprocesador CSS, como Less o Stylus. Ya no es necesario tener WinLess corriendo sobre nuestra máquina.
  • Jade, Yaml o Haml: preprocesador HTML. En este caso, voy a utilizar Jade, por la potencia que dan los includes y layouts, que Haml y Yaml no te dan.
  • Watcher: para realizar el compilado automático al guardar el archivo fuente
  • DevTools: para activar las notificaciones con la consola de Chrome

Para ello, tenemos dos archivos: package.json y gruntfile.js. Con el primero, especificamos variables que utilizaremos luego en gruntfile.js, así como las librerías que utilizaremos con Grunt, y detalles del proyecto como el nombre, autor, repositorio, licencias, etc. Con el segundo archivo, gruntfile.js, configuramos las tareas a automatizar, las notificaciones, los entornos, etc.

package.json

{
  "name": "base-grunt",
  "version": "0.1.0",
  "description": "Basic template for build projects with Bower + Grunt + Growl + Less + Jade + CoffeeScript",
  "devDependencies": {
    "matchdep": "latest",
    "grunt": "latest",
    "grunt-coffeelint": "latest",
    "grunt-contrib-jshint": "latest",
    "grunt-contrib-copy": "latest",
    "grunt-contrib-jade": "latest",
    "grunt-contrib-watch": "latest",
    "grunt-notify": "latest",
    "grunt-contrib-less": "latest",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-coffee": "latest",
    "grunt-devtools": "latest"
  },
  "main": "gruntfile.js",
  "repository": {
    "type": "git",
    "url": "ssh://git@bitbucket.org:alfonsomartinde/base-grunt.git"
  },
  "author": "alfonsomartide",
  "license": "BSD"
}

gruntfile.js

// Lee las propiedades definidas en packaje.json
// Las podemos usar aquí así: <%= pkg.name %>

;(function () {
    "use strict";
    
    module.exports = function(grunt) {

        grunt.initConfig({
        
            pkg: grunt.file.readJSON('package.json'),

            coffeelint: {
                files: {
                    src: ['app/coffee/*.coffee']
                },
                options: {
                    'no_tabs': {'level': 'ignore'},
                    'no_trailing_whitespace': {'level': 'ignore'}
                }
            },

            // app/coffee > dev/js
            coffee: {
                dev: {
                    files: { 
                        'dev/js/commons.js': 'app/coffee/commons.coffee'
                    }
                }
            },

            // dev/js > pro/js
            uglify: {
                pro: {
                    files: {
                        'pro/js/all.min.js':['dev/js/*.js']
                    }
                }
            },

            // Comprobamos errores
            jshint: {
                files: ['gruntfile.js','app/js/**/*.js','app/test/**/*.js'],
                options: {
                    ignores: ['app/js/vendors/*.js'],
                    globals: {
                        jQuery: true,
                        console: true,
                        module: true
                    }
                }
            },


            copy: {
                gen: {
                    files: [
                        // generator > dev
                        {
                            expand: true,
                            cwd: 'app/generator/',
                            src: ['**/*'],
                            dest: 'dev/'
                        },
                        // generator > pro
                        {
                            expand: true,
                            cwd: 'app/generator/',
                            src: ['**/*'],
                            dest: 'pro/'
                        }
                    ]
                },
                dev: {
                    files: [
                        // app/test/js > dev/test/js
                        {
                            expand: true,
                            cwd: 'app/test/js/',
                            src: ['**/*'],
                            dest: 'dev/test/js/'
                        },
                        // app/js/vendors > dev/js/vendors
                        {
                            expand: true,
                            cwd: 'app/js/vendors/',
                            src: ['**/*'],
                            dest: 'dev/js/vendors/'
                        },
                        // app/js > dev/js
                        {
                            expand: true,
                            cwd: 'app/js/',
                            src: ['*.*'],
                            dest: 'dev/js/'
                        },
                        // bower/jquery > dev/js/vendors
                        {
                            expand: true,
                            cwd: 'bower/jquery/',
                            src: ['jquery.min.js'],
                            dest: 'dev/js/vendors/'
                        },
                        // bower/qunit > dev/test/qunit
                        {
                            expand: true,
                            cwd: 'bower/qunit/qunit/',
                            src: ['**/*'],
                            dest: 'dev/test/qunit/'
                        }
                    ]
                },
                pro: {
                    files: [
                        // dev/js/vendors > pro/js/vendors
                        {
                            expand: true,
                            cwd: 'dev/js/vendors/',
                            src: ['**/*'],
                            dest: 'pro/js/vendors/'
                        },
                    ]
                }
            },

            less: {
                dev: {
                    files: {
                        'dev/css/all.css': 'app/less/all.less'
                    }
                },
                pro: {
                    options: {
                        compress: true
                    },
                    files: {
                        'pro/css/all.min.css': 'app/less/all.less'
                    }
                },
            },
    
            jade: {  
                dev: {  
                    options:{  
                        pretty: true,  
                        data: function(){
                            return {developing: "true"};
                        }  
                    },  
                    files:[
                        // app/jade > dev/html
                        {
                            expand: true, 
                            src: "*.jade", 
                            dest: "dev/html/", 
                            ext: ".html", 
                            cwd: "app/jade/"
                        },
                        // app/test > dev/test
                        {
                            expand: true, 
                            src: "*.jade", 
                            dest: "dev/test/", 
                            ext: ".html", 
                            cwd: "app/test/"
                        },
                    ]
                },  
                pro: {  
                    options:{  
                        pretty: false,  
                        data: function(){
                            return {developing: "false"};
                        }
                    },
                    // app/jade > pro/html
                    files:[{
                        expand: true,
                        src: "*.jade",
                        dest: "pro/html/", 
                        ext: ".html", 
                        cwd: "app/jade/"
                    }]
                }  
            }, 

            notify: {
                coffeelint : {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "coffeelint iniciado!"
                    }
                },
                coffee : {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "coffee iniciado!"
                    }
                },
                jshint: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "jshint iniciado!"
                    }
                },
                uglify: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "uglify iniciado!"
                    }
                },
                jade: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "jade iniciado!"
                    }
                },
                less: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "less iniciado!"
                    }
                }
            },

            watch: {
                coffeelint: {
                    files: ["app/coffee/{,*/}*.coffee"], 
                    tasks: ["notify:coffeelint","coffeelint"]
                },
                coffee: {
                    files: ["app/coffee/{,*/}*.coffee"], 
                    tasks: ["notify:coffee","coffee:dev"]
                },
                jshint: {
                    files: ["app/js/{,*/}*.js","app/test/js/{,*/}*.js"], 
                    tasks: ["notify:jshint","jshint","copy:dev"]
                },
                jade: {
                    files: ["app/jade/{,*/}*.jade","app/test/{,*/}*.jade"],
                    tasks: ["notify:jade","jade:dev"]
                },
                less: {
                    files: ["app/less/{,*/}*.less"],
                    tasks: ["notify:less","less:dev"]
                }
            }

        });


        /**
         * Cargamos todos los tasks declarados en package.json
         *
         */

        require('matchdep')
            .filterDev('grunt-*')
            .forEach(grunt.loadNpmTasks);




        /**
         * Definimos las tareas
         *
         */
        grunt.registerTask("gen", function (target) {
            grunt.task.run([
                "copy:gen"
            ]);
        });

        grunt.registerTask("default", function (target) {
            grunt.task.run([
                "coffeelint",
                "coffee",
                "jshint",
                "uglify", 
                "copy",
                "jade",
                "less",
                "watch",
            ]);
        });

        grunt.registerTask("dev", function (target) {
            grunt.task.run([
                "coffeelint",
                "coffee:dev", 
                "jshint",
                "copy:dev",
                "jade:dev",
                "less:dev",
                "watch",
            ]);
        });

        grunt.registerTask("pro", function (target) {
            grunt.task.run([
                "uglify:pro", 
                "copy:pro",
                "jade:pro",
                "less:pro"
            ]);
        });

    };
}());

Instalando todo

A continuación abrimos la consola MSDOS, nos metemos en el directorio raíz de nuestro proyecto y ejecutamos el comando:

npm install

Hecho esto, se nos habrán descargado las librerías que especificamos en package.json a la carpeta node_modules. El siguiente paso, es descargar las librerías de Bower:

bower install

Si estamos detrás de un proxy, deberemos especificar la variable HTTP_PROXY antes de ejecutar el bower install:

set HTTP_PROXY=http://user:pass@server.url:port

En nuestra carpeta de bower, tendremos descargadas las librerías que hemos indicado. Y con esto, si todo ha ido bien, ya tendremos preparado nuestro entorno de trabajo.

Últimos pasos

Para empezar a programar, sólo nos queda:

  1. Arrancar Growl
  2. Ejecutar la tarea grunt dev, grunt pro, o grunt default, según queramos generar un entorno u otro:
    grunt dev
    

jueves, 11 de julio de 2013

Instalar Jenkins en CentOS 6.3

sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum -y install jenkins

Instalamos JDK

sudo yum -y install java-1.7.0-openjdk.x86_64

Cambiamos a usuario jenkins, y especificamos el shell, ya que por defecto para la mayoría de las instalaciones, éste viene capado a /bin/false

sudo su jenkins -s /bin/bash
ssh-keygen
exit

Arrancamos el servicio:

sudo service jenkins start

Con esto, si todo ha ido bien, podremos acceder a nuestro Jenkins en: http://localhost:8080, o el dominio donde lo hayamos instalado. Recordar protegerlo, porque de lo contrario, permanecerá abierto para cualquier usuario.

miércoles, 10 de julio de 2013

Nested Revealing Module Pattern

Uno de los patrones que más utilizo para el desarrollo de pequeñas aplicaciones, es el patrón "Revealing Module". Me gusta porque te permite encapsular funciones públicas y privadas, y dejar el global scope limpio.

Sin embargo, bajo determinadas circunstancias, dicho patrón puede quedarse pequeño, especialmente si nos gusta modularizar, u organizar el código de un modo más fácil de cara a mantenerlo y a futuras expansiones.

Imaginemos que tenemos una web con tabs, menus y un par de carruseles. Podríamos organizar nuestro código de muchas maneras. Una de ellas es usando el patrón "Module":

var AppUI = {

  Tabs: {
    show: function(){},
    hide: function(){},
    init: function(){}
  },

  Menus: {
    show: function(){},
    toggle: function(){},
    Options: {
      add: function(){},
      remove: function(){}
    },
    init: function() {}
  },

  Carruseles: {
    start: function(){},
    stop: function(){},
    next: function(){},
    prev: function(){},
    init: function(){}
  }

};

La ventaja de este sistema, es que tengo el código organizado, y el global scope sólo se ve afectado por una nueva variable, o namespace, llamada AppUI, pero todas las funciones dentro de AppUI son públicas, lo que no es muy correcto. Fíjate que por error, yo podría escribir AppUI.Tabs.show(), en vez de AppUI.Menus.show(), y estar obteniendo una funcionalidad indeseada. Lo cual, podría arreglarlo escribiendo nombres de propiedades más descriptivas, como Tabs.showTabs(), o Menus.showMenus(), de tal modo que no pudiese cometer el error anterior, pero esto no deja de tener cierta redundancia, y con objetos con muchas propiedades, me obligaría a poner nombres quizá demasiado extensos, para hacerlos más descriptivos, y puede que perdiese algo de legibilidad.

No digo que lo anterior sea incorrecto, todo depende de la solución que más se adapte a nuestras necesidades. Si lo que queremos es evitar que todo sea público dentro de AppUI, lo anterior no es la mejor manera.

Me gusta que el global scope sólo se vea afectado por una única variable nueva, AppUI. Pero quiero evitar que dentro de ella, todas las funciones sean públicas. Es decir, quiero mantener lo más limpio posible, el scope de AppUI. ¿Cómo resolverlo? Pues aquí es donde entran en juego otros patrones, en concreto el "Revealing Module".

Refactorizando el anterior objeto AppUI, para adaptarlo a dicho patrón, nos quedaría algo como esto:

var AppUI = {};

AppUI = (function(){

  function init(){}

  return {
    init:init
  }

})();

AppUI.Tabs = (function(){

  function show(){}
  function hide(){}
  function init(){}

  return {
    init:init
  }

})();

AppUI.Menus = (function(){

  function show(){}
  function toggle(){}
  function init(){}

  return {
    init:init
  }

})();

AppUI.Menus.Options = (function(){

  function add(){}
  function remove(){}

  return {
    add:add,
    remove:remove
  }

})();

AppUI.Carruseles = (function(){

  function start(){}
  function stop(){}
  function next(){}
  function prev(){}
  function init(){}

  return {
    start:start,
    stop:stop,
    init:init
  }

})();

Lo que tenemos ahora es un patrón "Module", con varios patrones "Revealing Module" en su interior. Nos vamos acercando. Mantenemos la modularidad, ya que AppUI.Carruseles, así como el resto, los puedo definir en archivos distintos. Y hemos conseguido ocultar algunas funciones, haciéndolas privadas para el global scope y nuestro namespace. Por ejemplo, AppUI.Carruseles.next() es totalmente invisible. Sin embargo, me sigue sin gustar... Desde el global scope puedo acceder a todos los objetos de AppUI: Tabs, Menus y Carruseles. Desde AppUI.Carruseles, puedo acceder a AppUI.Menus.Options.add(), etc., y puede que no me interese.

Entonces se me ocurrió anidar el patrón "Revealing Module", dentro de otro "Revealing Module". Esto solo puede ser fruto de una mente retorcida... Veamos el resultado:

var AppUI = (function(){

  var Tabs = (function(){

    function show(){}
    function hide(){}
    function init(){
      show();
    }

    return {
      init:init
    }

  })();

  var Menus = (function(){

    var Options = (function(){

      function add(){}
      function remove(){}
      function init(){
        add(4);
      }

      return {
        init:init
      }

    })();

    function show(){}
    function toggle(){}
    function init(){
      show();
    }

    return {
      init:init
    }

  })();

  var Carruseles = (function(){

    function show(){}
    function start(){}
    function stop(){}
    function next(){}
    function prev(){}
    function init(){
      show();
      start();
    }

    return {
      init:init
    }

  })();

  function init(){
    Tabs.init();
    Menus.init();
    Carruseles.init();
  }

  return {
    init:init
  }

})();

De este modo, lo único que es visible desde el global scope, es AppUI.init(). Y por supuesto, desde Carruseles, no puedo ver a Menu.Options, como sucedía antes. A lo sumo, desde AppUI, tendría acceso únicamente a las funciones de Tabs, Menus y Carruseles, que hayamos querido hacer públicas, como por ejemplo, Menus.init(). Pero nada más.

El inconveniente, es que no podría dividirlo en varios archivos, por lo que hemos perdido toda modularidad: mal. Me sigue sin servir. Aunque puede que esto no suponga un inconveniente para el proyecto en el que estés...

Este patrón anidado, no es ni mejor ni peor que otros. Todo depende de lo que necesites, y supone simplemente una vuelta de tuerca más, para aquellos que sean un poco maniáticos con la limpieza del scope, en todos sus ámbitos. A mi personalmente me gusta más un namespece muuuuy limpio.

Si lo único que necesitamos es un lugar donde encapsular e inicializar todo un conjunto de funciones estancas entre ellas, pero mantener el scope lo más limpio posible, nos sobra más de la mitad del código anterior, y nos serviría con:

var AppUI = function(){
  
  (function Tabs (){
    function show(){}
    function hide(){}
    show()
  })();

  (function Menus (){
    (function Options (){
      function add(){}
      function remove(){}
      add(4);
    })();
    function show(){}
    function toggle(){}
    show();
  })();

  (function Carruseles (){
    function show(){}
    function start(){}
    function stop(){}
    function next(){}
    function prev(){}
    show();
    start();
  })();

}

Y cuando queramos iniciarlizar nuestra función, por ejemplo, una vez cargado el DOM:

AppUI();

domingo, 7 de julio de 2013

Node.js: qué es y cuándo utilizarlo

Se trata de una "nueva" tecnología del lado del servidor, como pueda ser PHP, Ruby, .NET, etc., basada en el motor de JavaScript V8 de Google: una potente máquina virtual creada por Lars Bak

Ventajas de Node.js a nivel de servidor

Una ventaja de Node.js es el lenguaje con el que se crean sus aplicaciones: JavaScript. Si! Ahora podemos realizar consultas a base de datos, lectura de ficheros, procesamiento de imágenes, todo con javascript en el lado del servidor. Esto está haciendo que JavaScript, sea el lenguaje del momento, como lo fue PHP en su día, o Ruby.

Utilizando un servidor web tradicional, como pueda ser Apache, cada vez que solicitas un recurso web, éste crea un hilo separado, o invoca un nuevo proceso, para atender dicha solicitud. A pesar de que Apache responde rápidamente a las solicitudes, y limpia el proceso una vez que ha terminado, este sistema puede necesitar un montón de recursos. Una aplicación web con muchas visitas, puede tener serios problemas de rendimiento.

Node.js, por el contrario, utiliza un sistema de E/S asíncrono y basado en eventos y callbacks de funciones. Es decir, todo lo realiza en un único hilo: se queda escuchando a ciertos eventos que ocurran en nuestra aplicación, y cuando ocurren, responde en consecuencia, de modo asíncrono, es decir, un evento no bloquea a otro. Dicho de otro modo, crea miles de subprocesos y trata a sus outputs como streams. Esto supone una mayor velocidad de respuesta. Os dejo un enlace donde podéis comprobar vosotros mismos, que Node.js es muchísimo más rápido sirviendo peticiones que Apache: https://code.google.com/p/node-js-vs-apache-php-benchmark/wiki/Tests

Ventajas de Node.js a nivel de desarrollador

Dejo para el final, la que para mi supone a día de hoy la principal ventaja: tener Node.js instalado en tu entorno de desarrollo local, te abre la puerta a una inmensa cantidad de herramientas, con las que automatizar tediosos procesos y mejorar tu rendimiento como desarrollador: es decir, te permite centrarte en el desarrollo y olvidarte de lo demás. Independientemente del lenguaje que uses después, con Node.js instalado en local, puedes utilizar:

  • Grunt.js: imprescindible procesador de tareas, y watchers, con el que automatizar el compilado de tus archivos a varios entornos. Es decir, podemos trabajar con preprocesadores como Sass, Haml, CoffeScript, etc., con la ventaja que esto ya supone, pero además, unificar todos sus watchers en un único lugar, y compilar varias salidas al mismo tiempo, en diferentes formatos, según lo necesitemos. Por ejemplo, si trabajas con Sass, Less o Stylus, puedes decidir compilar para producción un CSS minificado, y al mismo tiempo, compilar para un entorno de desarrollo, otro sin minificar en otra carpeta. Con Grunt puedes también usar JSHint, que en tiempo real irá diciéndote si en tus .js estás cometiendo errores sintácticos, en qué linea, y cómo resolverlos. Y si a la vez le pones Haml, Yaml, Ugligy, etc. pues lo mismo, puedes minificar la salida para producción, pero para tu entorno de pruebas, dejarlo sin minificar. Más información sobre Grunt.
  • Bower: gestor de paquetes o librerías desarrollado por Twitter. Cada vez que inicias un proyecto, Bower se encargará por ti, de descargar las versiones que necesites de las librerías javascript que necesites, o si lo prefieres, que descargue siempre la última versión, y coloque los archivos donde tu le digas: jQuery, bootstrap, CoffeeScript, etc.: aquí tenéis toda la lista de paquetes: http://sindresorhus.com/bower-components/. Más información sobre Bower.
  • Yo: un paquetizador de entornos, por decirlo de alguna manera, que se integra con Grunt. Te permite configurar el árbol de directorios de cada nuevo proyecto como a ti te guste: él te crea la estructura por ejemplo, para un proyecto con HTML5, o PHP. Aún no he conseguido que funcione demasiado bien bajo Windows, y estoy en búsqueda de alternativas... Más información sobre Yo.
  • All together now!: Yeoman, un conjunto de herramientas integradas entre sí: el gestor de entornos Yo, el gestor de librerías Bower, y el automatizador de tareas Grunt. Más información sobre Yeoman.

¿Cúando utilizar Node.js?

Existen varios casos donde no tiene sentido utilizar Node.js. Por ejemplo, cuando nuestra aplicación necesite muchísima CPU, y tenga pocos procesos de E/S, como podría ser, codificando vídeo. Para eso, casi mejor utilizar otros lenguajes como C o C++.

Otro ejemplo donde no vamos a ver una gran ventaja utilizando Node.js, es si estamos programando una aplicación clásica contra base de datos: alta, baja, edición y modificación de registros. Aquí, Node.js no te va a dar más ventajas de las que te pueda dar PHP, o Ruby, ya que trabajarás con un único canal de E/S.

Donde si es adecuado Node.js, es en aplicaciones de una sola página, con mucho AJAX, o cuando necesitas hacer muchas cosas al mismo tiempo, sobre todo muchas operaciones E/S (acceso a ficheros, bases de datos,...) a la vez.

También obtendrás una gran ventaja en velocidad y rendimiento, con aplicaciones en tiempo real, que necesitan mantener una conexión persistente entre el navegador y el servidor (juegos online, chats, herramientas de colaboración, etc ).

Otro punto interesante, es que para Node.js, las peticiones y respuestas HTTP son streams, lo que supone poder parsear subidas de archivo en tiempo real: http://nodejs.org/api/stream.html

Conclusiones

Por supuesto, no a todo el mundo le gusta Node.js, especialmente cuando se convierte en un lenguaje de moda. Hace poco leí en un post que las gráficas de rendimiento y velocidad, se hacen contra Apache, y no contra PHP con Nginx, para favorecer a Node.js, y que éste último, era inseguro. Obviamente, sus conclusiones tampoco eran del todo ciertas: Nginx es un servidor web/proxy inverso, y puede ser usado también con Node.js. Es decir, para ser justos, habría que comparar PHP/Nginx, con Node/Nginx. Y respecto a la seguridad, depende del desarrollador, no de la herramienta.

Está claro, que cada tecnología, tiene su sentido en un determinado contexto, y hay que saber aplicar la mejor según las necesidades del proyecto. Supongo que por esto es importante conocer bien qué opciones tenemos, para ofrecer siempre el mejor servicio, y no quedarse atascado en tecnologías clásicas. Dicho de otro modo: todos los caminos conducen a Roma, pero por unos llegas antes que por otros. Cuantas más opciones conozcas, mejor para ti como desarrollador.

sábado, 6 de julio de 2013

¿Cómo conectar por SSH a nuestra microinstancia EC2 con Windows?

Este tutorial, forma parte de una serie de artículos comprendidos dentro de: Cómo crear un entorno de desarrollo gratuito, en Amazon EC2.

En este caso, suponemos que ya hemos creado nuestra microinstancia, y tenemos nuestro archivo .pem descargado. Si aun no lo has hecho, te recomiendo entonces que leas el artículo Crear un entorno de desarrollo gratis en Amazon EC2: microinstancias

Descargamos el PuTTY

La herramienta que utilizaremos para conectar por SSH con nuestra clave .pem que nos generó Amazon al crear la microinstancia, desde Windows a nuestro servidor, se llama PuTTY y la podéis descargar aquí. Os sugiero que descarguéis el instalador de Windows: a día de hoy es putty-0.62-installer.exe, que incluye el PuTTY, el generador de claves PuTTYGen, y el gestor de claves Peagent

Convertimos el archivo .pem a .ppk

Una vez que tenemos instalado el PuTTY, ejecutamos el generador de claves PuTTYGen. Lo necesitaremos, para generar el archivo .ppk a partir del .pem, porque con Windows, los .pem no funcionan muy bien. Los archivos .pem son certificados X.509 o claves RSA, pero tampoco hace falta que sepamos mucho más de ellos para lo que pretendemos hacer. Simplemente, guárdalo en un lugar seguro, y no se lo des a nadie. Por decirlo de alguna manera, es un certificado que asegura que eres tu, y no otra persona, a Amazon.

Bueno, pues una vez que hayamos arrancado el PuTTYGen, veremos algo como esto:

Hacemos click en File > Load private key, y nos pedirá que le indiquemos dónde está el archivo .pem a transformar. Recordar que debéis seleccionar "All files", para que os lo muestre. A veces, viene seleccionada la opción de mostrar únicamente los archivos .ppk:

Lo siguiente que nos debe aparecer, es un aviso de que la llave, ha sido correctamente importada, como este:

Podemos ponerle un nombre a dicha llave, si modificamos el campo "Key comment", y añadirle una contraseña si modificamos el campo, que por defecto viene en blanco "Key passphrase". No es necesario hacer ninguna de las dos cosas, aunque en un futuro nos puede venir bien que el comentario, al menos, sea algo más descriptivo. Lo más importante es que la opción SSH2 esté seleccionada. Una vez hecho esto, pulsamos en "Save private key":

Nos preguntará si estamos seguros de querer guardar el archivo .ppk sin contraseña, en caso de que no le hayamos puesto ninguna. Le decimos que si, y guardamos nuestro .ppk en un lugar seguro, pero que recordemos, porque lo vamos a necesitar varias veces.

Con esto, hemos terminado con PuTTYGen, y abrimos ahora el PuTTY, que será la herramienta que usaremos para conectar. Si ya tienes convertido tu .pem en un .ppk, puedes empezar por este paso directamente:

Configurando el PuTTY para conectar a EC2 por SSH

Al abrir el programa, debemos ver una ventana como esta:

  • En el campo Host, ponemos la dirección de nuestra microinstancia. Debe ser algo como ec2-111-111-111-111.eu-west-1.compute.amazonaws.com. Os explicaré más adelante cómo convertir esta URL, en una IP más asequible, creando desde el panel de gestión de EC2, una "Elastic IP". En caso de que ya tengáis una "Elastic IP" asociada a vuestra microinstancia, en host siemplemente podéis meter dicha IP, en vez del nombre ec2-111-111-111-111.eu-west-1.compute.amazonaws.com
  • En el campo Port, ponemos 22. Recordad que tenéis que haber abierto dicho puerto, en vuestra microinstancia, o si no, no podréis acceder.
  • En Conection Type: SSH
  • En el campo "Saved sessions", ponemos el nombre con el que queramos guardar nuestra conexión, y por último...
  • Pulsamos en "Save"

A continuación, sin cerrar esa ventana, en el menú de la izquierda, seleccionamos Conection > Data, y en el campo "Auto-login username", escribimos el usuario con el que queramos acceder por defecto. En mi caso, es ubuntu, en otros tutoriales he visto que ponen root... Yo probaría primero con ubuntu, si habéis montado un Ubuntu 12.04.

Seguimos en esa ventana, sin cerrarla, y vamos a Connection > SSH > Auth, y allí indicamos dónde está nuestro archivo .ppk que creamos antes

Importante: este paso, si se os olvida, tendréis que volver a configurarlo todo. Sin cerrar la ventana del PuTTY, tenéis que volver, desde el menú de la izquierda, a la opción "Session", ponerle un nombre a la configuración de sesión, y darle a "Save". De este modo, se os guardará la configuración en la lista de abajo, y la próxima vez que queráis acceder, ya simplemente es pinchar en ella, y "Load".

Para terminar, una vez hayamos guardado la configuración, sólo nos queda conectarnos: pulsamos en "Open":

Y si todo ha ido bien, debemos ver una ventana como esta:

Y listo! Estamos en nuestro EC2, en el directorio home del usuario!!. Espero que os haya ayudado este tutorial.

Cómo crear una alarma de costes en AWS

Este tutorial, es la continuación de cómo crear un entorno de desarrollo gratuito, en Amazon EC2.

Las alarmas de coste, sirven para notificar a los usuarios de AWS de cuándo se exceden del plan gratuito, o de una determinada cantidad. Valen para cualquier servicio que utilicemos: EC2, RDS, etc. Desde el perfil de usuario (My account), dentremos monitorizado casi en tiempo real, los costes que se realizarán sobre nuestra cuenta, y por qué razón.

Una vez que ya tenemos creada nuestra microinstancia, lo siguiente que debemos hacer, por tanto, es configurar una alarma de costes. En nuestro caso, el límite a partir del cual debe avisarnos, es si se superan los 0€. Para ello, hacemos accedemos con nuestro email y contraseña a AWS, y vamos a "My account":

Importante: Amazon trabaja con varias regiones. Yo recomiendo que las microinstancias y todo lo que podamos, lo creéis en EU (Ireland). Sin embargo, las alarmas de costes, sólo pueden ser creadas en N.Virginia, así pues, seleccionamos dicha región:

Esto de las regiones, si no estás pendiente, puede marearte un poco, porque crees que estás en una, y Amazon de golpe te cambia a otra, sin avisar. O al hacer login, entras en una región que no es la tuya, y cuando te das cuenta, ya has instalado 4 instancias, que tienes que borrar, para crearlas de nuevo en tu región. Ojo con eso. Si alguien sabe cómo evitar esto, que me lo comente por favor! :)

Bueno, una vez seleccionada la región de N.Virginia, que es la única donde se pueden crear alarmas de costes, vamos a "Account Activity"

Esto nos conducirá a una página donde veremos la factura que nos tiene Amazon preparada para el mes que viene, en función de los recursos utilizados. En nuestro caso, debe estar a 0. Además, desde aquí podremos crear alarmas de costes. Para ello hacemos click en crear nuestra primera alarma de coste: (lo marco en amarillo)

Nos debe aparecer una pantalla como esta:

En ella elegimos, que nos envíe un email, siempre que el coste total de los servicios con Amazon (AWS), supere la cantidad que le indiquemos, en este caso 0. ¡Y listo! Podemos dormir tranquilos, que si nos excedemos del plan gratuito que nos ofrece Amazon, nos llegará un email avisando.

Como nota curiosa, os cuento que configuré la alarma, y sin saberlo, cree dos instancias micro: una en N.Virginia, y otra en EU Ireland. Con esto de que cambian de zona sin enterarte... pues me lié. Enseguida me llegó un email, diciéndome que los costes se habían incrementado en 5 dólares. Así que me puse en contacto con el servicio técnico, cual cliente indignado que se queja sin enterarse ni del nodo. Me atendieron estupendamente, me explicaron que es que había dado de alta dos microinstancias, y que me devolvían 25 dólares por las molestias, pero que espabilase. Así que gané con el error 20 pavos... Con esto quiero decir, que si por alguna razón hacéis algo mal, si tenéis una alarma configurada, tampoco os van a sacar los ojos... porque el coste cuenta por horas, como los taxistas. Y si ponéis remedio enseguida, no creo que el coste sea muy alto. E incluso si lloráis un poco, igual los de soporte se tiran al royo. Calidad del servicio: 10

viernes, 5 de julio de 2013

Crear un entorno de desarrollo gratis en Amazon EC2: microinstancias

Tal y como describen en su web, Amazon nos ofrece con sus Servicios Web (AWS), un conjunto completo de servicios de infraestructuras y aplicaciones para ejecutar prácticamente todo en la nube, desde aplicaciones empresariales y proyectos de grandes datos hasta juegos sociales y aplicaciones móviles. Es decir, podremos tener nuestro entorno de desarrollo en la nube.

El hecho de ser un cluod service, significa que los servicios que contratemos tendrán total escalabilidad, y se adaptarán en tiempo real a los recursos que el proyecto vaya necesitando. Es decir, pagamos por lo que necesitemos, y nada más.

Pero además, Amazon nos ofrece una capa gratuita, para los que queramos empezar en el mundillo cloud, trastear con ella y aprender. Aquí podéis encontrar toda la información que necesitéis sobre dicha capa, y en español: http://aws.amazon.com/es/free/. Yo básicamente la voy a resumir, centrándome simplemente en el servicio EC2. Aquí os dejo un enlace con más características del servicio EC2: https://aws.amazon.com/es/ec2/

¿Qué nos ofrece?

Básicamente, la instancia de EC2 gratuita, la llaman "micro", y ofrece 750 horas al mes durante un año, de un servidor Windows o Linux. 750 horas al mes, equivalen a 31 días, 24 horas al día, que es lo mismo que decir, un año "by the face". Lo miden por horas, porque nuestro servidor lo podremos arrancar o detener según lo necesitemos, y el tiempo y coste, dejan de correr. Esto no lo puedes hacer con los sistemas de alojamiento tradicionales, es una de las ventajas de la nube.

Crear una cuenta en Amazon

Lo primero que debemos hacer, es crearnos una cuenta en AWS: https://aws.amazon.com/

Lo más seguro, es que nos pida los datos de una tarjeta de crédito, o una cuenta Paypal, y tengamos que hacer todo el proceso de comprobación de que dicha cuenta realmente es vuestra. Esto lo hace Amazon, porque no tiene una cuota fija mensual, como en las empresas de hosting clásicas, sino que cobra bajo demanda: pagas por lo que usas, y si no usas nada no pagas nada. No os preocupéis. Yo llevo casi un año con ellos y no me han cobrado nada. Siempre que no os salgáis del paquete gratuito, no tendréis problema. Además podréis configuraros un aviso, para que en el caso de que empiecen a cobraros, desde el primer céntimo, os llegue un email a vuestra cuenta de correo. Es importante, que deis una cuenta de correo que soléis mirar a diario... no sea que... os avisen ¡y no os enteréis!

Todo este proceso de alta de la cuenta o tarjeta de crédito, me lo salto porque es bastante intuitivo, y es seguir los pasos: no tiene misterio. Una vez que lo hayáis termiando, vais a la consola de gestión de nuestro AWS (Dashboard), y hacemos click en EC2

Crear nuestra primera instancia gratuita

Una vez dentro de la pantalla de gestión de nuestro EC2, deberemos ver algo como esto, y hacemos click en "Launch instance" para crear nuestra primera instancia:

Amazon, dispone de una serie de máquinas preconfiguradas, listas para usar con el software que necesitems ya instalado (php, mysql, apache, etc, etc), con lo cual no tendremos que preocuparnos de nada y ganaremos bastante tiempo. Ellos las llaman "AMI", y hay casi 2000 disponibles (aquí tienes la lista completa). Puedes elegir la que quieras, pero OJO: las gratuitas son las que llevan una estrella.

Volviendo a nuestra instancia, acabamos de pulsar en "Launch instance", y debe aparecernos una pantalla como esta, donde podremos elegir qué queremos instalar, si una máquina preconfigurada, una máquina comprada en la tienda de ámazon, o arrancar el asistente, y que nos vaya guiando. Para el tutorial, voy a elegir la opción de máquina preconfigurada, o "Quick Launch", en concreto, una Ubuntu 12.04, que es gratis.

He marcado en amarillo, lo más importante:

  • Debemos especificar el nombre a dicha instancia, con un nombre que sirva para luego identificarla, porque podremos tener más de una instancia micro, aunque luego sólo arranquemos una a la vez, para que no nos cueste dinero.
  • Para poder conectarnos a la instancia por SSH, o sFTP, necesitaremos una clave pública y otra privada, es decir un par de claves (Key pair). Debemos especificar el nombre a dicha clave, y descargarla a una carpeta segura (se trata de un archivo con extensión .pem), donde nadie más tenga acceso a ella. A las máquinas de EC2, se accede con dicha clave, no se pide usuario ni contraseña. Si le das a alguien esa clave, que es un archivito que pesa muy poco, pues le estarás dando el acceso a todas las instancias que lleven dicha clave asociada. Es como una llave que puede abrir las puertas (instancias) que tu indiques. Así que cuidado dónde la guardas :)
  • Debemos elegir el AMI preconfigurado que queramos
  • Y por último pulsamos en continuar

La siguiente pantalla que nos aparecerá, será un resumen de la configuración. En general, aquí no tenemos que tocar nada, salvo la configuración de seguridad. Así que le damos a "Edit details", luego marcamos el checkbox de "Security Setting", después marcamos el checkbox de "Create new Security Group", le ponemos un nombre, una descripción, y añadimos unas cuantas reglas.

Estas reglas, no son otra cosa, que puertos a través de los cuales podremos acceder a nuestra microinstancia. Tendremos que habilitar el 21 si queremos FTP, el 22 si queremos sFTP, o conexión por SSH, el 80 si queremos tener acceso a la máquina con un navegador web, el 3306 si queremos acceder a un mysql que le instalemos, etc. Desde el menú desplegable, veremos ya unas cuantas reglas preconfiguradas. Una vez que terminemos, pulsamos en "Save details", y a continuación "Launch".

El siguiente paso, es crearnos alarmas. Nuestro sistema estará continuamente monitorizado, y como nos cobran por tiempo que tengamos la instancia encendida, como los taxistas, pues está bien que nos avisen de si, por la razón que sea, ha dejado de funcionar. Incluso, podemos decirle que pare la máquina, y no nos siga cobrando... (por algo que no funciona). Está bien pensado, ¿no?

Para configurar alarmas, lo siguiente que tenemos que hacer por tanto, es pulsar en "Create Status Check Alarm". Como verás, debajo en pequeñito, pone que puede costar dinero. Efectivamente, puede, si se te va la olla añadiendo alarmas como si no hubiese mañana. En este tutoríal, vamos a dar de alta sólo una de ejemplo.

Elegiremos que nos mande un email a nuestro correo, y que además detenga la instancia, en caso de que ésta falle más de dos veces seguidas, en periodos de 5 minutos

Pulsamos en siguiente, y ¡enhorabuena! Ya tienes tu servidor de desarrollo funcionando!! Deberías verlo en una pantalla como esta, que se llama la consola de gestión de EC2. En concreto, esta es la lista de instancias.

Desde aquí, si marcamos su checkbox y pulsamos en Acciones, podremos ver todo lo que podemos hacer con ella. Por ejemplo, pararla si no la vamos a usar, para que no nos sigan cobrando por ella, en caso de que no estemos usando la microinstancia gratuita.

Otra ventaja que nos ofrece Amazon, son las gráficas de monitorización, que tiene para aburrir. Podemos verlas, en la misma pantalla donde estamos, es decir, donde la lista de instancias, pinchando en la pestaña "Monitoring":

Creando una alarma de costes

Lo siguiente, y más importante que debemos hacer, es configurar una alarma de coste, desde nuestra cuenta de Amazon Web Services (AWS). Es decir, que nos manden un correo en cuanto empiecen a cobrarnos, para que podamos enseguida, entrar y parar las instancias que nos estén causando coste, si lo deseamos.

Para eso, puedes visitar el siguiente artículo de la saga: ¿Cómo configurar una alarma de costes en AWS? :) Espero que os haya ayudado este minitutorial!