パッケージ図とは何か
パッケージ図は「大きな箱同士の関係」を描く図です。
クラス図が「クラスとクラスの関係の地図」だとすると、パッケージ図は「パッケージとパッケージの関係の地図」です。
Java の com.example.shop.order.domain や com.example.shop.order.app といったパッケージを、四角い箱として描き、その箱同士を矢印でつなぐことで「どのパッケージがどのパッケージに依存しているか」を見える化します。
細かいクラス単位ではなく、塊として設計を見たいときに使います。
パッケージ図の基本記号の読み方
パッケージは、上にタブの付いたフォルダのような箱で描かれます。
タブ部分や箱の中に「パッケージ名」が書かれます。
例えば com.example.shop.order.domain なら、図では order.domain のように省略して書かれることも多いです。
パッケージ同士を結ぶ矢印は「依存」を表します。
矢印の根本側のパッケージが、先のパッケージを使っている、つまり「そっちに依存している」という意味です。
Java で言えば、矢印の根本側のコードが、矢印の先側のクラスやインターフェースを import しているイメージです。
例えば、order.app パッケージから order.domain パッケージへ矢印が伸びていたら、「アプリケーション層はドメイン層に依存している」と読みます。
Java のパッケージとパッケージ図の対応
Java では、ソースファイルの先頭に package com.example.shop.order.domain; のように書きます。
パッケージ図では、このパッケージを一つの箱にして扱います。
例えば、次のような構成を考えます。
// ドメイン
package com.example.shop.order.domain;
public class Order { /* 省略 */ }
public interface OrderRepositoryPort {
void save(Order order);
}
// アプリケーション
package com.example.shop.order.app;
import com.example.shop.order.domain.Order;
import com.example.shop.order.domain.OrderRepositoryPort;
public class PlaceOrderUseCase {
private final OrderRepositoryPort repo;
public PlaceOrderUseCase(OrderRepositoryPort repo) {
this.repo = repo;
}
public void place(Order order) {
repo.save(order);
}
}
// インフラ
package com.example.shop.order.infra;
import com.example.shop.order.domain.Order;
import com.example.shop.order.domain.OrderRepositoryPort;
public class JdbcOrderRepository implements OrderRepositoryPort {
@Override
public void save(Order order) {
System.out.println("save JDBC");
}
}
Javaこれをパッケージ図で見ると、イメージとしては次のようになります。
一つ目の箱が order.domain。
二つ目の箱が order.app。
三つ目の箱が order.infra。
矢印は order.app から order.domain に向かい、order.infra からも order.domain に向かいます。
つまり「app と infra は domain に依存している」が、「domain は app や infra を知らない」という構造です。
パッケージ図の矢印を見たときは、「このパッケージのクラスは、どのパッケージの型を import してよいのか」を表現している、と読むと分かりやすくなります。
依存の方向と循環依存(重要な深掘り)
パッケージ図で一番重要なのは「矢印の向き」です。
矢印の向きがぐちゃぐちゃになると、依存の輪(循環依存)ができて、設計が壊れやすくなります。
例えば、order.app から order.domain へ矢印があり、同時に order.domain から order.app へも矢印があると、それは循環依存です。
コードで言うと、domain のクラスが app のクラスを import し、app のクラスも domain のクラスを import している状況です。
こうなると「どっちが上か下か」「どちらがビジネスルールの中心か」が曖昧になり、変更の影響範囲が読めなくなります。
良いパッケージ図は、矢印が「一方向」に揃っています。
典型的には、次のような流れです。
UI やコントローラのパッケージから、アプリケーションパッケージへ矢印が伸びる。
アプリケーションパッケージから、ドメインパッケージへ矢印が伸びる。
インフラパッケージは、ドメインパッケージの抽象(インターフェース)へ矢印を伸ばす。
図を読むときは、矢印を目で追いながら「依存が一方向に揃っているか」「輪になっていないか」を確認すると、設計の良し悪しが見えてきます。
例題で読む:注文機能のパッケージ図
注文機能に関するパッケージを次のように分けたとします。
com.example.shop.order.presentationcom.example.shop.order.appcom.example.shop.order.domaincom.example.shop.order.infra
それぞれの役割は、ざっくり次のようなイメージです。
presentation はコントローラや HTTP リクエストの受付。
app はユースケース(ビジネス操作の流れ)。
domain はエンティティや値オブジェクト、ドメインサービスなどビジネスルールの中心。
infra は DB や外部 API への接続実装。
パッケージ図では、presentation から app へ矢印、app から domain へ矢印、infra から domain へ矢印、というように描きます。
この図を読むと、「プレゼンテーション層はアプリケーションに依存し、アプリケーション層はドメインに依存し、インフラ層はドメインで決めた契約を実装している」という構造になっている、と理解できます。
コードとの対応で言えば、presentation のクラスは app のクラスを import してよいが、逆はダメ。
app のクラスは domain のクラスを import してよいが、逆はダメ。
infra のクラスは domain のインターフェースを implements するので domain を import してよいが、domain は infra を知らない。
パッケージ図を見て「どこからどこへ import が許されているか」をイメージできるようになると、設計がだいぶクリアになります。
パッケージ図で見える「粒度」と「境界」の感覚
パッケージ図が役に立つのは、「どこで境界を引くか」「どの単位で独立させるか」を考えるときです。
一つの巨大な service パッケージに何でも入れてしまうと、パッケージ図にしたときに、そこから大量の矢印が出て、また大量の矢印が入ってくる「神パッケージ」になります。
パッケージ図を眺めて、あるパッケージに矢印が集中しているようなら、「責務を分けて別のパッケージに切り出すべきかもしれない」と読み取れます。
逆に、ある機能が一つのパッケージに閉じていて、外部との矢印が少ないなら、「この機能はよくまとまっている」と評価できます。
パッケージ図は、クラスの細かい関係ではなく、「この機能はどの箱に入っていて、周りとどれくらい繋がっているか」を俯瞰するための地図です。
依存の向きが揃っているか、循環がないか、特定のパッケージが重すぎないか、といった観点で読むと設計の問題点が浮かび上がってきます。
まとめと実務での活かし方
パッケージ図は「Java のパッケージ同士の依存関係を、箱と矢印で表したもの」です。
パッケージ名が箱のラベルになり、矢印が「どちらがどちらを import してよいか」の方向を示します。
矢印が一方向に揃っていれば、依存はきれいに整っており、循環していれば設計の危険信号です。
読み方のコツは、次のように考えることです。
この矢印の根本側のパッケージの中のクラスは、矢印の先側のパッケージのクラスを使っている。
このパッケージは、どこからも矢印が来ていないなら“下位”であり土台になっている。
たくさんの矢印が集まっているパッケージは責務が重すぎるかもしれない。
