---
title: 変化に強いテストを育てるSpringBootのレイヤー設計
tags:  #java #spring boot #テスト #設計 #アーキテクチャ #レイヤー分離  
author: [Takeshi Miyajima](https://docswell.com/user/tmiya-tech)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/8EDK8G8K7G.jpg?width=480
description: JJUG CCC 2026 Spring 登壇資料
published: May 30, 26
canonical: https://docswell.com/s/tmiya-tech/53JENE-2026-05-30-095125
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/8EDK8G8K7G.jpg)

変化に強いテストを育てる
SpringBootのレイヤー設計
2026-05-30 | JJUG CCC 2026 Spring
株式会社サンアーチ Takeshi Miyajima


# Page. 2

![Page Image](https://bcdn.docswell.com/page/V7PK8383J8.jpg)

⾃⼰紹介
Takeshi Miyajima
宮島 健
“スピーキングエンジニア”を育てる
株式会社サンアーチ
業務システム開発 / 運⽤ツール開発
Java / Spring Boot / React / Vue / etc
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/2JVVN4NNJQ.jpg)

はじめに
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/5EGLK1K5JL.jpg)

今⽇話すテストのスコープ
開発者テスト
QAテスト
受⼊テスト
こっちの話
4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/4JQYNDNL7P.jpg)

今⽇話すテストのスコープ
E2E
パフォーマンス
要件
設計
継続的インテグレーション
実装
テスト
リリース
CI
ここら辺のテストの話
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/K74WGZG5E1.jpg)

今⽇の話のモチベーション
• 開発を⽀えてくれる、価値あるテストを作るのは難しい
• 設計が抱える問題点がテストを難しくしている事例が多い
→”価値あるテストを⽀えるのは良い設計だ” という話をしたい
• Spring Bootに限らない当たり前の話が多いが、
誰かの気づきになったら嬉しい
6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/LJ1YDRD2EG.jpg)

今⽇話すこと・話さないこと
今⽇話すこと
• 現場でよくある⾃動テストの悩みと、その原因
• 変化に強いテストを⽬指すための、設計からのアプローチ
• 責務の分離が変化に強いテストを⽣み出す
話さないこと
• JUnit・Mockito等の使い⽅
• アーキテクチャパターンの詳細 / ドメイン駆動設計の詳細
• 負荷テスト・パフォーマンステスト・E2Eテストの⼿法
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/GJWGY1Y272.jpg)

変化に強いテスト
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/4EZLXPX473.jpg)

変化に強いテストとは
変化に強いテストとは
継続開発を⽀える品質ガード
•
•
•
•
関係ないテストは壊れない
リファクタリングの影響を受けない
仕様が変わったらテストが検知してくれる
いつでも素早く実⾏できる
つまり・・・
として働くもの
• 信頼性が⾼い
• 保守コストが低い
• フィードバックが早い
9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/Y76W4M4G7V.jpg)

変化に強いテストは、
プロダクトの進化を 加速 させる
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/G75MQZQX74.jpg)

現場でよくある “変化に弱いテスト”
あなたのプロジェクトも同じ悩みを抱えていませんか︖
保守コストが⾼い
信頼性が低い
•
•
•
⾃動テストは通るのに、
動かしてみると動かない
テストの動作が不安定
結局⼿動テストが必要
•
•
変更のたびに、⼤量の
テスト修正が発⽣
仕様は変わらないのに
テストだけ壊れる
フィードバックが遅い
•
•
実⾏が遅い
エラー原因がわかりづらい
→ どうしたら “変化に強いテスト” が作れるのか
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/9J29PRPQER.jpg)

変化に強いテストの鍵は ”プロダクトコードの改善”
プロダクトコード
まずい設計
構造を反映
テストコード
・・・結果的に・・・
変化に弱いテスト
・・・結果的に・・・
変化に強いテスト
改善
良い設計
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/DEY45D5YJM.jpg)

“責務の混在” が変化に弱いテストを⽣み出す
変化に弱いテスト
責務の混在
⼊⼒チェック 処理フロー
データの加⼯ 永続化
トランザクション
整合性チェック
ルール
計算
判断
観点
無理やりテスト
観点
観点
観点
観点
観点
観点
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/VJNYN6NR78.jpg)

“責務の分離” が変化に強いテストを⽣むポイント
責務を分離
Presentation
リクエスト受け取り
形式チェック
レスポンス返却
Application
データの加⼯・計算
整合性チェック
トランザクション管理
DataAccess
変化に強いテスト
観点
観点
観点
観点
観点
観点
データ⼊出⼒
観点
観点
データ変換
観点
観点
テスト
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/YE9PRLRZJ3.jpg)

ここまでのまとめ
• 変化に強いテスト = 継続開発を⽀える品質ガード
• 変化に強いテストには、良い設計 が必要
• 変化に強いテストには、責務の分離 が重要
16


# Page. 16

![Page Image](https://bcdn.docswell.com/page/GE8DWXGYED.jpg)

“変化に強いテスト” を⽬指す責務分離
① 外部接続の分離
② レイヤー内の分離
③ 参照系の分離
17


# Page. 17

![Page Image](https://bcdn.docswell.com/page/LELMN8G97R.jpg)

前提とするアーキテクチャ
よくある三層アーキテクチャで考えていきます
クライアント
Presentation
Application
DataAccess
Controller
Service
Repository
リクエスト受け取り
形式チェック
レスポンス返却
処理フロー制御
データの加⼯・計算
整合性チェック
業務ロジック
トランザクション管理
データ⼊出⼒
データ変換
DB
18


# Page. 18

![Page Image](https://bcdn.docswell.com/page/4JMYX6QVJW.jpg)

①“外部接続” と “それ以外” を明確に分ける
Presentation
役割
外部接続
Application
分ける
その他
DataAccess
分ける
外部接続
19


# Page. 19

![Page Image](https://bcdn.docswell.com/page/PJR9NP8W79.jpg)

②外部接続の責務を薄く、処理をシンプルにする
Presentation
Application
DataAccess
役割
外部接続
その他
外部接続
責務
薄く
処理内容
シンプル
寄せる
厚く
複雑
寄せる
薄く
シンプル
20


# Page. 20

![Page Image](https://bcdn.docswell.com/page/PEXQN38VJX.jpg)

よくある失敗① Controller肥⼤化
Controllerが処理フローを担ってしまう
処理フローの制御はサービスに寄せ、Controllerはサービスを呼び出すだけ
シンプル &lt; 複雑
複雑 &gt; シンプル
Presentation
Application
制御・呼び分け
Controller
CheckService
改善
Presentation
Application
Controller
ProcessService
ProcessService1
テスト
ProcessService2
テスト増・遅い・壊れやすい
テスト
1.
2.
3.
チェック
処理1
処理2
テスト少・早い・安定
21


# Page. 21

![Page Image](https://bcdn.docswell.com/page/3EK9NYK5ED.jpg)

よくある失敗② Repositoryへの業務知識漏洩
SQLで状態チェック、SQLに業務仕様が埋め込まれている
RepositoryはシンプルなデータIOに特化、チェック・判断はサービスやモデルへ
SELECT * FROM orders o
WHERE o.customer_id = ?
AND o.status = &#039;CONFIRMED&#039; AND o.cancelled_at IS NULL
AND o.created_at &gt;= NOW() - INTERVAL 30 DAY
改善
SELECT * FROM orders o
WHERE o.customer_id = ?
Order
isValidForShipping()
業務仕様埋め込み
シンプルIO
+ モデルメソッド
22


# Page. 22

![Page Image](https://bcdn.docswell.com/page/L73WV9Z175.jpg)

分離してからどうテストするか︖
Presentation
Application
DataAccess
役割
外部接続
その他
外部接続
責務
薄い
厚い
薄い
処理内容
シンプル
複雑
シンプル
テスト戦略
︖
︖
︖
23


# Page. 23

![Page Image](https://bcdn.docswell.com/page/87DK8GRKJG.jpg)

テスト戦略︓Presentationレイヤー
•
•
•
@WebMvcTest、MockMvc等を使う
Applicationレイヤーをモックする
主なテスト観点
•
リクエスト/レスポンスのやり取り
•
⼊⼒チェック・認証認可チェック
•
Applicationレイヤーの呼び出し
これ以外の観点が必要なら
責務混在の懸念あり
Presentation
Application
Controller
Service
モック
テスト
＠WebMvcTest
MockMvc
24


# Page. 24

![Page Image](https://bcdn.docswell.com/page/VJPK83W3E8.jpg)

テスト戦略︓Presentationレイヤー サンプルコード
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
OrderService orderService;
@Test
void 注⽂作成が成功したら200を返す() {
// モックのセットアップ
when(orderService.create(any())).thenReturn(OrderId.of(1));
// mockMvcでControllerの挙動をテスト
mockMvc.perform(post(&quot;/orders&quot;)...)
.andExpect(status().isOk());
}
}
// モックの呼び出しを確認
verify(orderService).create(orderRequest());
25


# Page. 25

![Page Image](https://bcdn.docswell.com/page/2EVVN48NEQ.jpg)

テスト戦略︓DataAccessレイヤー
•
•
•
@DataJpaTest、JdbcClient等を使う
H2やTestcontainersで実DBテストをする
主なテスト観点
•
検索・登録・更新・削除SQL
•
Javaクラスとの相互変換
•
取得件数ごとの結果パターン
業務観点のパターンが必要なら
責務混在の懸念あり
DataAccess
Repository
H2 / Testcontainers
テスト
＠DataJpaTest/@JdbcTest
JdbcClient
26


# Page. 26

![Page Image](https://bcdn.docswell.com/page/57GLK155EL.jpg)

テスト戦略︓DataAccessレイヤー サンプルコード
@DataJpaTest
class OrderRepositoryTest {
@Autowired
OrderRepository orderRepository;
@Autowired
JdbcClient jdbcClient;
@Test
void IDで注⽂を検索できる() {
// データのセットアップ
// @Sqlを使うのもおすすめ
jdbcClient.sql(&quot;INSERT INTO ...&quot;).update();
// メソッドを実⾏
var found = orderRepository.findById(1);
}
}
// 結果の確認
assertThat(found).hasValue(new Order(...));
27


# Page. 27

![Page Image](https://bcdn.docswell.com/page/4EQYNDZLJP.jpg)

補⾜︓H2かTestcontainersか
RepositoryがシンプルIOに特化できれば、H2で⾜りるはず
DB固有のSQLが必要な場合のみTestcontainersを使うのがおすすめ
H2
おすすめ
Testcontainers
実⾏速度
◎ 速い（インメモリ）
△ 遅い（コンテナ起動）
SQL互換性
△ ⽅⾔⾮対応のことあり
◎ 本番DBと同じ
CI環境
◎ 追加設定不要
△ Docker必須
28


# Page. 28

![Page Image](https://bcdn.docswell.com/page/KJ4WGZ3571.jpg)

テスト戦略︓Applicationレイヤー
•
•
•
Springは使わずにテスト
インメモリRepositoryを使う
主なテスト観点
•
•
•
•
処理フロー
データの加⼯・計算
整合性チェック
業務ロジック
⾃動テストの主戦場
テスト量も最も多いはず
Application
DataAccess
Service
Repository
インメモリ
Repository
テスト
SpringExtension
29


# Page. 29

![Page Image](https://bcdn.docswell.com/page/LE1YDR127G.jpg)

テスト戦略︓Applicationレイヤー サンプルコード
class OrderServiceTest {
private InMemoryOrderRepository repository = new InMemoryOrderRepository();
private OrderCreationPolicy policy = new OrderCreationPolicy();
private OrderService sut = new OrderService(repository, policy);
@Test
void 注⽂を作成すると保存される() {
var request = new OrderRequest(...);
var orderId = sut.createOrder(request);
}
}
var order = repository.findById(orderId);
assertThat(order).hasValue(new Order(...));
@Test
void ⾦額が0以下の場合は注⽂を作成できない() {
assertThatThrownBy(() -&gt; sut.createOrder(new OrderRequest(...)))
.isInstanceOf(IllegalArgumentException.class);
}
30


# Page. 30

![Page Image](https://bcdn.docswell.com/page/GEWGY182J2.jpg)

補⾜︓Spring Testは使わず早いテストを⽬指すべし
Applicationレイヤーは、Springの⼒を借りずにテストが動かせる唯⼀のレイヤー
→あえて責務を集約することで、軽量なテストの分量を増やす
Large
テストピラミッド
Small &gt; Medium &gt; Large のバランスが良い
Medium ・・・ Presentation / DataAccessレイヤーのテスト 少
Small
・・・ Applicationレイヤーのテスト 多
31


# Page. 31

![Page Image](https://bcdn.docswell.com/page/47ZLXP84J3.jpg)

補⾜︓インメモリRepositoryがおすすめ
• Repositoryをモックしてしまうと、テストの保守性が下がる
• インメモリRepositoryを使えば、モックの⾟みを軽減できる
モック
Repository
Repository
findById(long id)
findByName(String name)
整合性・定義もれ
振る舞い1
振る舞い2
代替
インメモリ
Repository
ずれない・漏れない
Map
32


# Page. 32

![Page Image](https://bcdn.docswell.com/page/YJ6W4MPGJV.jpg)

補⾜︓インメモリRepositoryのサンプル
class InMemoryOrderRepository
implements OrderRepository {
private final Map&lt;Long, Order&gt; store = new HashMap&lt;&gt;();
private long nextId = 1L;
@Override
public Order save(Order order) {
var saved = order.withId(nextId++);
store.put(saved.getId(), saved);
return saved;
}
@Override
public Optional&lt;Order&gt; findById(long orderId) {
var order = this.store.get(orderId);
return Optional.ofNullable(order);
}
}
33


# Page. 33

![Page Image](https://bcdn.docswell.com/page/GJ5MQZKXJ4.jpg)

外部接続の分離まとめ
Presentation
Application
DataAccess
役割
外部接続
分ける
その他
分ける
外部接続
責務
薄い
寄せる
厚い
寄せる
薄い
処理内容
シンプル
複雑
シンプル
テスト戦略
Springありき
Appはモック
早いテスト
インメモリRepo
Springありき
実DB
34


# Page. 34

![Page Image](https://bcdn.docswell.com/page/9E29PRWQ7R.jpg)

“変化に強いテスト” を⽬指す責務分離
① 外部接続の分離
② レイヤー内の分離
③ 参照系の分離
35


# Page. 35

![Page Image](https://bcdn.docswell.com/page/D7Y45DLYEM.jpg)

意図的に厚くしたレイヤーとどう戦うか
Presentation
Application
DataAccess
役割
外部接続
その他
外部接続
責務
薄い
厚い
薄い
処理内容
シンプル
複雑
シンプル
36


# Page. 36

![Page Image](https://bcdn.docswell.com/page/VENYN64RJ8.jpg)

⼤きい塊のままだと、テストも⼤きくなる
BigService
構造を反映
BigServiceTest
保守コストが⾼い
変化に弱い
37


# Page. 37

![Page Image](https://bcdn.docswell.com/page/Y79PRLQZE3.jpg)

⼩さく分ければ、テストも⼩さくなる
SmallService
SmallClass
SmallClass
構造を反映
SmallServiceTest
SmallClassTest
SmallClassTest
⼩さいテストは扱いやすい
変化に強い︖
38


# Page. 38

![Page Image](https://bcdn.docswell.com/page/G78DWXQY7D.jpg)

分け⽅次第で変化への強さは変わる
変化に弱い
変化に強い
適当に区切る
責務の境界で区切る
⼊⼒チェック 処理フロー
データの加⼯ 永続化
トランザクション
整合性チェック
ルール
計算
判断
処理フロー
ルール
判断
計算
通知
構築
39


# Page. 39

![Page Image](https://bcdn.docswell.com/page/L7LMN8X9JR.jpg)

よく使う責務の分離パターン
① 判断 と 処理 の分離
② 処理フロー と 実処理 の分離
③ 構築 と 実⾏ の分離
40


# Page. 40

![Page Image](https://bcdn.docswell.com/page/4EMYX6LVEW.jpg)

分離パターン① 判断と処理の分離
業務知識としての判断はドメインモデルへ、判断を使う処理はServiceへ
OrderService
処理フロー
判断
OrderService
参照
Order
情報
Order
処理フロー
判断
情報
参照
41


# Page. 41

![Page Image](https://bcdn.docswell.com/page/PER9NPKWJ9.jpg)

分離パターン① 修正前
public class Order {
private OrderStatus status;
// getter / setter のみ
}
public class OrderService {
public void cancel(Long orderId) {
判断
var order = orderRepository.findById(orderId).orElseThrow();
if (order.getStatus() != OrderStatus.PENDING
&amp;&amp; order.getStatus() != OrderStatus.CONFIRMED) {
throw new IllegalStateException(&quot;キャンセルできません&quot;);
}
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
}
42


# Page. 42

![Page Image](https://bcdn.docswell.com/page/P7XQN3LVEX.jpg)

分離パターン① 判断を移動
public class Order {
private OrderStatus status;
// getter / setter
public boolean isCancellable() {
return status == OrderStatus.PENDING
|| status == OrderStatus.CONFIRMED
}
}
判断
public class OrderService {
public void cancel(Long orderId) {
var order = orderRepository.findById(orderId).orElseThrow();
if (!order.isCancellable()) {
throw new IllegalStateException(&quot;キャンセルできません&quot;);
}
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
}
43


# Page. 43

![Page Image](https://bcdn.docswell.com/page/37K9NYL57D.jpg)

分離パターン① さらに、判断を伴う処理ごと移動
public class Order {
判断を伴う
private OrderStatus status;
public boolean isCancellable() { ... }
処理
public void cancel() {
if (!isCancellable()) {
throw new IllegalStateException(&quot;キャンセルできません&quot;);
}
setStatus(OrderStatus.CANCELLED);
}
}
public class OrderService {
public void cancel(Long orderId) {
var order = orderRepository.findById(orderId).orElseThrow();
order.cancel();
orderRepository.save(order);
}
}
44


# Page. 44

![Page Image](https://bcdn.docswell.com/page/LJ3WV931J5.jpg)

分離パターン① テストに対するメリット
モデルがロジックを持つと、独⽴性が⾼くテストしやすくなる
Service側のテストパターンも減らせる
テスト内容
OrderService
Test
処理フロー
判断内容
テスト内容
OrderService
Test
処理フロー
OrderTest
判断内容
テスト
しやすい
ロジックが
安定
45


# Page. 45

![Page Image](https://bcdn.docswell.com/page/8JDK8G4KEG.jpg)

よく使う責務の分離パターン
① 判断 と 処理 の分離
② 処理フロー と 実処理 の分離
③ 構築 と 実⾏ の分離
46


# Page. 46

![Page Image](https://bcdn.docswell.com/page/VEPK83M378.jpg)

分離パターン② 処理フローと実処理の分離
処理の塊を別クラスに逃し、処理フローを独⽴させる
OrderService
処理フロー
データ更新
イベント通知
OrderUseCase
処理フロー
OrderRecord
Service
データ更新
イベント通知
47


# Page. 47

![Page Image](https://bcdn.docswell.com/page/27VVN49N7Q.jpg)

分離パターン② 修正前
OrderService
// バリデーション
validate(req);
var order = buildOrder(req);
// 在庫チェックと分岐
if (inventoryService.isAvailable(order)) {
// データ更新
orderRepository.save(order);
inventoryService.decrease(order);
// イベント通知
eventPublisher.publish(new OrderPlaced(order));
notificationService.notify(order);
} else {
// 在庫不⾜
orderRepository.saveAsPending(order);
notificationService.notifyOutOfStock(order);
}
処理の塊
48


# Page. 48

![Page Image](https://bcdn.docswell.com/page/5JGLK1Z57L.jpg)

分離パターン② 実処理を移動
OrderUseCase
OrderRecordService
// バリデーション
validate(req);
var order = buildOrder(req);
// 在庫チェックと分岐
if (inventoryService.isAvailable(order)) {
orderRecordService.record(order);
} else {
// 在庫不⾜
orderRecordService
.recordAsPending(order);
}
void record(Order order) {
// データ更新
orderRepository.save(order);
inventoryService.decrease(order);
// イベント通知
eventPublisher.publish(
new OrderPlaced(order));
notificationService.notify(order);
}
分離
void recordAsPending(Order order) {
// 在庫不⾜時
orderRepository.saveAsPending(order);
notificationService
.notifyOutOfStock(order);
}
49


# Page. 49

![Page Image](https://bcdn.docswell.com/page/47QYNDLLEP.jpg)

分離パターン② テストに対するメリット
テストパターンが “掛け合わせ” から “⾜し算” になる
テスト内容
テスト内容
OrderService
Test
分岐
処理内容
OrderUseCase
Test
分岐
OrderRecord
ServiceTest
処理内容
50


# Page. 50

![Page Image](https://bcdn.docswell.com/page/KE4WGZD5J1.jpg)

よく使う責務の分離パターン
① 判断 と 処理 の分離
② 処理フロー と 実処理 の分離
③ 構築 と 実⾏ の分離
51


# Page. 51

![Page Image](https://bcdn.docswell.com/page/L71YDRZ2JG.jpg)

分離パターン③ 構築と実⾏の分離
外部APIアクセス (インフラレイヤー) でも使えるパターン
複雑な情報構築と、愚直な実⾏役に分離する
Notification
Service
メッセージ構築
通知送信
Notification
MessgeBuilder
メッセージ構築
Notification
Sender
通知送信
52


# Page. 52

![Page Image](https://bcdn.docswell.com/page/G7WGY192E2.jpg)

分離パターン③ 修正前
NotificationService
構築
public void notify(Order order) {
// 複数ソースからメッセージ構築
var user = userRepository.findById(order.getUserId());
var product = productRepository.findById(order.getProductId());
// 通知内容の構築
var subject = ...
var body = ...
実⾏
// 送信実⾏
var mail = new SimpleMailMessage();
mail.setTo(user.getEmail());
mail.setSubject(subject);
mail.setText(body);
sender.send(mail);
}
53


# Page. 53

![Page Image](https://bcdn.docswell.com/page/4JZLXP94E3.jpg)

分離パターン③ 情報の構築処理を抽出
NotificationService
public void notify(Order order) {
// メッセージを構築
var message = messageBuilder.build(order);
// メッセージを送信
sender.send(message);
}
NotificationMessageBuilder
public NotificationMessage build(...) {
// 複数ソースからメッセージ構築
var user = ...
var product = ...
// 通知内容の構築
var subject = ...
var body = ...
NotificationSender
public void send(
NotificationMessage message) {
var mail = new SimpleMailMessage();
mail.setTo(message.getEmail());
mail.setSubject(mesage.getSubject());
mail.setText(mesage.getBody());
sender.send(mail);
}
return new NotificationMessage(
user.getEmail(), subject, body);
}
54


# Page. 54

![Page Image](https://bcdn.docswell.com/page/YE6W4MKGEV.jpg)

分離パターン③ テストに対するメリット
複雑なデータ構築パターンや、外部アクセスを切り出して重点的にテスト可能
NotificationMessgeBuilderTest
NotificationSenderTest
• ⼊⼒データバリエーション
• テンプレート置換
• メッセージパターン
• 送信先・フォーマット
55


# Page. 55

![Page Image](https://bcdn.docswell.com/page/GE5MQZPXE4.jpg)

補⾜︓テストの分離しすぎに注意
テストを分離すると、プロダクションコードの構造の変更に弱くなる
→境界が安定するまでは、あえてテストは分離しない作戦もアリ
OrderUseCase
use
OrderRecord
Service
OrderUseCase
Test
まとめてテストすれば
クラス間で処理を移動しても壊れない
56


# Page. 56

![Page Image](https://bcdn.docswell.com/page/9729PR6QJR.jpg)

よく使う責務の分離パターン まとめ
① 判断 と 処理 の分離
② 処理フロー と 実処理 の分離
③ 構築 と 実⾏ の分離
57


# Page. 57

![Page Image](https://bcdn.docswell.com/page/DJY45D9Y7M.jpg)

“変化に強いテスト” を⽬指す責務分離
① 外部接続の分離
② レイヤー内の分離
③ 参照系の分離
58


# Page. 58

![Page Image](https://bcdn.docswell.com/page/V7NYN6LRE8.jpg)

“更新系” と “参照系”
アプリ内の処理は、 “更新系” と “参照系” に分けて考えることができる
更新系
データ更新
画⾯
DB
参照系
データ取得
59


# Page. 59

![Page Image](https://bcdn.docswell.com/page/YJ9PRL4Z73.jpg)

共通モデルパターン
シンプルなアプリでは、更新系も参照系も、⼀つのモデルを共⽤する事が多い
更新系
データ更新
共通
Order
画⾯
参照系
データ取得
60


# Page. 60

![Page Image](https://bcdn.docswell.com/page/GJ8DWXVYJD.jpg)

共通モデルが引き起こす問題
更新系・参照系、相互に影響しあってテストが壊れる
更新系
参照系
Order
制約条件追加
テストが壊れる
+id: Long
+customerId: Long
+items: List&lt;OrderItem&gt;
+status: OrderStatus
テストが壊れる
フィールド追加
61


# Page. 61

![Page Image](https://bcdn.docswell.com/page/LJLMN859ER.jpg)

そもそも要求が違う
様々な要求の違いがあるため、⼀つのモデルで扱うと問題が起きる
観点
更新系
参照系
関⼼事
正確性・整合性
⾒やすさ・速さ
処理件数
1件 〜 少数
1件 〜 ⼤量
ビジネスルール検証
必要
不要
トランザクション
必要
基本不要
副作⽤
あり（DB書き込み・イベント）
なし
変更要因
ビジネスルール
UI / UX
62


# Page. 62

![Page Image](https://bcdn.docswell.com/page/47MYX63V7W.jpg)

参照系をまるっと分離するという解決策
更新系とは別に参照系の経路を作り、モデルも処理も完全分離する (いわゆるCQRS)
→ 要求の違いをそのままモデルに反映することで、モデル汚染をなくす
更新系
Presentation
Application
DataAccess
参照系
Query
63


# Page. 63

![Page Image](https://bcdn.docswell.com/page/P7R9NPQWE9.jpg)

更新系と参照系でモデルを分ける
更新系
Order
+id: Long
+customerId: Long
+items: List&lt;OrderItem&gt;
+status: OrderStatus
参照系
OrderView
+id: Long
+customer: CustomerView
+items: List&lt;OrderItemView&gt;
+status: OrderStatus
+shipping: ShippingView
OrderRepository
+findById()
OrderQueryRepository
+findByShippingStatus()
64


# Page. 64

![Page Image](https://bcdn.docswell.com/page/PJXQN35V7X.jpg)

分ければテストは安定する
更新系
参照系
Order
OrderView
影響なし
OrderService
OrderService
Test
OrderQuery
Service
OrderQuery
ServiceTest
65


# Page. 65

![Page Image](https://bcdn.docswell.com/page/3JK9NY65JD.jpg)

補⾜︓参照系の分離タイミング・範囲に注意
• アプリがシンプルな状態で分離すると、開発コストが上がる
• 全体に適⽤すると、シンプルな機能まで開発コストが上がる
• 分離メリットがタイミング・範囲を⾒極めて適⽤すべし
同じでいいなら
コストがかかるだけ
更新系
参照系
Order
OrderView
+id: Long
+customerId: Long
+items: List&lt;OrderItem&gt;
+status: OrderStatus
+id: Long
+customerId: Long
+items: List&lt;OrderItemView&gt;
+status: OrderStatus
66


# Page. 66

![Page Image](https://bcdn.docswell.com/page/LE3WV961E5.jpg)

参照系のテストサンプル
• Serviceの責務が薄いため、DataAccessレイヤーと⼀緒にテストもあり
• DB固有機能を使⽤する割合が多いため、Testcontainersを使う場⾯が増える
@JdbcTest
@Import({ OrderQueryService.class, OrderQueryRepository.class })
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Testcontainers
class OrderQueryServiceTest {
@Container @ServiceConnection
static PostgreSQLContainer&lt;?&gt; postgres = new PostgreSQLContainer&lt;&gt;(&quot;postgres:16&quot;);
@Autowired
OrderQueryService sut;
...
}
67


# Page. 67

![Page Image](https://bcdn.docswell.com/page/8EDK8GVK7G.jpg)

補⾜︓参照系のみGraphQL化する選択肢
• 参照系のみGraphQL化すると、
データツリー構築のコスト削減に有利
• フロントエンド側でGraphQLのメリットが
活かせれば、特に効果が⼤きい
• 通常のREST APIとは設計の考え⽅が
違うため注意
query GetOrder($id: ID!) {
order(id: $id) {
id status
customer {
id name
address {
prefecture city street
}
}
items {
quantity unitPrice
product {
id name
category {
id name
}
}
}
}
}
68


# Page. 68

![Page Image](https://bcdn.docswell.com/page/V7PK83V3J8.jpg)

まとめ
69


# Page. 69

![Page Image](https://bcdn.docswell.com/page/2JVVN4ZNJQ.jpg)

まとめ
• 変化に強いテスト = 継続開発を⽀える品質ガード
• 変化に強いテストには、良い設計 が必要
• 変化に強いテストには、責務の分離 が重要
• 外部接続の分離、レイヤー内の分離、参照系の分離
70


# Page. 70

![Page Image](https://bcdn.docswell.com/page/5EGLK145JL.jpg)

変化に強いテストで、
プロダクトの進化を 加速 させよう
71


# Page. 71

![Page Image](https://bcdn.docswell.com/page/4JQYNDGL7P.jpg)

ご静聴ありがとうございました
72


# Page. 72

![Page Image](https://bcdn.docswell.com/page/K74WGZ55E1.jpg)

APPENDIX
73


# Page. 73

![Page Image](https://bcdn.docswell.com/page/LJ1YDRW2EG.jpg)

テストの偽陽性・偽陰性
プロダクトコードが
!
&quot;
#
結
果
&amp;
正しい
誤っている
成功
期待通り
偽陰性
失敗
偽陽性
期待通り
サバンナ便り 〜ソフトウェア開発の荒野を⽣き抜く
第2回 偽陽性と偽陰性
https://gihyo.jp/dev/serial/01/savanna-letter/0002
74


# Page. 74

![Page Image](https://bcdn.docswell.com/page/GJWGY16272.jpg)

テストサイズとテストピラミッド
75


# Page. 75

![Page Image](https://bcdn.docswell.com/page/4EZLXPY473.jpg)

集約設計とRepository
• 集約とは、「データを⼊出⼒する情報の単位」のこと
•
ドメイン駆動設計における重要概念
• 基本は集約ごとにRepositoryを作ることが重要
集約ごとにRepositoryを作るメリット
• ApplicationレイヤーとDataAccessレイヤーのやりとりを単純化できる
•
•
テーブル単位だと、必要データの組み⽴てで複数回Repositoryアクセスが発⽣する
アクセス数を減らす⽬的で、業務知識がRepositoryに漏えいしやすくなる
• インメモリRepositoryが作りやすくなる
•
•
テーブル単位だと、Repository間のデータ整合性が壊れやすい
テーブル単位だと、作るインメモリRepositoryが増えがち
76


# Page. 76

![Page Image](https://bcdn.docswell.com/page/Y76W4MDG7V.jpg)

CQRS (Command Query Responsibility Segregation)
77


# Page. 77

![Page Image](https://bcdn.docswell.com/page/G75MQZ3X74.jpg)

Spring BootでのTestcontainersの使い⽅
78


# Page. 78

![Page Image](https://bcdn.docswell.com/page/9J29PRZQER.jpg)

Spring GraphQLの実装⽅法
79


# Page. 79

![Page Image](https://bcdn.docswell.com/page/DEY45DRYJM.jpg)

GraphQLのスキーマ設計ガイド
80


# Page. 80

![Page Image](https://bcdn.docswell.com/page/VJNYN6DR78.jpg)

参考リンク
• https://spring.pleiades.io/spring-boot/reference/testing/spring-bootapplications.html
• https://spring.pleiades.io/spring-boot/reference/testing/testcontainers.html
• https://spring.pleiades.io/springframework/reference/testing/annotations/integration-spring/annotationsql.html
• https://spring.pleiades.io/spring-framework/docs/current/javadocapi/org/springframework/test/context/jdbc/Sql.html
• https://spring.pleiades.io/spring-framework/docs/current/javadocapi/org/springframework/test/web/client/MockRestServiceServer.html
81


