Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to setup Gradle to improve legacy Java system

How to setup Gradle to improve legacy Java system

※ JJUG 2019 Spring のセッション "パッケージ管理していなかった既存システムに後付けで Gradle を導入した話" のスライドです (#jjug_ccc #ccc_a2)。

近代的なプロダクトでは Gradle, Maven, Ivy といったパッケージ管理ツールを既に使っていることが多いと思います。

しかし、それらのツールの登場以前からの古い Java プロジェクト等に「後から」パッケージ管理ツールを導入するための方法論や具体的手法の情報が意外と少ないように感じました。

そこで、実際に大きなプロダクトに「後から」パッケージ管理ツールを導入した事例をご紹介いたします:

- パッケージ管理ツールを入れた動機
- パッケージ管理ツールを「後から」導入する際の戦略
- パッケージ管理ツールを「後から」導入するための各種の技術的手法

この情報を活かしていただくことで、パッケージ管理ツールの利用がより広まると幸いです。

saiya_moebius

May 18, 2019
Tweet

More Decks by saiya_moebius

Other Decks in Programming

Transcript

  1. パッケージ管理していなかった既存
    システムに後付けで Gradle を導入した話
    〜 .jar
    ファイルを直接管理する世界から
    パッケージ管理ツールへの移行の進め方・テクニック 〜
    JJUG CCC 2019 Spring #jjug_ccc #ccc_a2
    エムスリー株式会社 矢崎 聖也
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話

    View Slide

  2. スピーカー紹介
    矢崎 聖也 @saiya_moebius
    エムスリー株式会社 CTO
    サーバーサイドを中心としつつ色々やっているエンジニア
    Java, Kotlin, Rails 等 (言語の仕様として好きなのは C#)
    最近良く触れているのは GKE (GCP の Kubernetes) や
    Node.js / TypeScript
    新規プロジェクトや大規模リファクタリングを複数経験
    今回紹介するのも、それらの足回り整備を通して得
    たノウハウ
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 2

    View Slide

  3. このスライドについて
    Speakers Desk および Qiita で公開しております。
    時間の都合でコードの詳細など詳述しない部分があるため、
    必要に応じて参照していただければと思います。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 3

    View Slide

  4. 今でこそ一般化している Maven や Gradle
    等ですが...
    2004 : Maven 1.0
    2005 : Maven 2.0
    2006 : Ivy 1.4.1 が Apache Incubator に入る

    Ivy = Scala (sbt) などでも使われているパッケージ管理ツール
    2007 : Gradle の Initial release
    2010 : Maven 3.0
    弊社 エムスリー(株) の創業は 2000 年 ( J2SE
    あたりの時代)。
    Maven, Gradle, Ivy がまだ無い時代から Java を使っていました。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 4

    View Slide

  5. Maven, Gradle がない時代に Java をフル
    活用した結果...
    git clone
    すると以下のようなファイルが...
    lib/app/hibernate-validator-4.3.0.Final.jar
    lib/app/aws-java-sdk-core-1.10.27.jar
    lib/app/aws-java-sdk-s3-1.10.27.jar
    lib/app/generator-runtime-0.3.0-20050901.1909.jar
    lib/app/xalan.jar
    lib/app/crimson.jar
    ...
    以下延々と続く ...
    歴史とともに利用ライブラリが増えた結果、182 の .jar
    が...
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 5

    View Slide

  6. .jar
    を直接管理するオペレーション
    このようなプロセスでメンテナンスしている状態だった:
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 6

    View Slide

  7. ライブラリのバージョンアップのつらみ
    例: 新機能を使うために aws-java-sdk
    のバージョンを上げたい

    aws‑java‑sdk 系は複数のライブラリに間接依存

    182 個の .jar
    をどうバージョンアップするべきか分からない

    AWS の新しい機能を使うのを諦める
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 7

    View Slide

  8. 間接依存のつらみ
    ある jar を使っていないので消したい

    もしかしたら、いずれかの jar がそれに依存しているかも...

    不要 jar の掃除ができない

    JVM バージョンアップや脆弱性対応などの足かせに
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 8

    View Slide

  9. 各種ツールを使えないつらみ
    脆弱性情報をチェックしたい

    定期的に目検で 182 個の .jar
    と脆弱性情報を照合

    しんどい & 限界がある

    放置しがちになってしまう
    Gradle, maven 等であれば OWASP dependency-check
    plugin で
    自動化できるのに...
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 9

    View Slide

  10. こうなってしまっていた背景
    当該システム以外は maven, Gradle 導入済みであるのに対して...
    当該システムは創業時からのものであり、特に歴史が長い
    Ant のビルドスクリプトがガッツリ組まれており、
    一括リプレースはつらい
    普段の開発作業は問題なく回るので...
    (※
    ライブラリ周りの闇を無視している限りは)
    こういった事情でパッケージ管理ツールが入っていない事例は
    世の中にもあるのではないでしょうか...。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 10

    View Slide

  11. パッケージ管理ツール(Gradle, maven 等)
    を入れましょう!
    とはいえ、既存のプロジェクトにどうやって導入するのか...?
    作業の負担が大きくなりがちな点をどうするのか?
    入れることによる副作用や影響をどうするのか?
    → 次章へ
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 11

    View Slide

  12. パッケージ管理ツール導入の戦略
    〜 歴史あるシステムの改善の 実現可能性のある スコープ定義 〜
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 12

    View Slide

  13. パッケージ管理ツールの導入での課題
    既存のシステムへの後付け導入では、課題がいろいろ現れる:
    ツール導入前後で jar が変わってしまうことを良しとするか?
    再入手困難な jar や central と一致しない jar をどうするか?
    ビルドプロセスにも手を入れるか?
    課題に振り回されないために、ポリシーがあったほうが良い。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 13

    View Slide

  14. 弊社事例で設定したポリシー
    パッケージ管理ツール導入前後で、 .jar
    ファイルに差分を
    極力出さないようにする
    アプリへの影響懸念・テスト負担を抑えるため
    差分を回避不能なものは、無害な差分しかないことを確
    かめる (手法は後述)
    ライブラリ更新などは次の改善のスコープにする
    ビルド・デプロイ(Ant)には手を入れない
    まずはパッケージ管理を改善する!
    パッケージ管理ツールの恩恵を得ることにスコープを絞ることで
    小さく・意味のあるチャレンジをする状況を作り出した。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 14

    View Slide

  15. パッケージ管理ツール導入前の状態
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 15

    View Slide

  16. パッケージ管理ツール導入でこうなった
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 16

    View Slide

  17. Git に .jar
    を上げることの是非
    メリット:
    Ant
    などからの移行が容易
    常に確実に同じ jar ファイルを使える
    デメリット:
    Ant が残る
    Ant で真に困り事が発生したらその時にリプレース
    .jar
    の更新を繰り返すとレポジトリが肥大化するであろう
    そのうち Git submodule なりに分離する
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 17

    View Slide

  18. レガシー改善の戦略
    真に 困っていること ・ 変えるメリットがあること を見極める

    そのために必要な 必要十分な最小スコープ を考える

    小さいチャレンジ で目的を達成する (コスパ高, 失敗してもダメージ小)
    「レガシーだから全部抹殺するしかない!!!」ではなく、
    今ある良い点や変える必要がない点は踏襲しつつ改善。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 18

    View Slide

  19. パッケージ管理ツール導入テクニック:
    Gradle を後付け導入する方法
    〜 後付け導入でありがちな諸課題への具体的な対処方法 〜
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 19

    View Slide

  20. Gradle or Maven
    今回のような用途には Gradle の方がオススメ:
    柔軟性: Groovy や Kotlin DSL で柔軟な処理を記述可能
    歴史的なプロジェクトの構成に maven は対応できないこ
    とがある
    調査性: Groovy Javadoc のほうが読みやすい (主観)
    生産性: IDE のデバッガをフル活用できる
    ブラックボックス的に格闘しなくて済む
    再現性: Gradle wrapper があるため、動作の再現性を(比較的)
    確保しやすい

    一般的には Maven の方が優れている事柄もある
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 20

    View Slide

  21. Gradle によるファイルコピー
    既存ビルドシステムを活かしつつ Gradle 導入するため、
    以下のファイルを Gradle で所定のディレクトリにコピーしたい:
    コンパイル時・実行時に依存する jar
    単体テスト時に依存する jar
    Source jar
    また、 .classpath
    ファイル(後述)も自動でメンテナンスしたい。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 21

    View Slide

  22. Gradle で jar ファイルをダウンロード
    意外と簡単:
    def compileJarCopyTo = 'development/lib/app'
    task downloadJars() {
    doLast {
    //
    コピー先にある *.jar
    を消す
    delete fileTree(dir: compileJarCopyTo, include: '*.jar')
    //
    コピーする
    copy {
    from configurations.compile
    into compileJarCopyTo
    }
    }
    }
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 22

    View Slide

  23. copy
    task
    コピー対象 jar の指定や include, exclude なども出来て便利:
    copy {
    // testCompile = test
    で依存する jar
    をコピー
    from configurations.testCompile
    into testCompileJarCopyTo
    //
    指定したパターンに該当する jar
    は無視
    exclude 'jmockit-*.jar'
    }
    大抵のディレクトリ構成に対応できるはず。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 23

    View Slide

  24. Source jar のコピーもやれば出来る
    Source jar のコピーは簡単には書けない。
    しかし以下をゴリゴリ操作するコードを書けば実現可能:
    import org.gradle.jvm.JvmLibrary
    import org.gradle.language.base.artifact.SourcesArtifact
    import org.gradle.api.artifacts.query.ArtifactResolutionQuery
    import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
    ( org.gradle.internal
    も現れてしまっていますが... )
    → 先人の Gradle 2.x 向けのコードを参考に、Gradle 5.x 対応版を
    Copy source jar files into directory (Qiita) へ投稿してあります
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 24

    View Slide

  25. .classpath
    ファイルの生成
    IDE に jar やソースコードを読み込ませるための設定ファイル。
    もともと Eclipse 用の形式だが、InteliJ IDEA にも対応している。
    せっかくなのでこのファイルも Gradle でメンテナンスさせたい。
    Gradle の eclipse
    plugin を適用するだけで生成できるが...
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 25

    View Slide

  26. .classpath
    絶対パス問題
    Plugin は .classpath
    に絶対パスを書き込む。
    環境依存のパスになってしまい、Git などで共有できず不便。
    解決策:
    各自の手元で gradle eclipseClasspath
    して .classpath
    を都度生成する
    全員が毎回実行する手間が発生
    ビルドの再現性が下がる (手元では動いたのに!現象)
    相対パス表記の .classpath
    を Gradle で生成し、jar ファイ
    ルと共に commit する (おすすめ)
    Jar ファイルのコピーなどとセットでまとめて実行す
    るだけで OK
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 26

    View Slide

  27. .classpath
    を相対パス表記で生成する
    参考サイト などにあるように whenMerged
    フックを使うと良い:
    eclipse.classpath.file {
    whenMerged { classpath ->
    classpath.entries.findAll { entry -> entry.kind == 'lib' }.each {
    it.path = it.path.replaceFirst("${projectDir.absolutePath}/", '')
    it.exported = false
    }
    }
    }
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 27

    View Slide

  28. eclipse.classpath.file

    whenMerged
    whenMerged
    は .classpath
    ファイルの内容を自由に制御できる
    例えば:
    jar ファイルの参照先のパスを任意のロジックで書き換える
    弊社事例では、一部ファイルだけ異なるディレクトリに
    置いている
    classpath.entries.sort
    で .classpath
    の並び順を制御
    JMockit のようにロード順に依存するライブラリで有用
    classpath.getEntries().removeAll { return (
    条件式) }
    IDE に読み込ませたくない jar の除外が可能
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 28

    View Slide

  29. jar のファイル名の変化
    手作業管理の jar ファイル名と、Gradle 由来の jar は
    ファイル名が一致しないことが多い。例えば:
    手動管理のファイル名: crimson.jar
    Gradle でのダウンロード結果: crimson-1.1.3.jar
    ファイル名決め打ちで参照している場合に問題が出る。
    特に以下がありがち:
    Ant などのビルドツールの classpath
    におけるファイル名の
    決め打ち
    プログラムの起動時の JVM の classpath
    オプションでの
    ファイル名の決め打ち
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 29

    View Slide

  30. jar のファイル名が変わることへの対処策
    1. 後処理で旧来のファイル名になるようにリネームする
    リネーム元の group, artifact 名が変わった場合に事故

    新しい jar が増えたときの対応に困る
    2. 個別のライブラリを意識しないようにする
    特定ディレクトリ以下の jar をすべて読み込むだけで
    OK な設計にする
    Ant, JVM classpath
    (>= 6), シェルなどの *
    *
    の解釈がそれぞれ微妙に異なる点には要注意
    一過性の手間はかかるが、後者のほうが長期的におすすめ。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 30

    View Slide

  31. パッケージ管理ツール導入テクニック:
    諸事情ある jar の管理
    〜 歴史の彼方からやってきた jar ファイルを扱うための手法 〜
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 31

    View Slide

  32. 古い jar ファイルにありがちなこと
    jar を再入手できたがバイナリが一致しない
    SNAPSHOT

    非 SNAPSHOT
    なのに差し替えリリースされている
    Maven repository 間で違うバイナリが置いてある
    経緯不明の独自パッチ
    公式ページが消えており、jar やソースを再入手できない
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 32

    View Slide

  33. jar のバイナリ不一致の原因を確かめる
    不一致は放置せずに、具体的な差分を確認すると良い:
    パッケージ管理ツール導入の影響範囲・リスクを限定できる
    テストすべき範囲やテスト手法も明らかになる
    筆者は Gradle 化前後で jar ファイルの SHA‑1 ハッシュを出し、
    ハッシュが一致しない jar について具体的な差分を調べた。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 33

    View Slide

  34. jar ファイルの差分の調べ方
    jar ファイルは zip であり、展開すると以下のファイルが出てくる:
    1. .class
    ファイル (Java バイトコード)
    2. META-INF
    などのメタデータファイル
    3. その他、プロパティファイルなどのリソースファイル
    .class
    以外はほとんどテキスト比較ツールで比較できる。
    lucene-analyzers-2.4.0
    の META-INF/MANIFEST.MF
    で差分が出たときの例:
    8c8
    < Implementation-Version: 2.4.0 701827 - 2008-10-05 16:46:27
    ---
    > Implementation-Version: 2.4.0 701827 - 2008-10-05 16:44:47
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 34

    View Slide

  35. jar の差分の調べ方: class ファイル
    .class
    ファイルは Java バイトコードのバイナリである。
    JDK に入っている javap -c
    でテキスト表現に変換することで、
    テキスト比較ツールで比較可能になる。
    guice-servlet-1.0.jar
    で差分が出たときの例:
    2c2
    < final class com.google.inject.servlet.ServletScopes$1 implements com.google.inject.Scope {
    ---
    > class com.google.inject.servlet.ServletScopes$1 implements com.google.inject.Scope {
    ( final
    を足した jar で差し替えられている)
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 35

    View Slide

  36. 再入手不能なライブラリ
    当該ライブラリの利用停止以外の対策:
    ( 非推奨) Central 等の公開レポジトリにアップロードする
    ライセンスや道義的に好ましくない
    公知の jar と一致する確証のない jar を公開することに
    ( 微妙) 対象のライブラリの .java
    ファイルを直接使う
    ソースを直接使うため、独自改造や密結合が起きがち
    ライセンスによっては問題あり
    (おすすめ) 内部用のプライベートなレポジトリで管理
    権利問題が起きにくい
    当該ライブラリの利用状況の把握や削除がやりやすい
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 36

    View Slide

  37. プライベートレポジトリ
    再入手不能 jar に限らず、内製のライブラリの管理などでも
    プライベートなレポジトリがあると便利。
    よくある構築方法:
    1. ローカル上の maven レポジトリを file://
    で参照する
    マシン間の同期が必要
    ビルドの再現性を損ないがち
    jar の紛失リスクやバックアップの手間もある
    2. Nexus や Artifactory などを設置する
    詳細なセットアップ方法については触れないが、
    新規でセットアップするならば Nexus の方が楽
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 37

    View Slide

  38. プライベートレポジトリの利用
    1. 世の中に存在するライブラリは、今までどおり maven central
    などから取得
    2. プライベートなものは、プライベートレポジトリから取得
    Gradle ならば以下のようにするだけで実現できる:
    repositories {
    maven {
    url 'http://central.maven.org/maven2/'
    } // mavenCentral()
    という短縮記法はあえて使っていない (
    後述)
    maven {
    url "http://
    プライベートなレポジトリのURL"
    }
    }
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 38

    View Slide

  39. レポジトリへ jar をアップロード
    メタデータを Maven の XML ( pom.xml
    ) 形式で書いて
    jar と共にアップロード・配置する。
    一見とっつきにくいかもしれないが、今回の目的に絞れば簡単:

    4.0.0

    com.m3 m3generator
    1.0.0



    log4j
    log4j
    1.2.17

    ...
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 39

    View Slide

  40. (Artifactory) Auto generated POM には注意
    Artifactory は POM を自動生成してくれるので便利に見えるが...

    が空だったり間違っていたりすることが多い

    アップロードした jar の依存ライブラリの情報を得られなくなる

    パッケージ管理ツールによる依存関係管理のメリットが失われる
    自動生成に丸投げせずに
    をちゃんと書こう。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 40

    View Slide

  41. レポジトリ間で重複するライブラリ
    同名だが一致しない(差分がある)ライブラリがある場合、
    どちらが読み込まれるか明示的に制御すべし:
    プライベートレポジトリへ別 version 名でアップロード
    または、Gradle 側で制御する (下記)
    repositories {
    maven { // Maven central
    url 'http://central.maven.org/maven2/'
    content {
    excludeGroup 'com.sun.jmx' //
    このグループをここから取得しない
    excludeModule 'jdom', 'jdom' //
    このライブラリをここから取得しない
    }
    //
    上記記述をするため mavenCentral()
    といった短縮記法は使っていない
    }
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 41

    View Slide

  42. Next Step
    パッケージ管理ツールを入れたら特にやっておきたいこと:
    Version 衝突の検知
    classpath 上の重複の検知
    OWASP dependency check による脆弱性情報のチェック
    JJUG 2018 Spring のスライドでより深く説明しています:
    タイトル: Spring Boot
    と一般ライブラリの折り合いのつけかた
    Speaker Deck と Qiita 両方に同じ内容で掲載しています

    資料の後半は Spring Framework 関係なく使える情報です
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 42

    View Slide

  43. まとめ
    今回、パッケージ管理ツールを入れた際のプロセス:
    ゴールを定義
    パッケージ管理でどう良くなるかのビジョンを描く
    戦略を構築
    実現のために何をするか・何をあえて「しない」のか
    テクニックで課題を解決: 今回言及した各種工夫
    Gradle の活用
    jar の差分チェック
    プライベートレポジトリの活用
    「闇が深くて手がつけられない」とも思える状況でも、
    適切なスコープを設定し
    、技術で実現する ことで前に進める。
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 43

    View Slide

  44. [PR] エムスリー (株) We're Hiring!
    Tech Keyword: JVM 言語, 高収益サービス, 少数精鋭チーム
    医療に関する web サービスを多数展開 (20 事業以上)
    全世界で約400万人の医師会員 (日本で約25万人)
    #jjug_ccc #ccc_a2 パッケージ管理していなかった既存システムに後付けで Gradle を導入した話 44

    View Slide