Hace unos días hablaba sobre inyección de dependencias en Javascript y hoy, echando un vistazo en microjs, he tropezado con un contenedor IoC para Javascript llamado Swiftcore.js.
Se trata de una librería bastante simple (que conste que lo digo como algo bueno) y he pensado que sería una buena oportunidad para para entender mejor cómo se podría implementar un contenedor IoC en Javascript. La documentación de Swiftcore.js es bastante clara, pero la mejor forma de comprender algo es jugar un poco con ello.
Vamos a ver un ejemplo. Supongamos que tenemos los dos componentes siguientes:
function Server() { this.getCustomer = function() { // This should be downloaded from a server return 'Paco Clavel'; } } function ViewModel(deps) { var server = deps.Server; this.customer = server.getCustomer(); }
Hasta aquí todo (casi) normal. Tenemos una función, Server
, para crear objetos que descargan datos desde un servidor, y otra función ViewModel
, con la que crear objetos que usan los datos descargados para dejarlos listos para vista.
La primera cosa «curiosa» es que la dependencia de ViewModel
sobre Server
, en lugar de definirla como un parámetro de la función, está encapsulada en un objeto deps
, que contendría todas las dependencias de esa función. Esta es la forma en que trabaja por defecto Swiftcore.js, encapsulando todas las dependencias de cada función en un único objeto.
Para registrar los componentes en el contenedor, tendríamos un código parecido a éste:
ViewModel.dependencies = ['Server']; swiftcore.register('Server', Server); swiftcore.register('ViewModel', ViewModel);
Las dos últimas líneas son muy similares a las que usaríamos en cualquier contenedor de un lenguaje estático, como Castle Windsor o similares. Cada componente se registra con su nombre y, aunque en el ejemplo no se ve, podríamos asignarle un ciclo de vida (singleton, transient, etc.).
Lo que resulta más extraño (e incómodo) es que tenemos que indicar explícitamente las dependencias de cada componente, como hacemos en la primera línea. Esto es así porque al definir la función ViewMode
no tenemos forma de indicar los tipos de los argumentos, por lo que no hay forma directa de detectar las dependencias.
Podríamos intentar aplicar alguna convención para evitar tanta configuración, por ejemplo haciendo que los nombres de los parámetros coincidiesen con los nombres de los dependencias y hacer así un enganche automático. Sin embargo, esto no funcionaría correctamente con los minifiers de Javacript porque cambiarán los nombres de funciones y argumentos haciendo que esa convención se vaya al traste.
A la hora de resolver componentes del contenedor, el código es calcado de Castle Windsor (y el resto de contenedores que conozco):
var model = swiftcore.resolve('ViewModel');
Aprovechando que Swiftcore.js usa QUnit para sus propios tests, todo este código lo he metido en un test para ver si funcionaba, quedando algo así:
test('simple', function() { function Server() { this.getCustomer = function() { return 'Paco Clavel'; } } function ViewModel(deps) { var server = deps.Server; this.customer = server.getCustomer(); } swiftcore.register('Server', Server); ViewModel.dependencies = ['Server']; swiftcore.register('ViewModel', ViewModel); var model = swiftcore.resolve('ViewModel'); ok(model.customer === 'Paco Clavel'); });
El test pasa, así que podemos estar contentos con nuestra primera incursión en el mundo de los contenedores IoC para Javascript.
Conclusiones
Aparte de para entretenerme un rato, este pequeño experimento me ha servidor para verificar algunas de las cosas que ya comentaba al hablar de inyección de dependencias en Javascript.
Para mi, la mayor limitación de estos contenedores es que necesitamos indicar las dependencias explícitamente. Podríamos decir que en C# también pasa eso porque las estamos indicando al definir los tipos de los parámetros de los constructores, pero mientras que en C# eso es algo natural, en Javascript resulta poco idiomático.
Además, en C# las dependencias quedan definidas en el propio constructor de la clase, mientras que aquí están definidas en otro punto distinto de la aplicación, por lo que, si se añade una dependencia a un componente, hace falta acordarse de añadirla también en su definición de dependencias.
Por otra parte, el hecho de tener que pasar todas las dependencias encapsuladas en un único objeto es algo que no me gusta. Es cierto que eso no es algo genérico de todos los contenedores IoC para Javascript, e incluso en Swiftcore.js se puede modificar, pero el hecho de que sea la opción por defecto no me convence. De las dos opciones, me parece mucho más clara la primera:
// ¿Es más legible esto... function Component(dependency1, dependency2) {} // ... o esto? function Component(deps) { var d1 = deps.Dependency1; var d2 = deps.Dependency2; }
Pese a todo, seguro que hay situaciones en que un contenedor IoC para Javascript puede ser útil. Cuando me encuentre con una ya tengo un candidato :-)