J'y ai pensé il y a un moment et cela a récemment refait surface alors que ma boutique fait sa première vraie application Web Java.
En guise d'introduction, je vois deux stratégies principales de dénomination des packages. (Pour être clair, je ne fais pas référence à toute la partie 'domain.company.project', 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:
Fonctionnel: Nommer vos packages en fonction de leur fonction architecturale plutôt que de leur identité en fonction du domaine métier. Un autre terme pour cela pourrait être la dénomination selon «couche». Ainsi, vous auriez un package * .ui et un package * .domain et un package * .orm. Vos emballages sont des tranches horizontales plutôt que verticales.
C'est beaucoup plus courant que la dénomination logique. En fait, je ne crois pas avoir jamais vu ou entendu parler d'un projet qui fait cela. Cela me rend bien sûr méfiant (un peu comme si vous pensiez que vous avez trouvé une solution à un problème de NP) car je ne suis pas très intelligent et je suppose que tout le monde doit avoir de bonnes raisons de le faire comme il le fait. D'un autre côté, je ne suis pas opposé au fait que les gens manquent simplement l'éléphant dans la pièce et je n'ai jamais entendu d'argument réel pour nommer les paquets de cette façon. Cela semble être la norme de facto.
Logique: Nommez vos packages en fonction de leur identité de domaine professionnel et placez chaque classe liée à cette tranche verticale de fonctionnalités dans ce package.
Je n'ai jamais vu ou entendu parler de cela, comme je l'ai déjà mentionné, mais cela a beaucoup de sens pour moi.
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. Évidemment, il y a de bonnes 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 veux implémenter une nouvelle fonctionnalité, ce serait bien de ne pas avoir à pêcher dans un tas de packages afin de trouver toutes les classes associées. Au lieu de cela, je regarde juste dans le package X parce que ce que je fais a à voir avec X.
Du point de vue du développement, je vois comme une victoire majeure 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 la plus difficile à gérer, car 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 paquets sache qu'il traite des commandes, des clients, des entreprises, des produits, etc. semble sacrément pratique.
Il semble que cela vous permettrait de tirer un bien meilleur parti des modificateurs d'accès de Java. Cela vous permet de définir beaucoup plus clairement les interfaces en sous-systèmes plutôt qu'en couches du système. Donc, si vous avez un sous-système de commandes que vous voulez être persistant de manière transparente, vous pourriez en théorie ne jamais laisser rien d'autre savoir qu'il est persistant en n'ayant pas à créer d'interfaces publiques vers ses classes de persistance dans la couche dao et à la place de conditionner la classe dao dans avec seulement les classes dont il s'occupe. Évidemment, si vous souhaitez exposer cette fonctionnalité, vous pouvez lui fournir une interface ou la rendre publique. Il semble que vous en perdiez beaucoup en ayant une tranche verticale des fonctionnalités de votre système réparties sur plusieurs packages.
Je suppose qu'un inconvénient que je peux voir est que cela rend l'extraction des couches un peu plus difficile. Au lieu de simplement supprimer ou renommer un package, puis d'en déposer un nouveau avec une technologie alternative, vous devez entrer et modifier toutes les classes de tous les packages. Cependant, je ne vois pas que ce soit 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 est pâle par rapport au nombre de fois que vous entrez et modifiez des tranches de fonctionnalités verticales dans votre système.
Donc, je suppose que la question vous serait 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 d'or ou quelque chose ici. Je suis assez nouveau dans tout cela avec une expérience principalement académique. Cependant, je ne peux pas repérer les trous dans mon raisonnement alors j'espère que vous le pourrez tous pour que je puisse passer à autre chose.
la source
Réponses:
Pour la conception des packages, je divise d'abord par couche, puis par une autre fonctionnalité.
Il existe quelques règles supplémentaires:
Ainsi, pour une application Web par exemple, vous pouvez avoir les couches suivantes dans votre niveau d'application (de haut en bas):
Pour la mise en page du package résultant, voici quelques règles supplémentaires:
<prefix.company>.<appname>.<layer>
<root>.<logic>
<root>.private
Voici un exemple de mise en page.
La couche de présentation est divisée par la technologie d'affichage et éventuellement par des (groupes) d'applications.
La couche d'application est divisée en cas d'utilisation.
La couche de service est divisée en domaines métier, influencés par la logique du domaine dans un niveau backend.
La couche d'intégration est divisée en «technologies» et objets d'accès.
L'avantage de séparer des packages comme celui-ci est 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 est en fait très naturel et tout le monde travaillant sur cette structure (ou similaire) la récupère 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 différents 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 la plus générale ne doit 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.
la source
Je me retrouve fidèle aux principes de conception d'emballage d' oncle Bob . En bref, les classes qui doivent être réutilisées ensemble et modifiées ensemble (pour la même raison, par exemple un changement de dépendance ou un changement de cadre) doivent être placées dans le même package. OMI, la ventilation fonctionnelle aurait de meilleures chances d'atteindre ces objectifs que la ventilation verticale / spécifique à l'entreprise 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 front-end Web est susceptible de changer ensemble lorsque le cadre Web sous-jacent doit être modifié. 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 ventilation verticale peut avoir un sens (en termes de réalisation des objectifs de réutilisabilité et de fermeture au changement) dans certains projets.
la source
Les deux niveaux de division sont généralement présents. Du haut, il y a des unités de déploiement. Ceux-ci sont nommés «logiquement» (dans 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, fonctionnalité est
com.feature
, et se compose decom.feature.client
,com.feature.core
etcom.feature.ui
plugins. À l'intérieur des plugins, j'ai très peu de divisions avec d'autres packages, même si ce n'est pas inhabituel non plus.Mise à jour: Btw, Juergen Hoeller parle très bien de l'organisation du code sur InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen est l'un des architectes de Spring et en sait beaucoup sur ce sujet.
la source
La plupart des projets java sur lesquels j'ai travaillé découpent d'abord les packages java de manière fonctionnelle, puis logiquement.
Habituellement, les parties sont suffisamment volumineuses pour être divisées en artefacts de construction séparés, où vous pouvez mettre des fonctionnalités de base dans un bocal, des API dans un autre, des éléments de l'interface Web dans un fichier de guerre, etc.
la source
Les packages doivent être compilés et distribués sous forme d'unité. Lors de l'examen des classes appartenant à 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 les classes avec des dépendances similaires dans un package. Cela limite l'impact d'un changement dans une bibliothèque, car seuls quelques paquets bien définis en dépendront.
Il semble que votre système vertical logique pourrait avoir tendance à «étaler» les dépendances dans la plupart des packages. Autrement dit, si chaque fonctionnalité est empaquetée sous forme de tranche verticale, chaque paquet dépendra de chaque bibliothèque tierce que vous utilisez. Toute modification apportée à une bibliothèque est susceptible de se répercuter sur l'ensemble de votre système.
la source
Personnellement, je préfère regrouper les classes logiquement puis à l'intérieur qui incluent un sous-paquet pour chaque participation fonctionnelle.
Objectifs de l'emballage
Les packages consistent après tout à regrouper des choses - l'idée étant que les classes liées vivent près les unes des autres. S'ils vivent dans le même package, ils peuvent profiter du package private pour limiter la visibilité. Le problème est que le regroupement de toutes vos vues et de votre persistance dans un seul package peut entraîner le mélange de nombreuses classes dans un seul package. La prochaine chose sensée à faire est donc de créer des vues, de la persistance, des sous-packages util et des classes de refactorisation en conséquence. Malheureusement, la portée privée des packages protégés et des packages ne prend pas en charge le concept du package et du sous-package actuels, car cela faciliterait l'application de ces règles de visibilité.
Je vois maintenant la valeur de la séparation via la fonctionnalité parce que la 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 en persistance et ainsi de suite.
Un exemple de ma structure d'emballage logique
À des fins d'illustration, nommons deux modules - utilisez le module de nom comme un concept qui regroupe des classes sous une branche particulière d'un arbre de pacckage.
apple.model apple.store banana.model banana.storeAvantages
Un client utilisant le Banana.store.BananaStore n'est exposé qu'aux fonctionnalités que nous souhaitons rendre disponibles. La version hibernate est un détail d'implémentation dont ils n'ont pas besoin d'être conscients et qu'ils ne devraient pas voir ces classes car elles ajoutent du fouillis aux opérations de stockage.
Autres avantages logiques v fonctionnels
Plus on monte vers la racine, plus la portée devient large et les éléments appartenant à un même paquet commencent à présenter de plus en plus de dépendances sur des éléments appartenant à leurs modules. Si l'on examinait par exemple le module "banane", la plupart des dépendances seraient limitées à l'intérieur de ce module. En fait, la plupart des assistants sous "banane" ne seraient pas du tout référencés en dehors de ce champ d'application.
Pourquoi la fonctionnalité?
Quelle valeur peut-on atteindre en regroupant les choses en fonction de la fonctionnalité. 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 des classes privées du package. Leur refactorisation dans leurs propres sous-packages gagne peu mais contribue à 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 ridicule 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 changements sont plus locaux dans la même partie de l'arborescence des packages, ce qui semble juste.
la source
Les approches fonctionnelles (architecturales) et logiques (caractéristiques) 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, le mappage de données et d'autres couches architecturales dans des packages séparés. Dans les exemples d'applications, chaque package ne contient souvent que quelques classes ou une seule.
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.
Au fur et à 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.), le développement d'une seule fonctionnalité logique commence à nécessiter des va-et-vient entre de nombreux packages. C'est à tout le moins encombrant, mais c'est pire que ça.
Puisque les types logiquement liés ne sont pas regroupés, l'API est trop médiatisée; l'interaction entre les types liés logiquement est forcée d'être «publique» afin que les types puissent importer et interagir les uns avec les autres (la capacité de réduire la visibilité par défaut / package est perdue).
Si je construis une bibliothèque de cadres, mes packages suivront certainement une approche de packaging fonctionnelle / architecturale. Mes consommateurs d'API pourraient même apprécier que leurs déclarations d'importation contiennent un package intuitif nommé d'après l'architecture.
Inversement, lors de la création d'une application métier, je vais créer un package 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 de déclarations d'importation ainsi que des dépendances inter-packages).
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éutilisables dans toutes 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 "courantes" ultérieures dans un projet séparé et les inclure dans mon application métier en tant que dépendance myorg-commons.jar.
la source
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 suis en ce moment se trompe 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. Je cherche à avoir les analyseurs XML spécifiques / writers pour chaque module dans ce package, plutôt que d'avoir un package XML (ce que j'ai fait précédemment), bien qu'il y ait toujours un package XML de base où la logique partagée va. Cependant, une raison à cela est qu'il peut être extensible (plugins) et que chaque plugin devra donc également définir son code XML (ou base de données, etc.), donc la centralisation pourrait poser des problèmes plus tard.
En fin de compte, cela semble être la façon dont cela semble le plus judicieux pour le projet particulier. Cependant, je pense qu'il est facile de créer un package le long du diagramme en couches typique d'un projet. Vous vous retrouverez avec un mélange d'emballages logiques et fonctionnels.
Ce qu'il faut, ce sont des espaces de noms balisés. Un analyseur XML pour certaines fonctionnalités Jython pourrait être étiqueté à la fois Jython et XML, plutôt que d'avoir à choisir l'un ou l'autre.
Ou peut-être que je vacille.
la source
J'essaie de concevoir des structures de packages de telle manière que si je devais dessiner un graphe de dépendances, 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. composant2.
Bien sûr, les composants partagés par les deux vont dans leur propre package.
la source
Je suis totalement et propose l'organisation logique ("par fonctionnalité")! Un package doit suivre le concept de "module" aussi étroitement que possible. L'organisation fonctionnelle peut répartir un module sur un projet, ce qui entraîne moins d'encapsulation et est sujette à des changements dans les détails de mise en œuvre.
Prenons un plugin Eclipse par exemple: mettre toutes les vues ou actions dans un seul paquet 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 beaucoup, dans des sous-packages (featureA.handlers, featureA.preferences, etc.)
Bien sûr, le problème réside dans le système de paquets hiérarchiques (dont Java a entre autres), qui rend la gestion des préoccupations orthogonales impossible ou du moins très difficile - bien qu'elles se produisent partout!
la source
J'irais personnellement pour la dénomination fonctionnelle. La raison courte: cela évite la duplication de code ou le cauchemar des dépendances.
Laissez-moi élaborer un peu. Que se passe-t-il lorsque vous utilisez un fichier jar externe, avec sa propre arborescence de packages? Vous importez efficacement le code (compilé) dans votre projet, et avec lui une arborescence de packages (fonctionnellement séparée). Serait-il judicieux d'utiliser les deux conventions de dénomination en même temps? Non, à moins que cela ne vous ait été caché. Et c'est le cas, si votre projet est suffisamment petit et n'a qu'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 voulez le partager entre des unités logiques, ne pas avoir de dépendances artificielles entre des unités logiquement non liées et ne pas avoir à choisir dans quelle unité vous allez placer 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.
Je vais essayer de répondre plus précisément à chacun de vos points sur la dénomination logique.
Si vous devez aller pêcher dans d'anciennes classes pour modifier des fonctionnalités lors d'un changement de plan, c'est le signe d'une 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 rassembler tout cela pour refléter votre intelligence d'affaires. De cette façon, vous serez en mesure de réutiliser plus de code, d'avoir une maintenance plus facile, une documentation plus claire et moins de problèmes de dépendance.
Cela dépend principalement de la façon dont vous réalisez votre projet. Décidément, 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 passer d'une convention de dénomination à une autre à une certaine profondeur.
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, bien que ce ne soit pas le cas. Je suis très fatigué 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.
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 granuleux dans un paquet dédié ...
la source
utils
package. Ensuite, toutes les classes qui en ont besoin peuvent simplement dépendre de ce package.C'est une expérience intéressante de ne pas utiliser du tout de paquet (sauf pour le paquet racine).
La question qui se pose alors est de savoir quand et pourquoi il est judicieux 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'une ou l'autre. Parfois, les balises seraient plus appréciées pour indiquer qu'une classe est utilisable dans de nombreux contextes.
la source
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
protected
etdefault
visibilité, ainsi quepublic
celles. Utiliser des 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 de la même couche
la source
Ça dépend. Dans mon métier, nous découpons 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.
la source
D'après mon expérience, la réutilisation crée plus de problèmes que la résolution. Avec les processeurs et la mémoire les plus récents et bon marché, je préférerais la duplication du code plutôt que de l'intégrer étroitement afin de le réutiliser.
la source