Spring Boot アプリケーションをDockerで動かす 第2回

河上です。

前回に引き続き、Spring BootアプリケーションをDockerで動かしていきます。

Spring Boot アプリケーションはインメモリDBでの開発がとても楽ですが、リリース前はやはり本番と同じデータベースサーバで動作を確認したくなります。
そして、それを行うにしても以下のような作業を行う必要があって本当に面倒です。

  • データベースサーバのインストールと起動
  • スキーマを作成するSQLの実行
  • テストデータの登録
  • アプリケーションの起動

もう1つ、これらを手動で行っている場合の大きな問題点としてデータベースの状態が毎回変わる、もしくはどういう状態かわからないのでテストしにくいという問題があります。

今回は、これらの問題を解消すべくDockerコンテナを使って自動化してしまいます。 続きを読む

Spring Boot アプリケーションを Dockerで動かす

河上です。

Spring Boot アプリケーションをDockerで動かすためのDockerfileを解説します。


なぜDockerとSpring Bootなのか

Spring Bootはサービスを小さく作って、小さなサービス同士の連携で大きなシステムを作り上げていくことを志向しているフレームワークだと勝手に思っています。

いわゆるマイクロサービスというやつですね。

しかし、個々のアプリケーションを作るのが簡単な一方、複数の小さなサービスを管理、運用していくのはそれなりに難しく、骨の折れる作業になるのは想像に難くないところです。

この問題を解決する第一歩として、まずは単純かつ単一のSpring Boot アプリケーションをDockerコンテナで動かすところから始めてみました。

Spring Boot のバージョンは1.2.1

Mac上でboot2docker v1.4.1 を使って構築しました。

続きを読む

ドメイン駆動設計と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というメソッドとしてより直接的に表現出来ている部分に好感がもてます。

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

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

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

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

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

ヘキサゴナルアーキテクチャ

河上です。

最近傾倒しているドメイン駆動設計という文脈で「ヘキサゴナルアーキテクチャ」というアプリケーションアーキテクチャについて色々と思う事を書きたいと思います。

ヘキサゴナルアーキテクチャはPofEAA(エンタープライズ・アプリケーションアーキテクチャパターン)という本で知りました。

アリスター・コーバーンのヘキサゴナルアーキテクチャ

Hexagonal-architecture

Applicationと記述してある内部の六角形はビジネスの感心事であるドメインモデルで、それ以外はすべて外部とのインターフェースであるというドメイン中心の考え方で、ポピュラーな3層アーキテクチャとはずいぶんと違う印象を受けますね。

この図を見てまず疑問に思ったのは【外部から内部への依存】なのか、【内部から外部への依存】なのかということなのですが、現時点の理解では、【外部から内部への依存】だと思っており、そちらに振り切って設計をしています。

私がこのように判断したのは「変更は予知できない」という経験からです。

いろいろ考えて、予想して、変更がありそうな部分を柔軟に作ったとしても、結局その柔軟性が発揮される事がなく無駄な複雑さだけが残ったという苦い経験があります…。
この経験から、下手に予想するのではなく、少なくともビジネスの文脈ですべてが規程されている形にしておけば、ビジネスの形との整合がとれているために変化点の見極めがしやすくなり、結果として変更がやりやすくなるはずだと考えました。そしてビジネスの形に合わせるということは、ビジネス(ドメイン)へ依存するという事なのではないかと思い、この依存の方向は外部から内部(ドメイン)へ向くべきであるという結論になりました。

また、この依存方向だと、単純なアプリケーションではドメインモデルが振る舞いらしい振る舞いを持たずただの構造のようになってしまったりしますが、そのときはそのドメインモデルがビジネス(業務)を表現できていれば良しとしています。

今後、実践していく中でまた違う結論になることもあるかと思いますが、「間違ってる」「こんな考え方があるよ」「こんなアーキテクチャなら良いのでは?」など、ディスカッションのネタがいただけると幸いです。

似たアーキテクチャで、より表現が近いかなというものを見つけたのでリンクを貼っておきます。

アンクル・ボブのクリーンアーキテクチャ

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ファイルなど)が肥大化してまうというデメリットもあり悩ましいところです。

変更を容易にするための技術

はじめまして。河上です。

アプリケーション開発における領域を分ける言葉としてアプリケーションドメインとソリューションドメインというものがあります。アプリケーションドメインはユーザの関心事を表現する領域であるのに対し、ソリューションドメインはそれらを実現するためのフレームワーク、ユーティリティ、ミドルウェア、その他ツールなど一般化可能な技術を表す領域(サブドメイン)となります。

ソリューションドメインは、既製のOSSフレームワークなどの抽象化されたインフラストラクチャの上でアプリケーションを記述している昨今では意識することも少ない領域になりつつある一方、まだまだフレームワークやミドルウェアとアプリケーションドメインの間を埋めるためのコードを書く必要があったり、さらには運用・保守フェーズなども含めたアプリケーションのライフサイクル全体を考えると知識として必要となる場面は当分なくならない領域といえるでしょう。

これは私の考えですが、「ユーザの関心事であるアプリケーションドメインの変更しやすさ」を最大現にするための要素としては以下のようなものがあり、どちらも重要なものです。

  • アプリケーションドメインをどのように表現するかに焦点を当てたDDDのような設計手法
  • 継続的インテグレーションのようなソリューションドメインの技術

もちろんこれ以外にはチームビルディングなどの人的要素も挙げられるでしょう。

このブログでは、ソリューションドメインの技術に焦点をあて、「アプリケーションドメインの変更のしやすさ」を目的としつつ、比較的私が得意とする以下の領域を中心として、私の興味と方向性に合う新しいものを混ぜながら、実際の現場における各種ツールやフレームワークの使いどころを書いていきたいと思います。

  • テスト自動化
  • Java周りのエコシステム全般
  • 継続的インテグレーション&デリバリー

よろしくお願いいたします。