Ember.js
Trabajando con el hamster de JavaScript
En los últimos meses he estado trabajando con Ember.js, una experiencia nueva para mi. EmberJS es de esos frameworks únicos porque tienen una visión propia de cómo debería funcionar una aplicación, y si bien esto tiene sus desventajas, es ventajoso en otros aspectos también.
A continuación contaré como fue este encuentro, y un vistazo general a cómo se estructuran las aplicaciones en EmberJS.
¿Por qué Ember.js?
¿Por qué? ¿No es esa la pregunta que todos nos hacemos en algún punto? ¿Por qué comenzaría un proyecto en una librería nueva, siendo que esto agrega una gran cantidad de riesgo al desarrollo del proyecto? La respuesta debería ser predecible: los beneficios que este framework nos ofrece.
Ember.js es un framework de aplicaciones JavaScript MVx (lo que denominaré “Model-View-_algo_”) que ofrece, a simple vista, las siguientes características:
- Binding de datos entre componentes
- Binding de datos de templates a variables y viceversa
- Templating dinámico (provisto por handlebars.js)
- Modularización de componentes visuales (“components”)
- Object-mapping contra distintos tipos de backend (RESTAdapter, LSAdapter, etc.)
- Ruteo por recursos, jerárquico
- Inyección de dependencias entre controladores
- Hooks para customización en rutas, controladores y vistas
- Juega bien con librerías de terceros
- …y seguramente más cosas que ya olvidé.
Para empezar con una aplicación mejor preparada aún en nuestro caso utilizamos Ember App Kit (conocido por sus iniciales EAK), que agrega las siguientes características:
- tareas grunt
- SASS / Compass para librerías CSS (Opcionalmente, LESS)
- RequireJS y AMD (Asynchronous Module Definition)
- Soporte para CoffeeScript
- Unit Testing automatizado con Qunit
- Acceptance testing automatizado con Testem
- API Stubs para desarrollar sin back-end server
Considerando estas características, era muy apropiado elegir este framework para este proyecto particular en el que yo estaba trabajando. Cualquier otra elección seguramente nos habría dejado con el trabajo pendiente de resolver integraciones más adelante – puesto que nuestro proyecto requirió un 90% de todo esto (es una aplicación algo compleja). En conclusión, Ember.js parecía la solución apropiada.
¡No tan rápido!
Todo esto suena muy bonito, pero no es sin sus problemáticas. Por un lado, la inexperiencia es un factor grande: hay mucho a qué acostumbrarse, y más aún en un framework como este en donde hay muchas convenciones a aprender. Nuestra configuración particular hacía esto especialmente confuso: variables en camelCase, los archivos deben ser dasherized-named, pero las propiedades en los objetos debían ser snake_case (DS.ActiveModelAdapter
), excepto propiedades hasMany
async
porque los desarrolladores de DS.ActiveModelSerializer
dijeron “no”.
Luego estaba el problema de dónde tienen que estar los archivos. La documentación de Ember App Kit explica esto para rutas, controllers, models, vistas y templates, pero no es tan específico en rutas hijas y sus controllers, vistas compuestas, etc.
Los observadores y propiedades calculadas parecen ser un poco de magia pero tienen sus inconsistencias también, dado que no se disparan en los mismos casos exactamente, agregando a la confusión, y mezclar eso con hooks de ciclos de vida del objeto lo hace un poco más difícil.
Por último, el peor de los pecados: la documentación es un poco limitada, pero por suerte el soporte en internet es grande. Es común encontrarse con un problema nuevo hasta que uno ya tenga un nivel de experiencia considerable, y a veces esos problemas pueden ser incluso bugs reales o incompatibilidades que aún no están resueltas.
Muchos de los componentes que mencioné se desprenden de Ember pero no son parte del mismo equipo: Ember Testing sigue su propio camino, y los plugins son, por supuesto, independientes. Esto genera problemas propios por diseños divergentes (ejemplo). Testem y QUnit son absolutamente independientes, generando otra serie de problemas completamente distintos, incluso pudiendo no haber solución sin un rediseño completo (ejemplo).
Ember-data es un componente grande y fuerte de Ember, pero se encuentra aún en desarrollo y algunos cabos no están completamente atados, lo que puede generar workarounds extraños o problemas en nuestro desarrollo (ejemplos).
Estructura de aplicación
Así y todo, creo que Ember es un framework muy sólido y potente. Esta es la forma en la que se generan aplicaciones en él, haciendolo muy flexible a distintas necesidades:
Primero y principal, casi absolutamente todo es opcional, lo que significa que Ember generará las partes necesarias de la aplicación si es que no fueron explícitamente declaradas. Esto es en cierta forma una ventaja, para que todo nuestro código tenga propósito más allá de necesidad del framework. Esta inteligencia significa que nuestro proyecto tendrá tres archivos si eso es todo lo que necesita, o tendrá doscientos si es eso lo que requerimos. Hasta donde sé, esto aplica a rutas, controllers y vistas.
En un ejemplo simple, nuestra aplicación se conformará de una ruta, un controller y una vista. Los nombres son algo confusos, no estamos hablando de un controller como lo tendríamos en una aplicación MVC y los modelos tampoco son el model, sino que las responsabilidades cambian bastante.
La ruta está asociada a una serie de URLs con determinado formato (por ejemplo: /post/:post_id/comments
), y la ruta será la encargada de instanciar el controlador, buscar el modelo y asignárselo. Para esto la ruta tiene sus hooks beforeModel
, model
, afterModel
y setupController
, todos opcionales. Si model no existiera, Ember buscará un modelo post con id :post_id
y eso será asignado al controlador como su propiedad model
.
El controlador entonces generará una serie de propiedades extras, que ayudarán al flujo de la funcionalidad. Los valores de estas propiedades serán actualizados según otros valores cambien (propiedades computadas) o según acciones realizadas o eventos disparados (actions).
La vista entonces hará lo mismo, generando propiedades que serán utilizadas más abajo en la jerarquía, pero esta vez las propiedades serán específicas de la representación visual o de las utilidades de usuario. Manejo de eventos, clases CSS, bindings de estilos, etc. Ese es el tipo de cosas que estarán presentes aquí. También tenemos hooks necesarios para cuando la vista haya sido generada e insertada en el DOM, removida, renderizada, etc. La vista puede estar asociada a un layout particular que indica en dónde está contenida: el layout sería la parte común del DOM a generar (por ejemplo, la estructura de la página) y la vista contenida en ella sería la porción de código específica de esta pantalla.
Finalmente, tendremos al template, un archivo HTML compilable en handlebars, en donde tendremos acceso a las propiedades del modelo, la vista y el controlador. Condicionales y loops es más o menos todo lo que podemos hacer, y esto está bien: no debería haber demasada lógica en el template más que bindeo de datos y algo de código condicional. Si por alguna razón necesitáramos más poder, podemos recurrir a componentes o helpers.
Los componentes son secciones de vista-template que pueden reutilizarse, sin el contexto de un modelo o un controlador. No están asociados a ninguna ruta en particular, pero pueden recibir cualuier cantdad de parámetros que querramos bindear de afuera hacia adentro del componente o hacia afuera también.
Los helpers son estrictamente helpers de handlebars, que simplemente permiten renderizar código con algo más de lógica, ya que se implementan en JavaScript y tras registrarse están disponibles para su uso.
Dentro de un template también pueden invocarse a otros controladores sólo en el contexto en donde nos encontramos (render
), o vistas sin controlador (view
) u otros templates (partial
).
El código generado entonces permitirá con el binding a variables cambiar propiedades que dependan de ellas, o con acciones disparar código con nuestra lógica. Las acciones pueden atraparse en una vista, o pueden ser atrapadas en el controlador, o en la ruta misma. Para hacer las cosas más difíciles de entender, también luego van a la vista de aplicación, el controlador de aplicación y la ruta de aplicación, pero simplemente podemos pensar en estos como “super-contenedores”. (Composición, no herencia.)
Finalmente, disponemos de serializers, que pueden sobreescibir comportamientos por defecto de cómo se serializan nuestros modelos hacia el backend, de adapters, que indicarán cómo trabajar con el backend (o cuál backend utilizar), initializers que cambiarán aspectos de nuestra aplicación cuando se está inicializando, los modelos que definen la forma de nuestros datos y más.
Estructura rígida
Imagino que tras leer esto muchos imaginarán que la estructura de Ember es muy rígida y compleja. Estoy de acuerdo, pero no lo considero una debilidad, sino una ventaja. Ember está preparado para aplicaciones grandes que requieren de mucho código, y la organización es algo necesario en este tipo de aplicaciones. Tener una estructura de este tipo permite conocer en todo momento en qué dirección fluye la información y qué tiene efecto sobre qué otra cosa.
La característica de auto-generación de estas partes hace que no tengamos en nuestro proyecto carpetas o archivos innecesarios: tendremos código complejo sólo cuando tengamos una aplicación compleja. Como siempre, la separación de responsabilidades es nuestro arma principal para mantener código mantenible.
Una estructura tan compleja, además, se asegura que la gran mayoría de nuestras necesidades estén cubiertas con lo que el framework soporta.
Experiencia hasta ahora
Mi experiencia con Ember no ha sido demasiada aún – apenas puedo considerar haber entendido su filosofía general. Para mi sorpresa, este increíble andamiaje que funciona debajo de una aplicación es bastante performante y permite interactuar con librerías de terceros sin mucho problema.
Me encantaría que la documentación fuera más completa, y StackOverflow viene a nuestro rescate. En la mayoría de los casos, esto nos ha permitido seguir adelante, y en tantos otros me he adentrado al código mismo de Ember, que es sorprendentemente entendible. Hay ciertos aspectos que no entiendo completamente aún, pero son los rincones oscuros que cualquier framework posee.
Recomendaría y usaría Ember.js para otros proyectos, especialmente aplicaciones grandes, pero definitivamente alertaría a equipos novatos que la curva de aprendizaje es muy elevada. Me costó mucho comprender cómo las distintas partes encajaban pero ahora que lo conozco tiene mucho sentido para mí. Hay quienes están en desacuerdo con esa visión pero los considero puntos de vista: sirve para lo que debe servir, y eso lo convierte en una buena herramienta. Está bien diagramado y va por buen camino. Los desarrolladores están pensando muy detenidamente cuáles son los pasos siguientes tanto para maximizar la practicidad de construir aplicacioens y el correcto uso de los conceptos involucrados.
Ember es definitivamente una herramienta que tener en la caja de herramientas. Con algunos agujeros para que el hamster pueda respirar, claro.