web-dev-qa-db-ja.com

Javaプロジェクトでのパッケージの命名にはどの戦略を使用しますか?

私はこれについて少し前に考えました、そして私の店がその最初の本当のJava Webアプリ。

導入部として、2つの主要なパッケージ命名戦略があります。 (明確にするために、この「domain.company.project」部分全体を参照するのではなく、その下のパッケージ規則について話します。)とにかく、私が見るパッケージの命名規則は次のとおりです。

  1. 機能:ビジネスドメインに応じたIDではなく、機能に応じてパッケージに名前を付けます。これの別の用語は、「レイヤー」に基づいた名前付けです。したがって、*。uiパッケージと* .domainパッケージと* .ormパッケージがあります。パッケージは、垂直ではなく水平のスライスです。

    これは、論理命名よりもmuchより一般的です。実際、これを行うプロジェクトを見たことも聞いたこともないと思います。もちろん、これは私を不安にさせます(NP問題)の解決策を思いついたと考えるようなものです)。一方で、私は部屋で象を逃しただけの人々に反対していませんand聞いたことがないaこの方法でパッケージの命名を行う実際の引数forこれは事実上の標準のようです。

  2. 論理:ビジネスドメインIDに応じてパッケージに名前を付けるおよび機能の垂直スライスに関連するすべてのクラスをそのパッケージに配置します。

    前に述べたように、私はこれを見たり聞いたりしたことはありませんが、それは私にとって非常に理にかなっています。

    1. 私はシステムを水平よりも垂直にアプローチする傾向があります。データアクセス層ではなく、注文処理システムを開発します。明らかに、そのシステムの開発でデータアクセス層に触れる可能性は十分にありますが、ポイントはそのように考えていないことです。これが意味することは、もちろん、変更命令を受け取ったり、新しい機能を実装したい場合、関連するすべてのクラスを見つけるためにたくさんのパッケージを探し回る必要はないということです。代わりに、Xパッケージに注目します。これは、Xに関係しているからです。

    2. 開発の観点からは、アーキテクチャではなくビジネスドメインをパッケージに文書化することが大きなメリットだと考えています。ドメインはほとんどの場合システムの一部であり、システムのアーキテクチャは、特にこの時点では、実装においてほとんどありふれたものになっているように感じます。このタイプの命名規則でシステムにアクセスでき、パッケージの命名から即座にシステムにアクセスできるという事実は、それが注文、顧客、企業、製品などを扱うことを知っています。

    3. これにより、Javaのアクセス修飾子をはるかに活用できるように思われます。これにより、システムのレイヤーではなく、サブシステムへのインターフェイスをより明確に定義できます。したがって、透過的に永続化するordersサブシステムがある場合、daoレイヤーの永続クラスへのパブリックインターフェイスを作成する必要がなく、代わりにdaoクラスをパッケージ化することにより、理論的には永続的であることを他に決して知らせることはできませんそれが扱うクラスのみで。明らかに、ifこの機能を公開したい場合は、インターフェイスを提供するか、公開することができます。システムの機能の垂直スライスを複数のパッケージに分割することで、この多くを失うように思えます。

    4. 私が見ることができる1つの欠点は、レイヤーをリッピングするのが少し難しくなることだと思います。パッケージを単に削除または名前変更してから、代替テクノロジーを使用して新しいパッケージを所定の場所にドロップする代わりに、すべてのパッケージのすべてのクラスを変更する必要があります。しかし、これは大したことではないと思います。それは経験の不足かもしれませんが、システム内の垂直フィーチャスライスを編集する回数に比べて、テクノロジーを交換する回数は少ないと想像する必要があります。

それで、質問はあなたに出て行くと思います、あなたはどのようにあなたのパッケージに名前を付けますか?私は必ずしもここで金色のガチョウや何かにつまずいたとは思わないことを理解してください。私はほとんどすべてが学術的な経験を積んでいるのでかなり新しいです。しかし、私は推論に穴を見つけることができませんので、私はあなたがすべて私が先に進むことができるように願っています。

88
Tim Visher

パッケージ設計では、まずレイヤーで分割し、次に他の機能で分割します。

追加のルールがいくつかあります。

  1. レイヤーは、最も一般的な(下)から最も具体的な(上)に積み重ねられます
  2. 各レイヤーにはパブリックインターフェイス(抽象化)があります
  3. レイヤーは、別のレイヤーのパブリックインターフェイスにのみ依存できます(カプセル化)
  4. レイヤーは、より一般的なレイヤーにのみ依存できます(上から下への依存関係)
  5. 層は、その直下の層に依存することが好ましい

そのため、たとえばWebアプリケーションの場合、アプリケーション層に次のレイヤーを(上から下に)持つことができます。

  • プレゼンテーション層:クライアント層に表示されるUIを生成します
  • アプリケーション層:アプリケーション固有のロジックを含み、ステートフル
  • サービス層:ドメインごとに機能をグループ化、ステートレス
  • 統合層:バックエンド層(db、jms、電子メールなど)へのアクセスを提供します

結果のパッケージレイアウトの場合、次の追加ルールがあります。

  • すべてのパッケージ名のルートは<prefix.company>.<appname>.<layer>
  • レイヤーのインターフェースは、機能によってさらに分割されます:<root>.<logic>
  • レイヤーのプライベート実装には、プライベートの接頭辞が付きます:<root>.private

以下にレイアウトの例を示します。

プレゼンテーションレイヤーは、ビューテクノロジ、およびオプションでアプリケーション(のグループ)によって分割されます。

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

アプリケーション層はユースケースに分割されます。

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

サービスレイヤーはビジネスドメインに分割され、バックエンド層のドメインロジックの影響を受けます。

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

統合層は「技術」とアクセスオブジェクトに分かれています。

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
...

このようなパッケージを分離することの利点は、複雑さを管理しやすくなり、テスト性と再利用性が向上することです。多くのオーバーヘッドのように思えますが、私の経験では実際に非常に自然になり、この構造(または類似の)で作業している人は誰でも数日でそれを拾います。

垂直アプローチがそれほど良くないと思うのはなぜですか?

階層化モデルでは、いくつかの異なる高レベルモジュールが同じ低レベルモジュールを使用できます。たとえば、同じアプリケーションに対して複数のビューを構築でき、複数のアプリケーションが同じサービスを使用でき、複数のサービスが同じゲートウェイを使用できます。ここでのコツは、レイヤーを移動するときに機能のレベルが変わることです。より具体的なレイヤーのモジュールは、より一般的なレイヤーのモジュールに1-1をマップしません。これは、それらが表現する機能のレベルが1-1をマップしないためです。

パッケージ設計に垂直アプローチを使用する場合、つまり、最初に機能別に分割する場合、機能の異なるレベルを持つすべてのビルディングブロックを同じ「機能ジャケット」に強制します。より具体的なモジュール用に汎用モジュールを設計できます。しかし、これは、より一般的な層はより具体的な層について知るべきではないという重要な原則に違反しています。たとえば、サービス層は、アプリケーション層の概念に基づいてモデル化しないでください。

33
eljenso

ボブおじさんの パッケージデザインの原則 にこだわっています。要するに、一緒に再利用され、一緒に変更されるクラス(同じ理由、例えば依存関係の変更やフレームワークの変更)は、同じパッケージに入れる必要があります。 IMO、機能の内訳は、ほとんどのアプリケーションの垂直/ビジネス固有の内訳よりも、これらの目標を達成する可能性が高くなります。

たとえば、ドメインオブジェクトの水平スライスは、さまざまな種類のフロントエンドやアプリケーションで再利用できます。また、Webフロントエンドの水平スライスは、基盤となるWebフレームワークを変更する必要がある場合に一緒に変更される可能性があります。一方、さまざまな機能分野のクラスがそれらのパッケージにグループ化されている場合、多くのパッケージでこれらの変更の波及効果を想像するのは簡単です。

明らかに、すべての種類のソフトウェアが同じというわけではなく、特定のプロジェクトでは垂直方向の内訳が意味をなす場合があります(再利用性と変更可能性の目標を達成するという点で)。

18
Buu Nguyen

通常、両方のレベルの分割が存在します。上から、展開ユニットがあります。これらは「論理的に」命名されます(あなたの用語では、Eclipseの機能を考えてください)。デプロイメントユニットの内部には、パッケージの機能分割があります(Eclipseプラグインを考えてください)。

たとえば、機能はcom.feature、およびcom.feature.clientcom.feature.coreおよびcom.feature.uiプラグイン。プラグインの内部では、他のパッケージにほとんど分割されていませんが、それも珍しいことではありません。

更新:ところで、InfoQのコード編成についてJuergen Hoellerによる素晴らしい講演があります: http://www.infoq.com/presentations/code-organization-large-projects 。 JuergenはSpringのアーキテクトの一人であり、このことについて多くのことを知っています。

5

ほとんどのJava私が取り組んだプロジェクトでは、Javaパッケージを最初に機能的に、次に論理的にスライスしました。

通常、パーツは十分に大きいため、別々のビルドアーティファクトに分割されます。コア機能を1つのjarに、apiを別のjarに、Webフロントエンドのものをwarfileに、など。

4
Ben Hardy

パッケージはユニットとしてコンパイルおよび配布されます。パッケージに属するクラスを検討する場合、重要な基準の1つはその依存関係です。このクラスは、他のパッケージ(サードパーティライブラリを含む)に依存します。適切に構成されたシステムは、パッケージ内の類似した依存関係を持つクラスをクラスター化します。いくつかの明確に定義されたパッケージだけがそれに依存するため、これにより、1つのライブラリの変更の影響が制限されます。

論理的な垂直システムは、ほとんどのパッケージで依存関係を「スミア」する傾向があるようです。つまり、すべての機能が垂直スライスとしてパッケージ化されている場合、すべてのパッケージは使用するすべてのサードパーティライブラリに依存します。ライブラリへの変更は、システム全体に波及する可能性があります。

3
erickson

個人的には、クラスを論理的にグループ化し、その中に各機能参加のサブパッケージを含めることを好みます。

包装の目標

パッケージは結局のところ、物事を一緒にグループ化することに関するものです-関連するクラスであるという考えは、互いに近くに住んでいます。同じパッケージに住んでいる場合、可視性を制限するためにパッケージプライベートを利用できます。問題は、すべてのビューとパーシスタンスを1つのパッケージにまとめていることです。これにより、多くのクラスが1つのパッケージに混在することになります。したがって、次に行うべき賢明なことは、ビュー、永続性、utilサブパッケージを作成し、それに応じてクラスをリファクタリングすることです。残念ながら保護されたパッケージプライベートスコープは、現在のパッケージおよびサブパッケージの概念をサポートしていません。これは、このような可視性ルールの実施を支援するためです。

ビューに関連するすべてのものをグループ化するためにどのような価値があるのか​​、機能を介した分離の価値がわかりました。この命名戦略の要素は、ビュー内の一部のクラスと切り離され、他のクラスは永続化などされます。

論理的なパッケージ構造の例

説明のために、2つのモジュールに名前を付けます。名前モジュールは、パッケッジツリーの特定のブランチの下にクラスをグループ化する概念として使用します。

長所

Banana.store.BananaStoreを使用するクライアントは、利用可能にする機能のみに公開されます。 hibernateバージョンは実装の詳細であり、ストレージ操作に煩雑さを追加するため、これらのクラスを認識したり、これらのクラスを見たりする必要はありません。

その他の論理v機能の利点

ルートに行くほど範囲が広くなり、1つのパッケージに属するものが、他のモジュールに属するものにますます依存するようになります。たとえば「バナナ」モジュールを調べると、ほとんどの依存関係はそのモジュール内に限定されます。実際、「バナナ」の下のほとんどのヘルパーは、このパッケージの範囲外ではまったく参照されません。

機能する理由

機能に基づいて物事をひとまとめにすることで、どのような価値が達成されますか。このような場合のほとんどのクラスは互いに独立しており、パッケージのプライベートメソッドまたはクラスを利用する必要性はほとんど、またはまったくありません。それらを独自のサブパッケージにリファクタリングすることはほとんど効果がありませんが、混乱を減らすのに役立ちます。

システムに対する開発者の変更

開発者が些細な変更を行うように任務を課されたとき、パッケージツリーのすべての領域からのファイルを含む変更がある可能性があることは愚かに思えます。論理構造化アプローチを使用すると、パッケージツリーの同じ部分内での変更がよりローカルになります。

3
mP.

functional(アーキテクチャ)とlogical(機能)パッケージングへのアプローチには場所があります。多くのサンプルアプリケーション(テキストブックなどにあります)は、プレゼンテーション、ビジネスサービス、データマッピング、その他のアーキテクチャレイヤーを個別のパッケージに配置するという機能的なアプローチに従っています。サンプルアプリケーションでは、各パッケージに含まれるクラスはごくわずかまたは1つだけです。

考案された例は、1)提示されているフレームワークのアーキテクチャを概念的にマップする、2)単一の論理的な目的(クリニックからペットを追加/削除/更新/削除する)で行われることが多いため、この初期アプローチは問題ありません。 。問題は、多くの読者がこれを境界のない標準と見なしていることです。

「ビジネス」アプリケーションが拡張され、より多くの機能が含まれるようになると、functionalアプローチに従うことが負担になります。アーキテクチャレイヤーに基づいて型を探す場所(たとえば、「web」または「ui」パッケージの下のWebコントローラーなど)は知っていますが、単一のlogicalこの機能では、多くのパッケージ間を行き来する必要があります。これは少なくとも面倒ですが、それよりも悪いです。

論理的に関連する型は一緒にパッケージ化されないため、APIは過度に公開されています。論理的に関連するタイプ間の相互作用は強制的に「パブリック」にするため、タイプは相互にインポートして相互作用できます(デフォルト/パッケージの可視性を最小化する機能は失われます)。

フレームワークライブラリを構築している場合、必ず私のパッケージは機能/アーキテクチャパッケージングのアプローチに従います。私のAPI消費者は、インポートステートメントにアーキテクチャにちなんで名付けられた直感的なパッケージが含まれていることを評価するかもしれません。

逆に、ビジネスアプリケーションを構築するときは、機能ごとにパッケージ化します。 Widget、WidgetService、およびWidgetControllerをすべて同じ「com.myorg.widget。」パッケージに配置し、デフォルトの可視性を活用することに問題はありません(インポートステートメントとパッケージ間の依存関係が少なくなります)。

ただし、クロスオーバーの場合があります。 WidgetServiceが多くの論理ドメイン(機能)で使用されている場合、「com.myorg.common.service。」パッケージを作成できます。機能間で再利用できるように意図してクラスを作成し、「com.myorg.common.ui.helpers。」および「com.myorg.common.util。」。後でこれらの「共通」クラスをすべて別のプロジェクトに移動して、myorg-commons.jar依存関係としてビジネスアプリケーションに含めることもできます。

3
i3ensays

論理的な(「機能別」)組織を完全にフォローして提案します。パッケージは、「モジュール」の概念に可能な限り忠実に従う必要があります。機能組織は、モジュールをプロジェクトに分散させ、カプセル化を減らし、実装の詳細を変更する傾向があります。

たとえば、Eclipseプラグインを考えてみましょう。すべてのビューまたはアクションを1つのパッケージに入れるのは面倒です。代わりに、機能の各コンポーネントは機能のパッケージに移動する必要があります。多くの場合は、サブパッケージ(featureA.handlers、featureA.preferencesなど)に移動する必要があります

もちろん、問題は階層パッケージシステム(Javaが持っている))にあります。これにより、直交関係の処理が不可能になります。

2
thSoft

パッケージ構造は、依存関係グラフを作成する場合、循環参照をできるだけ少なくして、一貫したパターンをたやすく追跡して使用できるように設計します。

私にとって、これは、水平よりも垂直の命名システムで保守および視覚化する方がはるかに簡単です。 component1.displayにcomponent2.dataaccessへの参照がある場合、display.component1にdataaccessへの参照がある場合よりも多くの警告ベルがスローされます。コンポーネント2。

もちろん、両方で共有されるコンポーネントは独自のパッケージに入れられます。

2
levand

論理プロセスの粒度に依存しますか?

スタンドアロンの場合、多くの場合、新しいパッケージではなく、ソース管理に新しいプロジェクトがあります。

私が現在取り組んでいるプロジェクトは、論理的な分割に失敗しています。jythonアスペクトのパッケージ、ルールエンジンのパッケージ、foo、bar、binglewozzleなどのパッケージがあります。XML固有のパーサーを探しています。 XMLパッケージ(以前に行ったもの)ではなく、そのパッケージ内の各モジュールの/ writers。ただし、共有ロジックが配置されるコアXMLパッケージは引き続き存在します。ただし、この理由の1つは、拡張性があるため(プラグイン)、各プラグインがXML(またはデータベースなど)コードも定義する必要があるため、これを集中化すると、後で問題が発生する可能性があるためです。

最終的には、特定のプロジェクトにとって最も賢明な方法であると思われます。ただし、典型的なプロジェクトの階層図の線に沿ってパッケージ化するのは簡単だと思います。論理的パッケージと機能的パッケージが混在することになります。

必要なのは、タグ付けされた名前空間です。一部のJython機能のXMLパーサーは、どちらか一方を選択するのではなく、JythonとXMLの両方にタグ付けできます。

または多分私はよそよそしいです。

2
JeeBee

私は個人的に機能的な命名に行きます。短い理由:コードの重複や依存関係の悪夢を回避します。

少し詳しく説明します。独自のパッケージツリーで外部jarファイルを使用している場合はどうなりますか? (コンパイルされた)コードをプロジェクトに効果的にインポートし、それと共に(機能的に分離された)パッケージツリーをインポートします。 2つの命名規則を同時に使用するのは理にかなっていますか?いいえ、それがあなたから隠されていない限り。そして、あなたのプロジェクトが十分に小さく、単一のコンポーネントを持っている場合です。しかし、複数の論理ユニットがある場合は、おそらく、データファイルの読み込みモジュールを再実装する必要はありません。論理ユニット間で共有し、論理的に無関係なユニット間で人為的な依存関係を持たないようにし、特定の共有ツールを配置するユニットを選択する必要はありません。

これが、特定のサイズに到達する、または到達することを意図しているプロジェクトで機能的な命名が最も使用される理由であり、特定の役割を追跡するためにクラス命名規則で論理命名が使用される理由だと思いますパッケージ。

論理的な命名の各ポイントに、より正確に対応するようにします。

  1. 計画を変更したときに機能を変更するために古いクラスで釣りに行かなければならない場合、それは悪い抽象化の兆候です:短い文で定義可能な、明確に定義された機能を提供するクラスを構築する必要があります。ビジネスインテリジェンスを反映するために、これらのすべてをアセンブルする必要があるのは、少数のトップレベルクラスのみです。これにより、より多くのコードを再利用でき、メンテナンスが容易になり、ドキュメントが明確になり、依存関係の問題が少なくなります。

  2. それは主に、プロジェクトの内容に依存します。間違いなく、論理的および機能的なビューは直交しています。そのため、ある命名規則を使用する場合、一定の順序を維持するために、他の命名規則をクラス名に適用するか、ある命名規則から別の命名規則にある程度の深さで分岐する必要があります。

  3. アクセス修飾子は、processingを理解する他のクラスがクラスの内部にアクセスできるようにする良い方法です。論理関係は、アルゴリズムまたは同時実行性の制約を理解することを意味しません。機能的かもしれませんが、そうではありません。パブリックおよびプライベート以外のアクセス修飾子は、適切なアーキテクチャとクラスの抽象化の欠如をしばしば隠すため、非常に疲れています。

  4. 大規模な商業プロジェクトでは、技術の変化は、あなたが信じるよりも頻繁に起こります。たとえば、すでに3倍のXMLパーサー、2倍のキャッシングテクノロジー、2倍のジオローカリゼーションソフトウェアを変更する必要がありました。専用のパッケージにザラザラした詳細をすべて隠していたのは良いことです...

1
Varkhan

パッケージをまったく使用しないことは興味深い実験です(ルートパッケージを除く)。

その際に生じる問題は、パッケージを導入するのがいつ、なぜ理にかなっているということです。おそらく、答えはプロジェクトの最初に答えたものとは異なるでしょう。

パッケージはカテゴリのようなものであり、どちらかを決定するのが難しい場合があるため、あなたの質問はまったく発生すると思います。クラスが多くのコンテキストで使用可能であることを伝えるために、タグがより感謝される場合があります。

1
user53682

純粋に実用的な観点から見ると、Javaの可視性構造により、同じパッケージ内のクラスは、protecteddefaultの可視性、およびpublicの可視性を使用してメソッドとプロパティにアクセスできます。コードの完全に異なるレイヤーから非公開メソッドを使用すると、間違いなく大きなコード臭になります。そのため、同じレイヤーのクラスを同じパッケージに入れる傾向があります。

クラスの単体テストを除き、これらの保護されたメソッドまたはデフォルトのメソッドを他の場所で使用することはあまりありませんが、使用する場合は、同じレイヤーのクラスからalwaysです

0
Bill Michell

場合によります。私の仕事では、機能(データアクセス、分析)または資産クラス(クレジット、株式、金利)ごとにパッケージを分割することがあります。チームにとって最も便利な構造を選択するだけです。

0
quant_dev