Inyección de dependencias en JavaScript

Cómo desacoplar lógica del código usando lo que se conoce como inyección de dependencias.

dependency-injection-javascript

¿Qué es la inyección de dependencias?

La idea es simple: en vez de crear una funcionalidad dentro de nuestros módulos, pasamos esta funcionalidad desde fuera. Análogamente en la vida real, sería como si en lugar de preparar o ir a buscar el café nosotros mismos, alguien nos lo trajera servido.

Inyectar una dependencia permite que una función o clase no dependa de implementaciones concretas, sino de contratos (funciones o interfaces que cumplen una tarea). Un ejemplo más pragmático sería por ejemplo pasar por parámetro el método fetch para hacer llamadas remotas, podríamos substituirla por otra librería que tuviera la misma funcionalidad sin tocar el código interno de nuestra función.

Un ejemplo sencillo con un cliente HTTP

Vamos a ver un ejemplo más ilustrativo. Supongamos que tenemos una clase ApiClient que necesita hacer peticiones. En lugar de usar directamente fetch, le pasamos una función que haga las peticiones. Eso es la dependencia que vamos a inyectar.

apiClient.js
export class ApiClient {
constructor(httpClient) {
this.httpClient = httpClient;
}
async getUser(id) {
const response = await this.httpClient(`/users/${id}`);
const data = await response.json();
return data;
}
}

Ahora, cuando queramos usar este cliente, podemos decidir qué función pasarle. Aquí un ejemplo usando fetch directamente:

index.js
import { ApiClient } from './apiClient.js';
const fetchClient = (url) => fetch(`https://api.example.com${url}`);
const client = new ApiClient(fetchClient);
client.getUser(3).then(user => {
console.log(user.name);
});

Lo interesante es que si luego queremos probar ApiClient sin hacer llamadas reales, podemos pasarle un cliente falso:

test/apiClient.test.js
import { ApiClient } from './apiClient.js';
const fakeHttpClient = (url) => {
return Promise.resolve({
json: () => Promise.resolve({ id: 3, name: 'Juan' })
});
};
const client = new ApiClient(fakeHttpClient);
client.getUser(3).then(user => {
console.log(user); // { id: 3, name: 'Juan' }
});

Ventajas sin complicaciones

  • Podemos cambiar la forma en que se hacen las peticiones sin tocar ApiClient.
  • Hacer pruebas unitarias es más sencillo.
  • Evitamos acoplar lógica a detalles concretos del entorno.

En resumen

La inyección de dependencias es un concepto usado en muchos lenguajes de programación y en JavaScript es especialmente útil en muchas circumstancias. Con solo pasar funciones o servicios de forma externa, logramos separar responsabilidades de una forma evidente. Nos permite pensar en nuestro código como piezas que pueden ensamblarse de distintas formas, y eso, a largo plazo, hace que sea más fácil de mantener y de adaptar. Permite intercambiar funcionalidad de forma independiente y facilita las pruebas unitarias.