Modèles et injection avec Aurelia

L'injection de dépendance n'est pas chose aisée quand on veut instancier des objet par soi même. J'ai trouvé un moyen assez élégant, et j'aimerais bien avoir des avis sur la méthode.
L'idée est d'avoir les éléments suivants :

  • Un service Api qui injecte HttpClient et qui sert à formatter les requêtes vers le backend à l'aide d'une méthode request().
  • Un service BiduleService qui injecte Api et qui permet de chopper tous les bidules via la méthode getAll().
  • Un modèle BiduleModel instancié pour chaque bidule récupéré par BiduleService.

La difficulté était donc de pouvoir injecter des services dans ce BiduleService. En effet, je l'instancie à la main dans le callback de BiduleService.getAll() et il faudrait alors que j'injecte les services requis dans BiduleService afin de les passer au constructeur comme on peut le voir dans cet exemple :

// bidule-model.js
class BiduleModel {  
  constructor (data, api) {
    this.data = data;
    this.api = api;
  }
}
// bidule-service.js
import Api from './api';  
import BiduleModel from './bidule-model';

export default class BiduleService {  
  static inject() { return [Api]; }
  constructor(Api) {
    this.api = Api;
  }
  getAll () {
    this.api.request('/api/bidules')
      .then(bidules => bidules.map(bidule => {
        return new BiduleModel(bidule, this.api);
      })
  }

Le gros problème avec cette approche, c'est que si on veut injecter un autre service plus tard dans BiduleModel, il faut aller modifier tous les endroits où on l'instancie. Il va falloir donc trouver autre chose.

On va alors utiliser une factory !

Factory

Le modèle devient un service injectable avec une méthode create qui va s'occuper d'instancier le modèle tout en lui passant automatiquement les services qu'il demande. On remplacera alors le new BiduleModel(data, service1, service2) par BiduleModelFactory.create(data).

Ça va ressembler à ça :

// model.js
class Model {  
  constructor (data, ...services) {
    this.data = data || {};

    for (let service of services) {
      this[service.constructor.name] = service;
    }
  }
}

const SERVICES = Symbol();  
export default class ModelFactory {  
  static Model () { return Model; }
  constructor (...services) {
    this[SERVICES] = services;
  }

  create (data) {
    let Model = this.constructor.Model();
    return new Model(data, ...this[SERVICES]);
  }
}
// bidule-model.js
import Api from './api';  
import ModelFactory from './model';

class BiduleModel extends ModelFactory.Model() {  
  getTrucs () {
    return this.Api.request(`/api/bidules/${this.id}/trucs`);
  }
}

export default class BiduleModelFactory extends ModelFactory {  
  static inject() { return [Api]; }
  static Model () { return BiduleModel; }
}
// bidule-service.js
import Api from './api';  
import BiduleModelFactory from './bidule-model';

export default class BiduleService {  
  static inject() { return [Api, BiduleModelFactory]; }
  constructor(Api, BiduleModelFactory) {
    this.api = Api;
    this.BiduleModelFactory = BiduleModelFactory
  }
  getAll () {
    this.api.request('/api/bidules')
      .then(bidules => bidules.map(bidule => {
        return this.BiduleModelFactory.create(bidule);
      });
  }
}

Qu'en pensez vous ?

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu