web-dev-qa-db-fra.com

Quelle stratégie utilisez-vous pour nommer les packages dans les projets Java et pourquoi?

J'y ai pensé il y a un moment et il a récemment refait surface alors que ma boutique faisait sa première vraie application web Java.

En guise d'introduction, je vois deux stratégies principales de nommage de packages. (Pour être clair, je ne fais pas référence à toute la partie 'domain.company.project' de cela, je parle de la convention de package en dessous.) Quoi qu'il en soit, les conventions de dénomination de package que je vois sont les suivantes:

  1. Fonctionnel: nommer vos packages en fonction de leur fonction architecturale plutôt que de leur identité en fonction du domaine d'activité. Un autre terme pour cela pourrait être nommé en fonction de la "couche". Ainsi, vous auriez un package * .ui et un package * .domain et un package * .orm. Vos colis sont des tranches horizontales plutôt que verticales.

    C'est beaucoup plus courant que la dénomination logique. En fait, je ne pense pas avoir jamais vu ou entendu parler d'un projet qui fasse cela. Bien sûr, cela me rend méfiant (un peu comme penser que vous avez trouvé une solution à un problème NP) car je ne suis pas très intelligent et je suppose que tout le monde doit avoir de bonnes raisons pour en revanche, je ne suis pas opposé à ce que les gens manquent juste l'éléphant dans la pièce et je n'ai jamais entendu un argument réel pour faire le nommage des paquets de cette façon. Il semble juste être la norme de facto.

  2. logique: nommer vos packages en fonction de leur identité de domaine métier et placer toutes les classes liées à cette tranche verticale de fonctionnalités dans ce package.

    Je n'ai jamais vu ou entendu parler de cela, comme je l'ai mentionné auparavant, mais cela a beaucoup de sens pour moi.

    1. J'ai tendance à aborder les systèmes verticalement plutôt qu'horizontalement. Je veux entrer et développer le système de traitement des commandes, pas la couche d'accès aux données. De toute évidence, il y a de fortes chances que je touche la couche d'accès aux données dans le développement de ce système, mais le fait est que je ne pense pas de cette façon. Ce que cela signifie, bien sûr, c'est que lorsque je reçois un ordre de modification ou que je souhaite implémenter une nouvelle fonctionnalité, ce serait bien de ne pas avoir à aller pêcher dans un tas de packages afin de trouver toutes les classes connexes. Au lieu de cela, je regarde juste dans le paquet X parce que ce que je fais a à voir avec X.

    2. Du point de vue du développement, je considère qu'il est très avantageux que vos packages documentent votre domaine d'activité plutôt que votre architecture. J'ai l'impression que le domaine est presque toujours la partie du système qui est plus difficile à maîtriser alors que l'architecture du système, en particulier à ce stade, devient presque banale dans sa mise en œuvre. Le fait que je puisse arriver à un système avec ce type de convention de dénomination et instantanément à partir de la dénomination des packages sait qu'il traite des commandes, des clients, des entreprises, des produits, etc. semble très pratique.

    3. Il semble que cela vous permettrait de tirer un meilleur parti des modificateurs d'accès de Java. Cela vous permet de définir des interfaces beaucoup plus proprement dans les sous-systèmes plutôt que dans les couches du système. Donc, si vous avez un sous-système de commandes que vous souhaitez conserver de manière transparente, vous ne pouvez en théorie rien faire savoir qu'il est persistant en n'ayant pas à créer d'interfaces publiques avec ses classes de persistance dans la couche dao et à la place empaqueter la classe dao dans avec seulement les classes dont il traite. Évidemment, si vous vouliez exposer cette fonctionnalité, vous pourriez lui fournir une interface ou la rendre publique. Il semble que vous perdiez beaucoup de cela en ayant une tranche verticale des fonctionnalités de votre système répartie sur plusieurs packages.

    4. Je suppose qu'un inconvénient que je peux voir est que cela rend un peu plus difficile l'extraction des couches. Au lieu de simplement supprimer ou renommer un package, puis d'en déposer un nouveau avec une autre technologie, vous devez entrer et modifier toutes les classes de tous les packages. Cependant, je ne vois pas que c'est un gros problème. Cela peut être dû à un manque d'expérience, mais je dois imaginer que le nombre de fois où vous échangez des technologies pâlit par rapport au nombre de fois où vous entrez et modifiez des tranches de fonctionnalités verticales dans votre système.

Je suppose que la question vous serait alors posée, comment nommez-vous vos colis et pourquoi? Veuillez comprendre que je ne pense pas nécessairement que je suis tombé sur l'oie dorée ou quelque chose ici. Je suis assez nouveau dans tout cela avec une expérience principalement universitaire. Cependant, je ne peux pas repérer les trous dans mon raisonnement, alors j'espère que vous pourrez tous le faire pour pouvoir continuer.

88
Tim Visher

Pour la conception de packages, je divise d'abord par couche, puis par d'autres fonctionnalités.

Il existe quelques règles supplémentaires:

  1. les calques sont empilés du plus général (en bas) au plus spécifique (en haut)
  2. chaque couche a une interface publique (abstraction)
  3. une couche ne peut dépendre que de l'interface publique d'une autre couche (encapsulation)
  4. une couche ne peut dépendre que de couches plus générales (dépendances de haut en bas)
  5. une couche dépend de préférence de la couche directement en dessous

Ainsi, pour une application Web par exemple, vous pouvez avoir les couches suivantes dans votre niveau d'application (de haut en bas):

  • couche de présentation: génère l'interface utilisateur qui sera affichée dans le niveau client
  • couche application: contient une logique spécifique à une application, avec état
  • couche service: regroupe les fonctionnalités par domaine, sans état
  • couche d'intégration: donne accès au niveau backend (db, jms, email, ...)

Pour la disposition du package résultant, voici quelques règles supplémentaires:

  • la racine de chaque nom de package est <prefix.company>.<appname>.<layer>
  • l'interface d'une couche est encore divisée par fonctionnalité: <root>.<logic>
  • l'implémentation privée d'une couche est précédée de private: <root>.private

Voici un exemple de disposition.

La couche de présentation est divisée par la technologie d'affichage et, éventuellement, par (groupes) d'applications.

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

La couche d'application est divisée en cas d'utilisation.

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

La couche de service est divisée en domaines métier, influencés par la logique du domaine dans un niveau backend.

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

La couche d'intégration est divisée en "technologies" et objets d'accès.

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

Les avantages de la séparation de packages comme celui-ci sont qu'il est plus facile de gérer la complexité et augmente la testabilité et la réutilisabilité. Bien que cela semble être beaucoup de frais généraux, d'après mon expérience, cela vient en fait très naturellement et tous ceux qui travaillent sur cette structure (ou similaire) la ramassent en quelques jours.

Pourquoi est-ce que je pense que l'approche verticale n'est pas si bonne?

Dans le modèle en couches, plusieurs modules de haut niveau différents peuvent utiliser le même module de niveau inférieur. Par exemple: vous pouvez créer plusieurs vues pour la même application, plusieurs applications peuvent utiliser le même service, plusieurs services peuvent utiliser la même passerelle. L'astuce ici est que lorsque vous vous déplacez à travers les couches, le niveau de fonctionnalité change. Les modules dans des couches plus spécifiques ne mappent pas 1-1 sur les modules de la couche plus générale, car les niveaux de fonctionnalité qu'ils expriment ne mappent pas 1-1.

Lorsque vous utilisez l'approche verticale pour la conception de packages, c'est-à-dire que vous divisez d'abord par fonctionnalité, vous forcez ensuite tous les blocs de construction avec niveaux de fonctionnalité dans la même `` veste de fonctionnalité ''. Vous pouvez concevoir vos modules généraux pour le plus spécifique. Mais cela viole le principe important selon lequel la couche plus générale ne devrait pas connaître les couches plus spécifiques. La couche service, par exemple, ne doit pas être modélisée d'après les concepts de la couche application.

33
eljenso

Je me retrouve avec l'oncle Bob principes de conception des packages . En bref, les classes qui doivent être réutilisées et modifiées ensemble (pour la même raison, par exemple un changement de dépendance ou un changement de framework) doivent être placées dans le même package. OMI, la panne fonctionnelle aurait plus de chances d'atteindre ces objectifs que la panne verticale/métier dans la plupart des applications.

Par exemple, une tranche horizontale d'objets de domaine peut être réutilisée par différents types de frontaux ou même d'applications et une tranche horizontale du frontal web est susceptible de changer ensemble lorsque la structure Web sous-jacente doit être modifiée. D'un autre côté, il est facile d'imaginer l'effet d'entraînement de ces changements sur de nombreux packages si des classes de différents domaines fonctionnels sont regroupées dans ces packages.

De toute évidence, tous les types de logiciels ne sont pas identiques et la répartition verticale peut avoir un sens (en termes de réalisation des objectifs de réutilisabilité et de fermeture au changement) dans certains projets.

18
Buu Nguyen

Il y a généralement les deux niveaux de division présents. Du haut, il y a des unités de déploiement. Ceux-ci sont nommés "logiquement" (selon vos termes, pensez aux fonctionnalités d'Eclipse). À l'intérieur de l'unité de déploiement, vous avez une division fonctionnelle des packages (pensez aux plugins Eclipse).

Par exemple, la fonction est com.feature, et il se compose de com.feature.client, com.feature.core et com.feature.ui plugins. À l'intérieur des plugins, j'ai très peu de division avec les autres packages, bien que ce ne soit pas inhabituel aussi.

Mise à jour: Btw, il y a un grand discours de Juergen Hoeller sur l'organisation du code chez InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen est l'un des architectes de Spring et en sait beaucoup sur ce genre de choses.

5
Peter Štibraný

La plupart des projets Java sur lesquels j'ai travaillé découpent les packages Java en premier), puis logiquement.

Habituellement, les pièces sont suffisamment grandes pour être divisées en artefacts de construction distincts, où vous pouvez mettre les fonctionnalités de base dans un bocal, les API dans un autre, les éléments Web frontend dans un fichier de guerre, etc.

4
Ben Hardy

Les packages doivent être compilés et distribués comme une unité. Lorsque l'on considère quelles classes appartiennent à un package, l'un des critères clés est ses dépendances. De quels autres packages (y compris les bibliothèques tierces) dépend cette classe. Un système bien organisé regroupera des classes avec des dépendances similaires dans un package. Cela limite l'impact d'un changement dans une bibliothèque, car seuls quelques packages bien définis en dépendent.

Il semble que votre système vertical et logique ait tendance à "étaler" les dépendances sur la plupart des packages. Autrement dit, si chaque fonctionnalité est présentée sous forme de tranche verticale, chaque package dépendra de chaque bibliothèque tierce que vous utilisez. Toute modification d'une bibliothèque est susceptible de se répercuter sur l'ensemble de votre système.

3
erickson

Personnellement, je préfère regrouper les cours de manière logique, puis inclure un sous-package pour chaque participation fonctionnelle.

Objectifs de l'emballage

Les packages consistent après tout à regrouper les choses - l'idée que les classes liées vivent proches les unes des autres. S'ils vivent dans le même package, ils peuvent profiter du package privé pour limiter la visibilité. Le problème est de regrouper tous vos éléments de vue et de persistance dans un seul package, ce qui peut entraîner le mélange de nombreuses classes dans un seul package. La prochaine chose sensée à faire est donc de créer la vue, la persistance, d'utiliser les sous-packages et les classes de refactorisation en conséquence. La portée privée des packages et des sous-packages malheureusement sous-protégés ne prend pas en charge le concept du package et du sous-package actuels, car cela aiderait à appliquer ces règles de visibilité.

Je vois maintenant la valeur de la séparation via la fonctionnalité, car quelle valeur est là pour regrouper toutes les choses liées à la vue. Les éléments de cette stratégie de dénomination sont déconnectés de certaines classes dans la vue tandis que d'autres sont persistantes, etc.

Un exemple de ma structure d'emballage logique

À des fins d'illustration, nommez deux modules - utilisez mal le module de nom comme concept qui regroupe les classes sous une branche particulière d'un arbre pacckage.

Les avantages

Un client utilisant Banana.store.BananaStore n'est exposé qu'aux fonctionnalités que nous souhaitons mettre à disposition. La version hibernate est un détail d'implémentation dont ils n'ont pas besoin d'être conscients et ne doivent pas voir ces classes car elles ajoutent de l'encombrement aux opérations de stockage.

Autres avantages logiques et fonctionnels

Plus on monte vers la racine, plus la portée s'élargit et les choses appartenant à un paquet commencent à présenter de plus en plus de dépendances sur les choses appartenant à ses modules. Si l'on devait examiner par exemple le module "banane", la plupart des dépendances se limiteraient à l'intérieur de ce module. En fait, la plupart des aides sous "banane" ne seraient pas référencées du tout en dehors de cette portée de package.

Pourquoi la fonctionnalité?

Quelle valeur peut-on obtenir en regroupant les choses en fonction des fonctionnalités. La plupart des classes dans un tel cas sont indépendantes les unes des autres avec peu ou pas besoin de tirer parti des méthodes ou classes privées du package. Les refactoriser ainsi dans leurs propres sous-paquets gagne peu mais aide à réduire l'encombrement.

Modifications apportées par le développeur au système

Lorsque les développeurs sont chargés d'apporter des modifications un peu plus que triviales, il semble stupide qu'ils aient potentiellement des modifications qui incluent des fichiers de toutes les zones de l'arborescence des packages. Avec l'approche structurée logique, leurs modifications sont plus locales dans la même partie de l'arborescence des packages, ce qui semble juste.

3
mP.

Les approches fonctionnelle (architecturale) et logique (caractéristique) de l'emballage ont leur place. De nombreux exemples d'applications (ceux trouvés dans les manuels, etc.) suivent l'approche fonctionnelle consistant à placer la présentation, les services commerciaux, la cartographie des données et d'autres couches architecturales dans des packages séparés. Dans les exemples d'applications, chaque package n'a souvent que quelques-uns ou une seule classe.

Cette approche initiale est bonne car un exemple artificiel sert souvent à: 1) cartographier conceptuellement l'architecture du cadre présenté, 2) le faire avec un seul objectif logique (par exemple, ajouter/supprimer/mettre à jour/supprimer des animaux de compagnie d'une clinique) . Le problème est que de nombreux lecteurs considèrent cela comme une norme sans limites.

À mesure qu'une application "métier" se développe pour inclure de plus en plus de fonctionnalités, suivre l'approche fonctionnelle devient un fardeau. Bien que je sache où chercher les types basés sur la couche d'architecture (par exemple, les contrôleurs Web sous un package "web" ou "ui", etc.), développer un seul logique = la fonctionnalité commence à nécessiter des allers-retours entre de nombreux packages. C'est lourd, à tout le moins, mais c'est pire que ça.

Étant donné que les types logiquement liés ne sont pas regroupés, l'API est trop médiatisée; l'interaction entre les types logiquement liés est forcée d'être "publique" afin que les types puissent importer et interagir les uns avec les autres (la possibilité de réduire la visibilité par défaut/package est perdue).

Si je construis une bibliothèque de framework, mes packages suivront certainement une approche de packaging fonctionnel/architectural. Mes clients API pourraient même apprécier que leurs instructions d'importation contiennent un package intuitif nommé d'après l'architecture.

Inversement, lors de la création d'une application métier, je regrouperai par fonctionnalité. Je n'ai aucun problème à placer Widget, WidgetService et WidgetController dans le même package " com.myorg.widget.", puis à profiter de la visibilité par défaut (et d'avoir moins d'instructions d'importation ainsi que des inter -dépendances de paquet).

Il existe cependant des cas croisés. Si mon WidgetService est utilisé par de nombreux domaines logiques (fonctionnalités), je pourrais créer un package " com.myorg.common.service.". Il y a aussi de bonnes chances que je crée des classes avec l'intention d'être réutilisable à travers les fonctionnalités et que je me retrouve avec des packages tels que " com.myorg.common.ui.helpers." et "= com.myorg.common.util. ". Je peux même finir par déplacer toutes ces classes "communes" ultérieures dans un projet distinct et les inclure dans mon application métier en tant que dépendance myorg-commons.jar.

3
i3ensays

Je suis totalement suivi et propose l'organisation logique ("par fonctionnalité")! Un package doit suivre le concept d'un "module" aussi étroitement que possible. L'organisation fonctionnelle peut répartir un module sur un projet, entraînant moins d'encapsulation et sujette à des changements dans les détails d'implémentation.

Prenons un plugin Eclipse par exemple: mettre toutes les vues ou actions dans un package serait un gâchis. Au lieu de cela, chaque composant d'une fonctionnalité doit aller dans le package de la fonctionnalité, ou s'il y en a plusieurs, dans des sous-packages (featureA.handlers, featureA.preferences etc.)

Bien sûr, le problème réside dans le système de package hiérarchique (qui, entre autres Java has), ce qui rend le traitement des problèmes orthogonaux impossible ou du moins très difficile - bien qu'ils se produisent partout!

2
thSoft

J'essaie de concevoir des structures de package de telle manière que si je dessinais un graphique de dépendance, il serait facile de suivre et d'utiliser un modèle cohérent, avec le moins de références circulaires possible.

Pour moi, c'est beaucoup plus facile à maintenir et à visualiser dans un système de dénomination vertical plutôt qu'horizontal. si component1.display a une référence à component2.dataaccess, cela déclenche plus de cloches d'avertissement que si display.component1 a une référence à dataaccess. composante2.

Bien sûr, les composants partagés par les deux vont dans leur propre package.

2
levand

Cela dépend de la granularité de vos processus logiques?

S'ils sont autonomes, vous avez souvent un nouveau projet pour eux dans le contrôle de code source, plutôt qu'un nouveau package.

Le projet sur lequel je travaille en ce moment s'oriente vers le fractionnement logique, il y a un package pour l'aspect jython, un package pour un moteur de règles, des packages pour foo, bar, binglewozzle, etc./writers pour chaque module de ce package, plutôt que d'avoir un package XML (ce que j'ai fait précédemment), bien qu'il y aura toujours un package XML de base où la logique partagée ira. Une raison à cela est cependant qu'il peut être extensible (plugins) et donc chaque plugin devra également définir son code XML (ou base de données, etc.), donc la centralisation pourrait introduire des problèmes plus tard.

En fin de compte, il semble que cela semble le plus judicieux pour le projet particulier. Je pense cependant qu'il est facile de regrouper le produit selon le schéma en couches typique d'un projet. Vous vous retrouverez avec un mélange de packaging logique et fonctionnel.

Ce qui est nécessaire, ce sont des espaces de noms balisés. Un analyseur XML pour certaines fonctionnalités Jython pourrait être balisé à la fois Jython et XML, plutôt que d'avoir à choisir l'un ou l'autre.

Ou peut-être que je vacille.

2
JeeBee

Je choisirais personnellement la dénomination fonctionnelle. La courte raison: cela évite la duplication de code ou le cauchemar de la dépendance.

Permettez-moi de développer un peu. Que se passe-t-il lorsque vous utilisez un fichier jar externe, avec sa propre arborescence de packages? Vous importez effectivement le code (compilé) dans votre projet, et avec lui une arborescence de packages (fonctionnellement séparés). Serait-il judicieux d'utiliser les deux conventions de dénomination en même temps? Non, à moins que cela ne vous soit caché. Et c'est le cas, si votre projet est suffisamment petit et comporte un seul composant. Mais si vous avez plusieurs unités logiques, vous ne voudrez probablement pas réimplémenter, disons, le module de chargement de fichiers de données. Vous souhaitez le partager entre des unités logiques, ne pas avoir de dépendances artificielles entre des unités logiquement indépendantes et ne pas avoir à choisir l'unité dans laquelle vous allez mettre cet outil partagé particulier.

Je suppose que c'est pourquoi la dénomination fonctionnelle est la plus utilisée dans les projets qui atteignent, ou sont censés atteindre, une certaine taille, et la dénomination logique est utilisée dans les conventions de dénomination de classe pour garder une trace du rôle spécifique, le cas échéant de chaque classe dans un paquet.

J'essaierai de répondre plus précisément à chacun de vos points sur la dénomination logique.

  1. Si vous devez aller pêcher dans les anciennes classes pour modifier les fonctionnalités lorsque vous avez un changement de plan, c'est un signe de mauvaise abstraction: vous devez construire des classes qui fournissent une fonctionnalité bien définie, définissable en une courte phrase. Seules quelques classes de haut niveau devraient assembler tout cela pour refléter votre intelligence d'affaires. De cette façon, vous pourrez réutiliser plus de code, avoir une maintenance plus facile, une documentation plus claire et moins de problèmes de dépendance.

  2. Cela dépend principalement de la façon dont vous envisagez votre projet. Certainement, la vue logique et fonctionnelle est orthogonale. Donc, si vous utilisez une convention de dénomination, vous devez appliquer l'autre aux noms de classe afin de garder un certain ordre, ou dériver d'une convention de dénomination à une autre à une certaine profondeur.

  3. Les modificateurs d'accès sont un bon moyen de permettre à d'autres classes qui comprennent votre traitement d'accéder aux entrailles de votre classe. Une relation logique ne signifie pas une compréhension des contraintes algorithmiques ou de concurrence. Fonctionnel peut, mais ce n'est pas le cas. Je suis très las des modificateurs d'accès autres que publics et privés, car ils cachent souvent un manque d'architecture appropriée et d'abstraction de classe.

  4. Dans les grands projets commerciaux, l'évolution des technologies se produit plus souvent que vous ne le pensez. Par exemple, j'ai déjà dû changer 3 fois l'analyseur XML, 2 fois la technologie de mise en cache et 2 fois le logiciel de géolocalisation. Heureusement que j'avais caché tous les détails dans un paquet dédié ...

1
Varkhan

C'est une expérience intéressante de ne pas utiliser du tout les packages (à l'exception du package racine.)

La question qui se pose alors est de savoir quand et pourquoi il est logique d'introduire des packages. Vraisemblablement, la réponse sera différente de ce que vous auriez répondu au début du projet.

Je suppose que votre question se pose du tout, car les packages sont comme des catégories et il est parfois difficile de décider pour l'un ou pour l'autre. Parfois, les balises seraient plus appréciées pour communiquer qu'une classe est utilisable dans de nombreux contextes.

1
user53682

D'un point de vue purement pratique, les constructions de visibilité de Java permettent aux classes du même package d'accéder aux méthodes et propriétés avec une visibilité protected et default, ainsi que celles public. L'utilisation de méthodes non publiques à partir d'une couche complètement différente du code serait certainement une grosse odeur de code. J'ai donc tendance à mettre les classes de la même couche dans le même package.

Je n'utilise pas souvent ces méthodes protégées ou par défaut ailleurs - sauf peut-être dans les tests unitaires de la classe - mais quand je le fais, c'est toujours d'une classe sur la même couche

0
Bill Michell

Ça dépend. Dans mon métier, nous répartissons parfois les packages par fonctions (accès aux données, analytique) ou par classe d'actifs (crédit, actions, taux d'intérêt). Sélectionnez simplement la structure qui convient le mieux à votre équipe.

0
quant_dev