--- title: YAVIによるValue ObjectのValidation tags: ["Java", "YAVI"] categories: ["Programming", "Java", "am", "ik", "yavi"] date: 2022-06-10T12:45:10Z updated: 2022-06-10T12:50:56Z --- [YAVI](https://github.com/making/yavi)には[`ValueValidator`](https://yavi.ik.am/#define-validator-for-a-single-value)という、あるXクラスの値を検証した後にYクラスの値に変換して返す便利な機能があります。 この機能を使うとValue ObjectのValidationをエレガントに定義できます。 次のRecordを題材にします。 ```java record Name(String value) { } record Age(Integer value) { } record Person(Name name, Age age) { } ``` `String`を検証し、`Name`に変換して返す`ValueValidator`や`Integer`を検証して`Age`を返す`ValueValidator`は次のように定義できます。 ```java final StringValidator nameValidator = StringValidatorBuilder .of("name", c -> c.notBlank().lessThanOrEqual(255)) .build(Name::new); final IntegerValidator ageValidator = IntegerValidatorBuilder .of("age", c -> c.notNull().greaterThanOrEqual(0)) .build(Age::new); ``` 検証結果は次のように`Validated`型として返ります。 ```java final Validated nameValidated = nameValidator.validate("John Doe"); final Validated ageValidated = ageValidator.validate(30); ``` 検証が成功する場合は変換されたインスタンスを取得し、検証が失敗する場合は例外をスローしたい場合は次のように取り出せます。 ```java final Name name = nameValidated.orElseThrow(ConstraintViolationsException::new); final Age age = ageValidated.orElseThrow(ConstraintViolationsException::new); ``` ショートカットしたい場合は、次のように書けます。 ```java final Name name = nameValidator.validated("John Doe"); final Age age = ageValidator.validated(30); ``` `Validated`と`Validated`から`Validated`を作ることができます。 ```java final Validated personValidated = nameValidated.combine(ageValidated).apply(Person::new); ``` この場合、`Name`と`Age`のエラーメッセージは集約されます。 次のようなファクトリメソッドを作ると便利でしょう。 ```java record Name(String value) { static final StringValidator validator = StringValidatorBuilder .of("name", c -> c.notBlank().lessThanOrEqual(255)) .build(Name::new); public static Validated of(String value) { return validator.validate(value); } } record Age(Integer value) { static final IntegerValidator validator = IntegerValidatorBuilder .of("age", c -> c.notNull().greaterThanOrEqual(0)) .build(Age::new); public static Validated of(Integer value) { return validator.validate(value); } } ``` 次のように利用できます。 ```java final Validated personValidated = Name.of("John Doe") .combine(Age.of(30)) .apply(Person::new); ``` recordの場合はパブリックコンストラクタができるため、`of`ファクトリを用意しても、入力チェックなしでコンストラクタからインスタンスを生成することができてしまいます。 コンストラクタ内で入力チェックし、不変条件を満たさないインスタンスを作れないようにしたい場合は、`lazy()`メソッドをつけることでコンストラクタ内でvalidateを実行可能です。 ```java record Name(String value) { static final StringValidator validator = StringValidatorBuilder .of("name", c -> c.notBlank().lessThanOrEqual(255)) .build(Name::new); // compact constructor Name { Name.validator.lazy().validated(value); } } record Age(Integer value) { static final IntegerValidator validator = IntegerValidatorBuilder .of("age", c -> c.notNull().greaterThanOrEqual(0)) .build(Age::new); // compact constructor Age { Age.validator.lazy().validated(value); } } ``` `lazy()`がないと、例えば`Name`コンストラクタ内で`Name`インスタンスを作ろうとしてまた`Name`コンストラクタが呼ばれ、`StackOverflowError`が発生します。 `lazy()`をつけると`Name`インスタンスを返す代わりに`Supplier`が返り、インスタンスを生成するタイミングを遅延できます。これにより`StackOverflowError`の発生が防がれます。 ```java Name name = new Name(" "); // => "name" must not be blank Age age = new Age(-1); // => "age" must be greater than or equal to 0 ``` `Validated`と`Validated`から`Validated`を作る代わりに、 `StringValidator`と`IntegerValidator`から`Arguments2Validator`を作り、 そこから直接`Validated`を作ることもできます。 ```java final Arguments2Validator personValidator = Name.validator .split(Age.validator) .apply(Person::new); ``` 次のように利用できます。 ```java final Validated personValidated = Person.validator.validate("John Doe", 30); ``` または ```java final Person person = Person.validator.validated("John Doe", 30); ``` エラーは集約されるので、次のような検証を行うと、 ```java final Person person = Person.validator.validated(" ", -1); ``` 次のようなメッセージを持つ例外がスローされます。 ``` am.ik.yavi.core.ConstraintViolationsException: Constraint violations found! * "name" must not be blank * "age" must be greater than or equal to 0 ``` なお、制約違反情報は`ConstraintViolationsException#violations`で取得できます。 --- YAVIを使ったValue ObjectのValidationについて紹介しました。 とても便利なので[YAVI](https://github.com/making/yavi)を使いましょう。