read

En Option SpA, la empresa donde trabajo actualmente, hace poco decidimos dar el salto de PHP a Node Js para el desarrollo de nuestros backend y apis REST. En la transición, he aprendido muchas cosas al respecto de como Javascript funciona, y en qué cosas es substancialmente diferente a PHP. He querido compartir este pequeño artículo como una guía para desarrolladores que quizás estén pasando por una transición similar.

En Node no necesitas un DI Container

En PHP es muy común desarrollar aplicaciones utilizando un container de inyección de dependencias. Si no estás familizarizado con el patrón, es una técnica por la cual un solo objeto mantiene referencias a otro dentro de su estado interno, y puede obtenerlos sin problema alguno (técnicamente, eso es un service locator, pero los términos se usan muchas veces de manera intercambiable).

Siempre me pregunté cómo se manejaban las dependencias en Node, ya que siempre que hablaba con desarrolladores JS, parecían no tener idea de lo que era un Contenedor de Inyección de Dependencias, o lo que era la Inversión de Control; y en PHP, la verdad es que no puedes casi programar si no tienes uno.

Quizás esto es así porque Javascript carece de un soporte apropiado de clases e interfaces (ES6 tiene “clases” que en realidad son objetos glorificados con cero encapsulamiento). No pasó mucho tiempo hasta que me di cuenta que desarrollar usando clases en ES6 no vale en nada la pena. El encapsulamiento y la modularización, a diferencia de PHP, no vienen de un set de interfaces e implementaciones de aquellas en múltiples clases, sino de algo llamado módulos.

En Javascript, cada archivo es un módulo, que puede ser requerido y puede exportar estado o comportamiento. Al final, se logra el mismo objetivo que con una clase: puedes mantener estado en un módulo y exponerlo con funciones, pero los clientes del módulo no pueden tocar el estado interno de éste.

Por ejemplo, un simple módulo de autenticación en express podría constar fácilmente de una función middleware que extraiga credenciales de una request, y encuentre un usuario con éstas. Luego, otras funciones de este módulo pueden ser usadas para ver si hay un usuario autenticado:

// src/services/auth.js

const users = require('models/user');

let authenticatedUser = null;

module.exports.attemptToAuthenticate = (req, res, next) => {
    const token = req.query.token || null;
    const user = users.findUserByToken(token);

    if (user !== null) {
        authenticatedUser = user;    
    }
    next();
    // After the middleware chain has been executed
    // and request goes out, we de-authenticate.
    // This is to get the same state in te next request.
    authenticatedUser = null;
};

module.exports.isGuest = () => {
    return authenticatedUser === null;
};

module.exports.getUser = () => {
    return authenticatedUser;
};

El manejo de dependencias se hace más simple. Ya no tengo que pensar en clases y en dependencias que necesita el constructor, ni tampoco en escribir los archivos de configuración para armarlas.. Puedo llamar a cualquier módulo de forma global.

Esto es tanto beneficioso (es rápido) como peligroso. Es peligroso porque no ayuda al programador a cómo pensar en reducir código. La tentación de acumular acciones en un solo módulo es demasiado grande, porque todo es accesible demasiado fácilemente. Al menos, cuando tabajamos con un Container, el tamaño de un constructor nos alerta de que una clase ya está haciendo demasiado. Pero en Node, he visto controladores con 900 líneas de código usando al menos 8-10 modulos. Eso es demasiado, sobretodo para un lenguaje que es funcional.

Por esta razón, animo a los desarrolladores a olvidarse de conceptos como los controladores en Node. No vamos a pensar más en controladores, sino en funciones http: funciones que hacen “algo” con la Request y la Response de nuestra aplicación, y que pueden componerse.

Sin embargo, hay que tener cuidado con los módulos. Los modulos son una especie de singletons en JS, y eso es lo que muchos desarrolladores JS no comprenden. Al final, el sistema de módulos de Node no es muy diferente a un Service Locator. El nombre del módulo es su identificador, y la primera vez que se requiere se ejecuta y exporta. Todas las demás veces que se llama al mismo módulo, el estado ya ejecutado de saca de la caché de memoria del manager de módulos de Js.

Debo admitir que es un patrón muy interesante. Cuando lo aprendí, estuve jugando con una implementación en PHP. Sería genial que PHP se olvidara un poco del modelo Java de 1 Clase = 1 Archivo, y comenzara a pensar en archivos que exportan funcionalidad y estado, como en Javascript, o Python. Podríamos prescindir de autoloading y sus problemas de performance y usar algo similar a CommonJs para cargar módulos o archivos que nos ayuden a hacer cosas. Al fin y al cabo, la mayoría de nosotros ya hace eso pero de forma más complicada: con un Di Container en medio que contiene puros singletons.

Si tuviera que elegir entre lo que más me gusta de Node, probablemente elegiría el sistema de módulos.

Olvidate de los Tipos

Creo que Javascript sería el lenguaje perfecto para mí si no fuera por su sistema de tipado tan endeble. PHP solía ir por los mismos pasos, pero gracias a Dios su arquitectura le permitió ir añadiendo tipos poco a poco. Con PHP 7.4 se termina este esfuerzo: las propiedades de clases serán tipadas por fin, y esto ayudará demasiado a eliminar código boilerplate y errores de programación.

Me gusta el tipado porque da mejores herramientas de autocompletado a los IDEs. Y no es que sea un pogramador flojo: es que la experiencia me ha enseñado a no confiar en mi memoria, que es falible. Necesito saber el nombre exacto de un método, y me ayuda cuando lo veo en mi pantalla. Me da tranquilidad, porque he escrito lo correcto.

Es cierto, con un par de tweaks tu IDE puede ayudarte a autocompletar tipos de express y node, pero no siempre. Y si hay algo que detesto es pasar debuggeando un buen tiempo por un typo en un nombre de propiedad o de función, que podría haberse evitado fácilemente.

Dudo algún día que Javascript tenga algún sistema de tipos más fuerte de lo que tiene ahora, y usar Typescript me parece más un hack que una solución. Si tuviera que usar Typescript, mejor estaría usando algo como Scala o Java.

Asi que, no esperes ayuda escribiendo Javascript. Preparate para confiar en tu peor enemigo: tu frágil memoria.

Funcionalidad

Cuando uno profundiza y domina el paradigma orientado a objetos, pronto evoluciona al paradigma funcional. Y es que, en POO bien hecha (eso quiere decir, en un software bien modularizado) las clases terminan teniendo uno o dos métodos públicos a lo mucho. Al final, con clases tan pequeñas, ya no hace falta hablar de “clases”, sino de métodos.

Mucho de mi código orientado a objetos consta de clases muy pequeñas. Esto hacía que al final los archivos de mi repositorio de multiplicaran. Ahora, en Javascript, puedo pensar en funciones altamente componibles. El encapsulamiento se lo dejo al sistema de módulos de Node, y yo me puedo preocupar de simplemente definir comportamiento. Puedo entender esto, y es más sencillo que POO, porque ya estaba haciendo un poco de POO media funcional. Incluso, en PHP solía usar clases invocables para tener lo mejor de los dos mundos: estado y dependencias internas via el constructor, y una alta funcionalidad.

Event Loop y Promesas

Este punto creo que tampoco me costó mucho ya que hace tiempo venía experimentando con un Event Loop en PHP, y realizando acciones asincronas en PHP gracias a la increíble librería llamada React PHP. De hecho, amo las promesas y su sintaxis clásica: creo que async y await es muy abusado por los desarrolladores más nuevos en JS. Ellas fueron diseñadas para manejar mejor el callback hell, y así hacer el código más legible, pero si tienes uno o dos niveles de nesting en tus callbacks, creo que aún usar promesas de la forma clásica es mejor. No sé, quizás es mi fijación con lo horribles que son los try catches y con el hecho de que los desarrolladores, en general, son bastante irresponsables manejando excepciones: un uncaught error puede detener el programa.

Resumen

En resumen, Node es más simple que PHP. No se mete tanto en tu camino con purismos y deja que hagas lo que quieras hacer, rápido.

Sin embargo, el gran peligro de Node radica en dos cosas que, si no andas con cuidado, te van a hacer pagar. Una, es la falta de tipado que puede introducir toda una gama de bugs inesperados. La segunda, es la gran libertad que te da para incluir dependencias, lo cual puede llevarte a crear modulos “monstruos” que podrían llegar a ser muy difíciles de desarmar y que van a darte dolores de cabeza en herramientas de control de versión. Esta libertad también no te ayuda a ver las diferentes capas que tiene una aplicación web. La capa HTTP se diluye en la capa de dominio y en la de infraestructura. Quizás esto no es un gran problema en Node, donde hay una sola abstracción HTTP y pocas librerías para asuntos de infraestructura; pero podría llegar a serlo. Quizás diría que Node te ayuda a ser productivo, pero no necesariamente a pesar mejor el software.

Como siempre, esta es mi opinión. Eres libre de pensar diferente y manifestar la tuya propia.

Blog Logo

Matías Navarro Carter


Published

Image

Matias

Backend Web Developer

Back to Overview