web-dev-qa-db-fra.com

Modèles de conception d'applications Web

Je conçois une application Web simple. Je suis novice dans ce domaine basé sur le Web. J'avais besoin de vos conseils concernant les modèles de conception, tels que la répartition des responsabilités entre Servlets, les critères de création d'un nouveau Servlet, etc.

En fait, ma page d’accueil contient peu d’entités et, pour chacune d’elles, nous avons peu d’options comme ajouter, éditer et supprimer. Auparavant, j'utilisais un servlet par options, par exemple Servlet1 pour ajouter une entité1, Servlet2 pour modifier une entité1, etc., ce qui nous a permis d'avoir un grand nombre de servlets.

Maintenant, nous changeons notre conception. Ma question est de savoir comment vous choisissez exactement comment vous choisissez la responsabilité d'un servlet. Devrions-nous avoir un Servlet par entité qui traitera toutes ses options et transmettra la demande à la couche service. Ou devrions-nous avoir un seul servlet pour la page entière qui traitera la demande de page entière et la transmettra ensuite à la couche de service correspondante? De plus, l'objet de requête doit-il être transmis à la couche service ou non?.

356
mawia

Une application Web un peu décente consiste en un mélange de modèles de conception. Je ne mentionnerai que les plus importants.


Modèle de contrôleur de vue de modèle

Le modèle de conception (architectural) principal que vous souhaitez utiliser est le modèle , modèle-vue-contrôleur . Le Controller doit être représenté par un servlet qui crée/utilise directement un modèle et vue en fonction de la requête. Le Model doit être représenté par des classes javabéennes. Ceci est souvent divisible en Business Model qui contient les actions (comportement) et Data Model qui contient les données (information). Le View doit être représenté par les fichiers JSP ayant un accès direct au (Données) Modèle par EL (Expression Language).

Ensuite, il existe des variations en fonction de la manière dont les actions et les événements sont gérés. Les plus populaires sont:

  • MVC basé sur une requête (action) : c'est le plus simple à implémenter. Le (Business) Model fonctionne directement avec les objets HttpServletRequest et HttpServletResponse. Vous devez collecter, convertir et valider (principalement) les paramètres de la demande vous-même. Le View peut être représenté par un langage HTML/CSS/JS simple et simplifié. Il ne conserve pas l'état des demandes. C'est entre autres que Spring MVC , Struts et Stripes .

  • MVC à base de composants : ceci est plus difficile à implémenter. Mais vous vous retrouvez avec un modèle et une vue plus simples dans lesquels toutes les API "brutes" de Servlet sont complètement abstraites. Vous ne devriez pas avoir besoin de rassembler, convertir et valider les paramètres de demande vous-même. Le Contrôleur effectue cette tâche et définit les paramètres de demande collectés, convertis et validés dans le Modèle. Tout ce que vous avez à faire est de définir des méthodes d'action qui fonctionnent directement avec les propriétés du modèle. Le View est représenté par des "composants" sous la forme de balises JSP ou d'éléments XML qui, à leur tour, génèrent HTML/CSS/JS. L'état de View pour les requêtes suivantes est conservé dans la session. Ceci est particulièrement utile pour les événements de conversion, de validation et de modification de valeur côté serveur. C'est entre autres que JSF , Wicket et Play! fonctionne .

En passant, passer du temps avec un framework MVC local est un exercice d'apprentissage très agréable, et je le recommande tant que vous le gardez à des fins personnelles/privées. Mais une fois que vous êtes devenu professionnel, il est fortement recommandé de choisir un cadre existant plutôt que de réinventer le vôtre. Apprendre un cadre existant et bien développé prend moins de temps à long terme que développer et maintenir vous-même un cadre robuste.

Dans l'explication détaillée ci-dessous, je me limiterai à demander MVC basé car c'est plus facile à implémenter.


modèle de contrôleur frontal ( modèle de médiateur )

Tout d’abord, la partie Contrôleur doit implémenter le modèle de contrôleur frontal (qui est un type spécialisé de modèle de médiateur ). Il ne doit comporter qu'un seul servlet fournissant un point d'entrée centralisé pour toutes les demandes. Il convient de créer le Modèle en fonction des informations disponibles dans la requête, telles que pathinfo ou servletpath, la méthode et/ou des paramètres spécifiques. Le Business Model s'appelle Action dans l'exemple ci-dessous HttpServlet =.

_protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        Action action = ActionFactory.getAction(request);
        String view = action.execute(request, response);

        if (view.equals(request.getPathInfo().substring(1)) {
            request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
        }
        else {
            response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
        }
    }
    catch (Exception e) {
        throw new ServletException("Executing action failed.", e);
    }
}
_

L'exécution de l'action doit renvoyer un identifiant pour localiser la vue. Le plus simple serait de l'utiliser comme nom de fichier du JSP. Mappez ce servlet sur un _url-pattern_ in _web.xml_ spécifique, par exemple. _/pages/*_, _*.do_ ou même simplement _*.html_.

Dans le cas de modèles de préfixe comme par exemple _/pages/*_, vous pouvez alors appeler des URL telles que http: //example.com/pages/register , http://example.com/pages/login , etc. et fournissez _/WEB-INF/register.jsp_, _/WEB-INF/login.jsp_ avec les actions GET et POST appropriées. Les parties register, login, etc. sont alors disponibles avec request.getPathInfo() comme dans l'exemple ci-dessus.

Lorsque vous utilisez des modèles de suffixe tels que _*.do_, _*.html_, etc., vous pouvez alors appeler des URL de type http: // ++ http: //example.com/register.do , http: //example.com/login.do , etc., et vous devez modifier les exemples de code dans cette réponse (également le ActionFactory) pour extraire les register et login pièces par request.getServletPath() à la place.


Modèle de stratégie

Action doit suivre le Stratégie Pattern . Il doit être défini comme un type abstrait/interface devant effectuer le travail en fonction des arguments passés-dans de la méthode abstraite (il s'agit de la différence avec le Command pattern , dans lequel le type abstrait/interface doit effectuer le travail en fonction des arguments qui ont été passés au cours de la création de l'implémentation).

_public interface Action {
    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
_

Vous souhaiterez peut-être rendre Exception plus spécifique avec une exception personnalisée telle que ActionException. C'est juste un exemple de coup d'envoi, le reste dépend de vous.

Voici un exemple de LoginAction qui (comme son nom l’indique) connecte l’utilisateur. Le User lui-même est à son tour un modèle de données. Le View est conscient de la présence de User.

_public class LoginAction implements Action {

    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = userDAO.find(username, password);

        if (user != null) {
            request.getSession().setAttribute("user", user); // Login user.
            return "home"; // Redirect to home page.
        }
        else {
            request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
            return "login"; // Go back to redisplay login form with error.
        }
    }

}
_

Modèle de méthode d'usine

ActionFactory doit suivre le modèle de méthode Factory . Fondamentalement, il devrait fournir une méthode de création qui renvoie une implémentation concrète de type abstrait/interface. Dans ce cas, il devrait renvoyer une implémentation de l'interface Action en fonction des informations fournies par la requête. Par exemple, le méthode et pathinfo (le pathinfo est la partie située après le contexte et le chemin de la servlet dans l'URL de la demande, à l'exclusion du chaîne de requête).

_public static Action getAction(HttpServletRequest request) {
    return actions.get(request.getMethod() + request.getPathInfo());
}
_

actions à son tour devrait être un type statique/d'application _Map<String, Action>_ qui contient toutes les actions connues. C'est à vous de choisir comment remplir cette carte. Codage dur:

_actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
_

Ou configurable en fonction d'un fichier de configuration propriétés/XML dans le chemin de classe: (pseudo)

_for (Entry entry : configuration) {
    actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
_

Ou dynamiquement basé sur une analyse dans le chemin de classe pour les classes implémentant une certaine interface et/ou une annotation: (pseudo)

_for (ClassFile classFile : classpath) {
    if (classFile.isInstanceOf(Action.class)) {
       actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
    }
}
_

N'oubliez pas de créer un "ne rien faire" Action dans le cas où il n'y a pas de mappage. Laissons par exemple renvoyer directement la request.getPathInfo().substring(1) alors.


D'autres modèles

Ce sont les modèles importants jusqu'à présent.

Pour aller plus loin, vous pouvez utiliser le Facade Pattern pour créer une classe Context qui encercle à son tour les objets requête et réponse et propose plusieurs méthodes pratiques pour déléguer les objets requête et réponse. et transmettez-le comme argument dans la méthode Action#execute() à la place. Cela ajoute une couche abstraite supplémentaire pour cacher l’API de servlet brute. Vous devriez alors vous retrouver avec essentiellement zéro _import javax.servlet.*_ déclarations dans chaque implémentation de Action. En termes JSF, c'est ce que font les classes FacesContext et ExternalContext . Vous pouvez trouver un exemple concret dans cette réponse .

Ensuite, il y a le State Pattern dans le cas où vous souhaitez ajouter une couche d'abstraction supplémentaire pour fractionner les tâches de collecte, de conversion, de validation et de mise à jour du modèle. valeurs et exécuter les actions. En termes JSF, c’est ce que fait LifeCycle .

Ensuite, il y a Composite Pattern pour le cas dans lequel vous souhaitez créer une vue basée sur les composants pouvant être attachée avec le modèle et dont le comportement dépend de l'état du cycle de vie basé sur la demande. . En termes JSF, c’est ce que UIComponent .

De cette façon, vous pouvez évoluer petit à petit vers un framework à base de composants.


Voir également:

485
BalusC

Dans le modèle MVC, le servlet est "C" - contrôleur.

Son travail principal consiste à évaluer les demandes initiales, puis à envoyer le traitement basé sur l'évaluation initiale à l'agent concerné. L'une des responsabilités du travailleur peut être de configurer des beans de la couche de présentation et de transmettre la demande à la page JSP pour le rendu HTML. Donc, pour cette seule raison, vous devez transmettre l'objet de requête à la couche service.

Je ne voudrais cependant pas commencer à écrire des classes brutes Servlet. Le travail qu'ils effectuent est très prévisible et standard, ce que le cadre fait très bien. Heureusement, il existe de nombreux candidats testés dans le temps (dans l'ordre alphabétique): Apache Wicket , Visages du serveur Java , Spring pour nommer a peu.

13

IMHO, il n'y a pas beaucoup de différence dans le cas d'une application Web si vous la regardez sous l'angle de l'attribution de responsabilité. Cependant, conservez la clarté dans le calque. Conservez tout ce qui est purement à des fins de présentation dans la couche présentation, comme le contrôle et le code spécifiques aux contrôles Web. Conservez simplement vos entités dans la couche de gestion et toutes les fonctionnalités (telles que l'ajout, la modification, la suppression), etc., dans la couche de gestion. Cependant, les rendre sur le navigateur pour les traiter dans la couche de présentation. Pour .Net, le modèle ASP.NET MVC est très efficace pour ce qui est de maintenir les couches séparées. Regardez dans le modèle MVC.

5
Kangkan

BalusC L’excellente réponse couvre la plupart des modèles d’applications Web.

Certaines applications peuvent nécessiter Chain-of-liability_pattern

Dans une conception orientée objet, le modèle de responsabilité est un modèle de conception constitué d'une source d'objets de commande et d'une série d'objets de traitement. Chaque objet de traitement contient une logique qui définit les types d'objets de commande qu'il peut gérer. le reste est passé à l'objet de traitement suivant dans la chaîne.

Cas d'utilisation pour utiliser ce modèle:

Lorsque le gestionnaire pour traiter une demande (commande) est inconnu et que cette demande peut être envoyée à plusieurs objets. En général, vous définissez successor sur object. Si l'objet actuel ne peut pas gérer la demande ou traiter partiellement la demande et transmettre la même demande à l'objet successeur .

Questions/articles utiles sur la SE:

Pourquoi devrais-je jamais utiliser une chaîne de responsabilité sur un décorateur?

sages communs pour la chaîne de responsabilité?

modèle de chaîne de responsabilité de oodesign

chain_of_responsibility de la source

3
Ravindra babu

J'ai utilisé le cadre struts et le trouve assez facile à apprendre. Lorsque vous utilisez le cadre struts, chaque page de votre site contient les éléments suivants.

1) Une action utilisée est appelée à chaque actualisation de la page HTML. L'action doit renseigner les données du formulaire lors du premier chargement de la page et gérer les interactions entre l'interface utilisateur Web et la couche de gestion. Si vous utilisez la page jsp pour modifier un objet Java mutable, une copie de l'objet Java doit être stockée dans le formulaire plutôt que dans l'original, afin que les données d'origine ne soient pas récupérées. modifié sauf si l'utilisateur enregistre la page.

2) Le formulaire utilisé pour transférer des données entre l’action et la page jsp. Cet objet doit consister en un ensemble de getter et de paramètres pour les attributs devant être accessibles au fichier jsp. Le formulaire comporte également une méthode pour valider les données avant qu’elles ne soient persistées.

3) Une page jsp utilisée pour rendre le code HTML final de la page. La page jsp est un hybride de HTML et de balises struts spéciales utilisées pour accéder aux données du formulaire et les manipuler. Bien que struts permette aux utilisateurs d'insérer du code Java dans des fichiers JSP, vous devez être très prudent, car cela rendra votre code plus difficile à lire. Java le code dans les fichiers jsp est difficile à déboguer et ne peut pas être testé. Si vous écrivez plus de 4 à 5 lignes de code Java dans un fichier jsp, le code doit probablement être déplacé vers l'action.

3
EsotericNonsense