導入
MVVMの3層は、Javaではパッケージとして表現します。パッケージを分けることで「この層はこの層しか使わない」という依存の向きが目に見えるようになり、うっかり逆向きに参照するミスに気づけます。
説明
このコースで作るタスク管理アプリは、次の構成になります。
flowchart TB
APP["app<br/>(Composition Root / main)"]
VIEW["app.view<br/>(FXML・Controller)"]
VM["app.viewmodel<br/>(ViewModelBase・TaskListViewModel・Command)"]
SVC["app.service<br/>(TaskService・TaskDto・TaskMapper)"]
REPO["app.repository<br/>(TaskRepository・InMemoryTaskRepository)"]
DOM["app.domain<br/>(TaskItem・TaskTitle・Priority・TaskFactory)"]
SUP["app.support<br/>(IdGenerator・Clock)"]
APP --> VIEW
APP --> VM
VIEW --> VM
VM --> SVC
SVC --> REPO
SVC --> DOM
REPO --> DOM
DOM --> SUP
依存の向きは「内向き」に一方通行
矢印はすべて外側(画面に近い側)から内側(業務ロジック側)へ向いています。これが崩れないようにするのがこの構成の目的です。
app.domainは誰も知らない。JavaFXも、Repositoryも、ViewModelも知らないapp.repositoryはapp.domainだけを知るapp.serviceはapp.repositoryとapp.domainを知るapp.viewmodelはapp.serviceを知る。JavaFXは知らないapp.viewだけがJavaFXを知る
矢印が内向きなので、内側だけを取り出してテストできます。Entityのテストには何も要りませんし、Serviceのテストには偽物(Fake)のRepositoryを1つ渡せば十分です。
実務のGradleプロジェクトではどうなるか
実務では、これがそのままディレクトリ構成になります。
src/
main/java/app/
Main.java ← Composition Root(依存を組み立てる場所)
domain/ TaskItem.java TaskTitle.java Priority.java TaskFactory.java
repository/TaskRepository.java InMemoryTaskRepository.java
service/ TaskService.java TaskDto.java TaskMapper.java
viewmodel/ ViewModelBase.java TaskListViewModel.java RelayCommand.java
view/ MainView.fxml MainViewController.java
support/ IdGenerator.java Clock.java
test/java/app/
domain/ TaskItemTest.java TaskTitleTest.java
service/ TaskServiceTest.java
viewmodel/ TaskListViewModelTest.java
src/main/java が本体、src/test/java がテストです。テストは本体と同じパッケージ名で置くのが慣習で、こうするとパッケージプライベートのクラスもテストから触れます。
このコースの実行エディタとの対応
このサイトの実行エディタは、上のタブがそのまま「ファイル」です。package 宣言は書いても書かなくても動きますが、実務の見た目に合わせて書く回もあります。テストのファイルには @Test の付いたメソッドを書き、実行するとテストメソッド単位で✅/❌が出ます。
まとめ
- 3層はパッケージで表す。
domain/repository/service/viewmodel/view/support - 依存の矢印は外→内の一方通行。
domainは誰も知らない - 内向きだから、内側の層だけを切り出してテストできる
次回: 一番外側のView、JavaFXの最低限を押さえます。