Spring Boot と一般ライブラリの折り合いのつけかた

Spring Boot と一般ライブラリの折り合いのつけかた

JJUG CCC 2018 Spring での発表資料です。

#jjug_ccc #ccc_g4

1a18bf1e50d7d2bdfe52a6c9fceec244?s=128

saiya_moebius

May 24, 2018
Tweet

Transcript

  1. Spring Boot と一般ライブラリの 折り合いのつけかた JJUG CCC 2018 Spring # j

    j u g _ c c c # c c c _ g 4 エムスリー 株式会社 矢崎 聖也 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 1
  2. 自己紹介 矢崎 聖也 @saiya_moebius エムスリー 株式会社 所属 17 年の歴史あるプロダクトの microservice

    化を推進中 Server Side + 主にサー バー サイド中心のエンジニア Java, Rails 等 ( 実は言語として好きなのは C#) 最近は terraform や Lambda も結構書いている 新規プロジェクトや大規模リファクタリングを複数手がける それらの足回り整備を通して得たノウハウを今回紹介 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 2
  3. Spring Boot とそれ以外のライブラリ Spring Initializr で Spring Boot 一式が動くプロジェクトは簡単に作れ る。

    しかし、 実際の web アプリケー ションでは追加でさまざまなライブラリ を使いたくなる ( 世の中や社内のコー ド資産の活用のため)。 とりあえず b u i l d . g r a d l e , p o m . x m l に dependency を追加すれば動 くが... #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 3
  4. ライブラリを追加によって発生しがちな課題 DI や Configuration の課題: 1. @ A u t

    o w i r e d 等の DI 機能を活用できていない ライブラリ側に s t a t i c やリフレクションがあるとなりがち 2. @ C o n f i g u r a t i o n が膨らんでしまい初期構築やメンテが大変 プロジェクトの管理の課題: 3. @ C o n f i g u r a t i o n や b u i l d . g r a d l e , p o m . x m l が複数プロジェ クト間でコピペ 4. ライブラリのバー ジョンを管理しきれておらず、 バー ジョン競合や class の競合による変な挙動や脆弱性に悩まされる #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 4
  5. 足回りの整備 アプリケー ションの本質的なコー ドに集中するために... 1. DI, Spring らしい方法で各種ライブラリのオブジェクトを使う 2. ライブラリの設定を簡潔に制御する

    3. プロジェクト間で共有できる足回りを共有する 4. ライブラリの脆弱性対応・ バー ジョンアップを安全に行う こういったストレスの小さい環境を整備するための @ C o n f i g u r a t i o n や b u i l d . g r a d l e , p o m . x m l のベストプラクティスをご紹介。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 5
  6. 1. 一般のライブラリを DI らしく使う DI, Spring らしい方法で各種ライブラリのオブジェクトを使うことで、 テストしやすさや保守性を向上する。 #jjug_ccc #ccc_g4

    Spring Boot と一般ライブラリの折り合いのつけかた 6
  7. DI らしく: まずは DI する s t a t i

    c f i n a l S e n t r y C l i e n t S E N T R Y _ C L I E N T = . . . ; S E N T R Y _ C L I E N T . s e n d E v e n t ( . . . ) ; ↓ @ A u t o w i r e d S e n t r y C l i e n t s e n t r y C l i e n t ; / / D I 先 s e n t r y C l i e n t . s e n d E v e n t ( . . . ) ; @ C o n f i g u r a t i o n c l a s s S e n t r y C o n f i g u r a t i o n { @ B e a n p u b l i c S e n t r y C l i e n t s e n t r y C l i e n t ( ) { r e t u r n . . . ; / / D I で注入するオブジェクト } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 7
  8. DI らしく: Constructor injection とテスト 外部ライブラリのクラスは mock 化したくなることが多い。 Constructor injection

    にしておけば mock を素直に注入できる。 @ C o m p o n e n t c l a s s テスト対象 { p r i v a t e S e n t r y C l i e n t s e n t r y C l i e n t ; @ A u t o w i r e d / / フィー ルドでなくコンストラクタ引数に D I p u b l i c テスト対象( S e n t r y C l i e n t s e n t r y C l i e n t ) { t h i s . s e n t r y C l i e n t = s e n t r y C l i e n t ; } } A l w a y s u s e c o n s t r u c t o r b a s e d d e p e n d e n c y i n j e c t i o n i n y o u r b e a n s ‑ How not to hate Spring in 2016 ‑ spring.io テスト対象 t a r g e t = n e w テスト対象( m o c k ( S e n t r y C l i e n t . c l a s s ) ) ; #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 8
  9. DI らしく: 都度 new しないよう心がける 都度 new が必要ではないのに都度 new しているケー

    ス: v o i d d o S o m e t h i n g ( ) { A m a z o n S 3 s 3 c l i e n t = n e w A m a z o n S 3 C l i e n t ( . . . ) ; s 3 c l i e n t . p u t O b j e c t ( . . . ) ; / / 公式のコー ド例をそのままコピペするとこうなる } こういったクラスが都度 new されがち: O b j e c t M a p p e r (Jackson の JSON <‑> Object mapper) A m a z o n S 3 C l i e n t 普通に DI すれば一元管理や mock 注入も可能な上、 初期化のエラー を アプリケー ション起動時に知ることができる。 ただし thread‑safe であることは要確認 ( S i m p l e D a t e F o r m a t などは NG)。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 9
  10. DI らしく: 都度 new が必要なものの DI 本当に都度 new する必要がある場合、Factory を

    DI するのがオススメ: @ C o n f i g u r a t i o n c l a s s S o m e t h i n g C o n f i g { @ B e a n S o m e t h i n g F a c t o r y s o m e t h i n g F a c t o r y ( ) { . . . } } @ C o m p o n e n t c l a s s A C o m p o n e n t { @ A u t o w i r e d A C o m p o n e n t ( S o m e t h i n g F a c t o r y s o m e t h i n g F a c t o r y ) { . . . } v o i d d o S o m e t h i n g ( ) { S o m e t h i n g s o m e t h i n g = s o m e t h i n g F a c t o r y . c r e a t e ( ) ; s o m e t h i n g . d o S o m e t h i n g ( . . . ) ; } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 10
  11. Prototype bean や BeanFactory の誤用 @ S c o p

    e ( S C O P E _ P R O T O T Y P E ) や BeanFactory は都度 new する意図に 適さないので留意のこと。 Factory を DI する方が、 小さいオー バー ヘッドかつ、 安全・ ライフサイ クルが明確になる。 Tips: Prototype Bean は Autowire 先に依存する Bean を生成する用途では有用。 例え ば Autowire 先のクラス名を元に Logger を生成するなど。 @ B e a n @ S c o p e ( S C O P E _ P R O T O T Y P E ) H o g e A p i R e q u e s t r e q u e s t ( ) { r e t u r n n e w . . . } @ C o m p o n e n t c l a s s H o g e A p i S e r v i c e { @ A u t o w i r e d H o g e A p i R e q u e s t r e q u e s t / / 常に同じインスタンス! / / 呼び出し元の全 B e a n を漏れなく P r o t o t y p e にすればよいが. . . / / 手動で g e t B e a n ( H o g e A p i R e q u e s t ) する手もあるが. . . } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 11
  12. DI らしく: リクエストに依存する注入 コンストラクタやフィー ルドへの注入(Autowired) だけが DI ではない。 H a

    n d l e r M e t h o d A r g u m e n t R e s o l v e r を作れば controller の引数に オブジェクトを注入できる 例えば、User Agent 情報を解析した結果のオブジェクトを controller 引数に入れることができる H a n d l e r I n t e r c e p t o r A d a p t e r を作れば M o d e l A n d V i e w にオブ ジェクトを注入できる リクエストやセッション内容に依存するオブジェクトは、 上記の手法で 注入することで DI の恩恵を受けつつもリクエストに閉じた注入が可能に なる。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 12
  13. DI らしく: static やグロー バル変数への対処 DI を前提としないライブラリによくある仕様: static メソッドとしての機能提供 S

    y s t e m # g e t P r o p e r t y , S e r v l e t C o n t e x t # g e t A t t r i b u t e といっ たグロー バル状態への依存 T h r e a d L o c a l の状態の依存 これらも DI に寄せたい: テストなどでの注入のしやすさ オブジェクトの初期化・ 設定処理を一元化し保守性向上 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 13
  14. DI らしく: static method と DI の橋渡し static method をラップするオブジェクトを

    Bean にする: @ C o m p o n e n t c l a s s A c c o u n t S e r v i c e { / / これを D I で取得して使う p u b l i c A c c o u n t g e t C u r r e n t A c c o u n t ( ) { / / 以下の s t a t i c m e t h o d はここ以外からは直接呼ばない r e t u r n A c c o u n t U t i l . g e t C u r r e n t A c c o u n t ( ) ; } } これだけで、 テスト時に mock/spy に差し替えるのが容易になる ログを出す・ エラー ハンドリングを変える、 といった改変も 1 箇所 で行える #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 14
  15. DI らしく: グロー バル状態の初期化 Bean 初期化直前に S y s t

    e m # s e t P r o p e r t y 等してしまうのが手。 @ C o n f i g u r a t i o n c l a s s S e n t r y C o n f i g u r a t i o n { @ B e a n p u b l i c S e n t r y C l i e n t s e n t r y C l i e n t ( / / a p p l i c a t i o n . p r o p e r t i e s / y m l の値を取得 @ V a l u e ( " s e n t r y . d s n " ) S t r i n g d s n ) { S y s t e m . s e t P r o p e r t y ( " s e n t r y . d s n " , d s n ) ; r e t u r n . . . ; } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 15
  16. DI らしく: グロー バル状態と Bean の順序 Configuration の実行順は不定なので、 順序を明示する必要あり。 @

    C o n f i g u r a t i o n c l a s s S e n t r y C o n f i g u r a t i o n { @ B e a n p u b l i c S e n t r y C l i e n t s e n t r y C l i e n t ( ) { S y s t e m . s e t P r o p e r t y ( " s e n t r y . d s n " , . . . ) r e t u r n . . . ; } } @ C o n f i g u r a t i o n @ A u t o C o n f i g u r e A f t e r ( S e n t r y C o n f i g u r a t i o n . c l a s s ) / / ← ここ c l a s s M y A p i C l i e n t C o n f i g { p u b l i c A p i C l i e n t a p i C l i e n t ( ) { / / 以下が内部で " s e n t r y . d s n " に依存しているとする r e t u r n n e w A p i C l i e n t ( ) ; } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 16
  17. ライブラリ側から new されるケー ス ライブラリ側が以下のようなデザインになっているケー スもある: ライブラリの初期化時に callback のクラス名を文字列で渡す ライブラリ側からそのクラスが

    n e w され呼び出される S e r v l e t F i l t e r ( 設定パラメタに文字列しか受け取れない) やプラグイ ン機構を提供するライブラリでありがち。 ライブラリ側から n e w されてしまったオブジェクトは DI 管理外になっ てしまい、 その中で @ A u t o w i r e d などの DI 機能を利用できない。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 17
  18. DI らしく: DI 管理下の Bean へ処理を移譲 DI 管理外のオブジェクトから DI 管理下の

    Bean へ処理を移譲する。 1. 処理の実体を Bean として実装 2. Bean への参照を T h r e a d L o c a l や S e r v l e t C o n t e x t に保持 3. ライブラリ側から n e w されてしまったオブジェクトは上記から Bean の参照を取得 4. 処理を Bean に移譲 5. 移譲先の Bean では DI のフル機能が使えるので、DI 管理下の Component を自由に使える ( Spring MVC の D e l e g a t i n g F i l t e r P r o x y が実例 ) #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 18
  19. DI らしく: Bean への処理の移譲 ( 概念コー ド) @ C o

    n f i g u r a t i o n c l a s s L o g i n C l i e n t C o n f i g { p u b l i c L o g i n C l i e n t c l i e n t ( I m p l B e a n i m p l ) { r e t u r n n e w L o g i n C l i e n t ( " c o m . e x a m p l e . L o g i n C a l l b a c k " ) } } c l a s s L o g i n C a l l b a c k { / / D I 管理外から n e w される p u b l i c v o i d l o g i n ( S t r i n g u s e r ) { L o g i n C a l l b a c k I m p l . c u r r e n t I n s t a n c e . g e t ( ) . l o g i n ( u s e r ) ; } } @ C o m p o n e n t c l a s s L o g i n C a l l b a c k I m p l { T h r e a d L o c a l c u r r e n t I n s t a n c e ; p u b l i c I m p l B e a n ( . . . ) { c u r r e n t I n s t a n c e . s e t ( t h i s ) ; } p u b l i c v o i d l o g i n ( S t r i n g u s e r ) { . . . } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 19
  20. Bean への処理の移譲 ( 補足) 概念コー ドからは省略したが気をつけたほうが良い点: 初期化の順序 移譲先 Bean を移譲元より先に作って

    ThreadLocal に入れな いとならない 先の例で L o g i n C l i e n t を生成するメソッドの引数に、 使わ ないのに I m p l B e a n を Autowired している背景 T h r e a d L o c a l からのリー ク 昔ながらの Servlet 環境では ThreadLocal に入れたままにする とメモリリー クで困ることがある 初期化完了時に ThreadLocal をクリアしておくと良い #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 20
  21. DI らしく: Bean の Lifecycle の活用 A u t o

    C l o s e a b l e , C l o s e a b l e や D i s p o s a b l e B e a n な Bean はアプリ ケー ション終了時に自動で c l o s e ( ) してもらえる。 しかし、 それ以外のメソッド( d i s c o n n e c t ( ) とか s h u t d o w n ( ) と か...) も、 d e s t r o y M e t h o d として指定すればアプリケー ション終了時に 自動で呼び出してもらえ、 クリー ンなシャットダウンが可能になる。 @ B e a n ( d e s t r o y M e t h o d = " s h u t d o w n " ) p u b l i c L e g a c y C l i e n t m y C l i e n t ( ) { . . . } 終了時処理のためだけに A u t o C l o s e a b l e な型でラップしたりする必要 はない。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 21
  22. DI らしく: まとめ Constructor injection でテストフレンドリー に 都度 new する必要がある場合は

    Factory を Bean にすると良い Thread‑safe ならばそもそも都度 new しなくても良い リクエスト毎に定まるオブジェクトならば Controller 引数や Model へ注入すると良い 一見すると DI と相性が悪いケー スでも、 一手間加えれば DI に出来 ることが多い 多くの場合はここまでに述べた手法が使えるはず 諦めない心をもって臨もう #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 22
  23. 2. @ C o n f i g u r

    a t i o n の記述効率化 DI に寄せる結果として @ C o n f i g u r a t i o n が膨らでいく。 そのためライブラリの設定を簡潔に制御する。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 23
  24. @Value で設定を受け取る例 @ B e a n H o g

    e A p i C l i e n t h o g e A p i C l i e n t ( @ V a l u e ( " h o g e . u r l " ) S t r i n g u r l , @ V a l u e ( " h o g e . a p i K e y " ) S t r i n g a p i K e y , @ V a l u e ( " h o g e . t i m e o u t " ) l o n g t i m e o u t , / / . . . ) { H o g e A p i C l i e n t c l i e n t = n e w H o g e A p i C l i e n t ( ) ; c l i e n t . s e t U r l ( u r l ) ; c l i e n t . s e t A p i K e y ( a p i K e y ) ; c l i e n t . s e t T i m e o u t ( t i m e o u t ) ; / / . . . r e t u r n c l i e n t ; } 問題なく動くが、 設定項目数に比例して冗長になる。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 24
  25. 記述効率化: @ C o n f i g u r

    a t i o n P r o p e r t i e s Spring Boot の @ C o n f i g u r a t i o n P r o p e r t i e s がオススメ: @ B e a n @ C o n f i g u r a t i o n P r o p e r t i e s ( p r e f i x = " h o g e " ) H o g e A p i C l i e n t h o g e A p i C l i e n t ( ) { r e t u r n n e w H o g e A p i C l i e n t ( ) ; } / / h o g e . 以下の全プロパティを H o g e A p i C l i e n t の s e t t e r へ代入 設定対象のプロパティが増減しても、application.properties/yml を更新 するだけで OK #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 25
  26. 記述効率化: @ C o n f i g u r

    a t i o n P r o p e r t i e s のカスタマイズ getter, setter を override すればカスタマイズも可: @ V a l i d a t e d p r i v a t e c l a s s M y H o g e A p i C l i e n t e x t e n d s H o g e A p i C l i e n t { @ O v e r r i d e @ N o t E m p t y @ U R L / / J S R - 3 0 3 の v a l i d a t o r を使える p u b l i c S t r i n g g e t U r l ( ) { r e t u r n s u p e r . g e t U r l ( ) ; } @ O v e r r i d e p u b l i c v o i d s e t U r l ( S t r i n g u r l ) { / * 任意の処理* / } } @ B e a n @ C o n f i g u r a t i o n P r o p e r t i e s ( p r e f i x = " h o g e " ) H o g e A p i C l i e n t h o g e A p i C l i e n t ( ) { r e t u r n n e w M y H o g e A p i C l i e n t ( ) ; } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 26
  27. 記述効率化: Map をラップ 設定を M a p で受け取ったり s e

    t ( S t r i n g , S t r i n g ) といったメソッ ドで受け取るライブラリもある。 @ C o n f i g u r a t i o n P r o p e r t i e s 化すれば以下のメリットを得られる: 型安全かつバリデー ションも可能 使っている設定・ 使っていない設定が明確になる @ C o m p o n e n t / / D I でこのインスタンスを取得可能 @ V a l i d a t e d @ C o n f i g u r a t i o n P r o p e r t i e s ( p r e f i x = " h o g e " ) c l a s s H o g e A p i C l i e n t C o n f i g { / * * この m a p をライブラリ側に渡す * / p r o t e c t e d M a p < S t r i n g , S t r i n g > m a p = n e w H a s h M a p < > ( ) ; @ N o t E m p t y @ U R L p u b l i c S t r i n g g e t U r l ( ) { r e t u r n m a p . g e t ( . . . ) ; } p u b l i c v o i d s e t U r l ( S t r i n g u r l ) { m a p . p u t ( . . . ) ; } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 27
  28. Bean のコレクションでありがちなパター ン Bean に複数の Bean を与える必要がある場合にありがちなコー ド: @ B

    e a n S o m e t h i n g s o m e t h i n g ( @ Q u a l i f i e r ( " s u b A " ) S u b C o m p o n e n t s u b A , @ Q u a l i f i e r ( " s u b B " ) S u b C o m p o n e n t s u b B , @ Q u a l i f i e r ( " s u b C " ) S u b C o m p o n e n t s u b C ) { S o m e t h i n g s o m e t h i n g = n e w S o m e t h i n g ( ) ; s o m e t h i n g . a d d ( s u b A ) ; s o m e t h i n g . a d d ( s u b B ) ; s o m e t h i n g . a d d ( s u b C ) ; / / . . . r e t u r n s o m e t h i n g ; } @ B e a n S u b C o m p o n e n t s u b A ( ) { . . . } @ B e a n S u b C o m p o n e n t s u b B ( ) { . . . } @ B e a n S u b C o m p o n e n t s u b C ( ) { . . . } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 28
  29. 記述効率化: Bean を列挙する Spring に Bean を列挙させる方が記述効率が良い: @ B e

    a n S o m e t h i n g s o m e t h i n g ( L i s t < S u b C o m p o n e n t > c o m p o n e n t s ) { S o m e t h i n g s o m e t h i n g = n e w S o m e t h i n g ( ) ; c o m p o n e n t s . f o r E a c h ( s o m e t h i n g : : a d d ) ; r e t u r n s o m e t h i n g ; } @ B e a n @ O r d e r ( 1 0 ) S u b C o m p o n e n t s u b A ( ) { . . . } @ B e a n @ O r d e r ( 2 0 ) S u b C o m p o n e n t s u b B ( ) { . . . } @ B e a n @ O r d e r ( 3 0 ) S u b C o m p o n e n t s u b C ( ) { . . . } @ Q u a l i f i e r ( " B e a n 名" ) の名前を間違えるリスクもない ハー ドコー ドでないため、 別の Config から @Bean を追加するこ とも容易 F i l t e r や C o n f i g u r e r A d a p t e r などと同じ原理である。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 29
  30. 記述効率化: 動的に Bean を生成 設定ファイルや DB 等の情報を元に Bean を動的に作ることも可能。 動的に作られる

    Bean の中で Autowire や AOP などを活用したり、 それ らの Bean を List などとして取得して呼び出すことができる。 動的 Bean 生成に使うものは大抵の場合これだけ: ImportBeanDefinitionRegistrar BeanDefinitionRegistry#registerBeanDefinition BeanDefinitionBuilder.genericBeanDefinition setAutowireMode addConstructorArgValue #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 30
  31. 動的な Bean の生成 擬似コー ド この程度のコー ド量で Bean の定義を動的生成できる: /

    * * このクラスを C o n f i g u r a t i o n から @ I m p o r t する * / c l a s s M y B e a n R e g i s t r a r i m p l e m e n t s I m p o r t B e a n D e f i n i t i o n R e g i s t r a r { v o i d r e g i s t e r B e a n D e f i n i t i o n s ( . . . , B e a n D e f i n i t i o n R e g i s t r y r e g i s t r y ) { r e g i s t r y . r e g i s t e r B e a n D e f i n i t i o n ( " b e a n 名" , B e a n D e f i n i t i o n B u i l d e r . g e n e r i c B e a n D e f i n i t i o n ( B e a n の実装型) / / コンストラクタへの A u t o w i r e を有効にする . s e t A u t o w i r e M o d e ( A b s t r a c t B e a n D e f i n i t i o n . A U T O W I R E _ C O N S T R U C T O R ) / / コンストラクタ引数を明示的に与える場合は以下 . a d d C o n s t r u c t o r A r g V a l u e ( . . . ) ) } } #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 31
  32. 記述効率化: まとめ @ C o n f i g u

    r a t i o n P r o p e r t i e s で DRY に書ける Validation も可能 M a p も型のあるオブジェクトでラップしよう Bean の列挙は L i s t < B e a n の型> と @ O r d e r がシンプル Bean を追加しやすくなるため、 拡張性も良くなる 動的な Bean 生成も意外と簡単 外部ファイル等デー タに応じて Bean を制御する作り込み可能 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 32
  33. 3. プロジェクト間の @ C o n f i g u

    r a t i o n の共 有 ( 脱コピペ) 単一の web アプリケー ションだけで完結せず、 複数のアプリケー ション を Spring Boot で作ることも少なくない ( 特に microservice 志向の場合)。 プロジェクト毎に個別に @ C o n f i g u r a t i o n を改善・ メンテナンスする のはもったいない。 プロジェクトごとの差に対応しつつも、 できるだけ共通化する。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 33
  34. Configuration の共有 基本的には以下のアプロー チになる: よく使う Configuration を入れた artifact を作っておく 上記の

    artifact に各アプリケー ションから dependency を張る アプリケー ション側で Configuration を @Import する とはいえ、 工夫しないと @Import がわかりにくくなりがち: @ S p r i n g B o o t A p p l i c a t i o n @ I m p o r t ( F o o C o n f i g . c l a s s , B a r C o n f i g . c l a s s , B a r S u b C o n f i g . c l a s s , B a z C o n f i g . c l a s s , / / . . . / / どの C o n f i g がどの C o n f i g に依存しているのか? / / このアプリ固有の C o n f i g とアプリ共通の C o n f i g はどれか? ) c l a s s M y A p p l i c a t i o n #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 34
  35. Config 共有: @ I m p o r t の連鎖

    @ I m p o r t ( C o n f i g r a t i o n クラス. c l a s s ) を使えば、Configuration から 他の Configuration を連鎖的に読み込むことができる。 @ C o n f i g u r a t i o n @ I m p o r t ( F o o S u b C o n f i g . c l a s s ) / / 連鎖的に読み込む c l a s s F o o C o n f i g { . . . } @ C o n f i g u r a t i o n c l a s s F o o S u b C o n f i g { . . . } Import 元を読み込めばそれが依存する Config (Import 先) も読み込まれ るので、 アプリケー ションは大元の Config のみを Import すれば良い。 アプリケー ション側が Config の依存関係を知る必要がなくなる。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 35
  36. Config 共有: Meta‑Annotation と Import Meta‑Annotation でアノテー ションをまとめてることも可能: / *

    * いつものアノテー ション一式をまとめたアノテー ション * @ S p r i n g B o o t A p p l i c a t i o n の代わりにこれを使えばよい。* / @ S p r i n g B o o t A p p l i c a t i o n @ C o m p o n e n t S c a n ( n a m e G e n e r a t o r = . . . ) @ I m p o r t ( M y B a t i s C o n f i g . c l a s s , E m b e d d e d P o s t g r e s C o n f i g . c l a s s , S e n t r y C o n f i g . c l a s s , T h y m e l e a f C o n f i g . c l a s s , T o m c a t C o n f i g . c l a s s , / / . . . ) a n n o t a t i o n c l a s s M 3 S p r i n g B o o t A p p l i c a t i o n アプリケー ション全体に影響するアノテー ションと全 Config の Import をセットにしておくと便利 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 36
  37. Config 共有: Config の切替 @Import の連鎖や Meta‑Annotation によって Config をまとめて読み込

    めるが、 常に全ての Config を有効にしたいとは限らない: 依存ライブラリがないと動かない Config は有効にしたくない 例えば API 用のアプリでは Thymeleaf を依存に入れたくない ので、Thymeleaf に依存する Config を走らせるとコケる 不要な Config は無効化してアプリケー ションの起動を早くしたい どうしても起動が遅くなりがちなので... Config を使う側は楽に Import しつつも、 読み込まれた Config をうまく オプトアウトできるようにするとより便利。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 37
  38. Config 共有: Class 有無による切替 @ C o n d i

    t i o n a l O n C l a s s を使えば、class がロー ドされているかで切替 られる: @ C o n f i g u r a t i o n @ C o n d i t i o n a l O n C l a s s ( T e m p l a t e M o d e . c l a s s , / / t h y m e l e a f I S p r i n g T e m p l a t e E n g i n e . c l a s s / / t h y m e l e a f - s p r i n g ) @ I m p o r t ( L a y o u t C o n f i g . c l a s s ) c l a s s T h y m e l e a f C o n f i g 条件にマッチしないときは @Import も行われなくなるので、 一塊の Config をまとめて切り替えることができる ( 上の例の T h y m e l e a f C o n f i g 自体 + L a y o u t C o n f i g )。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 38
  39. Config 共有: プロパティによる切替 @ C o n d i t

    i o n a l O n P r o p e r t y を使えば property によって切替を実現で きる: @ C o n f i g u r a t i o n @ C o n d i t i o n a l O n P r o p e r t y ( n a m e = " m 3 . s t a f f - o p e n i d . e n a b l e d " ) c l a s s M 3 S t a f f O p e n I d C o n f i g { . . . } Config を使うかどうかを property で opt‑in / opt‑out 可能にできる。 それによって Config の利用側は Config のクラスを意識せずに property の書き換えだけで挙動を制御できる。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 39
  40. Config 共有: まとめ @ I m p o r t

    は多段階に連鎖可能 依存関係にある Config は自動で Import するようにしよう アノテー ションの組み合わせは Meta‑Annotation でまとめられる @ C o n d i t i o n a l O n . . . で Config を切替可能 必要な Config を一括で Import しつつ、 プロジェクトごとに Config を取捨選択可能にしよう Config 利用者は Config クラスの実体を意識する必要がない #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 40
  41. 4. ライブラリの依存管理 依存ライブラリの管理はトラブルや大きな苦労の原因となりがち。 ライブラリ追加, バー ジョンアップ, 脆弱性対応 を安心して行うための仕 込みを紹介。 #jjug_ccc

    #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 41
  42. 依存管理: dependency のバー ジョン競合 (maven) 例えば以下の場合に、 ライブラリ X の 1.0

    が使われてしまう: ライブラリ A が X の 1.0 に依存 ライブラリ B が X の 2.0 に依存 B の実行時に N o S u c h M e t h o d E r r o r , C l a s s N o t F o u n d E x c e p t i o n などになる。 m v n d e p e n d e n c y : t r e e - D v e r b o s e に o m i t t e d f o r c o n f l i c t w i t h が出ているかを CI などで自動チェックするのがオススメ。 conflict の対処: A の < d e p e n d e n c y > に < e x c l u s i o n > を指定することで B の依 存バー ジョンに寄せる X に対して < v e r s i o n > 指定で < d e p e n d e n c y > を明示する #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 42
  43. 依存管理: dependency のバー ジョン競合 (Gradle) Gradle は競合時にデフォルトで新しい方のライブラリを使ってくれる。 とはいえ、 機能が廃止されている・ リネー

    ムされているといった場合に は実行時のエラー になるので、Gradle でも競合は意識的に解消したほう が良い。 r e s o l u t i o n S t r a t e g y で f a i l O n V e r s i o n C o n f l i c t ( ) しておくのが オススメ。 conflict の対処: e x c l u d e することで意識的に片方のバー ジョンに寄せる r e s o l u t i o n S t r a t e g y で特定バー ジョンを f o r c e する #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 43
  44. 依存管理: class, resource のダブり検知 異なる artifact 間に同じ class, resource が含まれてしまうことがある:

    commons‑beanutils と commons‑collections XML 処理ライブラリの Xerces の さまざまな artifact fat jar の類とその依存先ライブラリ本体 ロガー のアダプタ系全般 ( 例: c o m m o n s - l o g g i n g と j c l - o v e r - s l f 4 j ) ライブラリの artifact 名のリネー ム前とリネー ム後 実行時にどちらがロー ドされるかに依存して挙動が変わってしまうため 厄介。 d u p l i c a t e - f i n d e r - m a v e n - p l u g i n , g r a d l e - d u p l i c a t e - f i n d e r - p l u g i n によってビルド時に class の重複を検知するのがオススメ。 依存ライブラリの多くが module に対応してくれれば起動時に検知できるが... #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 44
  45. 依存管理: ライブラリの脆弱性チェック d e p e n d e n

    c y - c h e c k - { m a v e n , g r a d l e } プラグインが大変オススメ。 最新の脆弱性デー タベー スの情報と dependency をビルド時に突き合わ せられるので、 以下の悩みから解放される: 定期的な点検をやろうと思っても忘れがち 日々 増える脆弱性情報のキャッチアップ手段がない・ 大変 脆弱性の情報が来ても、 自身のプロジェクトで使っているライブラ リかどうかの判定が大変 特に間接依存先のライブラリがモレがち f a i l B u i l d O n C V S S = 7 などすれば CVSS スコアが指定値以上の時にビ ルドエラー になるのでより検知しやすい。 無視したい脆弱性は suppressionFile に指定することで無視可能。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 45
  46. 依存管理: バー ジョンの一括管理 exclusion やバー ジョン指定なども含む、 適切な version 指定をメンテ ナンスするのはそれなりに大変。

    以下の手法を使って依存ライブラリのバー ジョン管理を一元化すること ができる: Maven: BOM ないし parent‑pom に < d e p e n d e n c y M a n a g e m e n t > を定義し各プロジェクトはそれを参照 Gradle: Spring 提供の d e p e n d e n c y - m a n a g e m e n t - p l u g i n の m a v e n B o m を使って BOM を参照 dependency‑management‑plugin についての Spring 公式 Blog 参照 BOM に定義している dependencyManagement しているもの全てに依 存を貼ったプロジェクトを作っておき、CI で dpendency‑check や duplicate‑finder を実行することで各種検知もできる。 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 46
  47. 依存管理: まとめ m v n d e p e n

    d e n c y : t r e e や f a i l O n V e r s i o n C o n f l i c t ( ) で バー ジョン競合を確認 d u p l i c a t e - f i n d e r で class が衝突していないか自動チェック d e p e n d e n c y - c h e c k でライブラリの脆弱性情報を自動チェック BOM や parent‑pom で version 指定を一元管理 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 47
  48. 総まとめ spring‑boot を使いつつ Java のライブラリ資産を活用する上での 課題とその解決方法を紹介しました: 1. DI, Spring の仕組みに乗れる

    Configuration の書き方 素直に DI できないオブジェクトの扱い方 2. 簡潔かつ拡張性のある設定の記述方法 @ C o n f i g u r a t i o n P r o p e r t i e s , L i s t < B e a n 型> で DRY に記述 Bean の動的生成 3. プロジェクト間での Configuration 共有のためのテクニック @ I m p o r t と @ C o n d i t i o n a l O n . . . で選択的に共有 Meta‑Annotation で annotation をまとめて管理 4. 依存ライブラリの管理の効率化 バー ジョンや class の衝突といった問題の検知・ 解決 #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 48
  49. [PR] We're Hiring! Tech Keyword: JVM 言語, 高収益サー ビス, 少数精鋭チー

    ム 医療に関する web サー ビスを多数展開 (20 事業以上) 全世界で約400 万人の医師会員 ( 日本で約25 万人) #jjug_ccc #ccc_g4 Spring Boot と一般ライブラリの折り合いのつけかた 49