¿Por qué diablos usaría Node.js? Un tutorial caso por caso
Publicado: 2022-03-11Introducción
La creciente popularidad de JavaScript ha traído consigo muchos cambios, y la cara del desarrollo web actual es radicalmente diferente. Las cosas que podemos hacer en la web hoy en día con JavaScript ejecutándose en el servidor, así como en el navegador, eran difíciles de imaginar hace solo unos años, o estaban encapsuladas dentro de entornos de espacio aislado como Flash o Java Applets.
Antes de profundizar en las soluciones de Node.js, es posible que desee leer sobre los beneficios de usar JavaScript en toda la pila que unifica el idioma y el formato de datos (JSON), lo que le permite reutilizar de manera óptima los recursos del desarrollador. Como esto es más un beneficio de JavaScript que de Node.js específicamente, no lo discutiremos mucho aquí. Pero es una ventaja clave para incorporar Node en su pila.
Como dice Wikipedia: "Node.js es una compilación empaquetada del motor JavaScript V8 de Google, la capa de abstracción de la plataforma libuv y una biblioteca central, que está escrita principalmente en JavaScript". Más allá de eso, vale la pena señalar que Ryan Dahl, el creador de Node.js, tenía como objetivo crear sitios web en tiempo real con capacidad push , "inspirados en aplicaciones como Gmail". En Node.js, proporcionó a los desarrolladores una herramienta para trabajar en el paradigma de E/S sin bloqueo y controlado por eventos.
En una frase: Node.js brilla en las aplicaciones web en tiempo real que emplean tecnología push sobre websockets. ¿Qué tiene eso de revolucionario? Bueno, después de más de 20 años de web sin estado basada en el paradigma de solicitud-respuesta sin estado, finalmente tenemos aplicaciones web con conexiones bidireccionales en tiempo real, donde tanto el cliente como el servidor pueden iniciar la comunicación, lo que les permite intercambiar datos libremente. . Esto está en marcado contraste con el típico paradigma de respuesta web, donde el cliente siempre inicia la comunicación. Además, todo se basa en la pila web abierta (HTML, CSS y JS) que se ejecuta en el puerto estándar 80.
Se podría argumentar que hemos tenido esto durante años en forma de subprogramas Flash y Java, pero en realidad, esos eran solo entornos aislados que usaban la web como un protocolo de transporte para entregar al cliente. Además, se ejecutaron de forma aislada y, a menudo, operaron en puertos no estándar, lo que puede haber requerido permisos adicionales y demás.
Con todas sus ventajas, Node.js ahora juega un papel fundamental en la pila de tecnología de muchas empresas de alto perfil que dependen de sus beneficios únicos. La Fundación Node.js ha consolidado las mejores ideas sobre por qué las empresas deberían considerar Node.js en una breve presentación que se puede encontrar en la página de estudios de casos de la Fundación Node.js.
En esta guía de Node.js, analizaré no solo cómo se logran estas ventajas, sino también por qué es posible que desee usar Node.js, y por qué no , usando algunos de los modelos clásicos de aplicaciones web como ejemplos.
¿Como funciona?
La idea principal de Node.js: usar E/S sin bloqueo y controlada por eventos para seguir siendo liviano y eficiente frente a aplicaciones en tiempo real con uso intensivo de datos que se ejecutan en dispositivos distribuidos.
Eso es un bocado.
Lo que realmente significa es que Node.js no es una nueva plataforma milagrosa que dominará el mundo del desarrollo web. En cambio, es una plataforma que satisface una necesidad particular . Y entender esto es absolutamente esencial. Definitivamente no desea utilizar Node.js para operaciones de uso intensivo de CPU; de hecho, usarlo para cálculos pesados anulará casi todas sus ventajas. Donde Node realmente brilla es en la creación de aplicaciones de red escalables y rápidas, ya que es capaz de manejar una gran cantidad de conexiones simultáneas con un alto rendimiento, lo que equivale a una alta escalabilidad.
Cómo funciona bajo el capó es bastante interesante. En comparación con las técnicas tradicionales de servicio web en las que cada conexión (solicitud) genera un nuevo subproceso, ocupando la RAM del sistema y, finalmente, maximizando la cantidad de RAM disponible, Node.js funciona en un solo subproceso, utilizando I/ llamadas O, lo que le permite admitir decenas de miles de conexiones simultáneas en el bucle de eventos.
Un cálculo rápido: suponiendo que cada subproceso tenga potencialmente 2 MB de memoria, ejecutarlo en un sistema con 8 GB de RAM nos sitúa en un máximo teórico de 4000 conexiones simultáneas (cálculos tomados del artículo de Michael Abernethy "Just what is Node .js?”, publicado en IBM developerWorks en 2011; desafortunadamente, el artículo ya no está disponible) , más el costo del cambio de contexto entre subprocesos. Ese es el escenario con el que normalmente se enfrenta en las técnicas tradicionales de servicio web. Al evitar todo eso, Node.js logra niveles de escalabilidad de más de 1 millón de conexiones simultáneas y más de 600 000 conexiones websockets simultáneas.
Está, por supuesto, la cuestión de compartir un solo hilo entre todas las solicitudes de los clientes, y es un escollo potencial de escribir aplicaciones Node.js. En primer lugar, la computación pesada podría obstruir el hilo único de Node y causar problemas para todos los clientes (más sobre esto más adelante), ya que las solicitudes entrantes se bloquearían hasta que se completara dicha computación. En segundo lugar, los desarrolladores deben tener mucho cuidado de no permitir que una excepción llegue al bucle de eventos central (superior) de Node.js, lo que provocará que la instancia de Node.js finalice (lo que bloqueará el programa).
La técnica utilizada para evitar que las excepciones surjan a la superficie es devolver los errores a la persona que llama como parámetros de devolución de llamada (en lugar de lanzarlos, como en otros entornos). Incluso si alguna excepción no controlada logra surgir, se han desarrollado herramientas para monitorear el proceso de Node.js y realizar la recuperación necesaria de una instancia bloqueada (aunque probablemente no podrá recuperar el estado actual de la sesión del usuario), el más común es el módulo Forever, o usar un enfoque diferente con herramientas de sistema externas upstart y monit , o incluso simplemente upstart.
NPM: el administrador de paquetes de nodos
Cuando se habla de Node.js, una cosa que definitivamente no debe omitirse es el soporte integrado para la administración de paquetes usando NPM, una herramienta que viene de forma predeterminada con cada instalación de Node.js. La idea de los módulos NPM es bastante similar a la de Ruby Gems : un conjunto de componentes reutilizables disponibles públicamente, disponibles a través de una fácil instalación a través de un repositorio en línea, con gestión de versiones y dependencias.
Se puede encontrar una lista completa de los módulos empaquetados en el sitio web de npm, o se puede acceder a ellos mediante la herramienta CLI de npm que se instala automáticamente con Node.js. El ecosistema de módulos está abierto a todos, y cualquiera puede publicar su propio módulo que se incluirá en el repositorio de npm.
Algunos de los módulos npm más útiles hoy en día son:
- express : Express.js, o simplemente Express, un marco de desarrollo web inspirado en Sinatra para Node.js y el estándar de facto para la mayoría de las aplicaciones de Node.js que existen hoy en día.
- hapi : un marco centrado en la configuración muy modular y fácil de usar para crear aplicaciones web y de servicios
- connect : Connect es un marco de servidor HTTP extensible para Node.js, que proporciona una colección de "complementos" de alto rendimiento conocidos como middleware; sirve como base para Express.
- socket.io y sockjs : componente del lado del servidor de los dos componentes de websockets más comunes que existen hoy en día.
- pug (anteriormente Jade ): uno de los motores de plantillas populares, inspirado en HAML, predeterminado en Express.js.
- mongodb y mongojs : envoltorios de MongoDB para proporcionar la API para las bases de datos de objetos de MongoDB en Node.js.
- redis : biblioteca cliente de Redis.
- lodash (guión bajo, lazy.js) - El cinturón de herramientas de JavaScript. Underscore inició el juego, pero fue superado por uno de sus dos homólogos, principalmente debido a un mejor rendimiento y una implementación modular.
- forever - Probablemente la utilidad más común para garantizar que un script de nodo determinado se ejecute de forma continua. Mantiene su proceso de Node.js en producción ante cualquier falla inesperada.
- bluebird : una implementación completa de Promises/A+ con un rendimiento excepcionalmente bueno
- moment : una biblioteca de fechas de JavaScript para analizar, validar, manipular y formatear fechas.
La lista continua. Hay toneladas de paquetes realmente útiles, disponibles para todos (sin ofender a los que he omitido aquí).
Ejemplos de dónde se debe usar Node.js
CHAT
El chat es la aplicación multiusuario en tiempo real más típica. Desde IRC (en el pasado), a través de muchos protocolos propietarios y abiertos que se ejecutan en puertos no estándar, hasta la capacidad de implementar todo hoy en Node.js con websockets que se ejecutan en el puerto estándar 80.
La aplicación de chat es realmente el ejemplo perfecto para Node.js: es una aplicación liviana, de alto tráfico y de uso intensivo de datos (pero de bajo procesamiento/computación) que se ejecuta en dispositivos distribuidos. También es un excelente caso de uso para el aprendizaje, ya que es simple, pero cubre la mayoría de los paradigmas que usará en una aplicación típica de Node.js.
Intentemos representar cómo funciona.
En el ejemplo más simple, tenemos una sola sala de chat en nuestro sitio web donde la gente viene y puede intercambiar mensajes de uno a muchos (en realidad, todos). Por ejemplo, supongamos que tenemos tres personas en el sitio web, todas conectadas a nuestro tablero de mensajes.
Del lado del servidor, tenemos una aplicación Express.js simple que implementa dos cosas:
- Un controlador de solicitudes
GET /
que sirve a la página web que contiene un tablero de mensajes y un botón 'Enviar' para inicializar la entrada de mensajes nuevos, y - Un servidor de websockets que escucha los nuevos mensajes emitidos por los clientes de websockets.
En el lado del cliente, tenemos una página HTML con un par de controladores configurados, uno para el evento de clic del botón 'Enviar', que recoge el mensaje de entrada y lo envía por el websocket, y otro que escucha los nuevos mensajes entrantes en el cliente websockets (es decir, mensajes enviados por otros usuarios, que el servidor ahora quiere que el cliente muestre).
Cuando uno de los clientes publica un mensaje, esto es lo que sucede:
- El navegador detecta el clic del botón 'Enviar' a través de un controlador de JavaScript, recoge el valor del campo de entrada (es decir, el texto del mensaje) y emite un mensaje websocket utilizando el cliente websocket conectado a nuestro servidor (inicializado en la inicialización de la página web).
- El componente del lado del servidor de la conexión websocket recibe el mensaje y lo reenvía a todos los demás clientes conectados mediante el método de transmisión.
- Todos los clientes reciben el nuevo mensaje como un mensaje de inserción a través de un componente del lado del cliente websockets que se ejecuta dentro de la página web. Luego recogen el contenido del mensaje y actualizan la página web en el lugar agregando el nuevo mensaje al tablero.
Este es el ejemplo más simple. Para una solución más robusta, puede usar un caché simple basado en la tienda Redis. O en una solución aún más avanzada, una cola de mensajes para manejar el enrutamiento de mensajes a los clientes y un mecanismo de entrega más sólido que puede cubrir pérdidas de conexión temporales o almacenar mensajes para clientes registrados mientras están fuera de línea. Pero independientemente de las mejoras que realice, Node.js seguirá funcionando bajo los mismos principios básicos: reaccionar ante eventos, manejar muchas conexiones simultáneas y mantener la fluidez en la experiencia del usuario.

API EN LA PARTE SUPERIOR DE UN OBJECT DB
Aunque Node.js realmente brilla con las aplicaciones en tiempo real, es bastante natural para exponer los datos de las bases de datos de objetos (por ejemplo, MongoDB). Los datos almacenados en JSON permiten que Node.js funcione sin la discrepancia de impedancia y la conversión de datos.
Por ejemplo, si usa Rails, convertiría de JSON a modelos binarios, luego los volvería a exponer como JSON a través de HTTP cuando los datos sean consumidos por Backbone.js, Angular.js, etc., o incluso jQuery AJAX simple. llamadas Con Node.js, simplemente puede exponer sus objetos JSON con una API REST para que el cliente los consuma. Además, no necesita preocuparse por convertir entre JSON y cualquier otra cosa al leer o escribir desde su base de datos (si está usando MongoDB). En resumen, puede evitar la necesidad de múltiples conversiones mediante el uso de un formato de serialización de datos uniforme en el cliente, el servidor y la base de datos.
ENTRADAS EN COLA
Si recibe una gran cantidad de datos simultáneos, su base de datos puede convertirse en un cuello de botella. Como se muestra arriba, Node.js puede manejar fácilmente las conexiones simultáneas. Pero debido a que el acceso a la base de datos es una operación de bloqueo (en este caso), tenemos problemas. La solución es reconocer el comportamiento del cliente antes de que los datos se escriban verdaderamente en la base de datos.
Con ese enfoque, el sistema mantiene su capacidad de respuesta bajo una carga pesada, lo que es particularmente útil cuando el cliente no necesita una confirmación firme de una escritura de datos exitosa. Los ejemplos típicos incluyen: el registro o la escritura de datos de seguimiento de usuarios, procesados en lotes y no utilizados hasta un momento posterior; así como operaciones que no necesitan reflejarse instantáneamente (como actualizar un conteo de 'Me gusta' en Facebook) donde la consistencia eventual (tan usada en el mundo NoSQL) es aceptable.
Los datos se ponen en cola a través de algún tipo de almacenamiento en caché o infraestructura de cola de mensajes, como RabbitMQ o ZeroMQ, y se procesan mediante un proceso de escritura por lotes de base de datos independiente o servicios backend de procesamiento intensivo de cómputo, escritos en una plataforma de mejor rendimiento para tales tareas. Se puede implementar un comportamiento similar con otros lenguajes/marcos, pero no en el mismo hardware, con el mismo rendimiento alto y mantenido.
En resumen: con Node, puede empujar las escrituras de la base de datos a un lado y tratarlas más tarde, procediendo como si hubieran tenido éxito.
TRANSMISIÓN DE DATOS
En plataformas web más tradicionales, las solicitudes y respuestas HTTP se tratan como un evento aislado; de hecho, en realidad son corrientes. Esta observación se puede utilizar en Node.js para crear algunas funciones interesantes. Por ejemplo, es posible procesar archivos mientras aún se están cargando, ya que los datos ingresan a través de un flujo y podemos procesarlos en línea. Esto se puede hacer para la codificación de audio o video en tiempo real y la transmisión de datos entre diferentes fuentes de datos (consulte la siguiente sección).
APODERADO
Node.js se emplea fácilmente como un proxy del lado del servidor donde puede manejar una gran cantidad de conexiones simultáneas sin bloqueos. Es especialmente útil para hacer proxy de diferentes servicios con diferentes tiempos de respuesta o recopilar datos de múltiples puntos de origen.
Un ejemplo: considere una aplicación del lado del servidor que se comunica con recursos de terceros, extrae datos de diferentes fuentes o almacena activos como imágenes y videos en servicios en la nube de terceros.
Aunque existen servidores proxy dedicados, usar Node en su lugar puede ser útil si su infraestructura de proxy no existe o si necesita una solución para el desarrollo local. Con esto quiero decir que podría crear una aplicación del lado del cliente con un servidor de desarrollo Node.js para activos y solicitudes de API de proxy/stubbing, mientras que en producción manejaría tales interacciones con un servicio de proxy dedicado (nginx, HAProxy, etc.) .).
CORRETAJE - TABLERO DEL COMERCIANTE DE BOLSA
Volvamos al nivel de aplicación. Otro ejemplo en el que domina el software de escritorio, pero que podría reemplazarse fácilmente con una solución web en tiempo real, es el software comercial de los corredores, que se utiliza para realizar un seguimiento de los precios de las acciones, realizar cálculos/análisis técnicos y crear gráficos/tablas.
Cambiar a una solución basada en la web en tiempo real permitiría a los corredores cambiar fácilmente de estación de trabajo o de lugar de trabajo. Pronto, podríamos empezar a verlos en la playa de Florida... o Ibiza... o Bali.
TABLERO DE MONITOREO DE APLICACIONES
Otro caso de uso común en el que Node-with-web-sockets encaja perfectamente: rastrear a los visitantes del sitio web y visualizar sus interacciones en tiempo real.
Podría recopilar estadísticas en tiempo real de su usuario, o incluso moverlas al siguiente nivel introduciendo interacciones específicas con sus visitantes al abrir un canal de comunicación cuando llegan a un punto específico en su embudo. (Si está interesado, CANDDi ya está produciendo esta idea).
Imagine cómo podría mejorar su negocio si supiera lo que están haciendo sus visitantes en tiempo real, si pudiera visualizar sus interacciones. Con los sockets bidireccionales en tiempo real de Node.js, ahora puede hacerlo.
TABLERO DE MONITOREO DEL SISTEMA
Ahora, visitemos el lado de la infraestructura de las cosas. Imagine, por ejemplo, un proveedor de SaaS que quiera ofrecer a sus usuarios una página de monitoreo de servicios, como la página de estado de GitHub. Con el bucle de eventos de Node.js, podemos crear un poderoso tablero basado en la web que verifica los estados de los servicios de manera asíncrona y envía datos a los clientes mediante websockets.
Tanto los estados internos (dentro de la empresa) como los de los servicios públicos se pueden informar en vivo y en tiempo real utilizando esta tecnología. Lleve esa idea un poco más lejos e intente imaginar un centro de operaciones de red (NOC) que monitoree aplicaciones en un operador de telecomunicaciones, proveedor de nube/red/hosting o alguna institución financiera, todo ejecutado en la pila web abierta respaldada por Node.js y websockets en lugar de Java y/o Java Applets.
Dónde se puede utilizar Node.js
APLICACIONES WEB DEL LADO DEL SERVIDOR
Node.js con Express.js también se puede usar para crear aplicaciones web clásicas en el lado del servidor. Sin embargo, si bien es posible, este paradigma de solicitud y respuesta en el que Node.js transportaría HTML renderizado no es el caso de uso más típico. Hay argumentos a favor y en contra de este enfoque. Aquí hay algunos hechos a considerar:
Ventajas:
- Si su aplicación no tiene ningún cómputo intensivo de CPU, puede compilarla en Javascript de arriba a abajo, incluso hasta el nivel de la base de datos si usa una base de datos de objetos de almacenamiento JSON como MongoDB. Esto facilita significativamente el desarrollo (incluida la contratación).
- Los rastreadores reciben una respuesta HTML completamente procesada, que es mucho más compatible con SEO que, por ejemplo, una aplicación de una sola página o una aplicación websockets que se ejecuta sobre Node.js.
Contras:
- Cualquier cálculo intensivo de la CPU bloqueará la capacidad de respuesta de Node.js, por lo que una plataforma con subprocesos es un mejor enfoque. Alternativamente, puede intentar escalar el cálculo [*].
- Usar Node.js con una base de datos relacional sigue siendo un fastidio (ver más abajo para más detalles). Hágase un favor y elija cualquier otro entorno como Rails, Django o ASP.Net MVC si está tratando de realizar operaciones relacionales.
Dónde no se debe usar Node.js
APLICACIÓN WEB DEL SERVIDOR CON UNA BASE DE DATOS RELACIONAL DETRÁS
Al comparar Node.js con Express.js con Ruby on Rails, por ejemplo, solía haber una decisión clara a favor de este último cuando se trataba de acceder a bases de datos relacionales como PostgreSQL, MySQL y Microsoft SQL Server.
Las herramientas de bases de datos relacionales para Node.js aún estaban en sus primeras etapas. Por otro lado, Rails proporciona automáticamente una configuración de acceso a datos lista para usar junto con herramientas de soporte de migraciones de esquemas de base de datos y otras gemas (juego de palabras). Rails y sus marcos similares tienen implementaciones de capa de acceso a datos de Active Record o Data Mapper maduras y comprobadas.[*]
Pero las cosas han cambiado. Sequelize, TypeORM y Bookshelf han recorrido un largo camino para convertirse en soluciones ORM maduras. También podría valer la pena consultar Join Monster si está buscando generar SQL a partir de consultas GraphQL.
COMPUTACIÓN/PROCESAMIENTO PESADO DEL LADO DEL SERVIDOR
Cuando se trata de computación pesada, Node.js no es la mejor plataforma que existe. No, definitivamente no desea crear un servidor de cálculo de Fibonacci en Node.js. En general, cualquier operación intensiva de CPU anula todos los beneficios de rendimiento que ofrece Node con su modelo de E/S sin bloqueo y basado en eventos porque cualquier solicitud entrante se bloqueará mientras el subproceso está ocupado con su procesamiento de números, suponiendo que lo esté intentando. para ejecutar sus cálculos en la misma instancia de Nodo con la que está respondiendo a las solicitudes.
Como se indicó anteriormente, Node.js es de un solo subproceso y utiliza solo un núcleo de CPU. Cuando se trata de agregar simultaneidad en un servidor multinúcleo, el equipo central de Node está trabajando en forma de un módulo de clúster [ref: http://nodejs.org/api/cluster.html]. También puede ejecutar varias instancias de servidor Node.js con bastante facilidad detrás de un proxy inverso a través de nginx.
Con la agrupación en clústeres, aún debe descargar todos los cálculos pesados a procesos en segundo plano escritos en un entorno más apropiado para eso, y hacer que se comuniquen a través de un servidor de cola de mensajes como RabbitMQ.
Aunque su procesamiento en segundo plano podría ejecutarse inicialmente en el mismo servidor, este enfoque tiene el potencial de una escalabilidad muy alta. Esos servicios de procesamiento en segundo plano podrían distribuirse fácilmente a servidores independientes sin la necesidad de configurar las cargas de los servidores web frontales.
Por supuesto, también usaría el mismo enfoque en otras plataformas, pero con Node.js obtiene ese alto rendimiento de solicitudes por segundo del que hemos hablado, ya que cada solicitud es una pequeña tarea que se maneja de manera muy rápida y eficiente.
Conclusión
Hemos discutido Node.js desde la teoría hasta la práctica, comenzando con sus objetivos y ambiciones, y terminando con sus puntos clave y trampas. Cuando las personas tienen problemas con Node, casi siempre se reduce al hecho de que las operaciones de bloqueo son la raíz de todos los males: el 99 % de los usos indebidos de Node son una consecuencia directa.
Recuerde: Node.js nunca se creó para resolver el problema de escalado de cómputo. Fue creado para resolver el problema de escalado de E/S, lo cual hace muy bien.
¿Por qué usar Node.js? Si su caso de uso no contiene operaciones intensivas de CPU ni accede a ningún recurso de bloqueo, puede aprovechar los beneficios de Node.js y disfrutar de aplicaciones de red rápidas y escalables. Bienvenido a la web en tiempo real.