Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,6 +22,9 @@ public class Constraints {
private Constraints() {
}

public static Consumer<Validation<Supplier<Boolean>>> conformsCheck(Supplier<String> descriptionSupplier) {
return val -> val.constraint(!val.value().get(), descriptionSupplier);
}
// String

public static Consumer<Validation<String>> isNotNull() {
Expand Down Expand Up @@ -265,4 +269,8 @@ private static boolean isInFuture(Temporal temporal) {
throw new DateTimeException("Unsupported Temporal class: " + temporal.getClass());
}
}

public static Consumer<Validation<Object>> isNotNullObject() {
return val -> val.constraint(isNotNull(val.value()), () -> format(NULL_ERROR_MESSAGE_TEMPLATE, getValueFormatted(val)));
}
}
Original file line number Diff line number Diff line change
@@ -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<String> problems) {
super(format(ERROR_MESSAGE_PATTERN, "Value(s)", label, join(". ", problems)));
}
public InvariantException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> problems) {
super(format(ERROR_MESSAGE_PATTERN, label, join("\r\n ", problems)));
}
}
Original file line number Diff line number Diff line change
@@ -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<Consumer<Void>> toValidate = new ArrayList<>();
private final List<String> problems = new ArrayList<>();
private String objectName;

public ObjectValidation(Consumer<Void>[] validations) {
toValidate.addAll(List.of(validations));
}

@SafeVarargs
public static <T> ObjectValidation checks(Consumer<Void>... validations) {
return new ObjectValidation(validations);
}

public static <T> Consumer<Void> check(T value, String label, Consumer<Validation<T>> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> descriptionSupplier) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> problems) {
super(format(ERROR_MESSAGE_PATTERN, "Value(s)", label, join(". ", problems)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}