AngularJS: Comprendre le modèle de conception


147

Dans le cadre de cet article d'Igor Minar, responsable d'AngularJS:

MVC vs MVVM vs MVP . Quel sujet controversé sur lequel de nombreux développeurs peuvent passer des heures et des heures à débattre et à se disputer.

Pendant plusieurs années, AngularJS était plus proche de MVC (ou plutôt d'une de ses variantes côté client), mais au fil du temps et grâce à de nombreuses refactorisations et améliorations d'API, il est maintenant plus proche de MVVM - le $ scope objet pourrait être considéré comme le ViewModel en cours décoré par une fonction que nous appelons un contrôleur .

Être capable de catégoriser un framework et de le placer dans l'un des buckets MV * présente certains avantages. Il peut aider les développeurs à se familiariser avec ses API en facilitant la création d'un modèle mental qui représente l'application en cours de création avec le framework. Cela peut également aider à établir la terminologie utilisée par les développeurs.

Cela dit, je préfère voir les développeurs créer des applications efficaces qui sont bien conçues et qui suivent la séparation des préoccupations, plutôt que de les voir perdre du temps à se disputer sur le non-sens de MV *. Et pour cette raison, je déclare par la présente AngularJS être le framework MVW - Model-View-Whatever . Où tout ce qui signifie « tout ce qui fonctionne pour vous ».

Angular vous offre une grande flexibilité pour bien séparer la logique de présentation de la logique métier et de l'état de présentation. Veuillez l'utiliser pour alimenter votre productivité et la maintenabilité de vos applications plutôt que des discussions animées sur des choses qui à la fin de la journée n'ont pas beaucoup d'importance.

Existe-t-il des recommandations ou des directives pour l'implémentation du modèle de conception AngularJS MVW (Model-View-Whatever) dans les applications côté client?


voté pour ... que de les voir perdre du temps à se disputer sur le non-sens de MV *.
Shirgill Farhan

1
Vous n'avez pas besoin d'Angular pour suivre un modèle de conception de classe de mots.
utileBee

Réponses:


223

Grâce à une énorme quantité de sources précieuses, j'ai quelques recommandations générales pour implémenter des composants dans les applications AngularJS:


Manette

  • Le contrôleur doit être juste un intercalaire entre le modèle et la vue. Essayez de le rendre aussi mince que possible.

  • Il est fortement recommandé d' éviter la logique métier dans le contrôleur. Il doit être déplacé vers le modèle.

  • Le contrôleur peut communiquer avec d'autres contrôleurs en utilisant l'invocation de méthode (possible lorsque les enfants veulent communiquer avec le parent) ou les méthodes $ emit , $ broadcast et $ on . Les messages émis et diffusés doivent être réduits au minimum.

  • Le contrôleur ne doit pas se soucier de la présentation ou de la manipulation du DOM.

  • Essayez d' éviter les contrôleurs imbriqués . Dans ce cas, le contrôleur parent est interprété comme modèle. Injectez plutôt les modèles en tant que services partagés.

  • La portée du contrôleur doit être utilisée pour relier le modèle avec la vue et
    encapsuler le modèle de vue comme pour le modèle de conception du modèle de présentation .


Portée

Traitez la portée comme en lecture seule dans les modèles et en écriture seule dans les contrôleurs . Le but de la portée est de faire référence au modèle, pas d'être le modèle.

Lorsque vous effectuez une liaison bidirectionnelle (ng-model), assurez-vous de ne pas vous lier directement aux propriétés de la portée.


Modèle

Le modèle dans AngularJS est un singleton défini par service .

Le modèle offre un excellent moyen de séparer les données et les afficher.

Les modèles sont des candidats de premier ordre pour les tests unitaires, car ils ont généralement exactement une dépendance (une certaine forme d'émetteur d'événements, dans le cas courant de $ rootScope ) et contiennent une logique de domaine hautement testable .

  • Le modèle doit être considéré comme une mise en œuvre d'une unité particulière. Il est basé sur le principe de la responsabilité unique. L'unité est une instance qui est responsable de sa propre portée de logique associée qui peut représenter une seule entité dans le monde réel et la décrire dans le monde de la programmation en termes de données et d'état .

  • Le modèle doit encapsuler les données de votre application et fournir une API pour accéder et manipuler ces données.

  • Le modèle doit être portable pour pouvoir être facilement transporté vers une application similaire.

  • En isolant la logique d'unité dans votre modèle, vous avez facilité la localisation, la mise à jour et la maintenance.

  • Le modèle peut utiliser des méthodes de modèles globaux plus généraux communs à l'ensemble de l'application.

  • Essayez d'éviter la composition d'autres modèles dans votre modèle en utilisant l'injection de dépendances si elle n'est pas vraiment dépendante pour diminuer le couplage des composants et augmenter la testabilité et l' utilisabilité des unités .

  • Essayez d'éviter d'utiliser des écouteurs d'événements dans les modèles. Cela les rend plus difficiles à tester et tue généralement les modèles en termes de principe de responsabilité unique.

Implémentation du modèle

Comme le modèle doit encapsuler une certaine logique en termes de données et d'état, il doit restreindre architecturalement l'accès à ses membres afin que nous puissions garantir un couplage lâche.

La façon de le faire dans l'application AngularJS est de le définir à l'aide du type de service d' usine . Cela nous permettra de définir très facilement des propriétés et des méthodes privées et de renvoyer celles accessibles au public en un seul endroit, ce qui le rendra vraiment lisible pour le développeur.

Un exemple :

angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {

  var itemsPerPage = 10,
  currentPage = 1,
  totalPages = 0,
  allLoaded = false,
  searchQuery;

  function init(params) {
    itemsPerPage = params.itemsPerPage || itemsPerPage;
    searchQuery = params.substring || searchQuery;
  }

  function findItems(page, queryParams) {
    searchQuery = queryParams.substring || searchQuery;

    return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
      totalPages = results.totalPages;
      currentPage = results.currentPage;
      allLoaded = totalPages <= currentPage;

      return results.list
    });
  }

  function findNext() {
    return findItems(currentPage + 1);
  }

  function isAllLoaded() {
    return allLoaded;
  }

  // return public model API  
  return {
    /**
     * @param {Object} params
     */
    init: init,

    /**
     * @param {Number} page
     * @param {Object} queryParams
     * @return {Object} promise
     */
    find: findItems,

    /**
     * @return {Boolean}
     */
    allLoaded: isAllLoaded,

    /**
     * @return {Object} promise
     */
    findNext: findNext
  };
});

Créer de nouvelles instances

Essayez d'éviter d'avoir une fabrique qui retourne une nouvelle fonction capable car cela commence à décomposer l'injection de dépendances et la bibliothèque se comportera mal, en particulier pour les tiers.

Une meilleure façon d'accomplir la même chose est d'utiliser la fabrique comme API pour renvoyer une collection d'objets avec des méthodes getter et setter qui leur sont attachées.

angular.module('car')
 .factory( 'carModel', ['carResource', function (carResource) {

  function Car(data) {
    angular.extend(this, data);
  }

  Car.prototype = {
    save: function () {
      // TODO: strip irrelevant fields
      var carData = //...
      return carResource.save(carData);
    }
  };

  function getCarById ( id ) {
    return carResource.getById(id).then(function (data) {
      return new Car(data);
    });
  }

  // the public API
  return {
    // ...
    findById: getCarById
    // ...
  };
});

Modèle global

En général, essayez d'éviter de telles situations et concevez vos modèles correctement afin qu'ils puissent être injectés dans le contrôleur et utilisés dans votre vue.

Dans un cas particulier, certaines méthodes nécessitent une accessibilité globale au sein de l'application. Pour rendre cela possible, vous pouvez définir la propriété ' common ' dans $ rootScope et la lier à commonModel pendant le démarrage de l'application:

angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
  $rootScope.common = 'commonModel';
}]);

Toutes vos méthodes globales vivront dans la propriété « commune ». C'est une sorte d' espace de noms .

Mais ne définissez aucune méthode directement dans votre $ rootScope . Cela peut entraîner un comportement inattendu lorsqu'il est utilisé avec la directive ngModel dans votre étendue de vue, ce qui jonche généralement votre étendue et conduit à des problèmes de substitution des méthodes de portée.


Ressource

Resource vous permet d'interagir avec différentes sources de données .

Doit être mis en œuvre selon le principe de la responsabilité unique .

Dans un cas particulier, il s'agit d'un proxy réutilisable vers les points de terminaison HTTP / JSON.

Les ressources sont injectées dans les modèles et offrent la possibilité d'envoyer / récupérer des données.

Implémentation des ressources

Une fabrique qui crée un objet de ressource qui vous permet d'interagir avec des sources de données côté serveur RESTful.

L'objet de ressource retourné a des méthodes d'action qui fournissent des comportements de haut niveau sans avoir besoin d'interagir avec le service $ http de bas niveau.


Prestations de service

Le modèle et la ressource sont des services .

Les services sont des unités de fonctionnalités non associées , faiblement couplées et autonomes.

Les services sont une fonctionnalité qu'Angular apporte aux applications Web côté client du côté serveur, où les services sont couramment utilisés depuis longtemps.

Les services dans les applications angulaires sont des objets substituables qui sont câblés ensemble à l'aide de l'injection de dépendances.

Angular est livré avec différents types de services. Chacun avec ses propres cas d'utilisation. Veuillez lire Comprendre les types de services pour plus de détails.

Essayez de prendre en compte les principaux principes de l'architecture de service dans votre application.

En général, selon le glossaire des services Web :

Un service est une ressource abstraite qui représente une capacité à exécuter des tâches qui forment une fonctionnalité cohérente du point de vue des entités fournisseurs et des entités demandeurs. Pour être utilisé, un service doit être réalisé par un agent prestataire concret.


Structure côté client

En général, le côté client de l'application est divisé en modules . Chaque module doit pouvoir être testé en tant qu'unité.

Essayez de définir des modules en fonction de la fonctionnalité / fonctionnalité ou de la vue , et non par type. Voir la présentation de Misko pour plus de détails.

Les composants du module peuvent être classiquement regroupés par types tels que contrôleurs, modèles, vues, filtres, directives, etc.

Mais le module lui-même reste réutilisable , transférable et testable .

Il est également beaucoup plus facile pour les développeurs de trouver certaines parties du code et toutes ses dépendances.

Veuillez vous référer à Organisation du code dans les grandes applications AngularJS et JavaScript pour plus de détails.

Un exemple de structuration de dossiers :

|-- src/
|   |-- app/
|   |   |-- app.js
|   |   |-- home/
|   |   |   |-- home.js
|   |   |   |-- homeCtrl.js
|   |   |   |-- home.spec.js
|   |   |   |-- home.tpl.html
|   |   |   |-- home.less
|   |   |-- user/
|   |   |   |-- user.js
|   |   |   |-- userCtrl.js
|   |   |   |-- userModel.js
|   |   |   |-- userResource.js
|   |   |   |-- user.spec.js
|   |   |   |-- user.tpl.html
|   |   |   |-- user.less
|   |   |   |-- create/
|   |   |   |   |-- create.js
|   |   |   |   |-- createCtrl.js
|   |   |   |   |-- create.tpl.html
|   |-- common/
|   |   |-- authentication/
|   |   |   |-- authentication.js
|   |   |   |-- authenticationModel.js
|   |   |   |-- authenticationService.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |   |-- user/
|   |   |   |   |-- user-icon.png
|   |   |   |   |-- user-default-avatar.png
|   |-- index.html

Un bon exemple de structuration d'application angulaire est implémenté par angular-app - https://github.com/angular-app/angular-app/tree/master/client/src

Ceci est également pris en compte par les générateurs d'applications modernes - https://github.com/yeoman/generator-angular/issues/109


5
J'ai une préoccupation à propos de: "Il est fortement recommandé d'éviter la logique métier dans le contrôleur. Il devrait être déplacé vers le modèle." Cependant, à partir de la documentation officielle, vous pouvez lire: "En général, un contrôleur ne doit pas essayer d'en faire trop. Il ne doit contenir que la logique métier nécessaire pour une seule vue.". Parlons-nous de la même chose?
op1ekun

3
Je dirais - traiter le contrôleur comme un modèle de vue.
Artem Platonov

1
+1. Quelques bons conseils ici! 2. Malheureusement, l'exemple de searchModelne suit pas les conseils de réutilisation. Il vaudrait mieux importer les constantes via le constantservice. 3. Une explication de ce que cela signifie ici?:Try to avoid having a factory that returns a new able function
Dmitri Zaitsev

1
L'écrasement de la prototypepropriété de l'objet rompt également l'héritage, à la place on peut utiliserCar.prototype.save = ...
Dmitri Zaitsev

2
@ChristianAichinger, il s'agit de la nature de la chaîne de prototypes JavaScript qui vous oblige à utiliser un objectdans votre expression de liaison bidirectionnelle pour vous assurer d'écrire dans la propriété ou la setterfonction exacte . En cas d'utilisation de la propriété directe de votre portée ( sans point ), vous risquez de masquer la propriété cible souhaitée avec la propriété nouvellement créée dans la portée supérieure la plus proche de la chaîne de prototypes lors de l'écriture. Ceci est mieux expliqué dans la présentation de Misko
Artem Platonov

46

Je crois que le point de vue d'Igor à ce sujet, comme le montre la citation que vous avez fournie, n'est que la pointe de l'iceberg d'un problème bien plus grave.

MVC et ses dérivés (MVP, PM, MVVM) sont tous bons et dandy au sein d'un seul agent, mais une architecture serveur-client est à toutes fins un système à deux agents, et les gens sont souvent tellement obsédés par ces modèles qu'ils oublient que le problème à résoudre est beaucoup plus complexe. En essayant d'adhérer à ces principes, ils se retrouvent en fait avec une architecture défectueuse.

Faisons cela petit à petit.

Les lignes directrices

Vues

Dans le contexte angulaire, la vue est le DOM. Les lignes directrices sont:

Faire:

  • Variable de portée actuelle (lecture seule).
  • Appelez le contrôleur pour les actions.

Ne pas:

  • Mettez n'importe quelle logique.

Aussi tentant, court et inoffensif, cela semble:

ng-click="collapsed = !collapsed"

Cela signifie à peu près tout développeur qui, pour comprendre maintenant le fonctionnement du système, doit inspecter à la fois les fichiers Javascript et les fichiers HTML.

Contrôleurs

Faire:

  • Liez la vue au «modèle» en plaçant des données sur la portée.
  • Répondez aux actions des utilisateurs.
  • Gérez la logique de présentation.

Ne pas:

  • Traitez avec n'importe quelle logique métier.

La raison de la dernière directive est que les contrôleurs sont les sœurs des vues, pas les entités; ni ils ne sont réutilisables.

Vous pourriez affirmer que les directives sont réutilisables, mais les directives sont également sœurs des vues (DOM) - elles n'ont jamais été conçues pour correspondre à des entités.

Bien sûr, les vues représentent parfois des entités, mais c'est un cas assez spécifique.

En d'autres termes, les contrôleurs doivent se concentrer sur la présentation - si vous intégrez la logique métier, non seulement vous risquez de vous retrouver avec un contrôleur gonflé et peu gérable, mais vous violez également le principe de séparation des préoccupations .

En tant que tels, les contrôleurs en Angular sont vraiment plus de Presentation Model ou MVVM .

Et donc, si les contrôleurs ne doivent pas gérer la logique métier, qui devrait le faire?

Qu'est-ce qu'un modèle?

Votre modèle client est souvent partiel et périmé

À moins que vous n'écriviez une application Web hors ligne ou une application terriblement simple (peu d'entités), votre modèle client est très probablement:

  • Partiel
    • Soit il n'a pas toutes les entités (comme dans le cas de la pagination)
    • Ou il n'a pas toutes les données (comme dans le cas de la pagination)
  • Stale - Si le système a plus d'un utilisateur, à tout moment vous ne pouvez pas être sûr que le modèle que le client détient est le même que celui que le serveur contient.

Le vrai modèle doit persister

Dans le MCV traditionnel, le modèle est la seule chose persistante . Chaque fois que nous parlons de modèles, ceux-ci doivent être persistés à un moment donné. Votre client peut manipuler les modèles à volonté, mais tant que l'aller-retour vers le serveur n'est pas terminé avec succès, le travail n'est pas terminé.

Conséquences

Les deux points ci-dessus doivent servir de mise en garde - le modèle de votre client ne peut impliquer qu'une logique commerciale partielle, principalement simple.

En tant que tel, il est peut-être judicieux, dans le contexte du client, d'utiliser des minuscules M - c'est donc vraiment mVC , mVP et mVVm . Le gros Mest pour le serveur.

Logique métier

L'un des concepts les plus importants concernant les modèles commerciaux est peut-être que vous pouvez les subdiviser en 2 types (j'omets le troisième point de vue commercial car c'est une histoire pour un autre jour):

  • Logique de domaine - aka règles métier d'entreprise , la logique indépendante de l'application. Par exemple, donnez un modèle avec des propriétés firstNameet sirName, un getter comme getFullName()peut être considéré comme indépendant de l'application.
  • Logique d'application - aka règles métier d'application , qui sont spécifiques à l'application. Par exemple, les contrôles et la gestion des erreurs.

Il est important de souligner que ces deux éléments dans un contexte client ne sont pas de la «vraie» logique commerciale - ils ne traitent que de la partie qui est importante pour le client. La logique d'application (et non la logique de domaine) devrait avoir la responsabilité de faciliter la communication avec le serveur et la plupart des interactions de l'utilisateur; tandis que la logique du domaine est en grande partie à petite échelle, spécifique à l'entité et axée sur la présentation.

La question demeure: où les jetez-vous dans une application angulaire?

Architecture 3 vs 4 couches

Tous ces frameworks MVW utilisent 3 couches:

Trois cercles.  Intérieur - modèle, milieu - contrôleur, extérieur - vue

Mais il y a deux problèmes fondamentaux en ce qui concerne les clients:

  • Le modèle est partiel, périmé et ne persiste pas.
  • Pas de place pour mettre la logique d'application.

Une alternative à cette stratégie est la stratégie à 4 couches :

4 cercles, de l'intérieur à l'extérieur - Règles métier d'entreprise, règles métier d'application, adaptateurs d'interface, cadres et pilotes

La vraie affaire ici est la couche de règles métier de l'application (cas d'utilisation), qui va souvent mal sur les clients.

Cette couche est réalisée par des interacteurs (Uncle Bob), ce que Martin Fowler appelle une couche de service de script d'opération .

Exemple concret

Considérez l'application Web suivante:

  • L'application affiche une liste paginée d'utilisateurs.
  • L'utilisateur clique sur «Ajouter un utilisateur».
  • Un modèle s'ouvre avec un formulaire pour remplir les détails de l'utilisateur.
  • L'utilisateur remplit le formulaire et appuie sur Soumettre.

Quelques choses devraient se passer maintenant:

  • Le formulaire doit être validé par le client.
  • Une demande doit être envoyée au serveur.
  • Une erreur doit être traitée, s'il y en a une.
  • La liste des utilisateurs peut ou non (en raison de la pagination) avoir besoin d'être mise à jour.

Où jetons-nous tout cela?

Si votre architecture implique un contrôleur qui appelle $resource , tout cela se produira dans le contrôleur. Mais il existe une meilleure stratégie.

Une solution proposée

Le diagramme suivant montre comment le problème ci-dessus peut être résolu en ajoutant une autre couche logique d'application dans les clients angulaires:

4 cases - DOM pointe vers Controller, qui pointe vers la logique d'application, qui pointe vers $ resource

Nous ajoutons donc une couche entre controller à $ resource, cette couche (appelons-la interactor ):

  • Est un service . Dans le cas des utilisateurs, il peut être appelé UserInteractor.
  • Il fournit des méthodes correspondant à des cas d'utilisation , encapsulant la logique d'application .
  • Il contrôle les requêtes adressées au serveur. Au lieu d'un contrôleur appelant $ resource avec des paramètres de forme libre, cette couche garantit que les requêtes adressées au serveur renvoient des données sur lesquelles la logique de domaine peut agir.
  • Il décore la structure de données renvoyée avec un prototype de logique de domaine .

Et donc, avec les exigences de l'exemple concret ci-dessus:

  • L'utilisateur clique sur «Ajouter un utilisateur».
  • Le contrôleur demande à l'interacteur un modèle d'utilisateur vide, le est décoré avec une méthode de logique métier, comme validate()
  • Lors de la soumission, le contrôleur appelle la validate()méthode modèle .
  • En cas d'échec, le contrôleur gère l'erreur.
  • En cas de succès, le contrôleur appelle l'interacteur avec createUser()
  • L'interacteur appelle $ resource
  • En réponse, l'interacteur délègue toutes les erreurs au contrôleur, qui les gère.
  • En cas de réponse réussie, l'interacteur s'assure que si nécessaire, la liste des utilisateurs est mise à jour.

Donc AngularJS est défini MVW (où W est pour n'importe quoi) puisque je peux choisir d'avoir un contrôleur (avec toute la logique métier) ou un modèle / présentateur de vue (sans logique métier mais juste un peu de code pour remplir la vue) avec BL dans un service séparé? Ai-je raison?
BAD_SEED

Meilleure réponse. Avez-vous un exemple réel sur GitHub d'une application angulaire à 4 couches?
RPallas

1
@RPallas, non je ne le fais pas (j'aurais aimé avoir le temps pour ça). Nous essayons actuellement une architecture où la «logique d'application» n'est qu'un interacteur de frontière; un résolveur entre lui et le contrôleur et un modèle de vue qui a une certaine logique de vue. Nous expérimentons toujours, donc pas à 100% des avantages ou des inconvénients. Mais une fois cela fait, j'espère écrire un blog quelque part.
Izhaki

1
@heringer Fondamentalement, nous avons introduit des modèles - des constructions POO qui représentent des entités de domaine. Ce sont ces modèles qui communiquent avec les ressources, pas avec les contrôleurs. Ils encapsulent la logique de domaine. Les contrôleurs appellent des modèles, qui à leur tour appellent des ressources.
Izhaki

1
@ alex440 Non. Bien que cela fasse maintenant deux mois qu'un blog sérieux sur ce sujet est au bout de mes doigts. Noël arrive - peut-être alors.
Izhaki

5

Un problème mineur par rapport aux excellents conseils de la réponse d'Artem, mais en termes de lisibilité du code, j'ai trouvé préférable de définir l'API complètement à l'intérieur de l' returnobjet, afin de minimiser les allers-retours dans le code pour voir où les variables sont définies:

angular.module('myModule', [])
// or .constant instead of .value
.value('myConfig', {
  var1: value1,
  var2: value2
  ...
})
.factory('myFactory', function(myConfig) {
  ...preliminary work with myConfig...
  return {
    // comments
    myAPIproperty1: ...,
    ...
    myAPImethod1: function(arg1, ...) {
    ...
    }
  }
});

Si l' returnobjet devient "trop ​​encombré", c'est un signe que le Service en fait trop.


0

AngularJS n'implémente pas MVC de manière traditionnelle, mais plutôt quelque chose de plus proche de MVVM (Model-View-ViewModel), ViewModel peut également être appelé binder (dans le cas angulaire, cela peut être $ scope). Le modèle -> Comme nous le savons, le modèle en angulaire peut être simplement de vieux objets JS ou les données de notre application

La vue -> la vue dans angularJS est le HTML qui a été analysé et compilé par angularJS en appliquant les directives ou les instructions ou les liaisons, le point principal ici est en angulaire l'entrée n'est pas seulement la chaîne HTML simple (innerHTML), mais plutôt est le DOM créé par le navigateur.

Le ViewModel -> ViewModel est en fait le liant / pont entre votre vue et votre modèle dans le cas angularJS, c'est $ scope, pour initialiser et augmenter le $ scope que nous utilisons Controller.

Si je veux résumer la réponse: dans l'application angularJS, $ scope fait référence aux données, Controller contrôle le comportement et View gère la mise en page en interagissant avec le contrôleur pour se comporter en conséquence.


-1

Pour être clair sur la question, Angular utilise différents modèles de conception que nous avons déjà rencontrés dans notre programmation régulière. 1) Lorsque nous enregistrons nos contrôleurs ou directives, usine, services, etc. par rapport à notre module. Ici, il cache les données de l'espace global. Quel est le modèle de module . 2) Quand angular utilise sa vérification sale pour comparer les variables de portée, ici, il utilise Observer Pattern . 3) Toutes les portées enfants parents de nos contrôleurs utilisent le modèle prototypique. 4) En cas d'injection des services, il utilise Factory Pattern .

Dans l'ensemble, il utilise différents modèles de conception connus pour résoudre les problèmes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.