GradleLogoReg

Gradle Versions Plugin のご紹介

井上です。

普段 Java でシステム開発を行っているのですが、ビルドツールとして最近は Gradleを使っています。

Gradle は 日本語のドキュメント「Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築」 といった日本語の情報が豊富で、Java界隈におけるビルドツールのデファクトスタンダードとなりつつあります。

Gradle におけるライブラリのバージョンの指定方法

Gradle では、プロジェクトで使用するライブラリを指定することで、そのライブラリ及びライブラリが依存しているライブラリ群を自動的にダウンロードする機構が備わっています。

以下、Java を使ったプロジェクトで JUnit を使用する際のGradle のビルドスクリプト (build.gradle) の例です。

apply plugin: 'java'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    // (1) 特定のバージョンを指定
    testCompile 'junit:junit:4.11'
    // (2) 動的バージョンで指定 (4系の最新版)
    // testCompile 'junit:junit:4.+'
    // (3) 使用できる最新版を表すプレースホルダで指定
    // testCompile 'junit:junit:latest.release'
}

通常は (1) のように、特定のバージョンを指定しますが、開発中など、常に最新版を使いたい場合は、 (2) (3) のように指定することもできます。

ただし、ライブラリが更新されたことは知りたいけども、実際に最新版を適用するかは、その更新内容を確認した後にしたい場合も多いと思います。

そのような場合、上記の指定方法だけでは対応することができないので、 (1) のように、特定のバージョンを指定した上で、Maven Central Repository などでライブラリの更新状況を都度チェックすることになりますが、とても面倒です。

このような場合に便利な Gradle Versions Plugin を紹介します。

Gradle Versions Plugin とは

Gradle Versions Plugin は、ビルドスクリプトに指定したライブラリの更新情報を一括で確認する機能を提供するGradleのプラグインです。

では、実際に Gradle Versions Plugin の使い方を簡単に見てみましょう。

Gradle Versions Plugin の使い方

以下が、先ほど紹介したビルドスクリプトに Gradle Versions Plugin の設定を追加した例です。

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.github.ben-manes:gradle-versions-plugin:0.11.3'
    }
}

apply plugin: 'java'
apply plugin: 'com.github.ben-manes.versions'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    // (1) 特定のバージョンを指定
    testCompile 'junit:junit:4.11'
    // (2) 動的バージョンで指定 (4系の最新版)
    // testCompile 'junit:junit:4.+'
    // (3) 使用できる最新版を表すプレースホルダで指定
    // testCompile 'junit:junit:latest.release'
}

上記設定を追加することで、プロジェクトに dependencyUpdates タスクが追加されます

では、実際に dependencyUpdates タスクを実行してみましょう。
以下、dependencyUpdates タスク の実行例です。

% gradle dependencyUpdates                                                                                                                                                        (git)-[master]
:dependencyUpdates

------------------------------------------------------------
: Project Dependency Updates (report to plain text file)
------------------------------------------------------------

The following dependencies are using the latest milestone version:
 - com.github.ben-manes:gradle-versions-plugin:0.11.3

The following dependencies have later milestone versions:
 - junit:junit [4.11 -> 4.12]

Generated report file build/dependencyUpdates/report.txt

BUILD SUCCESSFUL

「Project Dependency Updates」以下に、ライブラリの更新情報が出力されています(標準出力だけでなく、build/dependencyUpdates/report.txt へも出力されています)。
出力内容から以下のことが分かります。

  • Gradle Versions Plugin (com.github.ben-manes:gradle-versions-plugin) は最新版 (0.11.3) を使用している
  • JUnit (junit:junit) は指定したバージョン (4.11) より新しいバージョン (4.12) が存在する

このように、dependencyUpdates タスクを実行するだけで、プロジェクトで使用するライブラリの更新情報を一括で取得することが出来ました。

また、dependencyUpdates タスクにオプションを指定することで、更新情報をreleaseバージョンに限定したり、出力フォーマットを xml や json にすることも可能です。
(詳細は ドキュメント を参照下さい)

gradle dependencyUpdates -Drevision=release -DoutputFormatter=json -DoutputDir=/any/path/with/permission

dependencyUpdates タスクを手動で実行するのが面倒な場合は、CI (継続的インテグレーション) で dependencyUpdates タスクの出力を解析し、更新情報があれば通知するといった運用も可能ですね。

まとめ

今回は、ライブラリの更新情報を一括で確認することができる Gradle Versions Plugin を紹介しました。
便利かつ簡単に導入することが出来るので、プロジェクトに導入してみてはいかがでしょうか?

ドメイン駆動設計とJava 8 ラムダ式

河上です。

Java 8 でやっとラムダ式が使えるようになって喜んでいる今日このごろですが、業務アプリケーション・プログラミングであるドメイン駆動設計という文脈でどういった変化がおこるのかを考えてみます。

まず適用できそうなところとして思いつくのは以下です。

  • Specificationパターンの実装
  • ファーストクラスコレクション内部のコレクション操作

それぞれの実装例として幾分恣意的ではありますが、シンプルな要件を、ラムダ式を利用しないバージョンと、利用するバージョンでそれぞれ実装してみたいと思います。

要件:「ある会員制サイトで、年収800万円以上、且つ、正社員、且つ、課長以上、というステータスを持つ会員を幹部候補として区別し、絞込などをおこないたい」

まずは、年収800万円以上、且つ、正社員、且つ、課長以上という、複合条件をSpecificationパターンの実装方法の1つである、「Compositeパターンによる条件ツリー方式」で記述してみます。

ラムダ式を利用しない

public class ExecutiveMemberSpecification {
    MemberSpecification 年収800万以上 = new MemberSpecification() {
        @Override
        public boolean isSatisfied(Member 会員) {
            return 会員.年収().greaterEqual(800);
        }
    };
    MemberSpecification 正社員 = new MemberSpecification() {
        @Override
        public boolean isSatisfied(Member 会員) {
            return 会員.雇用形態().equals(EmploymentType.正社員);
        }
    };
    MemberSpecification 課長以上 = new MemberSpecification() {
        @Override
        public boolean isSatisfied(Member 会員) {
            return 会員.役職().greaterEqual(Position.課長);
        }
    };

    public boolean isSatisfied(Member 会員) {
        return 年収800万以上
                .and(正社員)
                .and(課長以上)
                .isSatisfied(会員);
    }

}

abstract class MemberSpecification {
    abstract boolean isSatisfied(Member member);

    MemberSpecification and(MemberSpecification specification) {
        return new MemberSpecification() {
            @Override
            boolean isSatisfied(Member member) {
                return MemberSpecification
                        .this.isSatisfied(member)
                        && specification.isSatisfied(member);
            }
        };
    }

    MemberSpecification or(MemberSpecification specification) {
        return new MemberSpecification() {
            @Override
            boolean isSatisfied(Member member) {
                return MemberSpecification
                        .this.isSatisfied(member)
                        || specification.isSatisfied(member);
            }
        };
    }
}

ラムダ式を利用する

public class ExecutiveMemberSpecification {
    final Predicate<Member> 年収800万以上
            = it -> it.年収().greaterEqual(800);
    final Predicate<Member> 正社員
            = it -> it.雇用形態().equals(EmploymentType.正社員);
    final Predicate<Member> 課長以上
            = it -> it.役職().greaterEqual(Position.課長);

    public boolean isSatisfied(Member 会員) {
        return 年収800万以上
                .and(正社員)
                .and(課長以上)
                .test(会員);
    }
}

ラムダ式を利用しない場合に比べてコードにあるノイズがほぼ消えていることがわかります。

次は、このSpecificationを使って、実際に会員を絞り込むコレクション操作部分です。

ラムダ式を利用しない

public class Members {
    final List<Member> values;

    public Members(List<Member> values) {
        this.values = values;
    }

    ExecutiveMemberSpecification 幹部候補者条件 = new ExecutiveMemberSpecification();

    public List<Member> 幹部候補者リスト() {
        List<Member> members = new ArrayList<>();
        for (Member 会員 : values)
            if (幹部候補者条件.isSatisfied(会員)) members.add(会員);
        return members;
    }
}

ラムダ式を利用する

public class Members {
    final List<Member> values;

    public Members(List<Member> values) {
        this.values = values;
    }

    final ExecutiveMemberSpecification 幹部候補者条件 = new ExecutiveMemberSpecification();

    public List<Member> 幹部候補者リスト() {
        return values.stream()
                .filter(幹部候補者条件::isSatisfied).collect(Collectors.toList());
    }
}

こちらではラムダ式を利用しない場合に比べて絞り込むという部分がfilterというメソッドとしてより直接的に表現出来ている部分に好感がもてます。

コードの例としては以上となります。コードの細かい解説などはなく不親切とは思いますが、雰囲気だけでも感じていただければと思います。

上記例以外にもソートなどの記述がシンプルになったりと、メリットが多いと感じるラムダ式ですが、実を言うと、上記の例も含めて、私がドメインモデルのコードでラムダ式を使う部分は局所的です。

それは、例えば「ドメインモデルの外部からラムダ式を注入するようなモデルの汎用化をおこなう事」が、「用途や文脈に合わせた特殊化を行う」というドメイン駆動設計の思想と相反するのではないかという考えがあるからで、今のところそれを変えるような経験に出会っていません。

もちろん、ドメインモデルの外のインフラストラクチャやフレームワーク内部ではラムダ式をつかった汎用化がかなりの武器になることに疑問はありません。

他につっこみや「ドメインモデル内でのラムダ式活用例」としてなにか事例等あればご意見ください。

Effective Java 第2版 を可視化する

井上です。

どのプログラム言語においても、必読書と呼ばれる本が何冊か存在すると思います。
私は長らく使っているJavaでは「Effective Java 第2版」が真っ先に挙げられると思います。

この本、初版が出版されたのが2001年、第2版が出版されたのが2008年なのですが、諸事情により一時期絶版になっていました。
しかし、今年の3月にめでたく再販となりました。
私自身、今でもたまに読み返す程手放せない本になっています。
#ただ、2008年出版ということもあり、バージョンとしては Java 5 対応の内容となっており、Java 8 対応の第3版の発売を期待したいところです。

さて、Effective Java のような技術書を読む場合、皆さんはどのように読み進めるでしょうか?最初から順に読む人も多いのではないでしょうか?
しかし、Effective Java を初めて読む人がその方法で読もうとすると、理解するのがとても難しいです。
なぜなら、この本、前後関係なく他の項目への参照が大変多いのです。

続きを読む

gradle

Gradleプロジェクトで依存関係の競合を解決する

河上です。

前回記事に載せられていなかったので改めて自己紹介を。
関西に住んでフリーエンジニアをやっております。河上です。
よろしくお願いします。

GradleやMavenなどでJavaのプロジェクト構成を管理していて間接的に依存しているライブラリ同士のバージョン競合が起こることってありますよね?

これを「推移的な依存の競合」と呼ぶのですが、今回は単純な例で競合はどのようにして起こり、どう解決するのかを単純な例で示してみたいと思います。

想定するGradleのバージョンは2.1とします。

推移的な依存の競合状態の単純な例

      foo:example:lib-a-1.0.0-RELEASE
         └bar:example:lib-x-2.0.0-REALEASE
      foo:example:lib-b-1.0.0-RELEASE
         └bar:example:lib-x-1.9.0-RELEASE

上記ではlib-xの2.0.0と1.9.0が競合状態にありますがGradleでの推移的依存の解決方法はデフォルトで[Newest:最も新しいバージョンの依存関係を使用する]となっており、このままではlib-x-2.0.0-RELEASEが使用される状態です。

問題はlib-xの2.0.0には下位互換性が無く1.9.0の方を使いたいといった時に、Gradleではどのようにするのか?
ということですが、とりあえず以下の2つの方法を覚えておけば大体のケースに対応できると思います。

 

  • DependencyHandlerでどちらかの推移依存を除外

 

  • ResolutionStrategyで推移依存をどちらかに固定

 

実際に例にある推移的依存のうち古い方のバージョンを採用する形で競合を解決してみると以下のようになります。

1. DependencyHandlerでどちらかの推移依存を除外

      dependency {
         compile('foo:example:lib-a-1.0.0-RELEASE') {
            exclude 'bar:example:lib-x-2.0.0-RELEASE'      
         }
         compile('foo:example:lib-b-1.0.0-RELEASE')
      }

2. ResolutionStrategyで推移依存をどちらかに固定

      dependency {
         compile('foo:example:lib-a-1.0.0-RELEASE')
         compile('foo:example:lib-b-1.0.0-RELEASE')
      }

      configurations.all {
         resolutionStrategy {
            force 'foo:example:lib-x-1.9.0-RELEASE'
         }
      }

まとめ

この他にもResolutionStrategyでは競合が起こった場合はエラーにするような設定ができたりと、競合解決に関する設定がいろいろとあるので是非試してみて下さい。

参考資料:依存関係の管理

補足:なぜこんなに複雑な管理をしなければならないのか?

これはJavaにモジュールシステムと言えるものが無い事が原因だと考えられます。
Javaの実行時の依存ライブラリの関係はすべてフラットで、直接依存しているJarファイルから推移的に依存しているJarファイルまですべてをフラットに含めなければなりません。
もしこれが、直接依存しているJarファイルを指定しておくだけで、そこから推移する依存関係は直接依存しているJarファイルの
名前空間からしかアクセスしないというようなクラスローディングの仕組みがあれば、このような複雑な管理はなくなります。
しかしこれには、重複するJarファイルもすべて直接依存するJarファイルごとに保持しなければならず、アプリケーションのサイズ(warファイルなど)が肥大化してまうというデメリットもあり悩ましいところです。