Toda la entrada y salida es asincrona para asegurar que sea Non-Blocking, puesto que el procesador de eventos es de un solo thread.
La idea es que el procesador de eventos escucha los eventos entrantes y los envia (usando la libreria Libuv) al proceso correspondiente. Libuv optimiza los threads priorizando el bajo consumo de recursos.
Gestionando una petición HTTP cada petición genera un evento y se le asigna un proceso ligado a un callback luego es liberado el procesador para poder atender a otras peticiones o eventos. Cuando el proceso termina se llama al callback relacionado. El mismo comportamiento se reproduce cuando se realiza una operación de entrada y salida, por ejemplo escribir en un archivo.
NodeJS esta íntimamente ligado a Javascript una de las piezas clave de su núcleo es el intérprete de Javascript Google V8. Este componente se dedica a ejecutar o procesar eventos con tareas escritas en scripts de Javascript.
JavaScript es un lenguaje de programación interpretado, de alto nivel, dinámico, sin tipado fuerte y multi-paradigma. Es multi-paradigmo porque soporta los estilos de programación Orientada a Objetos, funcional y imperativa.
Inicialmente fue diseñado para ejecutarse en la parte cliente de las tecnologías web. Los navegadores. Pero actualmente es usado también en la parte servidor.
Una de las características que hace a un Framework interesante es que tenga todos los componentes necesarios para desarrollar nuestra solución.
Existen infinidad de Frameworks implementados en base a NodeJS. Para diferentes necesidades, por ejemplo:
De forma genérica en cualquier solución se necesita que un framework tenga soporte para:
Otra característica importante que necesita un buen Framework es que sea escalable.
A demás de implementaciones web en Node tambien se desarrollan herramientas para automatizar tareas por ejemplo para generación de código, minificado de front-end, gestión de dependencias, etc… * gulp * yeoman * bower * etc…
Sin duda pero una de las herramientas mas importantes de node es su gestor de paquetes. npm, centrado en la definición de paquetes y sus dependencias con un repositorio central y gestión de versiones.
Con unas cifras indudablemente grandes, a día de hoy, 241,473 paquetes en npm con más de 150 millones de descargas diarias y siendo javascript el lenguage más utilizado en GitHub: http://githut.info/ la comunidad de NodeJS es sin duda de gran importancia y a la que hay que tener en cuenta.
Todos los beneficios que acabamos de describir hacen a NodeJS adecuado o seductor para soluciones tipo:
Una vez vistas las ventajas, vamos a estudiar las limitaciones, Las repasaremos apartado por apartado siguiendo el orden de los anteriores:
Inicialmente la arquitectura de nodeJS presenta varios problemas:
Los patrones de diseño son muy buenos cuando se usan bien pero todo lo contrario cuando se usan mal.
La arquitectura de NodeJS resulta útil para algunas soluciones pero cuando no es así necesitamos poder cambiar el modelo. Esto no es posible en NodeJS. Veamos, por ejemplo, el caso en que no necesitamos que la entrada y salida sea asíncrona.
Hay muchísimos casos donde no me interesa tener I/O asíncrona:
Por ejemplo en los casos en que el backend realiza cálculos o necesita llamar a servicios por orden o que la llamada a un servicio desencadena o no otra llamada segun su resultado. Por supuesto que node y Javascript me propone muchas soluciones para hacerlo, pero todas tienden a complicar el código y es un problema más que tengo que tener en cuenta.
En los casos en los que no tengo que sincronizarlos sigo teniendo que lidiar con llamadas asincronas, vuelvo a pagar coste de desarrollo cuando no tengo tan claro que me aporte nada sobre ninguna necesidad real de mi solución.
Por ejemplo otro caso, si quiero hacer logs ordenados de mis operaciones de entrada y salida.
Se pueden encontrar por la red comparaciones de rendimiento, la arquitectura de NodeJS debería favorecer a contestar muchas peticiones por segundo. Lo interesante es que en la mayoría de soluciones no necesitamos tanto rendimiento y NodeJS nos impone un patrón de arquitectura. En muchas de ellas se habla de miles de peticiones por segundo. Tengamos en cuenta que por ejemplo Google.com tiene unos 5 mil millones de peticiones diarias, eso supone unas 50000 por segundo. En muchos casos 100 o 200 peticiones por segundo son más que suficientes.
JavaScript por su naturaleza tiende a empeorar muchísimo la calidad del código.
Dicen que un trozo de código vale más que 1000 palabras. He seleccionado 2 trozos de código para representar la sensación que se tiene cuando se lee código Java o Javascript:
Ejemplo de código Java originario de Spring:
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
boolean defaultFound = false;
for (MediaType mediaType : MediaType.parseMediaTypes(request.getHeader(HttpHeaders.ACCEPT))) {
MediaType rawtype = mediaType.removeQualityValue();
if (rawtype.equals(configuration.getDefaultMediaType())) {
defaultFound = true;
}
if (!rawtype.equals(MediaType.ALL)) {
mediaTypes.add(mediaType);
}
}
if (!defaultFound) {
mediaTypes.add(configuration.getDefaultMediaType());
}
return super.lookupHandlerMethod(lookupPath, new CustomAcceptHeaderHttpServletRequest(request, mediaTypes));
}
Ejemplo de código JavaScript originario de la libreria async
async.forEachOfSeries =
async.eachOfSeries = function (obj, iterator, callback) {
callback = _once(callback || noop);
obj = obj || [];
var nextKey = _keyIterator(obj);
var key = nextKey();
function iterate() {
var sync = true;
if (key === null) {
return callback(null);
}
iterator(obj[key], key, only_once(function (err) {
if (err) {
callback(err);
}
else {
key = nextKey();
if (key === null) {
return callback(null);
} else {
if (sync) {
async.setImmediate(iterate);
} else {
iterate();
}
}
}
}));
sync = false;
}
iterate();
};
Estos son dos códigos seleccionados al azar que creo que representan muy bien lo que nos solemos encontrar en Java y los que nos solemos encontrar en Javascript.
En el primer ejemplo (Java) encontramos nombres descriptivos, complejidad ciclomática baja, funciones pequeñas que atienden a su responsabilidad. Como resultado, podemos hacernos una idea de lo que hace el código de inmediato.
En el segundo caso (JavaScript), encontramos nombres de variables no descriptivos y ambiguos como: obj, callback, sync, control de errores sin excepciones que aumentan la complejidad ciclomatica, etc… Resultado: códigos mas difíciles de leer.
Si queréis un ejemplo con códigos más similares aquí tenéis una cesta de la compra en Java y otra similar en JavaScript En este ejemplo se ve como en Java se tiende a escribir más clases cada cual con su responsabilidad => (Clean Code) y en JavaScript es frecuente encontrar funciones largas con expresiones de distintos niveles de detalle en la misma función => (Bad Code).
Des de luego que se puede escribir buen código independientemente del lenguaje. Pero en la práctica los códigos que encontramos en los proyectos Open Source en JavaScript son de una calidad de inferior. Quizá por su naturaleza multi-proposito, o porqué en Javascript no hay una sola forma de hacer las cosas, se pueden definir objetos de varias formas distintas, no hay tipado fuerte, etc… Al final no creo que los programadores que escriben proyectos OpenSource en Javascript sean peores sinó que el lenguaje no les ayuda para escribir código de calidad.
Al no tener tipos la gestión de excepciones en JavaScript pierde una de sus grandes bazas. ¿Como especializo el control de excepciones? En su esencia controlar excepciones es delegar el error a otro nivel o a otra capa. Cuando cazo una excepción es porque puedo actuar en consecuencia, aportar algo a ese control. En JavaScript no puedo seleccionar fácilmente que tipos de excepción voy a controlar y para ello me veo obligado a hacer comprobación de tipos en tiempo de ejecución.
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
console.log(e.name + ': ' + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ': ' + e.message);
}
// ... etc
}
A demás el control de excepciones en códigos de naturaleza asíncrona, en esencia con muchos callbacks, se complica muchísimo.
Por su naturaleza tan dinámica y al tener cosas como el no tipado fuerte, objetos basándonos en prototipos y ningún compilador muchos errores en JavaScript se producen en tiempo de ejecución. Las soluciones suelen ser incrementar la cobertura el testing. Las capas de testing suelen ser bastante difíciles de mantener ahora a la postre estoy obligado a cubrir con testing aspectos que con otros lenguajes no son necesarios.
La deuda tecnológica es una de las grandes cosas a tener en cuenta en cualquier aplicación.
Realmente, si el BackEnd y el FrontEnd están separados como deberían, la cantidad de código que van a compartir es casi 0.
A demás el perfil de los programadores de Front-End y el de los de BackEnd es muy distinto. Raramente un programador de FrontEnd entenderá las implicaciones derivadas por ejemplo de una consulta a la base de datos, y por el otro lado, rara vez un programador de BackEnd entenderá las consecuencias de hacer, por ejemplo, cambios en un CSS.
Por tanto el poder compartir código en FrontEnd y en BackEnd no supone ninguna ventaja significativa.
La mayoría de los frameworks desarrollados sobre NodeJS sufren de:
Por ejemplo meteor desde febrero de 2011 a Febrero del 2016:
Cuando Spring con un periodo de tiempo 3 años mas largo y muchísimas más prestaciones:
En demás proyectos los números son muy parecidos.
Los cambios en los proyectos de NodeJS son muy elevados y normalmente poco rigurosos. Esto complica mucho mantenerse al día. Supone que nuestra solución se tenga que afianzar con unas versiones concretas de nuestras dependencias.
NodeJS también permite tener “Nested Dependencies”, de forma que las dependencias transitivas (Dependencias de mis dependencias) pueden formar un árbol con diferentes versiones de librerías entre sus nodos. Esto por un lado provoca que no se fuercen los cambios, pero por otro y en la práctica duplica considerablemente el número de archivos y de instancias de objetos a memoria, claro.
Por ejemplo en uno de mis proyectos con módulos de node habituales y básicos, grunt, karma, jshint tengo en local 39462 archivos de dependencias.
Comparando por ejemplo la documentación de Sails o ExpressJS con Solo una parte pequeña de Spring vemos que al menos por la cantidad ya hay una diferencia podríamos decir de unas 300 linias a 1.
Si nos fijamos en la calidad habrá muchas opiniones pero normalmente los ciclos tan rápidos de cambios, ramas y aportaciones también condicionan que la calidad de la documentación de los frameworks de NodeJS tienda a la baja.
¿Qué le pido a un Framework ?
Lo primero que busco en un Framework es compatibilidad, cohesión, capacidad para dar una amplia gama de soluciones con una misma base. Si comparamos el Framework de NodeJS con mas prestaciones con Spring, tenemos que el Framework de node no llega a darnos ni un 10% de lo que nos da Spring.
A día de hoy No he encontrado frameworks en NodeJS con todas necesidades básicas como:
Eso no significa que no tenga librerías para cubrirlo todo, significa que estas librerías no forman un Framework y por tanto no se han puesto de acuerdo. Para llegarlas a integrar puede que necesite conocerlas mejor que el desarrollador.
¿Que me pasará por ejemplo si quiero integrar un servicio SOAP con nodeJS y que intervenga por ejemplo en middleware de Sails?
No existe tampoco ningún estándar o sistema de empaquetado para nodeJS, los despliegues en el servidor suelen ser con herramientas para ejecutar como un servicio de forma redundante el núcleo de node. Se suele también versionar cada proyecto con el código de todas sus dependencias debido a que a veces npm tiene problemas de conectividad, caches, etc…
Una comunidad muy grande no tiene porque ser una ventaja. La calidad de las aportaciones es muy importante, también para facilitar a los integradores el poder incluir estas aportaciones de forma estable y segura. Indiscutiblemente el punto de entrada a NodeJS es muy rápido, mucho mas que en Java seguramente. Esto es bueno porque aporta programadores pero se convierte en malo cuando estos programadores no son de calidad. Al final que empecemos a hacer programas no significa que seamos buenos programadores.
Hemos hablado de que NodeJS es adecuado en algunos casos, pero cuando no es adecuado:
En los que supuestamente es adecuado hay que perfilar más. Por ejemplo que pasa con un servicio de Streaming que analice en tiempo real los datos ? Usará la CPU no ?
En un ecosistema de ese tipo, necesito gestores de configuración centralizados, sistemas para autodiscovery de servicios activos, circuit breakers, global locks, sesiones distribuidas, leadership elections, …
¿Qué Framework me proporciona esto? ¿Si uso distintas librerías Node que problemas de integración me supone? ¿Y si me integro con otras soluciones de distintas tecnologias?
Todo esto me hace pensar si hay alguna otra solución en el mercado con la que pueda disponer de las ventajas de Node sin sus limitaciones:
Solemos identificar Java con sus modelos clásicos a menudo propuestos por Sun Microsystems, como la especificación de Servlets lo interesante es que soluciones con la arquitectura de NodeJS ya existen en Java incluso desde antes que node. El problema fue quizá que al asociarse Java con su modelo tradicional, nadie lo tubo en cuenta como solución.
Existen muchas implementaciones de la arquitectura de nodeJS en Java:
Muchas de ellas más maduras que nodeJS. Luego el argumento de que la arquitectura de node es mejor que la de Java se ha de reformular quizá en que la arquitectura de NodeJS es mejor que la del modelo de aplicaciones web 3 capas o de servlet tradicional con el que se relaciona Java.
Saliendo de implementaciones de terceros y volviendo a Java tradicional, Java soporta entrada y salida asíncrona desde 2002 y Java SE 1.4. En 2009 la especificación de Servlet 3.0 estandarizó el procesado “Non-Blocking” pudiendo usarse si queremos servidores de aplicaciones tradicionales como GlassFish, Tomcat, Jetty o cualquier alternativa comercial.
¿Cuales serian pues los beneficios de usar la implementación de una arquitectura como la de node en Java?
El modelo de Event Driven
Debido a que existen modelos híbridos como Play, Spring o que es fácil implementar un método concreto de forma asíncrona. Si un proceso por ejemplo hace llamadas a una api que tienen que sincronizarse NodeJS pierde el sentido. No tenemos mas remedio que sincronizar código asíncrono cosa que complica nuestra implementación innecesariamente.
En Java es muy fácil implementar una de nuestras llamadas de forma explicitamente asíncrona. Ejemplo llamada asíncrona en Java:
@Service
public class GitHubLookupService {
RestTemplate restTemplate = new RestTemplate();
@Async
public Future<User> findUser(String user) throws InterruptedException {
System.out.println("Looking up " + user);
User results = restTemplate.getForObject("https://api.github.com/users/" + user, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return new AsyncResult<User>(results);
}
}
En este punto también podemos mencionar que existen implementaciones como akka que facilitan la programación asíncrona muchísimo más que las de los actuales de NodeJS.
Muchos lenguages compilan al ByteCode de Java, Por ejemplo Scala o Clojure, Groovy a demas son compatibles con sus herramientas de perfilado, gestión de dependéncias, etc…
http://www.olympum.com/java/java-aio-vs-nodejs/
Java es un lenguaje de propósito general basado en clases y especialmente diseñado para tener el mínimo de dependencias posibles. Es de tipado fuerte y compilado con la peculiaridad que se compila a un bytecode que lo ejecuta una máquina virtual. Al existir esa máquina virtual para todas las plataformas el código compilado de java puede funcionar en todas las plataformas.
En esencia mucho más eficiente que Javascript. V8 esta evolucionado en la parte cliente y Java lleva más de 20 años optimizando sus compiladores. Sin incluir NodeJS tenemos una penalización extra con los bindings de Node a V8. Aquí tenemos algunos ejemplos y pruebas de rendimiento: * Benchmark algoritmos * Comparación peticiones por segundo con distintos Frameworks
Java No tiene las limitaciones que hemos hablado anteriormente para el control de excepciones, los errores en tiempo de ejecución, etc… Y a diferencia de JavaScript tiende a obligar a escribir buen código. Por ejemplo puede imponer que se implemente una interfaz. Java, debido a sus contratos y su tipado fuerte es un lenguaje más Auto Documentado “Self-Documenting”. Esta es una característica que cobra mucha importancia en proyectos grandes. Pero es que a demás en términos de cantidad de proyectos que usan el lenguaje la adopción de java en los últimos años ha sido incluso superior al de Javascript.
A demás existen alternativas a lenguajes que compilan a byteCode de Java como scala o Clojure, Groovy, etc con los que puedo integrarme a nivel binario.
En el mundo de los Frameworks y las Herramientas existen una mayor cantidad en Java que en Node. Por ejemplo mirando a los repositorios de paquetes: De los 243k paquetes en npm pasamos a los 1246k en el repositorio de maven 5 veces mas. Voy a enumerar unas cuantas herramientas y frameworks que conozco personalmente:
Pero si hablamos de Frameworks existe uno que se lleva el premio Gordo.
Spring nació en 2002 para simplificar el desarrollo de Java partiendo con un núcleo de IOC o inyeccion de dependencias y AOP.
Una de las muchas cosas que ha hecho muy bien Spring es integrar un ecosistema de librerías para tener el honor de poderse llamar Full Stack:
Otra gran virtud de Spring es que es extremadamente escalable. Todas las capas que deben son abstractas. Por ejemplo la gestión de sesiones, de cache, de Transacciones todo es reimplementable o intercambiando piezas, configurando el contexto hago cosas como cambiar la persistencia de sesión sin tocar nada más que una definición de Bean.
Las herramientas conservan las ventajas de ser comunidades de código abierto, pero suelen priorizar la estabilidad y compatibilidad de los proyectos, los códigos no son tan crípticos ni las aportaciones tan descontroladas y caóticas.
Para todos los usos adecuados en NodeJS encontramos igual o mejores soluciones en Java y también para todos los usos no adecuados en node. De forma que Java me sirve para un mayor catálogo de soluciones. Aprender Java es mejor inversión y me permite sobretodo mantener soluciones a largo plazo.
El rápido punto de entrada de NodeJs resulta muy atractivo para acercar a los programadores de Front-End al Back-End, también es una buena herramienta para soluciones simples, prototipos y pruebas de concepto.
Pero para proyectos de cierta duración, envergadura y robustez no nos interesa un punto de entrada rápido, sino perfiles con amplio conocimiento de los patrones de diseño y arquitecturas que hay que aplicar. En estos casos, Java es mejor herramienta.
Mi experiencia en NodeJS con Sails y Restful. La aproximación fue inmediata, pero a la semana de trabajo ya empezamos a leer código de Sails por poca documentación. Luego al subir de complejidad la capa de servicio nos obligó a refactorizarla con poco éxito. Finalmente procesos con llamadas a servicios sincronizadas nos ocuparon mucho mas tiempo del esperado. A la semana de subir a producción teniamos un problema grabe en Sails que se arregló en una nueva versión que al instalarla rompió toda la capa de servicio ya que habían cambiado interfaces de su api.
Netflix uso NodeJs uno de sus motivos fue para que sus front-enders pudieran escribir también backend
Ebay tubo sus experiencias en node en 2013, en ella reconocen que no comprobaron otras opciones diferentes que el modelo tradicional de servlets en Java.