Mientras que la popularidad de Node.js va en aumento, la tracción de PHP va en descenso. Con ese contexto, este post va a profundizar en 5 aspectos prácticos imprescindibles de usar Node.js para los desarrolladores de PHP. Serán cosas de las que nadie habla ni escribe, es hora de ponerse en marcha.
- Node.js para desarrolladores PHP (no Node.js vs PHP)
- Node.js para desarrolladores PHP el lado práctico
- La ejecución de código en Node.js es asíncrona y no secuencial
- Node.js promete posibilidades
- El proceso de Node.js es de larga duración, a diferencia de PHP
- Ejemplo de memorización
- Ejemplo de pool de conexiones con MySQL
- La depuración es más fácil en Node.js que en PHP
- Las actualizaciones de versiones importantes en Node.js son perfectas con respecto a PHP
- Dockerizar una aplicación en Node.js es muy fácil comparado con PHP
- La ejecución de código en Node.js es asíncrona y no secuencial
- Conclusión
Node.js para desarrolladores de PHP (no Node.js vs PHP) #
Este artículo es una lista de cosas que, como desarrollador de PHP, debes conocer y aprender para usar Node.js de forma efectiva. Por el contrario, este post no es un escrito de Node.js vs PHP donde se golpea a PHP. He utilizado ambos lenguajes. Empecé a escribir más Node.js en 2016. Cuando empecé me enfrenté a algunas dificultades ya que estaba acostumbrado a PHP en el trabajo durante más de 7 años antes de eso. Hubo un libro publicado hacia finales de 2012 que cubría Node.js para los desarrolladores de PHP.
Esta entrada del blog no va a hablar de lo que es PHP o Node.js, puedes leer sobre ello en otras entradas. Tampoco hablaré mucho de la E/S sin bloqueo ni del bucle de eventos. Aún así, algo de esto se rozará cuando se discutan los aspectos prácticos de escribir un buen código Node.js.
Node.js para desarrolladores de PHP el lado práctico #
PHP ha estado vivo desde 1995 y, según se informa, todavía es utilizado por el 79,% de los sitios web monitoreados por W3tech (no puedo decir realmente si es todo Internet). Así que es muy probable que hayas usado PHP o desplegado algo escrito en PHP. Por ejemplo, con una tendencia creciente:
WordPress es utilizado por el 63,7% de todos los sitios web cuyo sistema de gestión de contenidos conocemos. Esto supone el 39,0% de todos los sitios web monitorizados por W3Tech.
Por otro lado, Node.js fue lanzado en 2009. Las principales empresas tecnológicas, como Linked In y Paypal, comenzaron a adoptarlo entre 2011 y 2013 por diversas razones, como los microservicios. Según la encuesta de desarrolladores de Stack Overflow de 2020:
Por segundo año consecutivo, Node.js ocupa el primer lugar, ya que es utilizado por la mitad de los encuestados.
No es un secreto que Node.js se está volviendo muy popular en los últimos 5 años.
Así que como desarrollador de PHP, estas son 5 cosas prácticas que debes saber para ser un gran ingeniero de software Node.js. Node.js para los desarrolladores de PHP es similar en algún sentido pero también diferente en algunos otros aspectos algunos se describen a continuación:
La ejecución de código en Node.js es asíncrona y no secuencial #
Este es un comportamiento que engaña a muchos desarrolladores de PHP. En PHP el código se ejecuta en secuencia, al principio la línea 1 luego la 2, y así sucesivamente. En Javascript y particularmente en Node.js eso puede no ser el caso. Usted puede potencialmente poner las cosas en el fondo con un buen uso de las promesas y las devoluciones de llamada.
A continuación se muestra un ejemplo de código modificado con una explicación tomada de mi repo de la moneda-api de código abierto:
async function getExternal(fromCurrency, toCurrency, onDate) {
const rate = await getRate(fromCurrency, toCurrency, onDate);
db.query(
`INSERT INTO exchange_rates (from_currency, to_currency, rate, on_date) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE rate = ?`,
).then(result => {
if (result.affectedRows === 0) {
console.error(`Exchange rate of ${rate} for ${fromCurrency} to ${toCurrency} on ${onDate} could not be saved`);
}
}).catch(err => {
console.log(`Error while writing to db: `, err);
}); //this is done async for the API to respond faster
console.log(`Fetched exchange rate of ${rate} for ${fromCurrency} to ${toCurrency} of ${onDate} from the API`);
return { fromCurrency, toCurrency, onDate, rate };
}
Si usted mira más de cerca, que inocente buscando db.query
en la línea 3, ha sido empujado en el fondo. Así que se ejecutará como a continuación:
- Obtener tasa
- Ejecutar la consulta de inserción en el fondo
- Mientras que la inserción se está ejecutando la función ya se devuelve la tasa
- Si hay un problema en la consulta de inserción se registra en la captura
No hay ninguna manera fuera de la caja para hacer algo como esto en PHP. Esto es lo primero que deja perplejos a los desarrolladores de PHP. Hace que sea más difícil entender Node.js para los desarrolladores de PHP. Este comportamiento de ejecución de código asíncrono también hace que sea más difícil encontrar el rastro de pila correcto en caso de errores en Node.js.
Para ser honesto, en 2020 se puede utilizar fácilmente async await. Aunque es azúcar sintáctico sobre Promises, hace que la programación asíncrona sea muchísimo más fácil. Cuando empecé en la era de Node 4/6 alrededor de 2016 con callbacks y Promises era un juego de pelota totalmente diferente. Aún así, ten cuidado cuando no uses async-await (como en el caso anterior) y sólo ve con promesas, then y catch. Sin embargo, no te enredes en el infierno de las promesas en el proceso. El infierno de las promesas es como la siguiente iteración del infierno de las devoluciones de llamada.
Consejo profesional: Para saber qué características de ES6 puedes usar con qué versión de Node.js, compruébalo en node.green.
Otro consejo profesional:
Las versiones de Node.js son LTS, las extrañas no. Así que usa Node 14 o 16 no 13 o 15 en producción.
Adentrándonos un poco más en la ejecución no secuencial, las promesas y la potencia que tiene juegan un papel importante aquí. La capacidad de hacer cosas concurrentes es genial en Node.js y en javascript en general.
Posibilidades de las promesas en Node.js #
Las promesas al ser asíncronas, puedes ejecutarlas concurrentemente. Hay formas de hacerlo. Podrías correr 3 promesas y obtener los resultados de la más rápida. Usted puede incluso hacer promise.all
donde si una promesa es rechazada, se detiene toda la operación. Por favor, lee más sobre Promise.race
, promise.all
y promise.any
en esta gran comparación.
Con esto en mente, puedes probar otras librerías NPM para limitar la concurrencia de promesas o incluso filtrar a través de promesas de forma concurrente. Puedes hacer algo de eso con ReactPHP. Pero no está incluido en el PHP nativo, ni siquiera en PHP 8. Esto es algo nuevo para envolver su cabeza en Node.js para los desarrolladores de PHP.
Pasemos al siguiente punto, el proceso no necesita morir en Node.js como en PHP.
El proceso de Node.js es de larga duración, a diferencia de PHP #
PHP está destinado a morir no en el sentido de que no será utilizado. En el sentido de que todos los procesos de PHP deben morir. PHP no está realmente diseñado para tareas/procesos de larga duración. En PHP cuando llega una nueva petición HTTP el proceso comienza, después de enviar la respuesta el proceso es matado. Así es como funciona PHP. Eso crea la necesidad de FPM y otros servidores. Puedes argumentar que PHP era serverless por diseño hace más de 20 años. Te lo dejo a ti.
Por otro lado, Node.js es un proceso de larga duración. Esto le permite compartir información entre las solicitudes, ya que el mismo servidor/proceso está manejando múltiples solicitudes. Con un proceso de larga ejecución, puedes explotar fácilmente cosas como la memoización en la memoria y la agrupación de conexiones para una base de datos. Abre otras posibilidades como contar el número de peticiones concurrentes en ese proceso, por ejemplo.
Ejemplo de memoización #
Si no conoces la memoización.
La memoización es una función de orden superior que almacena en caché otra función. Puede convertir algunas funciones lentas en rápidas. Guarda el resultado de una llamada a una función después de la primera vez a la caché, por lo que si se llama a la función de nuevo con los mismos argumentos, lo encontrará en la caché.
Se puede utilizar en Node.js pero no en PHP de forma nativa. Algunas soluciones son posibles en PHP como guardar el valor de retorno de la función en Redis.
Abajo hay un ejemplo de código de memoización en una ruta express con p-memoize:
const ONE_MINUTE_IN_MS = 60000;
const options = {
maxAge: ONE_MINUTE_IN_MS,
cacheKey: (arguments_) => arguments_.join(','),
};
app.get('/api/products', async (req, res, next) => {
try {
const memGetProducts = pMemoize(products.getMultiple, options);
res.json(await memGetProducts(req.query.page || 1, req.query.search));
} catch (err) {
next(err);
}
});
La clara ventaja de esto es menos carga en el datastore. Durante 1 minuto, responderá con la misma respuesta para los mismos parámetros. La salida de la función products.getMultiple
se almacena en la memoria durante un minuto. Esto hace que las respuestas sean muy rápidas.
Ejemplo de pool de conexiones con MySQL #
Otra cosa que no es posible por un proceso moribundo en PHP es el pool de conexiones. Según Wikipedia:
En ingeniería de software, un pool de conexiones es un caché de conexiones de bases de datos que se mantiene para que las conexiones puedan ser reutilizadas cuando se requieran futuras peticiones a la base de datos. Las agrupaciones de conexiones se utilizan para mejorar el rendimiento de la ejecución de comandos en una base de datos.
Así, usted tendrá 5 conexiones en una agrupación y si desea ejecutar 5 consultas a la base de datos se puede hacer de forma concurrente. Esto ahorra tiempo tanto en la conexión a la base de datos como en la ejecución de la consulta. Esto es fácil de hacer en Node.js pero no es fácilmente posible en PHP.
Tenga en cuenta el número de conexiones disponibles y para mantener su tamaño de la piscina de la conexión óptima.
Por ejemplo, si usted está usando Kubernetes y su aplicación tiene 5 vainas con un tamaño de la piscina de la conexión de 2. Eso significa que su base de datos siempre tendrá 10 conexiones abiertas a pesar de que no hay consultas que se ejecutan.
Tiempo para un ejemplo de la piscina de la conexión con la base de datos MySQL con el módulo npm de MySQL:
var pool = mysql.createPool({
connectionLimit : 5,
host : 'example.org',
user : 'app',
password : 'pass',
database : 'schema'
});
for(var i=0;i<5;i++){
pool.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
if (err) {
throw err;
}
console.log(rows.solution); //Shows 2
});
}
El código anterior se ejecutará la misma consulta 5 veces en paralelo con 5 conexiones MySQL tomadas de la piscina de la conexión. Me gustaría poder hacer esto en PHP fuera de la caja.
En mi experiencia, Node.js funciona muy bien con Mysql. Si quieres probar el pool de conexiones con Mongo DB, aquí tienes un ejemplo de Mongo.
Con un proceso de larga duración como desarrollador tienes que ser más cuidadoso con las fugas de memoria y hacer las cosas de la casa bien.
Aquí es donde los desarrolladores de Node.js para PHP necesitan un buen cambio de pensamiento sobre cómo se ejecuta el código. Por otra parte, esta es una gran ventaja en Node.js para desarrolladores de PHP.
La depuración es más fácil en Node.js que en PHP #
La depuración de código línea por línea es una parte importante de la experiencia del desarrollador para cualquier lenguaje de programación. Para depurar el código de PHP, puedes usar complementos como X-Debug con algunas configuraciones del IDE. X-Debug es desafiante de configurar, por decir lo menos. Tienes que instalarlo, habilitar la extensión. Después de eso configurarlo correctamente con un IDE como PHPStorm.
Básicamente, fácil es lo último que se dirá para hacer funcionar X-debug. A menos que esté todo bien configurado con un contenedor docker y la configuración del IDE también es fácil de cargar.
Por otro lado, ejecutar el depurador nativo de node o incluso ndb es mucho más fácil en comparación con PHP y X-debug. Con el uso de VS Code, depurar una aplicación Node.js es tan fácil que hasta un cavernícola puede hacerlo.
Abre Preferencias >Configuración y en el cuadro de búsqueda escribe «node debug». En la pestaña Extensiones, debería haber una extensión titulada «Depuración de nodos». Desde aquí, haz clic en la primera casilla: Depurar > Nodo: Auto Attach y pon el desplegable en «on». Ya está casi listo para funcionar. Sí, realmente es así de fácil.
A continuación, establece algunos puntos de interrupción en el código de VS con digamos index.js
y en el terminal escribe node --inspect index.js
.
¡BOOM! Tu depurador de Node.js paso a paso está funcionando bien en el editor de código VS sin mucho esfuerzo. Una buena diferencia con PHP, no hay necesidad de instalar una extensión diferente, habilitarla y configurarla para poder depurar un programa. No hay necesidad de instalar una extensión adicional es un beneficio que se encuentra en Node.js para los desarrolladores de PHP.
El siguiente punto es también acerca de una mejor experiencia de los desarrolladores, mientras que la actualización incluso múltiples versiones principales del lenguaje.
Las actualizaciones de versiones principales en Node.js es perfecta sobre PHP #
Saltar incluso múltiples versiones principales en Node.js es una experiencia perfecta. La actualización de PHP 5.x a PHP 7.x es un proceso que dura de una semana a un mes, dependiendo del tamaño y la complejidad del proyecto.
En mi experiencia personal, he actualizado microservicios Node.js de las versiones 0.12 a 4 en el pasado. Recientemente he actualizado una aplicación de Node.js 10 a 14. Todas mis actualizaciones de versiones principales de Node.js han sido fáciles.
Algunos cambios menores en package.json fueron los únicos pequeños problemas que encontré. Después del despliegue, rara vez hubo problemas relacionados con la compatibilidad del código. Como ventaja añadida, el rendimiento era normalmente mejor actualizando las versiones mayores.
Por otro lado, actualizar PHP no ha sido fácil. La actualización de la versión menor para una aplicación de PHP 5.4 a 5.6 no era muy engorrosa. Pero, pasar de PHP 5.6 a 7.2 para una aplicación relativamente grande fue un dolor. Tomó mucho tiempo, y necesitó múltiples cambios en composer.json. También fue una tarea difícil probarlo. El lado bueno de una actualización de versión importante en PHP fue seguramente el aumento de rendimiento.
Sólo una nota aquí, las aplicaciones PHP con las que trabajé eran más antiguas que las aplicaciones Node.js. Su experiencia seguramente puede ser diferente a la mía.
Dockerizar una aplicación Node.js es una brisa en comparación con PHP #
La popularidad de Docker ha estado aumentando constantemente en los últimos 5 años. Ha cambiado cómo trabajamos los ingenieros de software desde su lanzamiento. Usted debe utilizar Docker para el desarrollo local también. Teniendo esto en cuenta, Dockerizar una aplicación PHP puede ser una tarea difícil dependiendo de cómo se dispongan los componentes y la complejidad de la aplicación. Por el contrario, para la dockerización de una aplicación Node.js el esfuerzo es menor y el proceso es una brisa.
A continuación se muestra un ejemplo de un dockerfile para una aplicación PHP Laravel con Apache.
FROM composer:1.9.0 as build
WORKDIR /app
COPY . /app
RUN composer global require hirak/prestissimo && composer install
FROM php:7.3-apache-stretch
RUN docker-php-ext-install pdo pdo_mysql
EXPOSE 8080
COPY --from=build /app /var/www/
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
COPY .env.example /var/www/.env
RUN chmod 777 -R /var/www/storage/ && \
echo "Listen 8080" >> /etc/apache2/ports.conf && \
chown -R www-data:www-data /var/www/ && \
a2enmod rewrite
Lo bueno de esta imagen Docker para Laravel es que PHP está empaquetado con apache en la misma imagen. Se puede discutir si esta es una mejor manera de hacerlo que dividir PHP y Apache en dos imágenes Docker.
También observe la construcción Docker de varias etapas en la imagen Docker anterior. La instalación de Composer se hace en una imagen diferente y la salida se copia en la principal. Si hubiéramos utilizado PHP-FPM y Nginx en diferentes imágenes docker, habría sido más complejo. Habría que gestionar dos imágenes docker distintas.
Ahora es el momento de echar un vistazo a un Dockerfile de Node.js.
FROM node:14-alpine as base
WORKDIR /src
COPY package.json package-lock.json /src/
COPY . /src
EXPOSE 8080
FROM base as production
ENV NODE_ENV=production
RUN npm install
CMD
FROM base as dev
ENV NODE_ENV=development
RUN npm config set unsafe-perm true && npm install -g nodemon
RUN npm install
CMD
Como Node.js tiene un servidor web incorporado, el Dockerfile es mucho más limpio.
Cuando instalas node, npm viene incluido con él. Esto elimina la necesidad de instalar paquetes en una etapa diferente en la construcción de Docker.
En el Dockerfile anterior se utiliza la construcción de Docker en varias etapas para separar las imágenes de Docker de producción y de desarrollo. Tener el gestor de paquetes (npm) empaquetado y tener el servidor web como parte del lenguaje/runtime es algo diferente en Node.js para los desarrolladores de PHP. Si estás interesado en hacer Dockering a una aplicación Node.js paso a paso sigue este tutorial.
Conclusión #
Cuando se usa Node.js para desarrolladores PHP se necesita un ligero cambio de pensamiento para explotar bien los poderes de Node.js. Node.js no es una bala de plata. Hay inconvenientes y necesita adaptarse a diferentes formas de ejecución de código.
Ciertamente, hay algunos beneficios de usar Node.js para los desarrolladores de PHP como la programación asíncrona y la concurrencia. Otras ventajas se derivan de la tecnología Node.js siendo de larga duración.
Espero que este post te ayude a sacar más partido a Node.js como desarrollador PHP experimentado.