ERRATA - MAGASIN VIRTUEL 1 28 juillet 2005 Entre mars et juillet 2005, un certa

ERRATA - MAGASIN VIRTUEL 1 28 juillet 2005 Entre mars et juillet 2005, un certain nombre d'articles sont parus sur [http://tahe.developpez.com]. Ils avaient pour but de présenter le framework Spring aussi bien dans le monde Java que dans le monde .NET. Une application simplifiée d'achats de produits sur le web a servi de fil conducteur à tous les articles. Celle-ci a l'architecture suivante : Une erreur de conception s'est glissée dès les premiers articles parus en mars 2005, dans l'implémentation de la couche [domain]. Je n'ai détecté l'erreur que le 27 juillet. Mieux vaut tard que jamais... Cet article explique quelle est l'erreur et quelles sont ses conséquences pour les applications utilisant la couche [domain]. Pour comprendre cet "Errata", il faut avoir lu les premiers articles de la série afin de comprendre le rôle de la couche [domain]. En fait, nous conseillons au lecteur de lire d'abord les articles sans tenir compte de cet "errata" et de ne revenir qu'ensuite sur ce dernier. En effet, l'erreur n'apparaît pas lors d'une lecture rapide des articles et ne nuit en rien à leur compréhension. Il est probable que seuls des développeurs expérimentés la détecteront dès la première lecture. Nous présentons tout d'abord l'erreur dans sa version Java. Elle existe à l'identique dans la version .NET. Nous proposons ensuite des exemples de codes corrigeant l'erreur. Ceux-ci sont pris dans les versions .NET. 1.1 L'erreur dans sa version Java L'interface [IArticlesDomain] et sa classe d'implémentation [AchatsArticles] s'avèrent avoir une conception incorrecte pour ce qui est de l'achat du panier. La classe [AchatsArticles] est la suivante : package istia.st.articles.domain; // Imports import istia.st.articles.dao.IArticlesDao; import istia.st.articles.exception.UncheckedAccessArticlesException; import java.util.ArrayList; import java.util.List; public class AchatsArticles implements IArticlesDomain { // Champs private IArticlesDao articlesDao; private ArrayList erreurs; // Constructeurs public AchatsArticles(IArticlesDao articlesDao) { } // Méthodes public ArrayList getErreurs() {} public List getAllArticles() {} public Article getArticleById(int id) {} public void acheter(Panier panier) { } } L'achat d'un panier se fait en deux temps : 1. la méthode [acheter] est tout d'abord utilisée pour décrémenter les stocks des articles achetés, 2. la méthode [getErreurs] est ensuite utilisée pour obtenir la liste des éventuelles erreurs. Celles-ci listent les articles dont les stocks sont insuffisants pour satisfaire la demande du client. Si cette méthode est acceptable (quoique maladroite) lorsqu'il n'y a qu'un client, elle ne l'est plus lorsqu'il y en a plusieurs comme c'est le cas dans une application web où les clients sont multiples. Il faut tout d'abord comprendre qu'il n'y a qu'une seule instance de la classe [AchatsArticles] pour satisfaire les demandes des clients. On a affaire à un singleton. Prenons la situation suivante dans le cadre d'une application web : errata-magasin-virtuel, serge.tahe@istia.univ-angers.fr 1/7 Couche interface utilisateur [ui] Couche métier [domain] Couche d'accès aux données [dao] SPRING utilisateur Données 1. le client 1 veut acheter le panier [panier1]. Il est servi par un thread T1 côté serveur. Celui-ci commence par faire appel à la méthode [acheter] du singleton [AchatsArticles]. Celle-ci se termine. Les erreurs d'achats ont été stockées dans le champ privé [erreurs] du singleton. Le thread T1 est alors interrompu pour une raison quelconque. 2. le client 2 veut acheter le panier [panier2]. Il est servi par un thread T2 côté serveur. Celui-ci commence par faire appel à la méthode [acheter] du singleton [AchatsArticles]. Celle-ci se termine. Les erreurs d'achats ont été stockées dans le champ privé [erreurs] du singleton écrasant donc les erreurs du client 1. Le thread T2 n'est pas interrompu. Il fait alors appel à la méthode [getErreurs] du singleton et obtient la liste d'erreurs des achats de [panier2]. Le thread T2 envoie sa réponse au client 2 et se termine. 3. Le thread T1 récupère alors le processeur et fait ce qu'il n'a pas pu eu le temps de faire. Il fait appel à la méthode [getErreurs] du singleton [AchatsArticles] et obtient la liste des erreurs d'achats de [panier2] et non celle de [panier1] qui n'existe plus. On a un problème... Lorsqu'on a affaire à un singleton servant plusieurs clients, il est important de vérifier que ses données d'état ne concernent pas un client particulier. Dans la classe [AchatsArticles], les données d'état sont les suivantes : // Champs private IArticlesDao articlesDao; private ArrayList erreurs; articlesDao représente l'instance d'accès à la couche [dao] - est elle-même un singleton. N'est pas lié à un client particulier. erreurs la liste des erreurs qui se sont produites sur l'achat d'un panier. Cette donnée est liée au panier d'un client particulier. Elle ne peut faire partie de l'état du singleton qui est partagé par tous les clients. La solution à notre problème est ici relativement simple. Il faut changer la signature de la méthode [acheter]. Elle doit devenir : public ArrayList acheter(Panier panier); Le résultat de type [ArrayList] est la liste des erreurs survenues lors de l'achat du panier. Celle-ci n'a donc plus à faire partie de l'état du singleton implémentant l'interface [IArticlesDomain]. Cette dernière devient la suivante : 1. package istia.st.articles.domain; 2. 3. import istia.st.articles.dao.Article; 4. 5. import java.util.ArrayList; 6. import java.util.List; 7. 8. /** 9. * @author serge.tahe@istia.univ-angers.fr 10. * 11. */ 12.public interface IArticlesDomain { 13. // liste de tous les articles 14. public List getAllArticles(); 15. // obtenir un article particulier 16. public Article getArticleById(int id); 17. // acheter un panier 18. public ArrayList acheter(Panier panier); 19.} La méthode [getErreurs] a disparu. La méthode [acheter] a changé de signature, ligne 18. La classe d'implémentation [AchatsArticles] devient la suivante : 1. package istia.st.articles.domain; 2. 3. // Imports 4. import istia.st.articles.dao.IArticlesDao; 5. import istia.st.articles.exception.UncheckedAccessArticlesException; 6. import java.util.ArrayList; 7. import java.util.List; 8. 9. public class AchatsArticles implements IArticlesDomain { 10. 11. // Champs 12. private IArticlesDao articlesDao; 13. private ArrayList erreurs; 14. 15. // Constructeurs 16. public AchatsArticles(IArticlesDao articlesDao) { } 17. 18. // Méthodes 19. public List getAllArticles() {...} errata-magasin-virtuel, serge.tahe@istia.univ-angers.fr 2/7 20. public Article getArticleById(int id) {...} 21. public ArrayList acheter(Panier panier) {...} 22.} La méthode [getErreurs] a disparu. La méthode [acheter] a changé de signature et devient la suivante : 1. // valider un panier d'achats 2. public ArrayList acheter(Panier panier) { 3. // la liste des erreurs 4. ArrayList erreurs = new ArrayList(); 5. // on parcourt les achats 6. ArrayList achats = panier.getAchats(); 7. Article article = null; 8. Achat achat = null; 9. for (int i = achats.size() - 1; i >= 0; i--) { 10. // on récupère l'achat 11. achat = (Achat) achats.get(i); 12. // on tente de modifier le stock de l'article dans la base 13. int nbarticles = articlesDao.changerStockArticle(achat.getArticle() 14. .getId(), -achat.getQte()); 15. // a-t-on réussi ? 16. if (nbarticles != 0) { 17. achats.remove(i); 18. } else { 19. erreurs.add("Achat article [" + achat.getArticle() + "," 20. + achat.getQte() + "] impossible - Vérifiez son stock"); 21. } 22. } 23. // on rend la liste des erreurs 24. return erreurs; 25. } • la liste des erreurs est devenue une variable locale à la méthode - ligne 4 • elle est rendue comme résultat - ligne 24 Ce code est-il suffisant pour garantir l'étanchéité entre deux clients ? Dans cette méthode, seules des variables locales à la méthode sont utilisées sauf la variable [articlesDao] qui est une variable d'instance du singleton partagée par tous les clients. La méthode [articlesDao.changerStockArticle] utilisée ligne 13 est synchronisée (synchronized). Deux threads ne peuvent l'exécuter simultanément. Il semble donc que la méthode [acheter] assure bien l'étanchéité des clients. Nous utiliserons désormais cette nouvelle interface [IArticlesDomain] et sa nouvelle classe d'implémentation [AchatsArticles]. On peut conclure de cette erreur que les tests de la couche [domain] avaient été insuffisants. On avait négligé de tester l'étanchéité des clients entre-eux. 1.1 Conséquences de l'erreur Revenons sur l'architecture de l'application d'achats d'articles : Nous avons modifié l'interface [IArticlesDomain] de la couche [domain]. Très exactement nous avons : • supprimé la méthode [getErreurs] • modifié la signature de la méthode [acheter] Cela entraîne des modifications sur les éléments de la couche [ui] qui utilisent ces deux méthodes. Dans ces éléments, la modification à faire est la suivante : Dans l'ancienne version, l'achat d'un panier se faisait de la façon suivante (version Java) : 1. Panier panier; 2. ArrayList erreurs; 3. ... 4. [domain].acheter(panier); 5. ... 6. erreurs=[domain].getErreurs(); où [domain] représente une instance d'une classe implémentant [IArticlesDomain]. errata-magasin-virtuel, serge.tahe@istia.univ-angers.fr 3/7 Couche interface utilisateur [ui] Couche métier [domain] Couche d'accès aux données [dao] SPRING utilisateur Données Dans la nouvelle version, l'achat d'un panier se fera de la façon suivante (version Java) : 7. Panier panier; 8. ArrayList erreurs; 9. ... 10.erreurs=[domain].acheter(panier); Nous présentons des exemples de corrections apportées aux versions .NET des articles. 1.2 Corrections .NET - Exemples 1.2.1 L'interface IArticlesDomain 1. Imports istia.st.articles.dao 2. 3. Namespace istia.st.articles.domain 4. Public Interface IArticlesDomain 5. ' méthodes 6. Function acheter(ByVal panier As Panier) As ArrayList 7. Function getAllArticles() As IList 8. Function getArticleById(ByVal idArticle As Integer) As Article 9. End Interface 10.End Namespace • à noter le changement de signature de la méthode [acheter] ligne 6 1.2.2 La classe [AchatsArticles] 1. Imports istia.st.articles.dao 2. 3. Namespace istia.st.articles.domain 4. Public Class AchatsArticles uploads/Litterature/ errata-magasin-virtuel-etude-de-cas-pdf.pdf

  • 32
  • 0
  • 0
Afficher les détails des licences
Licence et utilisation
Gratuit pour un usage personnel Attribution requise
Partager