sábado, 12 de noviembre de 2016

Construyendo una app modular en node.js


Hace años que trabajo con node.js, llegando a convertirse en mi principal entorno en backend e incluso un poco en frontend con nw.js, y aun así, cada proyecto nuevo que inicio, es una odisea para encontrar la mejor estructura en la que tenga máxima flexibilidad sin crear un sistema de carpetas demasiado complejo.
Yo cada vez que inicio un proyecto nuevo ¡party hard!
No os podéis imaginar la de veces que he reestructurado mi código, buscando siempre la mejor forma.

Ahora me doy cuenta que la respuesta a una pregunta tan compleja es muy sencilla. La respuesta es que no hay mejor forma, sino estructuras que funcionan mejor que otras, dependiendo del tipo de aplicación que quieras hacer.

Lo que sí queda claro es que, hoy por hoy, un sistema modular es probablemente la mejor forma de desarrollar una aplicación, ya que es difícil saber cómo va a crecer. Por ello voy a explicar cómo crear una app modular en node.js, y si eso, dar una estructura base para empezar.

¿Por qué node.js? Porque node.js es uno de los entornos más flexibles, con más librerías open source que me han echado a la cara.

¿Por qué modular? Porque da muchas ventajas, entre las más notables:
  • Permite reutilizar código entre los diferentes módulos que forman la aplicación, incluso pudiendo reutilizar módulos entre servidor y cliente.
  • Cuando la aplicación tiene que crecer o cambiar, ayuda a minimizar la cantidad de código que hay que reescribir, incluso no siendo necesario reescribir nada si se plantea bien desde un principio.
  • Permite comprender el código mucho más fácilmente, al tener poco código en cada archivo en vez de miles de líneas.

Empecemos con el lío.

Para empezar, necesitamos tener un binario de node.js. Es tan sencillo como hacer click aquí para descargar un ejecutable standalone, sin instalaciones. Lo guardamos en una carpeta, abrimos la consola de comandos y vamos a crear nuestro primer script, para probar que todo está correcto. Lo vamos a llamar main.js, mismo, y lo guardamos junto a nuestro binario de node.js:
Ahora vamos a meter en el archivo esto:
console.log("Hola desde node.js :P");
Y lo ejecutamos escribiendo en la consola de comandos node main.js, y se debería ver el resultado:
Perfecto. Ahora creamos la carpeta lib y ya estamos listos. Vamos a por los módulos.

Node.js funciona mediante módulos. Hasta el mismo script inicial se ejecuta como tal. Incluso las funciones internas de node.js (para acceder al sistema de archivos, por ejemplo) son módulos.
Yaaay... ¿?
Tranquila April, eso no es malo. Un módulo es algo diferente a los includes de C a los que más de uno está acostumbrado. Un módulo es un componente aislado del resto de la aplicación, es decir, cuando se incluye un módulo, este se ejecuta aparte, sin mezclar su código con el código del módulo que lo carga. Puedes tener 3 módulos diferentes con código que declara los mismos nombres de variables sin problemas. Cada uno tendrá acceso a su variable "local".

Como supongo que te estarás preguntando ya, si los códigos no se mezclan y no tienen acceso a las variables del resto de módulos ¿cómo interactúan entre ellos?

Cada módulo debe exportar las variables que desee que sean accesibles desde otros módulos, utilizando module.exports.

Vamos a empezar por lo fácil. Creamos un simple script, tal que así:
var saludo = "Hola";

function saludar(nombre) {
    console.log(saludo + " " + nombre);
}

module.exports = saludar;
Esto ya es un módulo. Lo voy a guardar como saludo.js dentro de la carpeta lib.

Si quisiéramos saludar a alguien, tendríamos que cargar el módulo con require(), lo cual devolverá el contenido de module.exports, el cual contiene una referencia a la función saludar(), así que dentro de nuestro main.js simplemente tendríamos que hacer lo siguiente:
var saludar = require("./lib/saludo");

saludar("Pepe");
saludar("Juan Doe");
E ya. Con require() hemos cargado nuestro otro módulo, el cual exporta una referencia de una función, pero no exporta la variable saludo, por lo tanto si hiciéramos
console.log(saludo);
devolvería error, ya que no forma parte de este contexto.

Vamos a complicar más las cosas. Vamos a crear dos módulos, juan.js y pepe.js, los cuales cargarán nuestro módulo saludo.js, tal que así:
/* lib/juan.js */
var saludar = require("./saludo");

function saludarJuan() {
    return saludar("juan");
}

module.exports = saludarJuan;
/* lib/pepe.js */
var saludar = require("./saludo");

function saludarPepe() {
    return saludar("pepe");
}

module.exports = saludarPepe;
Y ahora vamos a jugar con ellos:
var juan = require("./lib/juan");
var pepe = require("./lib/pepe");

console.log(juan());
console.log(pepe());
En este código estamos cargando los módulos juan.js y pepe.js, y estos cargan el módulo saludo.js. Esto no quiere decir que el código de saludo.js es ejecutado 2 veces, ya que node.js es lo suficiente espabilado para cachear el módulo la primera vez que lo ejecuta y devolver esta caché las siguientes veces que se hace require().
Vamos, esos cinco April, que node masmola
Y con esto ya hemos sentado las bases de una aplicación modular en node.js. Como veis, si quisiéramos cambiar la forma de funcionar del saludo, tan sólo tendríamos que modificar saludo.js sin preocuparnos de nada más que exportar la función que queremos que se llame. Fácil, cómodo y JavaScript :P ¿Qué más se puede pedir?

Más adelante hablaré de una aplicación ¿supermodular? que estoy preparando. Con supermodular me refiero a una aplicación modular que es capaz de cargar módulos en caliente, sin necesidad de reiniciar el proceso principal y sin perder las referencias a los módulos antiguos mientras estos hagan falta. Es la magia de node.js.