Spring Boot利用を前提としたJavaライブラリ開発方法の提案

>100 Views

November 18, 25

スライド概要

profile-image

ウェルスナビ株式会社 技術広報チームの公式アカウントです。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Spring Boot利用を前提とした Javaライブラリ開発方法の提案 2025.11.15 星原 宏紀 JJUG CCC 2025 FALL

2.

⾃⼰紹介 星原 宏紀 (Koki Hoshihara) ウェルスナビ株式会社 サービス基盤グループ ソフトウェアエンジニアリング (SWE) チーム ウェルスナビでは 共通ライブラリ開発(⼤規模バッチ / 認証系)、バックエンド開発、 新規システムのパフォーマンスチューニング、新技術導⼊ を推進 ひとこと 最近「UNIXという考え⽅」を読んで感銘を受けました。 ⻑尺のセッションは初めてです。よろしくお願いいたします! 2 @2025 WealthNavi Inc.

3.

アジェンダ はじめに 共通ライブラリ開発の機能選定 実例: PostgreSQLのunnestを活⽤したバルクインサート テストとドキュメンテーション まとめ 3

4.

1 はじめに © WealthNavi Inc. All Rights Reserved. 44

5.

はじめに 本セッションの焦点は、Java/Spring Boot前提の共通ライブラリにおける機能選定です。 ⽣産性と品質を⾼めつつ認知負荷を下げる「⼩さな共通ライブラリ」の機能スコープの 決め⽅を、現場での実践から得た選定基準に基づき共有します。 併せて、ライブラリ化に⾄った機能と実装例を紹介し、ライブラリ化の是⾮を判断する 指針を提供します。具体的には、主に以下の内容をお話しします。 ● ライブラリ化のメリット‧デメリット ● ライブラリ化を検討する際の機能選定基準 ● ライブラリ実装の実例 (unnestの活⽤、JDBCとJPAの併⽤) © WealthNavi Inc. All Rights Reserved. 5

6.

はじめに 本セッションのターゲットは、以下のいずれかに当てはまる⽅を想定します ● Javaでの開発経験がある ● Spring Boot利⽤を前提としたライブラリ開発に興味がある ● 共通ライブラリとして開発するテーマの選定⽅法に興味がある © WealthNavi Inc. All Rights Reserved. 6

7.

はじめに 本セッションにおける「共通ライブラリ」は、以下で定義されます ● 既存のOSSライブラリではなく、社内で内製開発されたJavaライブラリ (Spring BootなどのJavaのWebフレームワークではない) ● 開発された共通ライブラリはいずれもSpring Boot上で動作する ● (現時点では)社外にリポジトリを公開する予定はない ● 現状のソフトウェアスタックは、Java 21、Spring Boot 3.5系、Gradle 8.13系 ○ ウェルスナビ全体で進めているJavaおよびSpring Bootのバージョンアップと 平仄を合わせるために上記を選定しました © WealthNavi Inc. All Rights Reserved. 7

8.

2 1 共通ライブラリ開発の機能選定 © WealthNavi Inc. All Rights Reserved. 88

9.

共通ライブラリが存在しない場合の課題 横断的関⼼ごと(認証‧DB処理など)が個別に実装されてしまう ● 単⼀プロダクトやプロダクトの規模が⼩さい場合 ○ 影響範囲が⾒えづらい © WealthNavi Inc. All Rights Reserved. 9

10.

共通ライブラリが存在しない場合の課題 横断的関⼼ごと(認証‧DB処理など)が個別に実装されてしまう ● 単⼀プロダクトやプロダクトの規模が⼩さい場合 ○ ● 影響範囲が⾒えづらい プロダクトが増えたり複数プロジェクトが並⾏に動くようになった場合 ○ プロダクトごとに横断的関⼼ごとが実装されてしまう (=業務ロジックの実装に割く時間が奪われる) ○ 横断的関⼼ごとの結論は、課題によって異なることもある※1 ○ 横断的に修正する場合の作業コストが⼤きくなる ○ 実装ノウハウが分散することにも繋がりかねない © WealthNavi Inc. All Rights Reserved. ※1: https://speakerdeck.com/irof/glowing-multiple-applications-with-shared-strategy?slide=8 10

11.

共通ライブラリ導⼊のメリット 横断的関⼼ごと(認証‧DB処理など)の実装を集約できる ● 共通ライブラリを使⽤する開発者は、業務ロジックの実装に割ける時間が増える ● 共通ライブラリ開発者を中⼼にドキュメンテーションが⾏われるため 横断的関⼼ごとの実装ノウハウを集約しやすい ● 有事の際に、業務ロジックに強い開発者と当該ライブラリの実装に詳しい開発者で 問題解決に臨むことができる © WealthNavi Inc. All Rights Reserved. 11

12.

共通ライブラリ導⼊のメリット(デメリットを⾚字で追記) 横断的関⼼ごと(認証‧DB処理など)の実装を集約できる ● 共通ライブラリを使⽤する開発者は、業務ロジックの実装に割ける時間が増える ● 共通ライブラリ開発者を中⼼にドキュメンテーションが⾏われるため 横断的関⼼ごとの実装ノウハウを集約しやすい ● 有事の際に、業務ロジックに強い開発者と当該ライブラリの実装に詳しい開発者で 問題解決に臨むことができる ● ただし、銀の弾丸ではない ○ メンテナンスコストがかかる ■ バージョニング⽅式の検討および実践が必要(当社はSemVerを採⽤) ■ 社内のMavenリポジトリへのデプロイパイプライン設計‧実装が必要 © WealthNavi Inc. All Rights Reserved. 12

13.

共通ライブラリ導⼊に伴う懸念事項‧対応する考え⽅ 誰が共通ライブラリを保守するのか ● 共通ライブラリ開発者が不在に なってもメンテナンスできるか 専任のチームが存在することが望ましい ● ● ウェルスナビの場合はSWEチーム ただし、開発リソースは有限 共通ライブラリが負債にならないか ● 責務が⼤きなライブラリにより 開発スピードが落ちないか © WealthNavi Inc. All Rights Reserved. 13

14.

共通ライブラリ導⼊に伴う懸念事項‧対応する考え⽅ 誰が共通ライブラリを保守するのか ● 共通ライブラリ開発者が不在に なってもメンテナンスできるか 共通ライブラリが負債にならないか ● 責務が⼤きなライブラリにより 開発スピードが落ちないか 専任のチームが存在することが望ましい ● ● ウェルスナビの場合はSWEチーム ただし、開発リソースは有限 以下の項⽬をより多く満たす機能を 「⼩さな共通ライブラリ」として実装する 1. ⾞輪の再開発ではなく再利⽤性が⾼い 2. 横断的関⼼ごと 3. プラットフォーム固有の知識が必要 © WealthNavi Inc. All Rights Reserved. 14

15.

「⼩さな共通ライブラリ」の定義 1. 実装する機能のユースケースが明確である ● 例) 特定のSQL構⽂のSQLビルダー機能 ○ 実装: JavaのEntity定義をインプットにリフレクションでSQLを⽣成する 2. 全体の構成に対して、単⼀責務のみを実現している ● 例) OAuth2.0 の「Resource Server」に必要な機能のみを提供 (認証系の機能は別のライブラリで提供する) など ○ 実装: JWTのValidatorや全社標準のJWTのプロパティ設定を提供する © WealthNavi Inc. All Rights Reserved. 15

16.

ライブラリ化の選定基準を再整理 1. ⾞輪の再開発ではなく再利⽤性が⾼い ● ⼀度しか利⽤しない機能はライブラリ化しない ● 既存のOSSライブラリで提供されている機能は原則開発しない 2. 横断的関⼼ごと ● 横断的関⼼ごとは設計にコストがかかり難易度が⾼いことが多い 例) ロギング(OTel)、キュー操作(SQS)、キャッシュ操作(Redis) など 3. プラットフォーム固有の知識が必要 ● IDaaSやデータベース固有の実装が必要なもの 例) Auth0のアクセストークンにおけるJWTのプロパティなど※2 © WealthNavi Inc. All Rights Reserved. ※2: https://auth0.com/blog/jwt-access-tokens-profiles-now-in-ga/ 16

17.

ライブラリ化の選定基準を再整理(優先順位付け) must 1. ⾞輪の再開発ではなく再利⽤性が⾼い should ● ⼀度しか利⽤しない機能はライブラリ化しない ● 既存のOSSライブラリで提供されている機能は原則開発しない 2. 横断的関⼼ごと ● should 横断的関⼼ごとは設計にコストがかかり難易度が⾼いことが多い 例) ロギング(OTel)、キュー操作(SQS)、キャッシュ操作(Redis) など 3. プラットフォーム固有の知識が必要 ● IDaaSやデータベース固有の実装が必要なもの 例) Auth0のアクセストークンにおけるJWTのプロパティなど※2 © WealthNavi Inc. All Rights Reserved. ※2: https://auth0.com/blog/jwt-access-tokens-profiles-now-in-ga/ 17

18.

ライブラリ化の選定基準を再整理(優先順位付け) must 1. ⾞輪の再開発ではなく再利⽤性が⾼い should ● ⼀度しか利⽤しない機能はライブラリ化しない ● 既存のOSSライブラリで提供されている機能は原則開発しない 3つ⽬の実装は様々な意⾒がありますが より専⾨的な知識が要求されるため以下 2点を満たす場合に実装することを推奨 ● 横断的関⼼ごとは設計にコストがかかり難易度が⾼いことが多い 「当該プラットフォームの有識者が揃っ 例) ロギング(OTel)、キュー操作(SQS)、キャッシュ操作(Redis) など ている」 should 「プラットフォームに依存することのデ メリットをメリットが上回る」 3. プラットフォーム固有の知識が必要 2. 横断的関⼼ごと ● IDaaSやデータベース固有の実装が必要なもの 例) Auth0のアクセストークンにおけるJWTのプロパティなど※2 © WealthNavi Inc. All Rights Reserved. ※2: https://auth0.com/blog/jwt-access-tokens-profiles-now-in-ga/ 18

19.

3 1 実例: PostgreSQLのunnestを活⽤した バルクインサート © WealthNavi Inc. All Rights Reserved. 19

20.

プロジェクト概要とunnestの採⽤に⾄った経緯 新規プロジェクトにて、 Java(Spring Boot)‧PostgreSQLの組み合わせで⼤規模バッチシステムを開発 ● 外部キー制約をサポートし、パーティション導⼊できる点からPostgreSQLを採⽤ ● ⼤規模バッチであることから、バルクインサートを実装する必要があった ● ウェルスナビではMySQLが中⼼であり、PostgreSQLの開発ノウハウは少なかった PostgreSQLバルクインサートの実装⽅法を調査し、SQLレベルでの性能を検証した ● 調査を踏まえチーム内レビューで以下の点を評価し、unnestを使ったINSERT⽅式を採⽤ ○ SQLのパラメータ数の上限を実質的に無視できる ○ パラメータ数の考慮が不要でありながら⼗分に⾼速※3 © WealthNavi Inc. All Rights Reserved. ※3: https://www.tigerdata.com/blog/boosting-postgres-insert-performance 20

21.

従来のVALUES句を使ったINSERTとunnest(配列展開)を使ったINSERTの違い INSERTにおける両者の違いを表に整理する VALUES句を使ったINSERT 構造/ INSERT⽂のサンプル unnestを使ったINSERT VALUES (:a1, :b1, :c1), :col1 -> [ a1, a2, a3 ] (:a2, :b2, :c2), :col2 -> [ b1, b2, b3 ] (:a3, :b3, :c3) :col3 -> [ c1, c2, c3 ] INSERT INTO t(c1,c2,c3) INSERT INTO t(c1,c2,c3) VALUES (:a1,:b1,:c1), SELECT * FROM unnest(:col1,:col2,:col3) (:a2,:b2,:c2), AS t(c1, c2, c3) (:a3,:b3,:c3) パラメータ数/ SQLの⻑さ ⾏×列/⻑くなりやすい 列だけ/短く保てる SQLの実⾏負荷 ⾏数に⽐例して増加 列数に概ね⽐例 © WealthNavi Inc. All Rights Reserved. 21

22.

参考: 複数⾏INSERTとunnestを⽤いたINSERTの性能⽐較 PostgreSQL は配列を⾏に展開する関数「unnest」をサポートする そこで、INSERT時に複数⾏のVALUES句を使⽤するケースとunnestを使⽤したケースの 性能を⽐較したところPlanning Time、Execution Timeともにunnestの⽅が優れていた ※M3 MacBookPro上でPostgreSQL17(コンテナ)で検証した結果を添付 © WealthNavi Inc. All Rights Reserved. 22

23.

要求仕様に対するライブラリ化の是⾮を検討 要求仕様 (再掲)ライブラリ化検討時の重要項⽬ 1. (複数バッチで使⽤されるため) unnest⽤のSQLビルダーを提供してほしい 1. ⾞輪の再開発ではなく再利⽤性が⾼い 2. (処理次第でJPAのメソッドを使いたいため) Spring Data JPAのJpaRepositoryと Spring JDBCのRepositoryの共存 3. プラットフォーム固有の知識が必要 2. 横断的関⼼ごと ライブラリ化選定基準の1~3をすべて満たすため、ライブラリ化が決定 © WealthNavi Inc. All Rights Reserved. 23

24.

要求仕様 1 に対する実装イメージ(1/4) 要求仕様 1. (複数バッチで使⽤されるため) unnest⽤のSQLビルダーを提供してほしい 実装 ● ライブラリ側の実装全量はキャプチャの7クラスのみ © WealthNavi Inc. All Rights Reserved. 24

25.

要求仕様 1 に対する実装イメージ(2/4) ライブラリで実装するクラスの⼀覧 ファイル名 概要説明 ● ● ● Entityのリストをバルクインサート用の配列に変換する基底クラス Javaのフィールドを指定された型の配列に変換するユーティリティを提供 アプリケーション側で AbstractBulkEntityを継承するクラスを実装する ● JdbcClientを用いてunnest構文を使ったバルクインサート SQLを作成・実行す る BulkColumn ● データベースのカラム名と PostgreSQLの型を保持するアノテーション バルクインサート用 の独自例外クラス群 ● ● バルクインサート用の独自例外を定義する 例外ハンドリングはライブラリ側で実装する AbstractBulkEntity BulkInsertExecutor © WealthNavi Inc. All Rights Reserved. 25

26.

要求仕様 1 に対する実装イメージ(3/4) 次スライドで以下の役割について実装イメージを説明 役割 Javaの型を DBの型に変換する ユーティリティ (継承用 ) INSERT文の 組み立て・実行 © WealthNavi Inc. All Rights Reserved. 概要説明 ● ● ● Entityのリストをバルクインサート用の配列に変換する基底クラス Javaのフィールドを指定された型の配列に変換するユーティリティを提供 アプリケーション側で AbstractBulkEntityを継承するクラスを実装する ● JdbcClientを用いてunnest構文を使ったバルクインサート SQLを作成・実行す る 26

27.

要求仕様 1 に対する実装イメージ(4/4) unnestを⽤いたバルクインサートの実⾏イメージ Handler Service Repository (tx境界) 1. ‧処理のエントリーポイント 2. ‧変換ユーティリティを継承したクラスを引数に取り Javaフィールドをテーブルの各カラムの型にマッピング 3. ‧INSERTで使⽤する以下の形式のSQLを組み⽴てる INSERT INTO ... SELECT * FROM unnest(...) as t(...) 4. ‧INSERTを実⾏しデータベースに永続化 ‧処理結果をServiceに返却 © WealthNavi Inc. All Rights Reserved. 27

28.

要求仕様 2 に対する実装イメージ 要求仕様 1. unnest⽤のSQLビルダーい 実装イメージ 2. Spring Data JPAのJpaRepositoryと Spring JDBCのRepositoryの共存 JpaRepository (interface) BookRepository (interface) extends JpaRepository, BookJdbcClientRepository Spring Dataが ランタイムに 合成※4 BookJdbcClientRepository (interface) BookJdbcClientRepositoryImpl (class) © WealthNavi Inc. All Rights Reserved. ※4:https://spring.pleiades.io/spring-data/jpa/reference/repositories/custom-implementations.html 28

29.
[beta]
Spring JDBCのみを使⽤する場合
本来、以下のようにSpring JDBCに依存したRepositoryのみを使⽤することになる
// package, importは省略
@Repository
public class BookJdbcRepository {
private final JdbcClient jdbc;
public BookJdbcRepository(JdbcClient jdbc) { this.jdbc = jdbc; }
//処理省略
String sql = """
INSERT INTO book (title, author)
SELECT * FROM unnest(:title, :author) AS t(title, author)
""";
return jdbc.sql(sql)
.param("title",

title)

.param("author", author)
.update();
}
}
© WealthNavi Inc. All Rights Reserved.

29

30.
[beta]
Spring JDBCとSpring Data JPAの併⽤(1/3)
以下のように実装することでSpring JDBCとSpring Data JPAを併⽤可能
JPA側の本体のRepository
package com.example.book.repository;
import com.example.book.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository
extends JpaRepository<Book, Long>, BookJdbcRepository {
// JPAのメソッドもここに定義可
}

© WealthNavi Inc. All Rights Reserved.

30

31.

Spring JDBCとSpring Data JPAの併⽤(2/3) 以下のように実装することでSpring JDBCとSpring Data JPAを併⽤可能 カスタム断⽚のInterface(JDBC側) package com.example.book.repository; import com.example.book.domain.Book; import java.util.List; public interface BookJdbcRepository { int bulkInsert(List<Book> books); } © WealthNavi Inc. All Rights Reserved. 31

32.
[beta]
Spring JDBCとSpring Data JPAの併⽤(3/3)
以下のように実装することでSpring JDBCとSpring Data JPAを併⽤可能
カスタム断⽚のInterface(JDBC側)のImplクラス(必ず <Interface名>Impl) にする必要あり
// package, importは省略
@Repository
public class BookJdbcRepositoryImpl implements BookJdbcRepository {
private final JdbcClient jdbc;
public BookJdbcRepositoryImpl(JdbcClient jdbc) { this.jdbc = jdbc; }
@Override
public int bulkInsert(List<Book> books) {
String[] title

= books.stream().map(Book::getTitle).toArray(String[]::new);

String[] author = books.stream().map(Book::getAuthor).toArray(String[]::new);
// 以降は P.28と同様のため省略
}
}
© WealthNavi Inc. All Rights Reserved.

32

33.

4 1 テストとドキュメンテーション © WealthNavi Inc. All Rights Reserved. 33

34.

共通ライブラリのテスト⽅針 ライブラリではユニットテスト、統合テストともに実施する ● ユニットテスト ○ 条件網羅、分岐網羅は100%を⽬指す ■ ● 可視化 ○ ● 難しければ、クラス/メソッド分割でよりテストしやすいコードに修正 JaCoCoカバレッジをコード品質管理ツール(当社ではQodana※5)に連携し可視化 統合テスト ○ サンプルアプリケーションを⽤いたテストを⾏う ○ 公開API、設定、独⾃例外のテストを⾏う © WealthNavi Inc. All Rights Reserved. ※5:https://www.jetbrains.com/ja-jp/qodana/ 34

35.

共通ライブラリを取り込むアプリケーションのテスト⽅針 アプリケーションでは必要最⼩限の統合テストのみを⾏う ● ユニットテスト ○ ● ● アプリケーション⾃⾝のユニットテストは⾏うが、ライブラリ内部に踏み込むテ ストはしない(⼆重化を避ける) 統合テスト ○ アプリケーションで使⽤するライブラリの公開APIのテストや設定値のバインド、 Bean起動などをテストする Tips ○ 初期はUtilクラスとして実装し、ライブラリ化後に Gradle/Maven 取り込みに切り 替えると、取り込み時のBean関連の問題切り分けが容易 © WealthNavi Inc. All Rights Reserved. 35

36.

共通ライブラリのドキュメンテーション Javadoc ● ● クラスレベルのJavadocを必ず書く メソッドは概要と(返却する例外が明確なときは@throws)、 @param、@returnを記載 ○ レビュアーの指摘負荷を減らすために上記の内容をcopilot-instructions※6に記載 GitHubのWiki ● GitHubではMarkdownファイルをWiki化できるため、この仕組みを利⽤ Design Doc ● ● 全社横断で閲覧可能なNotionページにDesign Docとして配置 実装する/しないの整理や、どの機能をどのVersionでリリースするかを記載 © WealthNavi Inc. All Rights Reserved. ※6:https://docs.github.com/ja/copilot/how-tos/configure-custom-instructions/add-repository-instructions#creating-custom-instructions 36

37.

5 1 まとめ © WealthNavi Inc. All Rights Reserved. 37

38.

まとめ 本セッションでは、各章で以下の内容をお話してきました。 1. ターゲットおよび共通ライブラリの定義 2. 共通ライブラリのメリット/デメリット そしてライブラリ化の選定基準 3. PostgreSQLのunnestを活⽤したバルクインサートの ライブラリ化までの過程 4. 共通ライブラリにおけるテストとドキュメンテーション 2章を厚く作成したのは「作成したライブラリは誰かが保守する必要があること」とその リスクに対して「⼩さな共通ライブラリ」を作成するメリットを伝えたかったためです。 今回のセッションが開発者の皆さまの参考になれば幸いです。 それでは、素敵なライブラリ開発ライフを! © WealthNavi Inc. All Rights Reserved. 38

39.

重要な注意事項 ● 本資料は、断定的判断を提供するものではなく、情報を提供することのみを目的としており、いかなる種類の商品も勧誘 するものではありません。最終的な決定は、お客様自身で判断するものとし、当社はこれに一切関与せず、また、一切の 責任を負いません。 ● 本資料には将来の出来事に関する予想が含まれている場合がありますが、それらは予想であり、また、本資料の内容の 正確性、信頼性、完全性、適時性等を一切保証するものではありません。本資料に基づいて被ったいかなる損害について も、当社は一切の責任を負いません。また、当社は、新しい情報や将来の出来事その他の情報について、更新又は訂正 する義務を負いません。 ● 本資料を利用することによりお客様に生じた直接的損害、間接的損害、派生的損害その他いかなる損害についても、当社 は一切の責任を負いません。 商号等:ウェルスナビ株式会社 金融商品取引業者 関東財務局長(金商) 第2884号 加入協会:日本証券業協会 一般社団法人日本投資顧問業協会 © WealthNavi Inc. All Rights Reserved. 39