From 78abf4d53c69e1e13b753fe638e64598a6e4fef5 Mon Sep 17 00:00:00 2001 From: sle Date: Fri, 4 Feb 2022 14:42:44 +0100 Subject: [PATCH] #2 First draft for DomainObject validation --- .../domainprimitives/object/Aggregate.java | 5 ++ .../object/ComposedValueObject.java | 5 ++ .../domainprimitives/object/DomainObject.java | 63 ++---------------- .../ddd/domainprimitives/object/Entity.java | 5 ++ .../validation/Constraints.java | 8 +++ .../validation/InvariantException.java | 30 +-------- .../validation/ObjectInvariantException.java | 15 +++++ .../validation/ObjectValidation.java | 51 +++++++++++++++ .../validation/Validation.java | 2 +- .../validation/ValueInvariantException.java | 32 ++++++++++ .../object/DomainObjectTest.java | 12 ++-- .../object/testdata/Person.java | 64 +++++++++++-------- 12 files changed, 177 insertions(+), 115 deletions(-) create mode 100644 src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectInvariantException.java create mode 100644 src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectValidation.java create mode 100644 src/main/java/de/novatec/ddd/domainprimitives/validation/ValueInvariantException.java diff --git a/src/main/java/de/novatec/ddd/domainprimitives/object/Aggregate.java b/src/main/java/de/novatec/ddd/domainprimitives/object/Aggregate.java index 113de98..7c45694 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/object/Aggregate.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/object/Aggregate.java @@ -1,4 +1,9 @@ package de.novatec.ddd.domainprimitives.object; +import de.novatec.ddd.domainprimitives.validation.ObjectValidation; + public abstract class Aggregate extends DomainObject { + protected Aggregate(ObjectValidation checkList) { + super(checkList); + } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/object/ComposedValueObject.java b/src/main/java/de/novatec/ddd/domainprimitives/object/ComposedValueObject.java index 51b5410..806d6a4 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/object/ComposedValueObject.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/object/ComposedValueObject.java @@ -1,4 +1,9 @@ package de.novatec.ddd.domainprimitives.object; +import de.novatec.ddd.domainprimitives.validation.ObjectValidation; + public abstract class ComposedValueObject extends DomainObject { + protected ComposedValueObject(ObjectValidation checkList) { + super(checkList); + } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/object/DomainObject.java b/src/main/java/de/novatec/ddd/domainprimitives/object/DomainObject.java index 50847c6..e1c57ab 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/object/DomainObject.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/object/DomainObject.java @@ -1,66 +1,15 @@ package de.novatec.ddd.domainprimitives.object; -import de.novatec.ddd.domainprimitives.validation.InvariantException; - -import java.util.ArrayList; -import java.util.List; - -import static java.lang.String.format; +import de.novatec.ddd.domainprimitives.validation.ObjectValidation; public abstract class DomainObject { - private final List violations = new ArrayList<>(); - - protected DomainObject() {} - - /** - * Providing a starting point this stub could be used check on invariant violations. - */ - protected abstract void validate(); - - /** - * Helping validation method to ensure the field is not null - * - * @param field The object with should be null-checked - * @param label The lable or a description of the field - */ - protected void validateNotNull(Object field, String label) { - if (field == null) { - violations.add(format("%s should not be null", label)); - } - } - - /** - * If you have more complex validations than only null-checks, - * the violation could be added to the final {@link InvariantException InvariantException}. - * - * @param violation Info about the violation. - * Maybe which constraints are violated. - */ - protected void addInvariantViolation(String violation) { - this.violations.add(violation); - } - - /** - * Evaluate all invariants. - * - * @param name The Name of the class the invariants are defined. - * If not provided the default is the simple class name, see {@link #evaluateValidations() evaluateValidations} method. - * @throws InvariantException containing all invariant violations. - */ - protected void evaluateValidations(String name) throws InvariantException { - if (!violations.isEmpty()) { - throw new InvariantException(name, violations); - } + protected DomainObject(ObjectValidation checkList) { + validate(checkList); } - /** - * Evaluate all invariants. - * The label describing the object, the invariants are checked on, is the simple class name. - * - * @throws InvariantException containing all invariant violations. - */ - protected void evaluateValidations() throws InvariantException { - this.evaluateValidations(this.getClass().getSimpleName()); + private void validate(ObjectValidation checkList) { + checkList.forObject(this.getClass().getSimpleName()); + checkList.validate(); } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/object/Entity.java b/src/main/java/de/novatec/ddd/domainprimitives/object/Entity.java index 3813057..1250305 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/object/Entity.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/object/Entity.java @@ -1,4 +1,9 @@ package de.novatec.ddd.domainprimitives.object; +import de.novatec.ddd.domainprimitives.validation.ObjectValidation; + public abstract class Entity extends DomainObject { + protected Entity(ObjectValidation checkList) { + super(checkList); + } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/Constraints.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/Constraints.java index 972390f..8968dbc 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/validation/Constraints.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/Constraints.java @@ -3,6 +3,7 @@ import java.time.*; import java.time.temporal.Temporal; import java.util.function.Consumer; +import java.util.function.Supplier; import static java.lang.String.format; @@ -21,6 +22,9 @@ public class Constraints { private Constraints() { } + public static Consumer>> conformsCheck(Supplier descriptionSupplier) { + return val -> val.constraint(!val.value().get(), descriptionSupplier); + } // String public static Consumer> isNotNull() { @@ -265,4 +269,8 @@ private static boolean isInFuture(Temporal temporal) { throw new DateTimeException("Unsupported Temporal class: " + temporal.getClass()); } } + + public static Consumer> isNotNullObject() { + return val -> val.constraint(isNotNull(val.value()), () -> format(NULL_ERROR_MESSAGE_TEMPLATE, getValueFormatted(val))); + } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/InvariantException.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/InvariantException.java index 35871a3..1c37c27 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/validation/InvariantException.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/InvariantException.java @@ -1,36 +1,12 @@ package de.novatec.ddd.domainprimitives.validation; -import java.util.List; - -import static java.lang.String.format; -import static java.lang.String.join; - /** * InvariantException should be thrown during the validation of an object. * It is an unchecked exceptions, its superclass is {@link RuntimeException RuntimeException}. */ public class InvariantException extends RuntimeException { - private static final String ERROR_MESSAGE_PATTERN = "%s of %s is not valid: %s."; - - /** - * Constructs a new invariant exception (a runtime exception) with the following pattern: {@value #ERROR_MESSAGE_PATTERN}, - * e.g. "Value Name is not valid: Too long." - * @param label The name of the object, which is not valid. - * @param message The message describing the invalidity. - */ - public InvariantException(String label, String message) { - super(format(ERROR_MESSAGE_PATTERN, "Value", label, message)); - } - - /** - * Constructs a new invariant exception (a runtime exception) for a list of invalidities for one lable. - * The message will have the following pattern: {@value #ERROR_MESSAGE_PATTERN}, - * e.g. "Value(s) Name is not valid: Too long. No special characters allowed." - * @param label The name of the object, which is not valid. - * @param problems All messages describing each invalidity. - */ - public InvariantException(String label, List problems) { - super(format(ERROR_MESSAGE_PATTERN, "Value(s)", label, join(". ", problems))); - } + public InvariantException(String message) { + super(message); + } } diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectInvariantException.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectInvariantException.java new file mode 100644 index 0000000..5647699 --- /dev/null +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectInvariantException.java @@ -0,0 +1,15 @@ +package de.novatec.ddd.domainprimitives.validation; + +import java.util.List; + +import static java.lang.String.format; +import static java.lang.String.join; + +public class ObjectInvariantException extends InvariantException { + + private static final String ERROR_MESSAGE_PATTERN = "Value(s) of %s is not valid:\r\n %s"; + + public ObjectInvariantException(String label, List problems) { + super(format(ERROR_MESSAGE_PATTERN, label, join("\r\n ", problems))); + } +} diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectValidation.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectValidation.java new file mode 100644 index 0000000..7018f99 --- /dev/null +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/ObjectValidation.java @@ -0,0 +1,51 @@ +package de.novatec.ddd.domainprimitives.validation; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public final class ObjectValidation { + + private final List> toValidate = new ArrayList<>(); + private final List problems = new ArrayList<>(); + private String objectName; + + public ObjectValidation(Consumer[] validations) { + toValidate.addAll(List.of(validations)); + } + + @SafeVarargs + public static ObjectValidation checks(Consumer... validations) { + return new ObjectValidation(validations); + } + + public static Consumer check(T value, String label, Consumer> constraint) { + return var -> Validation.validate(value, label, constraint); + } + + public void validate() { + if (toValidate.isEmpty()) { + //TODO Logger + System.out.println("No Validations found for Object: " + objectName); + } + else { + toValidate.forEach(check -> { + try { + check.accept(null); + } + catch (InvariantException e) { + problems.add(e.getMessage()); + } + }); + evaluateValidations(); + } + } + + private void evaluateValidations() throws InvariantException { + if (!problems.isEmpty()) throw new ObjectInvariantException(objectName, problems); + } + + public void forObject(String objectName) { + this.objectName = objectName; + } +} diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/Validation.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/Validation.java index 5668659..69555df 100644 --- a/src/main/java/de/novatec/ddd/domainprimitives/validation/Validation.java +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/Validation.java @@ -31,7 +31,7 @@ public void addViolation(String problem) { } private void validate() throws InvariantException { - if (!problems.isEmpty()) throw new InvariantException(label, problems); + if (!problems.isEmpty()) throw new ValueInvariantException(label, problems); } public void constraint(Boolean value, Supplier descriptionSupplier) { diff --git a/src/main/java/de/novatec/ddd/domainprimitives/validation/ValueInvariantException.java b/src/main/java/de/novatec/ddd/domainprimitives/validation/ValueInvariantException.java new file mode 100644 index 0000000..81f9dcf --- /dev/null +++ b/src/main/java/de/novatec/ddd/domainprimitives/validation/ValueInvariantException.java @@ -0,0 +1,32 @@ +package de.novatec.ddd.domainprimitives.validation; + +import java.util.List; + +import static java.lang.String.format; +import static java.lang.String.join; + +public class ValueInvariantException extends InvariantException { + + private static final String ERROR_MESSAGE_PATTERN = "%s of %s is not valid: %s."; + + /** + * Constructs a new invariant exception (a runtime exception) with the following pattern: {@value #ERROR_MESSAGE_PATTERN}, + * e.g. "Value Name is not valid: Too long." + * @param label The name of the object, which is not valid. + * @param message The message describing the invalidity. + */ + public ValueInvariantException(String label, String message) { + super(format(ERROR_MESSAGE_PATTERN, "Value", label, message)); + } + + /** + * Constructs a new invariant exception (a runtime exception) for a list of invalidities for one lable. + * The message will have the following pattern: {@value #ERROR_MESSAGE_PATTERN}, + * e.g. "Value(s) Name is not valid: Too long. No special characters allowed." + * @param label The name of the object, which is not valid. + * @param problems All messages describing each invalidity. + */ + public ValueInvariantException(String label, List problems) { + super(format(ERROR_MESSAGE_PATTERN, "Value(s)", label, join(". ", problems))); + } +} diff --git a/src/test/java/de/novatec/ddd/domainprimitives/object/DomainObjectTest.java b/src/test/java/de/novatec/ddd/domainprimitives/object/DomainObjectTest.java index fec40b7..d57af91 100644 --- a/src/test/java/de/novatec/ddd/domainprimitives/object/DomainObjectTest.java +++ b/src/test/java/de/novatec/ddd/domainprimitives/object/DomainObjectTest.java @@ -26,7 +26,8 @@ void should_throw_invariant_exception_if_field_is_null() { Name name = new Name("Peter"); InvariantException exception = assertThrows(InvariantException.class, () -> new Person(null, name)); - assertEquals("Value(s) of Person is not valid: Person ID should not be null.", exception.getMessage()); + assertEquals("Value(s) of Person is not valid:\r\n" + + " Value(s) of Person ID is not valid: Person ID (null) should not be null.", exception.getMessage()); } @Test @@ -35,13 +36,16 @@ void should_throw_invariant_exception_if_custom_violation() { Name name = new Name("Peter"); InvariantException exception = assertThrows(InvariantException.class, () -> new Person(personId,name)); - assertEquals("Value(s) of Person is not valid: This is not allowed.", exception.getMessage()); + assertEquals("Value(s) of Person is not valid:\r\n" + + " Value(s) of PersonID and Name Constraint is not valid: PersonID and Name Constraint Name Peter with id 12 is not allowed.", exception.getMessage()); } @Test void should_throw_invariant_exception_if_field_is_null_with_multiple_fields() { InvariantException exception = assertThrows(InvariantException.class, () -> new Person(null, null)); - assertEquals("Value(s) of Person is not valid: Person ID should not be null. Name should not be null.", exception.getMessage()); + assertEquals("Value(s) of Person is not valid:\r\n" + + " Value(s) of Person ID is not valid: Person ID (null) should not be null.\r\n" + + " Value(s) of Name is not valid: Name (null) should not be null.", exception.getMessage()); } -} \ No newline at end of file +} diff --git a/src/test/java/de/novatec/ddd/domainprimitives/object/testdata/Person.java b/src/test/java/de/novatec/ddd/domainprimitives/object/testdata/Person.java index 5788807..a49f925 100644 --- a/src/test/java/de/novatec/ddd/domainprimitives/object/testdata/Person.java +++ b/src/test/java/de/novatec/ddd/domainprimitives/object/testdata/Person.java @@ -2,32 +2,44 @@ import de.novatec.ddd.domainprimitives.object.Aggregate; -public class Person extends Aggregate { - - private final PersonId personId; - private final Name name; - - public Person(PersonId personId, Name name) { - this.personId = personId; - this.name = name; - - this.validate(); - } +import static de.novatec.ddd.domainprimitives.validation.Constraints.conformsCheck; +import static de.novatec.ddd.domainprimitives.validation.Constraints.isNotNullObject; +import static de.novatec.ddd.domainprimitives.validation.ObjectValidation.check; +import static de.novatec.ddd.domainprimitives.validation.ObjectValidation.checks; - public PersonId getPersonId() { - return personId; - } - - @Override - protected void validate() { - validateNotNull(personId, "Person ID"); - validateNotNull(name, "Name"); - - if ((personId != null && personId.getValue().equals(12L)) && - (name != null && "Peter".equals(name.getValue()))) { - addInvariantViolation("This is not allowed"); - } +public class Person extends Aggregate { - evaluateValidations(); - } + private final PersonId personId; + private final Name name; + + + /** + * This variant requires an all-args constructor + */ + public Person(PersonId personId, Name name) { + super(checks( + check(personId, "Person ID", isNotNullObject()), + check(name, "Name", isNotNullObject()), + check(() -> (personId != null && personId.getValue().equals(12L)) && (name != null &&"Peter".equals(name.getValue())), + "PersonID and Name Constraint", conformsCheck(() -> "Name " +name + " with id "+ personId+ " is not allowed")) + )); + this.personId = personId; + this.name = name; + } + + public Person(PersonId personId) { + this(personId, new Name("defaultName")); + } + + public Person(Name name) { + this(new PersonId(123L), name); + } + + public PersonId getPersonId() { + return personId; + } + + public Name getName() { + return name; + } }