怠惰系エンジニアのメモ帳

勉強した内容をメモしていきます。

【Spring + Doma】ドメインクラスを、フォームクラスのフィールドとして使用する

DomaではドメインクラスというValueObjectを使用できますが、どうせならフォームクラスのフィールドとしても使用したいなと思って検証してみました。
※そもそもドメインクラスをフォームクラスのフィールドとして利用するのはどうなのか、という疑問はあります。(個人的には有りな気がしてます)
記載内容に間違いなどがありましたら、指摘をお願いします。

結果

結果から書くと、

  • ドメインクラスにString型の引数を1つ受け取る非privateなコンストラクタ、またはpublicなstaticファクトリメソッドを定義すればいい

でした。 ドメインクラスとは書いてますが、ドメインクラス以外の任意クラスにも応用できるはずです。(ドメインクラスはDoma上の表現なので当たり前ですが)

環境

検証環境

ライブラリ

  • Java : 1.8
  • Spring Boot : 1.5.6.RELEASE
    • spring-boot-starter-web : 1.5.6.RELEASE
    • doma-spring-boot-starter : 1.1.1
    • spring-boot-starter-thymeleaf : 1.5.6.RELEASE

フォームクラス

IDNameドメインクラスをフィールドに持つ、HelloFormクラスを定義しています。
リクエストパラメータの userIdname を、以下のフィールドにバインドしたいとします。

public class HelloForm implements Serializable {
    private ID<User> userId;
    private Name<User> name;
    
    // getter, setterは省略
    // 余談ですがgetter, setter無いと怒られるんですねぇ...。
}

ドメインクラス

HelloFormクラスに定義している通り、IDName クラスを作成します。
ID クラスは内部的に Long 型の値を保持し、Name クラスは String 型の値を保持することとします。

IDクラス

本来であれば Long 型の引数を受け取るコンストラクタ、またはファクトリメソッドを定義するだけでいいのですが、リクエストパラメータ(String)から IDインスタンスを生成するためのファクトリメソッドを追加で定義する必要があります。

今回は of という名前で、publicなstaticメソッドを定義しています。
メソッド名は任意ですが、public staticである必要があります。

(2017/08/13 追記)
うらがみさんより、「ファクトリメソッドのメソッド名は valueOf, of, from の何れかでないとダメだよ」と重要な情報を教えていただきました。
ありがとうございます!!

確かに ObjectToObjectConverter#determineFactoryMethod メソッド内で、マッピング先が String でない場合 valueOf, of, from の順で Method オブジェクトの取得を試みていますね。
調査不足で申し訳ありません…。

@Domain(valueType = Long.class, factoryMethod = "of")
public class ID<E> {

    private final Long value;

    private ID(final Long value) {
        this.value = value;
    }

    public static <R> ID<R> of(final Long value){
        return new ID<>(value);
    }
    
    // String型の引数を1つだけ受け取るファクトリメソッド
    public static <R> ID<R> of(final String value){
        return new ID<>(Long.valueOf(value));
    }
    
    public Long getValue() {
        return value;
    }

    @Override
    public String toString() {
        return value.toString();
    }
}

staticファクトリメソッドではなく、コンストラクタを定義する場合には非privateであればいいようです。

// これはダメ
private ID(final String value){...}

Nameクラス

Name クラスは String 型の値を内部的に保持するクラスですので、必然的に String 型の値を1つ受け取る非privateなコンストラクタ、またはstaticファクトリメソッドを作成することになります。

@Domain(valueType = String.class, factoryMethod = "of")
public class Name<E> {

    private final String value;

    private Name(final String value) {
        this.value = value;
    }

    public static <R> Name<R> of(final String value){
        return new Name<>(value);
    }

    @Override
    public String toString() {
        return value;
    }

    public String getValue() {
        return value;
    }
}

まとめ

ドメインクラスをFormクラスのフィールドとして使用したい場合

  • String 型の値を1つだけ受け取る非privateなコンストラクタを定義する。
  • コンストラクタを定義しない場合には、public staticなファクトリメソッドを定義する。
    • ただし、メソッド名は valueOf, of, from の何れかであること。

ことで、利用可能となる。

Spring側でStringからユーザ定義クラスへの変換を解決しようとしてくれるみたいです。 ユーザがマッピング処理を定義することも出来そうな気もしますが、できるだけ楽したい…。

publicなフィールドにバインドする(2018/08/13 追記)

うらがみさんからオトクな情報も教えていただきました。
HelloFormクラスに // 余談ですがgetter, setter無いと怒られるんですねぇ...。 と書いてますが、publicなフィールドにバインドする方法もあるとのこと。

このために実際のサンプルコードも作成してくだいました。 恐縮です。