AirbnbEng

Follow

11 de noviembre, 2013 – 10 min read

Por Spike Brehm

Este post ha sido publicado de forma cruzada en VentureBeat.

En Airbnb, hemos aprendido mucho en los últimos años mientras construíamos experiencias web enriquecidas. Nos sumergimos en el mundo de las aplicaciones de una sola página en 2011 con nuestro sitio web para móviles, y desde entonces hemos lanzado Wish Lists y nuestra página de búsqueda recientemente rediseñada, entre otras. Cada una de ellas es una gran aplicación de JavaScript, lo que significa que la mayor parte del código se ejecuta en el navegador con el fin de apoyar una experiencia más moderna e interactiva.

Este enfoque es común hoy en día, y bibliotecas como Backbone.js, Ember.js y Angular.js han facilitado a los desarrolladores la construcción de estas aplicaciones ricas en JavaScript. Sin embargo, hemos descubierto que este tipo de aplicaciones tienen algunas limitaciones críticas. Para explicar por qué, primero vamos a dar un rápido rodeo por la historia de las aplicaciones web.

JavaScript crece

Desde los albores de la web, la experiencia de navegación ha funcionado así: un navegador web solicitaba una página concreta (digamos, «http://www.geocities.com/»), lo que hacía que un servidor en algún lugar de Internet generara una página HTML y la enviara de vuelta por el cable. Esto ha funcionado bien porque los navegadores no eran muy potentes y las páginas HTML representaban documentos mayoritariamente estáticos y autocontenidos. JavaScript, creado para permitir que las páginas web sean más dinámicas, no permitía mucho más que presentaciones de imágenes y widgets de selección de fechas.

Después de años de avances en la informática personal, los tecnólogos creativos han llevado la web a sus límites, y los navegadores web han evolucionado para seguir el ritmo. Ahora, la Web ha madurado hasta convertirse en una plataforma de aplicaciones con todas las funciones, y los rápidos tiempos de ejecución de JavaScript y los estándares de HTML5 han permitido a los desarrolladores crear las ricas aplicaciones que antes sólo eran posibles en las plataformas nativas.

La aplicación de una sola página

No pasó mucho tiempo antes de que los desarrolladores empezaran a crear aplicaciones enteras en el navegador utilizando JavaScript, aprovechando estas nuevas capacidades. Aplicaciones como Gmail, el ejemplo clásico de la aplicación de una sola página, podían responder inmediatamente a las interacciones del usuario, sin necesidad de hacer un viaje de ida y vuelta al servidor sólo para renderizar una nueva página.

Librerías como Backbone.js, Ember.js y Angular.js se conocen a menudo como bibliotecas MVC (Modelo-Vista-Controlador) del lado del cliente o MVVM (Modelo-Vista-Modelo). La típica arquitectura MVC del lado del cliente se parece a esto:

El grueso de la lógica de la aplicación (vistas, plantillas, controladores, modelos, internacionalización, etc.) vive en el cliente, y habla con una API para los datos. El servidor puede estar escrito en cualquier lenguaje, como Ruby, Python o Java, y se encarga principalmente de servir una página inicial de HTML. Una vez que el navegador descarga los archivos JavaScript, se evalúan y la aplicación del lado del cliente se inicializa, obteniendo los datos de la API y renderizando el resto de la página HTML.

Esto es genial para el usuario porque una vez que la aplicación se carga inicialmente, puede soportar la navegación rápida entre las páginas sin refrescar la página, y si se hace bien, puede incluso funcionar sin conexión.

Esto es genial para el desarrollador porque la aplicación idealizada de una sola página tiene una clara separación de preocupaciones entre el cliente y el servidor, promoviendo un buen flujo de trabajo de desarrollo y evitando la necesidad de compartir demasiada lógica entre los dos, que a menudo están escritos en diferentes idiomas.

En la práctica, sin embargo, hay algunos defectos fatales con este enfoque que impiden que sea correcto para muchos casos de uso.

SEO

Una aplicación que sólo puede ejecutarse en el lado del cliente no puede servir HTML a los rastreadores, por lo que tendrá un pobre SEO por defecto. Los rastreadores web funcionan haciendo una petición a un servidor web e interpretando el resultado; pero si el servidor devuelve una página en blanco, no tiene mucho valor. Hay soluciones, pero no sin pasar por el aro.

Rendimiento

Por la misma razón, si el servidor no renderiza una página completa de HTML sino que espera a que lo haga el JavaScript del lado del cliente, los usuarios experimentarán unos segundos críticos de página en blanco o spinner de carga antes de ver el contenido de la página. Hay muchos estudios que demuestran el efecto drástico que tiene un sitio lento en los usuarios y, por tanto, en los ingresos. Amazon afirma que cada reducción de 100 ms en el tiempo de carga de la página aumenta los ingresos en un 1%. Twitter pasó un año y 40 ingenieros reconstruyendo su sitio para renderizarlo en el servidor en lugar del cliente, afirmando una mejora de 5 veces en el tiempo de carga percibido.

Mantenibilidad

Aunque el caso ideal puede llevar a una bonita y limpia separación de preocupaciones, inevitablemente algunas partes de la lógica de la aplicación o la lógica de la vista acaban duplicadas entre el cliente y el servidor, a menudo en diferentes idiomas. Ejemplos comunes son el formato de fecha y moneda, las validaciones de formularios y la lógica de enrutamiento. Esto hace que el mantenimiento sea una pesadilla, especialmente para las aplicaciones más complejas.

Algunos desarrolladores, entre los que me incluyo, se sienten atraídos por este enfoque – a menudo es sólo después de haber invertido el tiempo y el esfuerzo para construir una aplicación de una sola página que se hace evidente cuáles son los inconvenientes.

Un enfoque híbrido

Al final del día, realmente queremos un híbrido de los nuevos y viejos enfoques: queremos servir HTML completamente formado desde el servidor para el rendimiento y SEO, pero queremos la velocidad y la flexibilidad de la lógica de la aplicación del lado del cliente.

Para ello, en Airbnb hemos estado experimentando con aplicaciones «Isomorphic JavaScript», que son aplicaciones JavaScript que pueden ejecutarse tanto en el lado del cliente como en el del servidor.

Una aplicación isomórfica podría tener este aspecto, denominado aquí «MVC cliente-servidor»:

En este mundo, parte de la lógica de la aplicación y de la vista puede ejecutarse tanto en el servidor como en el cliente. Esto abre todo tipo de puertas – optimizaciones de rendimiento, mejor mantenimiento, SEO por defecto, y aplicaciones web con más estado.

Con Node.js, un tiempo de ejecución de JavaScript del lado del servidor rápido y estable, ahora podemos hacer este sueño realidad. Creando las abstracciones apropiadas, podemos escribir la lógica de nuestra aplicación de manera que se ejecute tanto en el servidor como en el cliente – la definición de JavaScript isomórfico.

JavaScript isomórfico en la naturaleza

Esta idea no es nueva – Nodejitsu escribió una gran descripción de la arquitectura JavaScript isomórfica en 2011 – pero su adopción ha sido lenta. Ya han surgido algunos frameworks isomórficos.

Mojito fue el primer framework isomórfico de código abierto que obtuvo algo de prensa. Es un marco avanzado, basado en Node.js, pero su dependencia de YUI y las peculiaridades específicas de Yahoo no han dado lugar a mucha popularidad en la comunidad de JavaScript desde que lo abrieron en abril de 2012.

Meteor es probablemente el proyecto isomórfico más conocido hoy en día. Meteor está construido desde cero para soportar aplicaciones en tiempo real, y el equipo está construyendo todo un ecosistema alrededor de su gestor de paquetes y herramientas de despliegue. Al igual que Mojito, es un marco de trabajo de Node.js grande y con opiniones, sin embargo, ha hecho un trabajo mucho mejor al involucrar a la comunidad de JavaScript, y su muy esperado lanzamiento 1.0 está a la vuelta de la esquina. Meteor es un proyecto al que hay que seguir la pista: cuenta con un equipo de estrellas y ha recaudado 11,2 millones de dólares de Andreessen Horowitz, algo inaudito para una empresa centrada exclusivamente en el lanzamiento de un producto de código abierto.

Asana, la aplicación de gestión de tareas fundada por el cofundador de Facebook Dustin Moskovitz, tiene una interesante historia isomórfica. Sin problemas de financiación, teniendo en cuenta el estatus de Moskovitz como el multimillonario más joven del mundo, Asana pasó años en R&D desarrollando su framework de código cerrado Luna, uno de los ejemplos más avanzados de JavaScript isomórfico que existen. Luna, construido originalmente sobre v8cgi en los días previos a la existencia de Node.js, permite que una copia completa de la aplicación se ejecute en el servidor para cada sesión de usuario. Ejecuta un proceso de servidor separado para cada usuario, ejecutando el mismo código de aplicación JavaScript en el servidor que se está ejecutando en el cliente, lo que permite toda una clase de optimizaciones avanzadas, tales como un robusto soporte fuera de línea y ágiles actualizaciones en tiempo real.

Lanzamos una biblioteca isomórfica propia a principios de este año. Llamada Rendr, permite construir una aplicación Backbone.js + Handlebars.js de una sola página que también se puede renderizar completamente en el lado del servidor. Rendr es un producto de nuestra experiencia en la reconstrucción de la aplicación web móvil de Airbnb para mejorar drásticamente los tiempos de carga de la página, lo que es especialmente importante para los usuarios con conexiones móviles de alta latencia. Rendr se esfuerza por ser una biblioteca en lugar de un marco, por lo que resuelve menos de los problemas para usted en comparación con Mojito o Meteor, pero es fácil de modificar y extender.

Abstracción, Abstracción, Abstracción

El hecho de que estos proyectos tienden a ser grandes, marcos web full-stack habla de la dificultad del problema. El cliente y el servidor son entornos muy diferentes, por lo que debemos crear un conjunto de abstracciones que desacoplen nuestra lógica de aplicación de las implementaciones subyacentes, para poder exponer una única API al desarrollador de aplicaciones.

Rutas

Queremos un único conjunto de rutas que mapeen los patrones URI a los manejadores de rutas. Nuestros manejadores de ruta deben ser capaces de acceder a las cabeceras HTTP, las cookies y la información URI, y especificar las redirecciones sin acceder directamente a window.location (navegador) o req y res (Node.js).

Obtención y persistencia de datos

Queremos describir los recursos necesarios para renderizar una página o componente en particular independientemente del mecanismo de obtención. El descriptor de recursos puede ser un simple URI que apunte a un punto final JSON, o para aplicaciones más grandes, puede ser útil encapsular los recursos en modelos y colecciones y especificar una clase de modelo y una clave primaria, que en algún momento se traduciría a un URI.

Renderización de la vista

Ya sea que elijamos manipular directamente el DOM, seguir con plantillas HTML basadas en cadenas, u optar por una biblioteca de componentes de interfaz de usuario con una abstracción del DOM, tenemos que ser capaces de generar el marcado isomórficamente. Deberíamos ser capaces de renderizar cualquier vista en el servidor o en el cliente, dependiendo de las necesidades de nuestra aplicación.

Construcción y empaquetado

Resulta que escribir código de aplicación isomórfico es sólo la mitad de la batalla. Herramientas como Grunt y Browserify son partes esenciales del flujo de trabajo para realmente poner en marcha la aplicación. Puede haber una serie de pasos de compilación: compilar plantillas, incluir dependencias del lado del cliente, aplicar transformaciones, minificación, etc. El caso más sencillo es combinar todo el código de la aplicación, las vistas y las plantillas en un único paquete, pero en el caso de las aplicaciones más grandes, esto puede suponer la descarga de cientos de kilobytes. Un enfoque más avanzado es crear paquetes dinámicos e introducir lazy-loading de activos, pero esto se complica rápidamente. Las herramientas de análisis estático como Esprima pueden permitir a los desarrolladores ambiciosos intentar la optimización avanzada y la metaprogramación para reducir el código repetitivo.

Componiendo juntos pequeños módulos

Ser el primero en el mercado con un marco isomórfico significa que tienes que resolver todos estos problemas a la vez. Pero esto conduce a marcos grandes y difíciles de manejar que son difíciles de adoptar e integrar en una aplicación ya existente. A medida que más desarrolladores aborden este problema, veremos una explosión de módulos pequeños y reutilizables que pueden integrarse juntos para construir aplicaciones isomórficas.

Resulta que la mayoría de los módulos de JavaScript ya pueden utilizarse de forma isomórfica con poca o ninguna modificación. Por ejemplo, bibliotecas populares como Underscore, Backbone.js, Handlebars.js, Moment, e incluso jQuery se pueden utilizar en el servidor.

Para demostrar este punto, he creado una aplicación de ejemplo llamada isomorphic-tutorial que puedes consultar en GitHub. Combinando unos pocos módulos, cada uno de los cuales puede ser utilizado isomórficamente, es fácil crear una simple aplicación isomórfica en sólo unos cientos de líneas de código. Utiliza Director para el enrutamiento basado en el servidor y en el navegador, Superagent para las peticiones HTTP y Handlebars.js para las plantillas, todo ello construido sobre una aplicación básica de Express.js. Por supuesto, a medida que una aplicación crece en complejidad, uno tiene que introducir más capas de abstracción, pero mi esperanza es que a medida que más desarrolladores experimenten con esto, habrá nuevas bibliotecas y estándares para emerger.

La vista desde aquí

A medida que más organizaciones se sientan cómodas ejecutando Node.js en producción, es inevitable que más y más aplicaciones web comiencen a compartir código entre su código de cliente y servidor. Es importante recordar que el JavaScript isomórfico es un espectro – puede comenzar con sólo compartir plantillas, progresar hasta ser toda la capa de vista de una aplicación, hasta la mayoría de la lógica de negocio de la aplicación. Exactamente qué y cómo se comparte el código JavaScript entre los entornos depende totalmente de la aplicación que se está construyendo y su conjunto único de restricciones.

Nicholas C. Zakas tiene una buena descripción de cómo prevé que las aplicaciones comenzarán a tirar de su capa de interfaz de usuario hacia el servidor desde el cliente, permitiendo optimizaciones de rendimiento y mantenimiento. Una aplicación no tiene que arrancar su backend y sustituirlo por Node.js para utilizar JavaScript isomórfico, tirando esencialmente el bebé con el agua del baño. En su lugar, mediante la creación de APIs sensibles y recursos RESTful, el backend tradicional puede convivir con la capa Node.js.

En Airbnb, ya hemos empezado a retocar nuestro proceso de construcción del lado del cliente para utilizar herramientas basadas en Node.js como Grunt y Browserify. Es posible que nuestra aplicación principal de Rails nunca sea sustituida por completo por una aplicación de Node.js, pero al adoptar estas herramientas es cada vez más fácil compartir ciertas partes de JavaScript y plantillas entre entornos.

Lo has oído aquí primero – dentro de unos años, será raro ver una aplicación web avanzada que no esté ejecutando algo de JavaScript en el servidor.

Aprende más

Si esta idea te entusiasma, ven a ver el taller de JavaScript isomórfico que impartiré en DevBeat el martes 12 de noviembre en San Francisco, o en General Assembly el jueves 21 de noviembre. Vamos a hackear juntos el ejemplo de aplicación isomórfica Node.js que he creado para demostrar lo fácil que es empezar a escribir aplicaciones isomórficas.

También puedes seguir la evolución de las aplicaciones web de Airbnb siguiéndome en @spikebrehm y el equipo de ingeniería de Airbnb en @AirbnbEng.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.