mercredi 23 mars 2011

Couplage lâche ou loosely coupled

Dans les Systèmes d'Information (SI), le couplage lâche, appelé aussi loosely coupled en anglais, pour un composant consiste à être le plus indépendant possible des implémentations tierces.

En d'autres termes, le composant va utiliser des librairies d'interface plutôt que des librairies d'implémentation. On parle également de non adhérence à une implémentation donnée.

Prenons un cas concret de couplage lâche avec un exemple sur la persistance JEE. Dans ce domaine, des spécifications Java (JSR-317) existent et sont regroupées dans Java Persistence API 2 (JPA 2).

Cas d'utilisation
Utilisons l'implémentation Hibernate de JBoss par exemple. On pourrait utiliser  d'autres implémentations telles que TopLink d'Oracle, Cayenne d'Apache, ...
L'exemple de code ci-dessous utilise Hibernate 3.6.1.

Considérons que l'on a une table nommée "mytable" dans un schéma appelé "myschema". Pour simplifier, définissons "mytable" de la manière suivante :

id (type int, clé primaire)
nom (type varchar)
prenom (type varchar)
dateNaissance (type datetime)
0
mycommunityman
eGivMe
2011-02-02 08:26:00

Nous nous intéresserons uniquement à la méthode Read parmi les CRUD.

Sans JPA 2
La tâche est ardue dans la mesure où il faut définir les ressources suivantes :
  • Mytable.hbm.xml
  • le POJO Mytable.java
  • le ValueObject MytableVO.java
  • le DAO MytableDAOImpl (et si l'on veut bien faire, il faut également l'interface MytableDAO.java)
  • une classe singleton de gestion des sessions Hibernate HibernateSessionManager.java
  • et biensûr hibernate.cfg.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
<class
    name="mywithoutjpa.Mytable"
    table="mytable"
    batch-size="100"
>

    <id
        name="id"
        type="int"
        column="id"
    >
        <generator class="assigned" />
    </id>

    <property
        name="nom"
        type="java.lang.String"
        column="nom"
        length="32"
    />
    <property
        name="prenom"
        type="java.lang.String"
        column="prenom"
        length="32"
    />
    <property
        name="dateNaissance"
        type="java.util.Calendar"
        column="dateNaissance"
        length="19"
    />
</class>
</hibernate-mapping>


package mywithoutjpa;

import java.io.Serializable;
import java.util.Calendar;

public class MytableVO implements Serializable {

       private static final long serialVersionUID = -3955385105983953564L;

    private int id;
    private String nom;
    private String prenom;
    private Calendar dateNaissance;
}


package mywithoutjpa;

import java.util.Calendar;

public class MytableVO {

    private int id;
    private String nom;
    private String prenom;
    private Calendar dateNaissance;
}


package mywithoutjpa;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

public class MytableDAOImpl implements MytableDAO {

    private Session session;

    public MytableVO restituer(int id) throws HibernateException {
            MytableVO myObjectVO = null;
            Mytable myObjet = (Mytable) restituerPojo(id);
            if (myObjet != null) {
                        myObjectVO = new MytableVO();
                        myObjectVO.setId(myObjet.getId());
                        myObjectVO.setNom(myObjet.getNom());
                        myObjectVO.setPrenom(myObjet.getPrenom());
                        myObjectVO.setDateNaissance(myObjet.getDateNaissance());
            }
            return myObjectVO;
    }

    private Object restituerPojo(int id) throws HibernateException {
            Object objet = null;
            initialiseSession();
            Criteria criteria = session.createCriteria("mywithoutjpa.Mytable");
            criteria.add(Restrictions.eq("id", id));
            if (criteria.list().size() == 1) {
                        objet = criteria.list().get(0);
            }
            return objet;
    }

    private void initialiseSession() throws HibernateException {
            if ((session == null)
                                   || ((session != null) && (session.isOpen() == false))) {
                        session = HibernateSessionManager.getInstance().getSession();
            }
    }

}


<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
            <session-factory>
           
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.username">myschema</property>
<property name="connection.password">myschema</property>
<property name="connection.url">jdbc:mysql://localhost:3306/myschema</property>
                                  
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">false</property>
<property name="hibernate.connection.release_mode">auto</property>
<property name="hibernate.jdbc.fetch_size">10</property>
       
<mapping resource="mywithoutjpa/Mytable.hbm.xml" />

            </session-factory>
</hibernate-configuration>


La classe de test pourrait être la suivante :

package mywithoutjpa;

import static junit.framework.Assert.assertTrue;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.junit.Test;

public class MytableDAOImplTest {

    protected DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     
    @Test
    public void testRestituer() {
          // le couplage lâche commence déjà là; 
          // avec l'utilisation des types 
          // d'interface au lieu des types 
          // d'implémentation
          MytableDAO myDAO = new MytableDAOImpl();
          MytableVO myVO = myDAO.restituer(0);
           
          HibernateSessionManager.getInstance().closeSession();

          assertTrue(myVO.getId() == 0);
          assertTrue("mycommunityman".equals(myVO.getNom()));
          assertTrue("eGivMe".equals(myVO.getPrenom()));
           
          String strDateNaissance = simpleDateFormat.format(myVO.getDateNaissance().getTime());
          assertTrue("2011-02-02 08:26:00".equals(strDateNaissance));
    }
}


Avec JPA 2
La tâche est rendue facile, avec notamment l'utilisation des annotations. La librairie utilisée est  "hibernate-jpa-2.0-api-1.0.0.Final.jar". Les ressources à définir sont uniquement :
  • l'Entity Mytable.java
  • et persistence.xml dans le répertoire META-INF
 
package mywithjpa;

import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity(name="mywithjpa.Mytable")
@Table(name="mytable")
public class Mytable {

            @Id
            @Column(name = "id", nullable = false)
            @GeneratedValue(strategy = GenerationType.AUTO)
            private int id;

            @Column(name = "nom", length = 32, nullable = true)
            private String nom;

            @Column(name = "prenom", length = 32, nullable = true)
            private String prenom;

            @Column(name = "dateNaissance", nullable = true)
            @Temporal(TemporalType.TIMESTAMP)
            private Calendar dateNaissance;
}


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
            <persistence-unit name="myjpa" transaction-type="RESOURCE_LOCAL">

                        <provider>org.hibernate.ejb.HibernatePersistence</provider>
                        <properties>
                                  
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="myschema"/>
<property name="hibernate.connection.password" value="myschema"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/myschema"/>

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.connection.release_mode" value="auto"/>
<property name="hibernate.jdbc.fetch_size" value="10"/>

                        </properties>
            </persistence-unit>
</persistence>


La classe de test pourrait s'écrire :

package mywithjpa;

import static junit.framework.Assert.assertTrue;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.Test;

public class MyTest {

  protected DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     
  @Test
  public void testRestituer() {

      EntityManagerFactory emf = Persistence.createEntityManagerFactory("myjpa");
      EntityManager em = emf.createEntityManager();
           
      Mytable myObject = em.find(mywithjpa.Mytable.class, 0);
           
      em.close();
      emf.close();
           
      assertTrue(myObject.getId() == 0);
      assertTrue("mycommunityman".equals(myObject.getNom()));
      assertTrue("eGivMe".equals(myObject.getPrenom()));
           
      String strDateNaissance = simpleDateFormat.format(myObject.getDateNaissance().getTime());
      assertTrue("2011-02-02 08:26:00".equals(strDateNaissance));

      }

}

Et pour finir
Nous pouvons adopter la même démarche pour les parsers XML, pour les frameworks de présentation, pour les frameworks de log (avec notamment slf4j), ...

Dans une vision idéaliste des SI, les librairies d'interface et d'implémentation, dont dépend l'applicatif, sont embarquées côté serveur d'application. Cette approche a l'avantage de rassurer l'équipe d'exploitation qui se sent maître dans le changement d'une implémentation ou dans la montée de version d'un socle technique donné.

Aucun commentaire:

Enregistrer un commentaire