Introducción a
para
FLOQQ
Quién soy
Ingeniero de software con 15+ años de experiencia
Desarrollador senior en MediaSmart Mobile
Cacharreador impenitente desde siempre
@pinchito, alexfernandez, alejandrofer
Requisitos
Manejo de varios navegadores (Chrome, Firefox)
Conocimientos básicos de HTML
Conocimientos básicos de JavaScript
Familiaridad con algún tipo de servidor web
(Java, PHP, Apache)
Manejo básico de consola Unix / DOS
Sesiones
- Introducción a node.js
- Sesión práctica: hola, mundo (http)
- Introducción al manejo de eventos
- Sesión práctica: hola, mundo (sockets)
- Pruebas asíncronas con node.js
- Sesión práctica: pruebas de sockets
- Continuaciones y callbacks
- Sesión práctica: desmontando callbacks
- npm: Node Package Manager
- Sesión práctica: paquetes
Node.js
Una introducción amable
Breve historia de Node.js
Creado por Ryan Dahl (2009)
Usa el motor de Chrome JS: V8 (en cómic)
Software libre (código fuente)
Soporte de Joyent y StrongLoop
Tres generaciones
Hilos: Apache MTM, Java Servlets
Eventos: nginx, node.js
Modelo de ejecución
Asíncrono
No bloqueante
Dirigido por eventos
Servidores similares:
Ruby: EventMachine
Java: Akka
Node.js es rápido
¿Pero cómo de rápido?
No para correr código secuencial
... pero sí en ejecución concurrente
Sólo un hilo en ejecución (sin hilos)
Multi-proceso usando el módulo cluster
Node.js es muy escalable
Node.js es muy lineal
Fuente: MediaSmart Mobile
Historia de Éxito: PayPal
Página de Resumen de Cuentas
2 ingenieros durante 4 meses
Desarrollado dos veces más rápido que la versión Java
33% menos código, 40% menos ficheros
Dos veces más peticiones por segundo
Tiempos de respuesta un 35% más rápidos
Actualmente migrando toda su infraestructura
Historia de Éxito: LinkedIn
Backend de apps móviles (2011)
Migración desde Ruby on Rails
De 15 instancias a 4 con node.js
El doble de carga que Ruby on Rails
Actualmente migrando toda su infraestructura
Fuente: VentureBeat
Historia de éxito: MediaSmart Mobile
Plataforma inicial: un ingeniero durante un año
Plataforma actual: tres ingenieros durante otro año
Sirve anuncios en móvil para campañas performance
Aguanta 20K peticiones/segundo (al menos)
Escala a 30 servidores (que sepamos)
Latencia < 80 ms
Sesión práctica 1: Hola, mundo
Bajar node.js e instalarlo
Crear proyecto node.js
Probar el código de muestra
¡Hola, mundo!
Hola, mundo (http)
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
Fuente: node.js
Especificación técnica
Servidor http en el puerto 1337
en texto plano, que dice "Hello world"
Muestra una traza en consola al arrancar
Ejercicios
Cambiar la respuesta a una página HTML
que diga "Hola, mundo"
(Pista:
response.write()
)Cambiar el servidor para que responda sólo en /,
en otra URL debe dar error 500
(Pista:
request.url
)Eventos
Asincronía al poder
Emisor de eventos
Clase EventEmitter
Emite evento data
:
emitter.emit('data', data);
Para recibir el evento data
:
emitter.on('data', function(data) {
console.log('Data: %s', data);
});
API de recepción
emitter.on(event, function(data) {});
Registra un listener para un evento
emitter.once(event, function(data) {});
Registra un listener para una sola vez
emitter.removeListener(event, listener);
Elimina un listener
emitter.removeAllListeners([event]);
Elimina todos los listeners, opcionalmente para un eventoLa temible fuga de memoria
(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.
Ocurre cuando se añaden listeners a un emisor
y no se eliminan
Ejemplo: reutilización de conexiones en un servidor
Solución: al terminar cada petición eliminar listener
emitter.removeListener('event', listener);
Emitiendo Eventos
Podemos crear nuestros propios EventEmitters:
var events = require('events'); var util = require('util'); function MyEmitter() { events.EventEmitter.call(this); http.createServer(function (request, response) { this.emit('request', request); res.end('Hello World\n'); }).listen(1337, '127.0.0.1', function() { this.emit('init'); }); } util.inherits(LimitsUpdater, events.EventEmitter);
var emitter = new MyEmitter(); emitter.on('init', function() { console.log('Server started'); });
Esto nos permite gestionar eventos en lugar de callbacks
Sesión práctica 2: hola, mundo (again)
Servidor de sockets
Control a bajo nivel de la conexión
Recibe datos, envía respuesta
Manejado con eventos
Especificación técnica
Servidor de sockets, puerto 1702
Cuando se abre una conexión pregunta "Hello?"
Tras recibir "hello" responde "world" y termina
Cualquier otro mensaje resulta en "ERROR"
Nota técnica: ¡cuidado con los retornos de carro!
Siempre "\r\n".
Documentación: node.js net
Hola, mundo (sockets)
var net = require('net');
var port = 1702;
var server = net.createServer(function(connection) {
console.log('Connection open');
connection.write('Hello?\r\n');
connection.on('data', function(data) {
if (String(data).trim() != 'hello') {
connection.write('ERROR\r\n');
} else {
connection.end('world\r\n');
console.log('connection closed');
}
});
});
server.listen(port);
Contiene tres niveles de ejecución
Ejercicios
Admitir 'hello', 'Hello', 'HELLO, 'HeLlO'...
(Pista: String.toLowerCase()
)
Manejar los eventos
error
y
end
Logar un error o un mensaje normal
Convertir a EventEmitter
Pruebas Asíncronas
Anónimo¿Quién automatiza al automatizador?
Tipos de pruebas
Pruebas unitarias
Pruebas de integración / de sistemas
Pruebas de carga
Pruebas automáticas: ¡las mejores!
Pruebas de código asíncrono
No basta con correr el código y ver el resultado
Requieren una librería especial
Veremos cómo usar testing
Librería especializada
Éxito y fracaso
testing.failure("Failed to do something", callback);
Da la prueba por fallida con un mensaje
testing.success("Well done!", callback);
Da la prueba por buena con un mensaje
Aserciones
Para comprobar que un valor contiene lo que se espera
testing.assert(value, "Error in value", callback);
La variable value
debe ser cierta (true
o un objeto)
testing.assertEquals(sum(2, 2), 4, "Wrong sum", callback);
La función sum(2,2)
debe devolver 4Ejecución asíncrona
testing sigue la convención habitual de node para callbacks:
doSomething(function(error, result) {
if (error) {
console.error('Could not do something: ' + error);
return;
}
....
});
El error contiene algo sólo si la función falló
testing.check(error, "There is an error", callback);
Comprueba que no haya error; si lo hay, falla el testEjemplo de test
Comprobar que la función doSomethingAsync() no da error
y devuelve el valor 5
function testAsync(callback) {
doSomethingAsync(function(error, result) {
testing.check(error, 'Could not do something', callback);
testing.assertEquals(result, 5, 'Invalid result', callback);
testing.success(callback);
});
}
Casi todas las llamadas de testing terminan en una callbackControl
testing.run([testFirst, testSecond], timeout, callback);
Corre las pruebas testFirst()
y testSecond()
testing.show(results);
Muestra el resultado de las pruebas
Uso habitual:
// run tests if invoked directly
if (__filename == process.argv[1])
{
testing.run([testFirst, testSecond], testing.show);
}
¡Automatiza!
Diseña un API de control
Arranca y para el sistema usando el API
Tests auto-contenidos: limpia al terminar
Tres reglas básicas:
- Un único botón
- Haz los fallos visibles
- Sin intervención humana
Sesión práctica 3: pruebas
Diseñar las pruebas para el servidor de sockets
usando testing
Crear un API de control
Añadir a test.js
Diseño de la prueba
Arrancar el servidor de sockets en un puerto diferente (1705)
Conectar con un socket y enviar "hello"
Verificar que devuelve "world"
Cerrar el socket
Parar el servidor
API de control
Encapsular el arranque del servidor en una función
function start(port, callback) {
var server = net.createServer(function(connection) {
console.log('Conexión abierta');
...
});
server.listen(port, callback);
return server;
}
Añadir la parada del servidor: server.close()
Llamar en secuencia:
function testServer(callback) {
var server = start(1705, function(error, result) {
server.stop(function(error, result) {
....
});
});
}
Prueba completa
function testServer(callback) {
var port = 1705;
var server = start(port, function(error) {
testing.check(error, 'Could not start server', callback);
console.log('started');
var socket = net.connect(port, 'localhost', function(error) {
testing.check(error, 'Could not connect', callback);
socket.on('data', function(data) {
console.log('Received ' + data);
var message = String(data).trim();
if (message == 'Hello?')
{
socket.write('hello');
return;
}
testing.assertEquals(message, 'world', 'Bad response', callback);
server.close(function(error) {
testing.check(error, 'Could not stop server', callback);
testing.success(callback);
});
});
});
});
}
Ejercicios
Eliminar todos los console.log()
Añadir otra prueba testError()
:
envía otra cadena, comprueba que obtiene 'ERROR'
Añadir una prueba que arranque dos servidores
en puertos distintos y los pare
Continuaciones
Introducción al "infierno" de las callbacks
Continuaciones
Concepto de programación funcional
CPS: Continuation-passing Style
¡También conocidas como callbacks!
El estado pasa como parámetros y clausuras
Ejemplo: servidor sockets
var net = require('net');
var port = 1702;
var server = net.createServer(function(connection) {
console.log('Connection to %s open', port);
connection.write('Hello?\r\n');
connection.on('data', function(data) {
if (String(data).trim() != 'hello) {
connection.write('ERROR\r\n');
} else {
connection.end('world\r\n');
console.log('Connection to %s closed', port);
}
});
});
server.listen(port);
Continuaciones en amarillo
Parámetros en negrita
Clausuras en celeste
Infierno de las Callbacks
La temida pirámide de callbacks
Infinitas continuaciones anidadas
Abuso de lambdas (funciones anónimas)
Difícil de seguir, depurar y refactorizar
Soluciones Sencillas
Usar funciones con nombre
Crear un objeto con funciones con nombre
Las clausuras se convierten en atributos
Usar async: async.waterfall()
Usar eventos: EventEmitter
Otros modelos
Sesión práctica 4: desmontando callbacks
Revisar la pirámide de callbacks
Código original: test de socket server
Convertir a objeto con atributos
No dejar más de dos callbacks anidadas
Ejercicios
Convertir la función
start()
también a un objetoConvertir prueba a eventos
Evento
error
Leer la spec de generadores
o ver este vídeo (en inglés)
NPM
Node Package Manager
Anatomía de un paquete (o módulo)
Fichero Readme: README.md
Definición: package.json
Punto de entrada: index.js
Código: lib/
Documentación: doc/
Binarios: bin/Comandos NPM
-
npm install
: instala uno o más paquetes
-
npm install
-g: instalación global
-
npm update
: actualiza uno o más paquetes
-
npm remove
: elimina un paquete
-
npm test
: corre las pruebas
Resolución de directorios
Local:
node_modules
../node_modules
../../node_modules
...
$HOME/node_modules
./node_modules
Global:
{prefix}/lib/node_modules
donde {prefix} suele ser /usr/local
aNATOMía de un package.json
{
"name": "simplecached",
"version": "0.0.1",
"description": "Simplified memcached server.",
"contributors": ["Alex Fernández <alexfernandeznpm@gmail.com>"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alexfernandez/simplecached"
},
"dependencies": {
"testing": "1.2.x"
},
"keywords" : ["simplecached", "didactic", "memcached", "caching"],
"scripts": {
"test": "node test.js"
},
"private": "false"
}
Fuente: package.json
Sesión práctica 5: paquetes
Instalar una dependencia con npm install testing
Inspeccionar la estructura de directorios resultante
Listar los paquetes instalados con npm list
Eliminar una dependecia con npm remove testing
package.json
Crear un package.json siguiendo la especificación
partiendo del ejemplo anterior
Cambiar nombre, versión y descripción
Correr npm install
Crear tests
Exportar test()
desde el servidor.
Importarlo desde test.js
.
Correr
npm test
Ejercicios
Paquetizar los ficheros existentes
Código en lib/
Crear index.js
que exporte alguna función
Publicar con npm publish
¡Despublicarlo! con npm unpublish --force
¡Gracias!
Código: https://github.com/alexfernandez/floqq-node
Presentación: https://slid.es/alexfernandez/node-js-floqq
Curso de node.js Floqq
By Alex Fernández
Curso de node.js Floqq
Curso para Floqq sobre node.js. Si te gusta, ¡compra el vídeo completo y ayuda a que haga más presentaciones como ésta!
- 4,756