システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る

FormとValueObjectの関係を整理する(続)

前回はコードのイメージということで全量を公開していませんでした。

vermeer.hatenablog.jp

今回は、前回はしていない部分のコードの説明を中心にしていきたいと思います。

Form用のValidation関連

マーカーとなるアノテーションと優先度を示すアノテーションで、Formのアノテーションの検証でエラーになったら後続検証はしないようにしています。

やってみて、残念に思ったのはClass<?>[] groups() default {};FormValidation.classを指定できなかったこと。

これが出来たらForm側の検証アノテーションの記述をもっとスッキリできたのに。。

独自アノテーション

@Target({METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {FormNotBlankValidator.class})
public @interface FormNotBlank {

    String message() default "{org.vermeerlab.validation.FormNotBlank.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({METHOD, FIELD})
    @Retention(RUNTIME)
    @Documented
    @interface List {

        FormNotBlank[] value();
    }
}

独自アノテーションの実装

public class FormNotBlankValidator implements ConstraintValidator<FormNotBlank, FormObject<String>> {

    @Override
    public void initialize(FormNotBlank constraintAnnotation) {

    }

    @Override
    public boolean isValid(FormObject<String> value, ConstraintValidatorContext context) {
        return Objects.equals(value.getValidatationValue(), "") == false;
    }
}

検証順の指定

@GroupSequence({FormValidation.class})
public @interface ValidationPriority {
}
public interface FormValidation {
}

検証用情報を付与するためのインターフェース

Validationを実行する際、Formの検証したい値を返却するためのインターフェースです。

Webは物理的に「文字列」だと思っているので、String固定でも良いかなぁと思ったのですが、検証用の戻り値の型は任意に指定できるようにしておきました。

出力情報なので無理にキャストを強制させる必然もないだろうという考えと、数値としてBeanValidationを行いたいときに面倒なことになりそうな気がした、というところです。

public interface FormObject<ReturnType> {

    /**
     * Formを検証に使用する値を返却します.
     * <P>
     * BeanValidationで使用する値はプリミティブ型である必要があり、ValueObjectにはAnnotateできません。
     * したがって、本メソッドを使用して内部のプリミティブ値を返却します.
     *
     * @return BeanValidationに使用する値
     */
    public ReturnType getValidatationValue();
}

テストクラス(抜粋)

@Test
public void formItemIsBlankWithFormValidation() {
    Form form = new Form();
    FormItem item = new FormItem("");
    form.setItem(item);

    Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    Set<ConstraintViolation<Form>> results = validator.validate(form, FormValidation.class);

    System.out.println("<<< FormValidationを優先的に実行するので、ValueObjectのValidationは実装されません >>>");

    for (ConstraintViolation<Form> result : results) {
        BeanValidationMessageInterpolator interpolator = BeanValidationMessageInterpolator.create();
        String convertedMessage = interpolator.toMessage(result);

        System.out.println("設定した値                               = " + result.getInvalidValue());
        System.out.println("検証時に変換されたメッセージ = " + result.getMessage());
        System.out.println("遅延変換したメッセージ           = " + convertedMessage);
        System.out.println();
    }

}

@Test
public void formItemValidateValueObject() {
    Form form = new Form();
    FormItem item = new FormItem("1");
    form.setItem(item);

    Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    Set<ConstraintViolation<Form>> results = validator.validate(form);

    System.out.println("<<< FormValidationはエラーにならないので、ValueObjectの検証が実行されます >>>");

    for (ConstraintViolation<Form> result : results) {
        BeanValidationMessageInterpolator interpolator = BeanValidationMessageInterpolator.create();
        String convertedMessage = interpolator.toMessage(result);

        System.out.println("設定した値                               = " + result.getInvalidValue());
        System.out.println("検証時に変換されたメッセージ = " + result.getMessage());
        System.out.println("遅延変換したメッセージ           = " + convertedMessage);
        System.out.println();
    }

}

実際のコードとしては、FormValidationを先に実行して、エラーがなければ引数無しのvalidationを改めて実行するイメージです。

Code

Bitbucket

さいごに

ベタに実装するとForm用のvalidationと、全てのvalidationの2回実施になってしまうので どうも冗長です。

標準の仕組みから逸脱しない(やりすぎない)方式だと、私がざっくり試した感じのアイディアとしては こんな感じです。

validatorについては個人的にこうあってほしいという思いもあるので、結局「標準ライブラリをラップしたオレオレライブラリ」を作ってしまう気がします。

Easyのやりすぎは良くないと思っているのですが、面倒くさがりやなので 結局 何かしらを作りたくなるんですよね。。

かといって、すべて自作する気も無くて「イイ感じに標準仕様と参照実装を活用する」のが私の限界という感じです。