IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java EE Discussion :

Encore ces DTOs!


Sujet :

Java EE

  1. #1
    Membre confirmé
    Inscrit en
    Septembre 2008
    Messages
    139
    Détails du profil
    Informations forums :
    Inscription : Septembre 2008
    Messages : 139
    Par défaut Encore ces DTOs!
    Bonjour à tous,

    Je suis sur un projet personnel en J2EE et je voudrais introduire la notion de DTO afin d'ajouter une couche intermédiaire entre la couche d'accès aux données et la couche présentation JSF.

    J'ai bien compris le principe des DTOs, cela sert à diminuer le couplage entre la persistance et la présentation en ne transférant que des objets POJO "light" au lieu des BO annotées et pouvant contenir du code métier non utilisé par les couches plus hautes.
    Jusque la tout va bien ...

    Je me suis donc penché sur un pattern permettant de faire un mapping BO->DTO et DTO->BO.
    Le principe du mapping BO->DTO est trivial:
    Je crée un objet DTO que je remplie avec les données du BO correspondant.
    Le principe inverse est quand à lui plus délicat:
    En effet les BO contiennent beaucoup plus de données que les DTOs. On ne peut donc pas créer une BO de toute pièce en l'initialisant avec les données du DTO.

    Exemple simple:
    ArticleBO: id, title, creationDate, content
    ArticleDTO: id, title, content

    Si je crée une BO à partir de la DTO, que j'initialise son id et que je la merge(), j'écraserai l'attribut creationDate.

    Il faut donc récupérer l'article BO correspondant par un find(id) puis le modifier avec les données du DTO.

    C'est la ou l'idée d'un Mapper m'est venu:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    public abstract class Mapper<BO extends AbstractRootEntity, DTO extends BusinessTO> {
     
        protected static Mapper instance; //singleton
     
        public abstract void mapDTOtoBO(DTO dto, BO bo);
        public abstract void mapBOtoDTO(BO bo, DTO dto);
     
    }
    Ceci est un Mapper abstrait qui va être la base de l'héritage de tous les Mapper.
    AbstractRootEntity est la super class abstraite des BO, BusinessTO pour les DTO.

    Voici par exemple un Mapper concret:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    public class UserSiteMapper extends Mapper<SiteUser, SiteUserDTO>{
     
        public static UserSiteMapper getInstance() {
            if(instance == null)
                instance = new UserSiteMapper();
            return (UserSiteMapper) instance;
        }
     
        @Override
        public void mapDTOtoBO(SiteUserDTO dto, SiteUser bo) {
            bo.getAccount().setLogin(dto.getPseudo());
        }
     
        @Override
        public void mapBOtoDTO(SiteUser bo, SiteUserDTO dto) {
            dto.setPseudo(bo.getAccount().getLogin());
        }
     
    }
    Tout cela semble bien mais les choses se compliquent lorsqu'on a des agrégations.
    Je reprend un exemple simple de mon projet:
    J'ai 3 BO: Article, SiteUser et Comment (un article (Article) a un auteur (SiteUser) et n commentaires (Comment))
    J'ai créé les DTO à l'identique niveau architecture mais bien évidement elles contiennent moins de données.

    Je sais que c'est long à lire mais j'aimerait bien tout expliquer ^^.
    Je voudrais avoir vos points de vue sur ces implémentations:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
     
    public class ArticleMapper extends Mapper<Article, ArticleTO> {
     
        public static ArticleMapper getInstance() {
            if (instance == null) {
                instance = new ArticleMapper();
            }
            return (ArticleMapper) instance;
        }
        private static CommentMapper commentMapperInstance;
     
        public ArticleMapper() {
            commentMapperInstance = new CommentMapper();
        }
     
        @Override
        public void mapDTOtoBO(ArticleTO ato, Article abo) {
            //je modifie le BO avec les données du DTO
            //je ne modifie pas l'auteur car il est dans la dto que pour être affiché et non modifié
            abo.setContent(ato.getContent());
            //ici ça devient compliqué car je dois savoir quels sont les commentaires à ajouter ou a modifier
            for (CommentTO cto : ato.getComments()) {//pour tout les commentaires de l'article ato
                Long id = cto.getId();
                if (id != null) {//le commentaire existe en temps que BO
                    for (Comment cbo : abo.getComments()) {//je le cherche dans l'article abo
                        if (cbo.getId() == id) {
                            commentMapperInstance.mapDTOtoBO(cto, cbo);
                        }
                    }
                } else {//sinon j'ajoute un commentaire cbo qui sera persisté en cascade plus tard
                    Comment cbo = new Comment();
                    commentMapperInstance.mapDTOtoBO(cto, cbo);
                    abo.getComments().add(cbo);
                }
            }
        }
     
        @Override
        public void mapBOtoDTO(Article abo, ArticleTO ato) {
            //je remplie mon DTO avec les données de mon BO
            ato.setCreationDate(abo.getCreationDate());
            //j'utilise les mapper annexes
            UserSiteMapper.getInstance().mapBOtoDTO(abo.getCreator(), (SiteUserTO) ato.getAutor());
            ato.setTitle(abo.getTitle());
            for (Comment cbo : abo.getComments()) {//pour chaque commentaires de ato
                CommentTO cto = new CommentTO();//je crée un commentaire DTO
                commentMapperInstance.mapBOtoDTO(cbo, cto);
                ato.getComments().add(cto);//que j'ajoute a l'article DTO
            }
     
        }
        //j'ai fait cette subclass car elle n'est utilisée que dans la classe ArticleMapper
        private class CommentMapper extends Mapper<Comment, CommentTO> {
     
            @Override
            public void mapDTOtoBO(CommentTO cto, Comment cbo) {
                cbo.setContent(cto.getContent());
            }
     
            @Override
            public void mapBOtoDTO(Comment cbo, CommentTO cto) {
                cto.setId(cbo.getId());
                cto.setContent(cbo.getContent());
            }
        }
    }
    Voila donc où j'en suis... Je me rend compte que créer cette couche intermédiaire est un boulot de fourmi et je ne sais pas si mon design est optimal ou peut être amélioré...
    Vos points de vue me seraient super utiles.

    Merci beaucoup

  2. #2
    Membre éclairé
    Homme Profil pro
    Inscrit en
    Juillet 2002
    Messages
    705
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 705
    Par défaut
    J ai pas tout compris de ta logique

    D une facon generale la coche metier a besoin de faire des operations CRUD et fait appel a la couche Persistance.

    La couche persistance de son cote a donc le role d acceder a differente source de donnees pour les remonter vers la business logique.

    Pour se faire elle implemente donc le pattern DAO. Quand l information repose dans un unique DataStore le DAO est simple.

    Par contre quand on accede a plusieurs bases, (qui existaient avant l application par exemple, ou mixte avec LDAP ou XML...) on doit recuperer l information de droite et de gauche et reconstruire un objet metier tel que le souhaite la couche business logique. Les DTO sont les objets intermediares qui mappe la structure des differents data store. Ceci perment d avoir des DAO qui respose sur des sous DAO. Donc tout ce fait avec le pattern DAO, qui gere la logique de persistance d une facon globale.

    Voila

  3. #3
    Membre confirmé
    Inscrit en
    Septembre 2008
    Messages
    139
    Détails du profil
    Informations forums :
    Inscription : Septembre 2008
    Messages : 139
    Par défaut
    Je vais essayer de mieux expliquer mon architecture:

    DAO(JPA) <---BO---> Service (mapping BO/DTO) <---DTO---> Client

    Les DAO et Services sont des Session Beans.
    Un exemple simplifié:

    Le DAO:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    @Stateless(mappedName = ArticleIDAO.JNDI_NAME)
    public class ArticleDAO implements ArticleIDAOLocal {
     
        @PersistenceContext(unitName = "db.ejbPU")
        private EntityManager em;
     
        @Override
        public Article getArticle(Long articleId)  {
            return em.find(Article.class,articleId);
        }
     
    }
    Et le Service:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    @Stateless(mappedName = ArticleIService.JNDI_NAME)
    public class ArticleService implements ArticleIServiceRemote {
     
        @EJB
        ArticleIDAOLocal dao;
     
        @Override
        public ArticleDTO getArticle(Long articleId) {
            Article abo = dao.getArticle(articleId);
            ArticleDTO adto = new ArticleDTO();
            ArticleMapper.getInstance().mapBOtoDTO(abo, adto);
            return ato;
        }
     
    }

  4. #4
    Membre chevronné
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    365
    Détails du profil
    Informations personnelles :
    Localisation : Maroc

    Informations forums :
    Inscription : Janvier 2006
    Messages : 365
    Par défaut
    Citation Envoyé par saveriu Voir le message
    Je suis sur un projet personnel en J2EE et je voudrais introduire la notion de DTO afin d'ajouter une couche intermédiaire entre la couche d'accès aux données et la couche présentation JSF.

    J'ai bien compris le principe des DTOs, cela sert à diminuer le couplage entre la persistance et la présentation en ne transférant que des objets POJO "light" au lieu des BO annotées et pouvant contenir du code métier non utilisé par les couches plus hautes.
    Jusque la tout va bien ...
    Oui, mais les entity annotées JPA sont déjà de simples POJO "light" qui peuvent être exploités par la couche présentation, donc en général il n'y a pas besoin d'introduire une couche supplémentaire de DTOs juste pour découpler les couches présentation et persistence.

    Citation Envoyé par saveriu Voir le message
    Voila donc où j'en suis... Je me rend compte que créer cette couche intermédiaire est un boulot de fourmi et je ne sais pas si mon design est optimal ou peut être amélioré...
    Vos points de vue me seraient super utiles.
    Effectivement, je pense que ça complique ton code sans que les bénéfices ne soient évidents. Il ne faut pas oublier que le pattern DTO vient de l'époque des Entity Beans (EJB2) qui étaient des composants de persistence très complexes avec interfaces Home, Local, Remote, plus une classe d'implémentation abstraite, sans compter la configuration dans au moins deux fichiers xml, etc. Bref, loin d'un simple POJO.
    Les DTOs avaient été introduits notamment pour résoudre de nombreux problèmes résultant du fait même que les Entity Beans étaient accessibles à distance, afin donc de réduire le couplage entre présentation et persistence.

    Mais l'une des choses qui avaient sonné la révolte des développeurs par rapport aux DTOs étaient aussi que cela créait plusieurs hiérarchies de classes parallèles (les Entity Beans, les DTOs, les Mappers) juste pour persister des données. Et la maintenance de toutes ces classes est inutilement compliquées.

    Les frameworks comme Hibernate (et donc JPA par la suite) ont permis de réduire la couche DTO par le fait que les entities sont de simples POJO et peuvent transiter à travers les couches. Le besoin de DTO n'est plus systématique, mais on peut toujours l'utiliser dans le cas par exemple d'un objet agrégat (obtenu avec une requête de projection sur plusieurs tables).

    En tout cas, un des avantages des POJOs annotés JPA c'est qu'on peut réutiliser la même classe pour la validation par exemple. Il suffirait d'ajouter des annotations Bean Validation (jsr-303) une seule fois et la validation serait activée sur toutes les couches (JSF et JPA) sans avoir à introduire une classe supplémentaire, ce qui simplifie ton code.

    Evidemment ton modèle peut être conçu de telle sorte qu'il y ait des méthodes qui ne soient pas accessibles aux couches hautes, et dans ce cas tu aurais certainement besoin de beaucoup de DTOs.
    Je te conseillerais de jeter un oeil sur le projet Dozer pour te simplifier les choses.

  5. #5
    Membre confirmé
    Inscrit en
    Septembre 2008
    Messages
    139
    Détails du profil
    Informations forums :
    Inscription : Septembre 2008
    Messages : 139
    Par défaut
    C'est vrai qu'avant j'utilisait des DTO pour modéliser des statistiques à partir de requête JPQL, mais j'ai voulu tenter de réellement faire une couche complète intermédiaire.
    J'ai jeté un coup d'oeil sur Dozer, ça m'a l'air pas mal pour mon cas d'utilisation.
    Mais je pense que je vais arrêter de me prendre la tête sur cette couche DTO qui me fait perdre du temps pour pas grand chose au final...
    Je pense que j'aurait une couche supplémentaire mais qui sera uniquement utilisée pour des objets agrégés. Je passerai les BO en direct pour le reste.
    En tout cas merci pour vos réponses.

Discussions similaires

  1. Doit on encore aider ces entreprises?
    Par Matthieu2000 dans le forum Politique
    Réponses: 19
    Dernier message: 25/01/2010, 16h28
  2. Réponses: 20
    Dernier message: 08/01/2010, 19h25
  3. Programmer encore en VB 6 c'est pas bien ? Pourquoi ?
    Par Nektanebos dans le forum Débats sur le développement - Le Best Of
    Réponses: 85
    Dernier message: 10/03/2009, 14h43
  4. Encore de ces trucs bizarres...
    Par avator dans le forum Haskell
    Réponses: 5
    Dernier message: 18/11/2008, 23h16
  5. Pouvez vous m'aider a resoudres ces 3 exercices
    Par algorithmique dans le forum Algorithmes et structures de données
    Réponses: 11
    Dernier message: 09/08/2002, 17h26

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo