JSUG勉強会 2017年その3 ~ ドメイン駆動設計 powered by Spring に行ってきた

JSUG勉強会 2017年その3 に行ってきました。簡単に所感をまとめます。

jsug.doorkeeper.jp

今回のテーマはドメイン駆動設計と Spring です。資料はこちら。

www.slideshare.net

  • ドメインロジックに集中する
  • Spring Frameworkドメインモデル以外のことをすべて用意するフレームワーク
  • ドメインモデルは振る舞いとデータの両方を組み込んだオブジェクトモデル
    • ドメインロジックをオブジェクトで表現する
    • データを持つクラスが唯一ロジックを持つ
    • 変更容易性を向上する
  • ドメインロジックをモジュール化する場合はドメインオブジェクトとして部品化する
  • ドメインロジックを自己文書化する
    • ドメインオブジェクトを使う他のレイヤのコードに自己文書化が波及する
    • 引数や戻り値の型で何をしているかが分かる
  • ドメインを隔離する
  • ドメインオブジェクトに getter/setter を書かない
    • DirectFieldAccess (↓のスライドを参照)
  • Service クラスに @Validated を付ける
    • メソッドの引数や戻り値にバリデーションができる
    • 契約による設計 (事前条件/事後条件) の考え方が実現できる
  • モデルの一部としてインタフェースを宣言する
    • Repository インタフェース
    • インタフェースで実装を分離する
    • データベースの都合をドメインオブジェクトに持ち込まない
    • データソース層がドメインモデルに依存する (依存関係が逆転する)

今回、3層のレイヤ (@Controller, @Service, @Repository) ごとに説明されていて、自分には理解しやすくとても参考になりました。Spring 特有の話ではあったかもしれませんが、難しく考えがちなドメイン駆動設計が身近なものに感じられました。

一気に全体を書き替えるのではなく、まずはトランザクションスクリプトの中に埋め込まれてるドメインロジックをドメインオブジェクトに切り出すところからスタートする感じです。ようするにリファクタリングですかね。

DirectFieldAccess は初めて知りました。あと、クラスに @Validated を付けられるんですね...。これも初めて知りました。契約による設計をシンプルに実装できそう。あとで試してみよう。

java.util.function パッケージのインタフェースサマリー (用途別)

公式ドキュメントにインタフェースのサマリーが記載されているが、アルファベット順でイマイチ読み辛いので用途別に並べる。

java.util.function (Java Platform SE 8)

Supplier インタフェース

インタフェース 説明 抽象メソッド
Supplier<T> 結果のサプライヤを表します。 get()

Supplierの戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntSupplier int値の結果のサプライヤを表します。 getAsInt()
LongSupplier long値の結果のサプライヤを表します。 getAsLong()
DoubleSupplier double値の結果のサプライヤを表します。 getAsDouble()
BooleanSupplier boolean値の結果のサプライヤを表します。 getAsBoolean()

Predicate インタフェース

インタフェース 説明 抽象メソッド
Predicate<T> 1つの引数の述語(boolean値関数)を表します。 test(T)

Predicateの引数に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntPredicate 1つのint値引数の述語(boolean値関数)を表します。 test(int)
LongPredicate 1つのlong値引数の述語(boolean値関数)を表します。 test(long)
DoublePredicate 1つのdouble値引数の述語(boolean値関数)を表します。 test(double)

Predicateの引数の数における特殊化型

インタフェース 説明 抽象メソッド
BiPredicate<T,U> 2つの引数の述語(boolean値関数)を表します。 test(T, U)

Consumer インタフェース

インタフェース 説明 抽象メソッド
Consumer<T> 単一の入力引数を受け取って結果を返さないオペレーションを表します。 accept(T)

Consumerの引数に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntConsumer 単一のint値引数を受け取って結果を返さないオペレーションを表します。 accept(int)
LongConsumer 単一のlong値引数を受け取って結果を返さないオペレーションを表します。 accept(long)
DoubleConsumer 単一のdouble値引数を受け取って結果を返さないオペレーションを表します。 accept(double)

Consumerの引数の数における特殊化型

インタフェース 説明 抽象メソッド
BiConsumer<T,U> 2つの入力引数を受け取って結果を返さないオペレーションを表します。 accept(T, U)

BiConsumerの引数に対する特殊化型

インタフェース 説明 抽象メソッド
ObjIntConsumer<T> オブジェクト値とint値の引数を受け取って結果を返さないオペレーションを表します。 accept(T, int)
ObjLongConsumer<T> オブジェクト値とlong値の引数を受け取って結果を返さないオペレーションを表します。 accept(T, long)
ObjDoubleConsumer<T> オブジェクト値とdouble値の引数を受け取って結果を返さないオペレーションを表します。 accept(T, double)

Function インタフェース

インタフェース 説明 抽象メソッド
Function<T,R> 1つの引数を受け取って結果を生成する関数を表します。 apply(T)

Functionの引数に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntFunction<R> 1つのint値引数を受け取って結果を生成する関数を表します。 apply(int)
LongFunction<R> 1つのlong値引数を受け取って結果を生成する関数を表します。 apply(long)
DoubleFunction<R> 1つのdouble値引数を受け取って結果を生成する関数を表します。 apply(double)

Functionの戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
ToIntFunction<T> int値の結果を生成する関数を表します。 applyAsInt(T)
ToLongFunction<T> long値の結果を生成する関数を表します。 applyAsLong(T)
ToDoubleFunction<T> double値の結果を生成する関数を表します。 applyAsDobule(T)

Functionの引数および戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntToLongFunction 1つのint値引数を受け取ってlong値の結果を生成する関数を表します。 applyAsLong(int)
IntToDoubleFunction 1つのint値引数を受け取ってdouble値の結果を生成する関数を表します。 applyAsDobule(int)
LongToIntFunction 1つのlong値引数を受け取ってint値の結果を生成する関数を表します。 applyAsInt(long)
LongToDoubleFunction 1つのlong値引数を受け取ってdouble値の結果を生成する関数を表します。 applyAsDobule(long)
DoubleToIntFunction 1つのdouble値引数を受け取ってint値の結果を生成する関数を表します。 applyAsInt(double)
DoubleToLongFunction 1つのdouble値引数を受け取ってlong値の結果を生成する関数を表します。 applyAsLong(double)

Functionの引数の数における特殊化型

インタフェース 説明 抽象メソッド
BiFunction<T,U,R> 2つの引数を受け取って結果を生成する関数を表します。 apply(T, U)

BiFunctionの戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
ToIntBiFunction<T,U> 2つの引数を受け取ってint値の結果を生成する関数を表します。 applyAsInt(T, U)
ToLongBiFunction<T,U> 2つの引数を受け取ってlong値の結果を生成する関数を表します。 applyAsLong(T, U)
ToDoubleBiFunction<T,U> 2つの引数を受け取ってdouble値の結果を生成する関数を表します。 applyAsDouble(T, U)

Functionの特殊化型で、Function<T,T>のサブインタフェース

インタフェース 説明 抽象メソッド
UnaryOperator<T> 単一のオペランドに作用してオペランドと同じ型の結果を生成する演算を表します。 apply(T)

UnaryOperatorの引数および戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntUnaryOperator 単一のint値オペランドに作用してint値の結果を生成する演算を表します。 applyAsInt(int)
LongUnaryOperator 単一のlong値オペランドに作用してlong値の結果を生成する演算を表します。 applyAsLong(long)
DoubleUnaryOperator 単一のdouble値オペランドに作用してdouble値の結果を生成する演算を表します。 applyAsDouble(double)

BiFunctionの特殊化型で、BiFunction<T,T,T>のサブインタフェース

インタフェース 説明 抽象メソッド
BinaryOperator<T> 同じ型の2つのオペランドに作用してオペランドと同じ型の結果を生成する演算を表します。 apply(T, U)

BinaryOperatorの引数および戻り値に対するプリミティブ特殊化型

インタフェース 説明 抽象メソッド
IntBinaryOperator 2つのint値オペランドに作用してint値の結果を生成する演算を表します。 applyAsInt(int, int)
LongBinaryOperator 2つのlong値オペランドに作用してlong値の結果を生成する演算を表します。 applyAsLong(long, long)
DoubleBinaryOperator 2つのdouble値オペランドに作用してdouble値の結果を生成する演算を表します。 applyAsDouble(double, double)

自分のブログを振り返る

ブログを始めて1年半近くが経とうとしています。ブログを始めた当初は、3日坊主レベルであまり続かないんではないかと思っていましたが、我ながらそこそこいいペースで更新できているんではないかと思います。内容はたいしたことないけども…。
というわけで少し振り返ってみる。

きっかけ

仕事柄、普段から割と技術情報を中心にインプットすることはあるんですが、アウトプットはあまりできていませんでした。
何かしらの形でアウトプットする習慣を付けたいなと思ったのが、ブログを始めたきっかけです。あと文章を書く練習をするためでもありました。

気付いたこと

当初、書くネタが尽きて続かないんではないかと思っていましたが、意外とネタはいろんなところに転がっているものです。というか、仕事中にいろいろと技術ネタを収集することが多いので、備忘録として書き残しておきたいと思うケースがほとんどです。
あと、仕事中に調べたことや試行錯誤したことをあとからブログにまとめることによって、頭の中をきちんと整理できて理解を深めることができます。論理的に考えて情報を整理しないとなかなか言語化することはできないものです。

よかったこと

人間というのは過去にやったことがあってもしばらくすると忘れるものです。普段、備忘録としてブログを書くことが多いですが、あとになって自分のブログが役に立つことがよくあります。まさに備忘録。ありがとう、過去の自分。

まとめ

基本的に自分の備忘録ではあるけども、とりあえず今の更新ペースを維持しよう。あと、ブログ1本を書くスピードを上げたい。そのためには表現力とか語彙力を向上させる必要があるかもです。頑張ります。

HttpServletRequest#isRequestedSessionIdFromCookie は初回アクセスのときは false を返す

JSESSIONID とか Cookie とか URL リライティングに関連する備忘録。
Servlet API 3.x の話です。

レガシーな Web アプリのちょっとしたリプレース中なんですが、携帯サイトがなくなったり、サイト自体が SSL アクセスされるようになったりしたので、セッション維持に使ってた JSESSIONID の URL リライティング (JSESSIONID を URL に埋め込むやつ) をやめて、Cookie のセッションIDが使われるように設定を変更しました。

web.xml に以下の設定を追加。

<session-config>
  <tracking-mode>COOKIE</tracking-mode>
</session-config>

しかし、初回アクセスかつリダイレクトが絡むようなケースの場合、なぜかリダイレクト先の URL に jsessionid パラメータが付与される...。で、調べてみたところアプリの中にこんなコードがありました。(抜粋)

boolean useCookie = request.isRequestedSessionIdFromCookie();
if (!useCookie) {
    // jsessionid を URL にくっ付ける処理
    // もともとは携帯サイト向けの処理
}

意図的に URL に jsessionid を付与してました。ただ、今回は Cookie を使うように設定しているため、useCookie は true となって if 文のブロックは実行されないはず。

どうやら、isRequestedSessionIdFromCookie() は、web.xml ではなくリクエストに Cookie が含まれているかどうかで判定しているっぽい。なので、初回アクセスの場合には false を返すわけですね。(たぶん)

というわけで、このあたりの処理は削除して解決。

Bean Validation の initialize でアノテーションの属性値をインスタンス変数に保持する件

一応、調べてみます。ドキュメントはこちら。
http://docs.oracle.com/javaee/7/api/javax/validation/ConstraintValidator.html

This method can be accessed concurrently, thread-safety must be ensured by the implementation.

バリデータのインスタンスは同一のものが使い回されるようです。そのため、isValid はスレッドセーフに実装する必要があります。

で、アノテーションのパラメータを initialize の中でインスタンス変数に保持するコードは割と一般的ですが、アノテーションのパラメータに異なる値を渡して検証する場合、isValid の処理で不整合が発生しないんだろうか、ということを調べてみます。

こんなアノテーションを用意。param でパラメータを取れるようにしておきます。

gist.github.com

こんなバリデータを用意。initializeアノテーションのパラメータをインスタンス変数に保持して、isValid で利用することを想定します。バリデーションロジックは適当です。

gist.github.com

こんなテストコードを用意。Bean のフィールドに指定している @SampleValidation には同じパラメータを渡します。

gist.github.com

これを実行すると以下のように出力されます。同一のインスタンスが使われています。

@initialize : param=hoge, hashCode=1858609436
@isValid : param=hoge, hashCode=1858609436, value=bar
@isValid : param=hoge, hashCode=1858609436, value=foo

次に、Bean のフィールドに指定しているアノテーションに別々のパラメータを渡します。

@SampleValidation(param = "hoge")
private String name;
@SampleValidation(param = "fuga")
private String value;

で、先ほどのテストコードを実行すると以下のように出力されます。アノテーションごとに別々のインスタンスが使われるようになります。

@initialize : param=fuga, hashCode=1858609436
@isValid : param=fuga, hashCode=1858609436, value=bar
@initialize : param=hoge, hashCode=786041152
@isValid : param=hoge, hashCode=786041152, value=foo
まとめ

アノテーションに別々のパラメータを渡すと、バリデータはアノテーションごとに別々のインスタンスが使われる。なるほど。

JMockit の部分モックを試す

JMockit の部分モックを試してみる。このあたりの API はバージョンによって変更や削除となることがあるため要注意。ちなみに今回試したバージョンは以下。

gist.github.com

メソッドをモック化する場合は、Expectations の中でモック化したいメソッドを登録する。登録していないメソッドはオリジナルのメソッドが呼び出される。
Expectations の引数にクラスを指定する場合、以降で生成されるそのクラスのインスタンスがモック化される。Expectations の引数にインスタンスを指定する場合、そのインスタンスのみモック化される。

昔は、@Mock("method()") とか書けたようだが、今は使えない模様。

参考

JMockit - Tutorial - Mocking

Atom から Visual Studio Code に乗り換える

普段、Markdown エディタとして使っている Atom がどうにも重たい。起動が重たいのはしょうがないにしても普通に文字を入力したいときに固まったりするし。
そこで、Atom よりは軽量と噂の Visual Studio Code に乗り換えようかと。標準で Emmet が使えるし。

code.visualstudio.com

現在のバージョンは v1.9.1 の模様。とりあえずインストール

これまで Atom を使っていたからなのか、UI が日本語だと見た目にもっさり感がある。

  1. Ctrl + Shift + P で Command Palette を開く
  2. Configure Language を実行する
  3. locale.json{ "locale":"en-US" } を設定する
  4. 再起動

その他の設定。

  1. File > Preferences > Settings で settings.json を開く
  2. 上書きする設定を右のペインに記載する
{
  "editor.fontSize": 13,
  "editor.fontFamily": "MeiryoKe_Gothic",
  "editor.tabSize": 2,
  "editor.renderWhitespace": "boundary"
}

等幅メイリオについてはこちら
"editor.renderWhitespace":"boundary" : 単語間の半角スペースを表示しない
※プロキシ環境の場合は "http.proxy" を設定


(2017/02/28 追記)
Atom では任意のフォルダを Project Folder として登録することができる。
で、普段利用するフォルダをいくつか登録しておいて、フォルダツリーから開きたいファイルを選択するような感じで使っていた。

Visual Studio Code にも EXPLORER でフォルダツリーを表示できるが、Atom のように任意のフォルダを複数登録することはできない模様。
というわけで、あるフォルダのファイルを手早く開きたいときは Ctrl + o でファイルを開くようにしている。

(2017/04/17 追記)
Visual Studio Code で JSON をフォーマットするショートカットは Alt+Shift+F だが、minify ができない。というわけで、以下の拡張機能をインストールする。

JSON Tools - Visual Studio Marketplace

  • pretty ⇒ Alt+M
  • minify ⇒ Shift+Alt+M