Open3

『手を動かしてわかるクリーンアーキテクチャ』読書メモ

horie-thorie-t

結論

パッケージ構成

アーキテクチャの構成要素を意識して、以下のようなパッケージ構成にする。

$ tree src/main/java/com/example/app_name
src/main/java/com/example/app_name
├── adapter
│   ├── in
│   │   └── web
│   └── out
│       └── persistence
└── application
    ├── domain
    │   ├── model
    │   └── service
    └── port
        ├── in
        └── out

Spring Bootでのクラス配置

src/main/java/com/example/app_name
├── adapter
│   ├── in
│   │   └── web           # @Controllerクラスやクライアント向けDTO等(*1)
│   └── out
│       └── persistence    # @Repository, @Entityクラス等
└── application
    ├── domain
    │   ├── model          # ドメイン駆動設計でのモデル。
    │   └── service        # @Service, @Transactionalクラス等のドメイン駆動設計での
    │                         # アプリケーションサービス、ドメインサービス(*2)。
    └── port
        ├── in             # adapter.inから利用される、外部システムからの呼び出し用Interface
        │                    # Validationを行う入力モデルクラス(*3)
        └── out            # adapter.outで実装される、外部システムへの保存・送信用Interface(*4)

*1: Controllerクラスは@Mapping毎にクラスを分ける事が推奨されている。(テストクラスが肥大化しやすい。ReadやUpdateの内容がごちゃまぜになりやすい。)
*2: この本では、ドメイン駆動設計でのアプリケーションサービスとドメインサービスの区別は微妙な判断を強いられるので、分けなくてもよいという立場である。
*3: この本ではCommandやQueryと名付けられている。一般的なexecute()メソッドを持つCommandパターンのCommandとは違うので要注意。コマンドクエリ責務分離の方を指している。
*4: データベースのCRUDのような操作は、C, R, U, D毎にInterfaceを分ける。(Repositoryが複数のInterfaceを実装するのは構わない。集約毎にRepositoryを作成する等)

パッケージの作成方法

まとめてJavaのパッケージを作成する方法。(gitにコミットするために.gitkeepファイルも作成)

cd src/main/java/com/example/app_name
for DIR in adapter/in/web adapter/out/persistence \
    application/domain/model application/domain/service \
    application/port/in application/port/out
do
  mkdir -p $DIR
  touch $DIR/.gitkeep
done
horie-thorie-t

例: ヘキサゴナル・アーキテクチャのコーヒーポット管理システム

全体図

パッケージ構成

horie-thorie-t

テスト

単体テスト

domainパッケージのクラス、ドメインモデルやユースケースについて単体テストを行う。永続化アダプタや外部送信アダプタなどは、テストダブルを使うようにする。

統合テスト

Webアダプタ

コントローラー部分のみを取り出してテストする。

@WebMvcTest(controllers = GetCoffeePodController.class)
class GetCoffeePodControllerTest{
    @AutoWired
    private MocMvc mockMvc;
    @MockBean
    private GetCoffeePodUseCase;

    private static final String ENDPOINT = "/coffee-pod/{id}";

    @WebMvcTest
    void test指定したIDのポッドが存在する場合はポッドの情報を返す() {
        mockMvc
            .perform(
                get(ENDPOINT, "foo-uuid"))
            .andExpect(status().isOk());

        then(GetCoffieePodUseCase).should(
            .getCorffiePod(eq("foo-uuid")));
    }
}

永続化アダプタ

TestContainer等を使って、実際のDBにアクセスしてテストすると良い。

システムテスト

外部システムも含めて、テスト用のシステムを起動してテストを行う。TestRestTemplateクラスを使ってテストするとよい。JGiven等の振る舞い駆動開発用ツールを使うのも検討する。