Yo uso Node.js en el trabajo, y encuentro que es muy poderoso. Obligado a elegir una palabra para describir Node.js, diría "interesante" (que no es un adjetivo puramente positivo). La comunidad es vibrante y en crecimiento. JavaScript, a pesar de sus rarezas, puede ser un gran lenguaje para codificar. Y diariamente repensará su propia comprensión de la "mejor práctica" y los patrones de código bien estructurado. En este momento hay una enorme energía de ideas que fluye hacia Node.js, y trabajar en él te expone a todo este pensamiento: un gran levantamiento de pesas mental.
Node.js en producción es definitivamente posible, pero lejos de la implementación "llave en mano" aparentemente prometida por la documentación. Con Node.js v0.6.x, "cluster" se ha integrado en la plataforma, proporcionando uno de los bloques de construcción esenciales, pero mi script "production.js" sigue siendo ~ 150 líneas de lógica para manejar cosas como crear el registro directorio, reciclaje de trabajadores muertos, etc. Para un servicio de producción "serio", también debe estar preparado para limitar las conexiones entrantes y hacer todo lo que hace Apache para PHP . Para ser justos, Ruby on Rails tiene este problema exacto . Se resuelve mediante dos mecanismos complementarios: 1) Poner Ruby on Rails / Node.Apache / Lighttd ). El servidor web puede servir eficientemente contenido estático, acceder al registro, reescribir URL, terminar SSL , aplicar reglas de acceso y administrar múltiples sub-servicios. Para las solicitudes que llegan al servicio de nodo real, el servidor web envía la solicitud a través de proxy. 2) Utilizando un marco como Unicorn que gestionará los procesos de los trabajadores, los reciclará periódicamente, etc. Todavía no he encontrado un marco de servicio Node.js que parezca completamente horneado; puede existir, pero aún no lo he encontrado y todavía uso ~ 150 líneas en mi "production.js" enrollado a mano.
Leer marcos como Express hace que parezca que la práctica estándar es simplemente servir todo a través de un servicio Node.js de Jack-of-all-trades ... "app.use (express.static (__ dirname + '/ public'))" . Para servicios y desarrollo de menor carga, probablemente esté bien. Pero tan pronto como intente poner una gran carga en su servicio y hacer que funcione las 24 horas, los 7 días de la semana, descubrirá rápidamente las motivaciones que impulsan a los sitios grandes a tener un código C bien horneado y endurecido como Nginx al frente de su sitio y manejando todo de las solicitudes de contenido estático (... hasta que configure un CDN , como Amazon CloudFront )). Para una versión algo humorística y descaradamente negativa de esto, vea a este chico .
Node.js también está encontrando más y más usos que no son de servicio. Incluso si está usando algo más para servir contenido web, aún puede usar Node.js como herramienta de compilación, usar módulos npm para organizar su código, Browserify para unirlo en un solo activo y uglify-js para minimizarlo para la implementación . Para tratar con la web, JavaScript es una combinación perfecta de impedancia y, con frecuencia, es la ruta de ataque más fácil. Por ejemplo, si desea avanzar a través de un montón de cargas útiles de respuesta JSON , debe usar mi módulo subrayado-CLI , el cinturón de utilidades de datos estructurados.
Pros contras:
- Pro: Para un servidor, escribir JavaScript en el back-end ha sido una "droga de entrada" para aprender patrones modernos de IU. Ya no temo escribir código de cliente.
- Pro: tiende a fomentar la comprobación de errores adecuada (prácticamente todas las devoluciones de llamada devuelven err, lo que molesta al programador para que lo maneje; también, async.js y otras bibliotecas manejan el paradigma de "falla si alguna de estas subtareas falla" mucho mejor que el código síncrono típico )
- Pro: algunas tareas interesantes y normalmente difíciles se vuelven triviales, como obtener el estado de las tareas en vuelo, comunicarse entre los trabajadores o compartir el estado de la memoria caché
- Pro: gran comunidad y toneladas de excelentes bibliotecas basadas en un sólido administrador de paquetes (npm)
- Con: JavaScript no tiene una biblioteca estándar. Te acostumbras tanto a importar la funcionalidad que se siente extraño cuando usas JSON.parse o algún otro método integrado que no requiere agregar un módulo npm. Esto significa que hay cinco versiones de todo. Incluso los módulos incluidos en el "núcleo" de Node.js tienen cinco variantes más si no está satisfecho con la implementación predeterminada. Esto lleva a una rápida evolución, pero también a cierto nivel de confusión.
Frente a un modelo simple de un proceso por solicitud ( LAMP ):
- Pro: escalable a miles de conexiones activas. Muy rápido y muy eficiente. Para una flota web, esto podría significar una reducción de 10 veces en el número de cajas requeridas en comparación con PHP o Ruby
- Pro: Escribir patrones paralelos es fácil. Imagine que necesita obtener tres (o N) blobs de Memcached . Haz esto en PHP ... ¿acabas de escribir código para obtener el primer blob, luego el segundo y luego el tercero? Wow, eso es lento. Hay un módulo PECL especial para solucionar ese problema específico de Memcached, pero ¿qué sucede si desea obtener algunos datos de Memcached en paralelo con la consulta de su base de datos? En Node.js, debido a que el paradigma es asíncrono, es muy natural que una solicitud web haga varias cosas en paralelo.
- Con: el código asincrónico es fundamentalmente más complejo que el código síncrono, y la curva de aprendizaje inicial puede ser difícil para los desarrolladores sin una comprensión sólida de lo que realmente significa la ejecución concurrente. Aún así, es mucho menos difícil que escribir cualquier tipo de código multiproceso con bloqueo.
- Con: si se ejecuta una solicitud de cálculo intensivo durante, por ejemplo, 100 ms, detendrá el procesamiento de otras solicitudes que se están manejando en el mismo proceso Node.js ... AKA, cooperativo-multitarea . Esto se puede mitigar con el patrón Trabajadores web (derivando un subproceso para lidiar con la tarea costosa). Alternativamente, podría usar una gran cantidad de trabajadores de Node.js y solo dejar que cada uno maneje una sola solicitud al mismo tiempo (aún bastante eficiente porque no hay proceso de reciclaje).
- Con: Ejecutar un sistema de producción es MUCHO más complicado que un modelo CGI como Apache + PHP, Perl , Ruby , etc. Las excepciones no controladas derribarán todo el proceso, lo que requiere lógica para reiniciar los trabajadores fallidos (ver clúster ). Los módulos con código nativo con errores pueden bloquear el proceso. Cada vez que un trabajador muere, cualquier solicitud que maneja se descarta, por lo que una API con errores puede degradar fácilmente el servicio para otras API cohospedadas.
Versus escribir un servicio "real" en Java / C # / C (C? ¿Realmente?)
- Pro: Hacer asíncrono en Node.js es más fácil que hacer seguridad de subprocesos en cualquier otro lugar y podría proporcionar un mayor beneficio. Node.js es, con mucho, el paradigma asincrónico menos doloroso en el que he trabajado. Con buenas bibliotecas, es solo un poco más difícil que escribir código sincrónico.
- Pro: Sin errores de subprocesamiento / subprocesamiento múltiple. Es cierto que invierte por adelantado en escribir código más detallado que exprese un flujo de trabajo asincrónico adecuado sin operaciones de bloqueo. Y necesita escribir algunas pruebas y hacer que funcione (es un lenguaje de script y los nombres de variables de digitación gorda solo se detectan en el momento de la prueba de la unidad). PERO, una vez que lo hace funcionar, el área de superficie para errores de seguridad (problemas extraños que solo se manifiestan una vez en un millón de carreras) esa área de superficie es mucho más baja. Los impuestos que escriben el código Node.js se cargan en la fase de codificación. Entonces tiendes a terminar con un código estable.
- Pro: JavaScript es mucho más liviano para expresar la funcionalidad. Es difícil probar esto con palabras, pero JSON , tipeo dinámico, notación lambda, herencia de prototipos, módulos livianos, lo que sea ... simplemente toma menos código para expresar las mismas ideas.
- Con: ¿Tal vez realmente te gustan los servicios de codificación en Java?
Para obtener otra perspectiva sobre JavaScript y Node.js, consulte De Java a Node.js , una publicación de blog sobre las impresiones y experiencias de un desarrollador de Java al aprender Node.js.
Módulos
Al considerar el nodo, tenga en cuenta que su elección de bibliotecas JavaScript DEFINIRÁ su experiencia. La mayoría de las personas usan al menos dos, un asistente de patrón asíncrono (Step, Futures, Async) y un módulo de azúcar JavaScript ( Underscore.js ).
Ayudante / JavaScript Sugar:
- Underscore.js : use esto. Simplemente hazlo. Hace que su código sea agradable y legible con cosas como _.isString () y _.isArray (). No estoy realmente seguro de cómo podría escribir código seguro de lo contrario. Además, para una línea de comando-fu mejorada, consulte mi propia Underscore-CLI .
Módulos de patrones asincrónicos:
- Paso : una forma muy elegante de expresar combinaciones de acciones en serie y paralelas. Mi recomendación personal. Vea mi publicación sobre cómo se ve el código de paso.
- Futuros : forma mucho más flexible (¿es realmente algo bueno?) De expresar el pedido a través de los requisitos. Puede expresar cosas como "comience a, b, c en paralelo. Cuando A y B terminen, comience AB. Cuando A y C terminen, comience AC". Dicha flexibilidad requiere más cuidado para evitar errores en su flujo de trabajo (como nunca llamar a la devolución de llamada o llamarla varias veces). Vea la publicación de Raynos sobre el uso de futuros (esta es la publicación que me hizo "obtener" futuros).
- Asíncrono : biblioteca más tradicional con un método para cada patrón. Comencé con esto antes de mi conversión religiosa a paso y posterior comprensión de que todos los patrones en Async podrían expresarse en Paso con un solo paradigma más legible.
- TameJS : escrito por OKCupid, es un precompilador que agrega un nuevo lenguaje "aguarda" primitivo para escribir elegantemente flujos de trabajo en serie y paralelos. El patrón se ve increíble, pero requiere una compilación previa. Todavía estoy decidiéndome en este caso.
- StreamlineJS - competidor de TameJS. Me estoy inclinando hacia Tame, pero puedes decidirte.
O para leer todo sobre las bibliotecas asíncronas, vea esta entrevista de panel con los autores.
Marco web:
- Exprese el marco Great Ruby on Rails-esk para organizar sitios web. Utiliza JADE como un motor de plantillas XML / HTML, lo que hace que la creación de HTML sea mucho menos dolorosa, incluso elegante.
- jQuery Aunque técnicamente no es un módulo de nodo, jQuery se está convirtiendo rápidamente en un estándar de facto para la interfaz de usuario del lado del cliente. jQuery proporciona selectores similares a CSS para 'consultar' conjuntos de elementos DOM que luego se pueden operar (controladores de conjuntos, propiedades, estilos, etc.). En la misma línea, el framework CSS Bootstrap de Twitter , Backbone.js para un patrón MVC y Browserify.js para unir todos sus archivos JavaScript en un solo archivo. Todos estos módulos se están convirtiendo en estándares de facto, por lo que al menos debería consultarlos si no ha oído hablar de ellos.
Pruebas:
- JSHint - Debe usar; Al principio no usé esto, que ahora parece incomprensible. JSLint agrega varias verificaciones básicas que obtienes con un lenguaje compilado como Java. Paréntesis no coincidentes, variables no declaradas, tipos de muchas formas y tamaños. También puede activar varias formas de lo que yo llamo "modo anal", donde verifica el estilo de espacios en blanco y demás, lo cual está bien si esa es su taza de té, pero el valor real proviene de obtener comentarios instantáneos sobre el número de línea exacto donde olvidó un cierre ")" ... sin tener que ejecutar su código y presionar la línea ofensiva. "JSHint" es una variante más configurable de Douglas Crockford 's JSLint .
- Mocha competidor de votos que estoy empezando a preferir. Ambos marcos manejan los conceptos básicos lo suficientemente bien, pero los patrones complejos tienden a ser más fáciles de expresar en Mocha.
- Votos Votos es realmente bastante elegante. E imprime un informe encantador (--spec) que muestra qué casos de prueba pasaron / fallaron. Dedique 30 minutos a aprenderlo y puede crear pruebas básicas para sus módulos con un mínimo esfuerzo.
- Zombie : prueba sin cabeza para HTML y JavaScript utilizando JSDom como un "navegador" virtual. Cosas muy poderosas. Combínelo con Replay para obtener pruebas deterministas rápidas del código en el navegador.
- Un comentario sobre cómo "pensar en" las pruebas:
- La prueba no es opcional. Con un lenguaje dinámico como JavaScript, hay muy pocos controles estáticos. Por ejemplo, pasar dos parámetros a un método que espera que 4 no se rompa hasta que se ejecute el código. Barra bastante baja para crear errores en JavaScript. Las pruebas básicas son esenciales para compensar la brecha de verificación con lenguajes compilados.
- Olvídate de la validación, solo ejecuta tu código. Para cada método, mi primer caso de validación es "nada se rompe", y ese es el caso que se dispara con mayor frecuencia. Probar que su código se ejecuta sin arrojar capturas del 80% de los errores y hará tanto para mejorar la confianza de su código que se encontrará regresando y agregando los casos de validación matizados que omitió.
- Comience con poco y rompa la barrera de inercia. Todos somos perezosos y estamos presionados por el tiempo, y es fácil ver las pruebas como "trabajo extra". Así que empieza con algo pequeño. Escriba el caso de prueba 0: cargue su módulo e informe el éxito. Si te obligas a hacer exactamente esto, entonces la barrera de inercia para las pruebas se rompe. Eso es <30 minutos para hacerlo la primera vez, incluida la lectura de la documentación. Ahora escriba el caso de prueba 1: llame a uno de sus métodos y verifique que "nada se rompa", es decir, que no reciba un error. El caso de prueba 1 debería llevarle menos de un minuto. Con la inercia desaparecida, se vuelve fácil expandir gradualmente su cobertura de prueba.
- Ahora desarrolle sus pruebas con su código. No se deje intimidar por cómo se vería la prueba "correcta" de extremo a extremo con servidores simulados y todo eso. El código comienza simple y evoluciona para manejar nuevos casos; las pruebas también deberían hacerlo. A medida que agrega nuevos casos y nueva complejidad a su código, agregue casos de prueba para ejercer el nuevo código. A medida que encuentre errores, agregue verificaciones y / o casos nuevos para cubrir el código defectuoso. Cuando esté depurando y pierda la confianza en un fragmento de código, regrese y agregue pruebas para demostrar que está haciendo lo que cree que está haciendo. Capture cadenas de datos de ejemplo (de otros servicios que llame, sitios web que raspe, lo que sea) y aliméntelos con su código de análisis. Algunos casos aquí, validación mejorada allí, y terminarás con un código altamente confiable.
Además, consulte la lista oficial de módulos Node.js recomendados. Sin embargo, el Wiki de módulos de nodo de GitHub es mucho más completo y un buen recurso.
Para comprender Nodo, es útil considerar algunas de las opciones de diseño clave:
Node.js está BASADO EN EVENTOS y ASINCRÓNICO / SIN BLOQUEO. Los eventos, como una conexión HTTP entrante, activarán una función de JavaScript que hace un poco de trabajo y desencadena otras tareas asincrónicas como conectarse a una base de datos o extraer contenido de otro servidor. Una vez que se han iniciado estas tareas, la función de evento finaliza y Node.js vuelve a dormir. Tan pronto como sucede algo más, como la conexión de la base de datos establecida o el servidor externo respondiendo con contenido, las funciones de devolución de llamada se activan y se ejecuta más código JavaScript, lo que puede iniciar aún más tareas asincrónicas (como una consulta de base de datos). De esta manera, Node.js felizmente intercalará actividades para múltiples flujos de trabajo paralelos, ejecutando cualquier actividad que esté desbloqueada en cualquier momento. Es por eso que Node.js hace un gran trabajo al administrar miles de conexiones simultáneas.
¿Por qué no usar solo un proceso / hilo por conexión como todos los demás?En Node.js, una nueva conexión es solo una asignación de montón muy pequeña. Acelerar un nuevo proceso requiere mucha más memoria, un megabyte en algunas plataformas. Pero el costo real es la sobrecarga asociada con el cambio de contexto. Cuando tiene 10 ^ 6 hilos de kernel, el kernel tiene que hacer mucho trabajo para averiguar quién debe ejecutar a continuación. Se ha trabajado mucho en la construcción de un planificador O (1) para Linux, pero al final, es mucho más eficiente tener un solo proceso impulsado por eventos que 10 ^ 6 procesos que compiten por el tiempo de CPU. Además, en condiciones de sobrecarga, el modelo multiproceso se comporta muy mal, privando de servicios de administración y administración críticos, especialmente SSHD (lo que significa que ni siquiera puede iniciar sesión en la caja para descubrir qué tan mal está realmente).
Node.js es de UN SOLO ROSCADO y SIN BLOQUEO . Node.js, como una elección de diseño muy deliberada, solo tiene un solo hilo por proceso. Debido a esto, es fundamentalmente imposible que múltiples hilos accedan a los datos simultáneamente. Por lo tanto, no se necesitan cerraduras. Los hilos son duros. Muy, muy duro. Si no lo crees, no has hecho suficiente programación de hilos Obtener el bloqueo correcto es difícil y genera errores que son realmente difíciles de rastrear. La eliminación de bloqueos y subprocesos múltiples hace que una de las clases de errores más desagradables desaparezca. Esta podría ser la mayor ventaja del nodo.
Pero, ¿cómo aprovecho mi caja de 16 núcleos?
Dos caminos:
- Para grandes tareas de cómputo pesado como la codificación de imágenes, Node.js puede iniciar procesos secundarios o enviar mensajes a procesos de trabajo adicionales. En este diseño, tendría un subproceso que administra el flujo de eventos y N procesos que realizan tareas de computación pesadas y mastican las otras 15 CPU.
- Para escalar el rendimiento en un servicio web, debe ejecutar varios servidores Node.js en una caja, uno por núcleo, usando el clúster (con Node.js v0.6.x, el módulo oficial "clúster" vinculado aquí reemplaza la versión de learnboost que tiene una API diferente) Estos servidores locales de Node.js pueden competir en un socket para aceptar nuevas conexiones, equilibrando la carga entre ellas. Una vez que se acepta una conexión, queda estrechamente vinculada a uno solo de estos procesos compartidos. En teoría, esto suena mal, pero en la práctica funciona bastante bien y le permite evitar el dolor de cabeza de escribir código seguro para subprocesos. Además, esto significa que Node.js obtiene una excelente afinidad de caché de la CPU, utilizando más efectivamente el ancho de banda de la memoria.
Node.js te permite hacer cosas realmente poderosas sin sudar. Suponga que tiene un programa Node.js que realiza una variedad de tareas, escucha en unpuerto TCP los comandos, codifica algunas imágenes, lo que sea. Con cinco líneas de código, puede agregar un portal de administración web basado en HTTP que muestre el estado actual de las tareas activas. Esto es fácil de hacer:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(myJavascriptObject.getSomeStatusInfo());
}).listen(1337, "127.0.0.1");
Ahora puede presionar una URL y verificar el estado de su proceso en ejecución. Agregue algunos botones y tendrá un "portal de administración". Si tiene un script Perl / Python / Ruby en ejecución, simplemente "lanzar un portal de administración" no es exactamente simple.
¿Pero no es JavaScript lento / malo / malvado / engendro del diablo? JavaScript tiene algunas rarezas extrañas, pero con "las partes buenas" hay un lenguaje muy poderoso allí, y en cualquier caso, JavaScript es EL lenguaje en el cliente (navegador). JavaScript está aquí para quedarse; otros idiomas lo están apuntando como un IL, y el talento de clase mundial está compitiendo para producir los motores JavaScript más avanzados. Debido al papel de JavaScript en el navegador, se está haciendo un enorme esfuerzo de ingeniería para hacer que JavaScript sea increíblemente rápido. V8es el último y mejor motor de JavaScript, al menos para este mes. Deslumbra a los otros lenguajes de secuencias de comandos tanto en eficiencia como en estabilidad (observándote, Ruby). Y solo va a mejorar con grandes equipos trabajando en el problema en Microsoft, Google y Mozilla, compitiendo para construir el mejor motor de JavaScript (ya no es un "intérprete" de JavaScript, ya que todos los motores modernos hacen toneladas de JITcompilando bajo el capó con interpretación solo como una alternativa para el código de ejecución única). Sí, todos deseamos poder solucionar algunas de las opciones de lenguaje JavaScript más extrañas, pero en realidad no es tan malo. Y el lenguaje es tan flexible que realmente no está codificando JavaScript, está codificando Step o jQuery, más que cualquier otro lenguaje, en JavaScript, las bibliotecas definen la experiencia. Para crear aplicaciones web, de todas formas debes saber JavaScript, por lo que codificarlo en el servidor tiene una especie de sinergia de habilidades. Me ha hecho no tener miedo de escribir el código del cliente.
Además, si REALMENTE odias JavaScript, puedes usar azúcar sintáctico como CoffeeScript . O cualquier otra cosa que cree código JavaScript, como Google Web Toolkit (GWT).
Hablando de JavaScript, ¿qué es un "cierre"? - Prácticamente una forma elegante de decir que retiene variables con ámbito léxico en las cadenas de llamadas. ;) Me gusta esto:
var myData = "foo";
database.connect( 'user:pass', function myCallback( result ) {
database.query("SELECT * from Foo where id = " + myData);
} );
// Note that doSomethingElse() executes _BEFORE_ "database.query" which is inside a callback
doSomethingElse();
¿Ves cómo puedes usar "myData" sin hacer nada incómodo como guardarlo en un objeto? Y a diferencia de Java, la variable "myData" no tiene que ser de solo lectura. Esta poderosa característica del lenguaje hace que la programación asincrónica sea mucho menos detallada y menos dolorosa.
Escribir código asincrónico siempre será más complejo que escribir un script simple de un solo subproceso, pero con Node.js, no es mucho más difícil y obtienes muchos beneficios además de la eficiencia y la escalabilidad de miles de conexiones simultáneas. ..