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:
Ceci est un Mapper abstrait qui va être la base de l'héritage de tous les Mapper.
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); }
AbstractRootEntity est la super class abstraite des BO, BusinessTO pour les DTO.
Voici par exemple un Mapper concret:
Tout cela semble bien mais les choses se compliquent lorsqu'on a des agrégations.
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()); } }
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:
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é...
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()); } } }
Vos points de vue me seraient super utiles.
Merci beaucoup
Partager