mercredi 17 mai 2017

Pourquoi le mapping ORM viole les principes de la programmation orientée objet (POO)

Le sujet de cet article - pourquoi l’implémentation ORM dans Java (n’importe quelle : JPA, Hibernate, etc) ne peut pas être considérée comme un bon exemple du paradigme POO et qu'est-ce qu’on pourrait changer pour améliorer cet exemple et faire Java plus « objet-orientée ».
Tout le monde sait quand on parle d’ORM, on parle des frameworks qui nos permettent à travailler avec une base de données relationnelle. Ces frameworks JPA sont utiles pour un travail plus productif dans l’environnement Java, ou on travail avec des objets et pas avec des procédures.
Comme exemple, on regarde une classe d’unité typique Hibernate :

@Entity
@Table(name = "Users")
public class User {
   @Id @GeneratedValue
   @Column(name = "id")
   private int id;

   @Column(name = " name ")
   private String name;

   public User() {}
   public int getId() {
      return id;
   }
   public void setId( int id ) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName( String name) {
      this. name = name;
   }
}


Ici on sait qu’on parle d’une table SQL qui s’appelle « Users» , qui a 2 colonnes et on sait que la classe « Users» représente la table « Users» dans la domaine Java.
Je trouve ce code un peu terrible et j’essaie de vous expliquer pourquoi cette approche n’est pas acceptable dans le domaine POO.

Le problème.

En fait le problème principal est que la classe User se comporte pas vraiment ni comme une classe, ni comme un objet dans le sens du paradigme objet-orienté.
Ci-dessous, sur le diagramme, vous voyez que notre programme principal appelle la classe «User » et l’assigne un nom « Jeff » et ensuite appelle la session pour actualiser la BD (l’objet de notre session obtient le nom d’utilisateur depuis l’objet User a travers de u.getName() et ensuite actualise la BD a travers d’une query JDBC).



Notre factory Hibernate fait les procédures suivants : ouvre une transaction, obtient notre « user » depuis la BD, change son nom et le passe a la session qui actualise la BD.



Retour a la programmation procédurale?

Comme je l'ai déjà dit, notre objet « User» ne se comporte pas comme un objet dans le sens de la POO, mais simplement un conteneur. Le paradigme POO a été inventé précisément pour éviter les problèmes liés à la programmation procédurale. A l'époque des langues comme C ou Assembleur nous avions une séquence des commands qui a été exécutée une après autre étés et ces commands ont manipulé les données. Cette approche a bien fonctionné avec des petits programmes avec peu de code. Quand la quantité de code a été augmentée, ce paradigme n'était plus efficace: il est devenu difficile de gérer et maintenir le code.
Pour cette raison, il a été inventé la programmation orientée objet (POO). Ce paradigme nous a permis d'arrêter de penser aux procédures et commencer à penser aux objets. Avec l'encapsulation chaque objet ne devrait exécuter que les méthodes strictement liés à cet objet et rien d'autre. Et les grandes classes devraient "faire la confiance" aux objets et les appeler uniquement puisqu'ils mêmes exécutent leurs propres méthodes. C'est aux objets de « savoir » faire le nécessaire pour obtenir le résultat nécessaire! Les choses qu'on devrait éviter dans la POO,  c'est prendre les propriétés des classes pour les transmettre aux autres classes, etc. A la fin on va avoir un code "spaghetti" que personne sera capable a mantenir.

Dans ce sens, Hibernate nous offre la façon inefficace de travailler: on a des données et une procédure qui manipule les données. La classe « User» ne vient pas utilisé comme un objet, sinon comme un stockage temporaire de données. Cela contredit les principes de la POO, qui nous oblige à avoir des objets encapsulées. Un bon objet selon la POO ne devrait pas avoir ni « getter » ni « setter » (qui sont le premier signe que ce n'est pas un objet POO, si non seulement le stockage). Un bon objet encapsule sa logique et de comportement et ne pas exposer sa logique en dehors.

Un DAO est contre la POO

Malheureusement, dans le monde Java on trouve beaucoup des mauvaises exemples : Spring, Hibernate (des autres JPA), Jackson, Hadoop – tous ces frameworks essaient d’utiliser le paradigme procédural dans le monde POO. Ils essaient de convertir le langage Java en le langage C avec ses constructions monstrueuses, ou on n'arrive plus a tracer d’où a ou les données viennent transférés, ou plusieurs objets ont l'accès aux plusieurs objets et peuvent les changer, etc. Le but de Java- c'est d'utiliser les avantages de la POO, pour cette raison C++ et ensuite Java sont étés créés. Le code devient plus lisible, plus facile a maintenir, debugger et tester.

Dans ce sens le concept DAO est complètement anti-POO. Le code ci-dessus est difficile a tester et maintenir, spécialement, si p.e. on change MySQL pour NoSQL (dans ce cas on devrait changer tous les DAO qu’on utilise dans notre programme).

La solution

La solution est très simple :



Ici on délègue a l’objet User le travail et toute la responsabilité de changer son nom. Comme vous pouvez noter, au lieu de « setName » on utilise « rename()» pour faire le nécessaire pour actualiser son nom: se connecter a la BD, récupérer son ancien nom et mettre à jour la BD avec le nouveau nom. On utilise JOOQ (on peut aussi utiliser une autre bibliothèque pour générer SQL)
Toutes les opérations pour actualiser User sont encapsulées dans l’objet User et sont pas visibles dehors de cet objet.

Exemple JooQ :

public class User {
   private int id;
   public User() {}
   public int getId() {
      return id;
   }
   public String retrieveName() {
      return this.x.select("name")
      .from("users")
      .where("id", this.id)
      .fetchOne();
   }
   public void updateName( String name ) {
      this.x.update("user")
      .set("name", name=
      .where("id", this.id)
      .execute();
   }
}

S’on veut faire des transactions, on pourrait marquer ces méthodes  « transactional » ou créer une classe spéciale qui va actualiser tous les 2 objets - "User" et "Order" - au même temps avec un méthode transactionnel.  Dans ce méthode on peut appeler chaque objet (User et Order)pour l’actualiser.

Dans l’exemple ci-dessus on a utilisé JooQ, mais il y a un autre bon exemple implémenté dans Active Record qui s’appelle ActiveJPA, ou on travaille plus avec les objets DAO, sinon directement avec les modèles.
En autres paroles, dans notre cas, on n’a pas plus besoin d’obtenir un objet User  avec sa propriété "name" pour les passer a la session, etc. On appelle l’objet User (qui hérite d’un classe d’ActiveJPA qui s’appelle org.activejpa.entity.Model)  pour actualiser soit même.

Exemple ActiveJPA :

User user = User.findById(id);
Map attributes = new HashMap();   
attributes.put("name", name);
user.updateAttributes(attributes);

Aucun commentaire:

Enregistrer un commentaire