diff --git a/aggregates/README.md b/aggregates/README.md new file mode 100644 index 0000000..bd13c87 --- /dev/null +++ b/aggregates/README.md @@ -0,0 +1,3 @@ +# cqrs4j-example-aggregates +DDD related code for all demo applications (aggregates, entities and business exceptions). + diff --git a/aggregates/pom.xml b/aggregates/pom.xml new file mode 100644 index 0000000..ce6b96a --- /dev/null +++ b/aggregates/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + + org.fuin.cqrs4j.example + cqrs4j-example-aggregates + 0.1.0-SNAPSHOT + cqrs4j-example-aggregates + DDD related code for all demo applications (aggregates, entities and business exceptions) + + + UTF-8 + 1.8 + 1.8 + 1.8 + 0.3.1-SNAPSHOT + + + + + + + + org.fuin.cqrs4j.example + cqrs4j-example-shared + 0.1.0-SNAPSHOT + + + + org.fuin + ddd-4-java + 0.2.1 + + + + org.fuin + cqrs-4-java + 0.2.1 + + + + org.fuin + objects4j + 0.6.9 + + + + org.slf4j + slf4j-api + 1.7.25 + + + + org.glassfish + javax.json + 1.1.4 + + + + + + junit + junit + 4.12 + test + + + + org.assertj + assertj-core + 3.10.0 + test + + + + org.fuin + units4j + 0.8.4 + test + + + + nl.jqno.equalsverifier + equalsverifier + 2.4.6 + test + + + + commons-io + commons-io + 2.6 + test + + + + org.eclipse + yasson + 1.0.3 + test + + + + org.hibernate.validator + hibernate-validator + 6.0.10.Final + test + + + jaxb-impl + com.sun.xml.bind + + + jaxb-api + javax.xml.bind + + + + + + + + + + sonatype.oss.snapshots + Sonatype OSS Snapshot Repository + https://oss.sonatype.org/content/repositories/snapshots + + false + + + always + true + + + + + + diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java new file mode 100644 index 0000000..753eea0 --- /dev/null +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.aggregates; + +import javax.validation.constraints.NotNull; + +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.cqrs4j.example.shared.PersonName; + + +/** + * A name that should be unique does already exist. + */ +public final class DuplicatePersonNameException extends Exception { + + private static final long serialVersionUID = 1000L; + + private PersonId personId; + + private PersonName name; + + /** + * Constructor with mandatory data. + * + * @param personId + * Identifier of the resource that caused the problem. + * @param name + * Name of the resource that caused the problem. + */ + public DuplicatePersonNameException(@NotNull final PersonId personId, @NotNull final PersonName name) { + super("The name '" + name + "' already exists: " + personId.asString()); + this.personId = personId; + this.name = name; + } + + /** + * Returns the identifier of the entity that has the name. + * + * @return Identifier. + */ + public final PersonId getPersonId() { + return personId; + } + + /** + * Returns the name that already exists. + * + * @return Name. + */ + public final PersonName getName() { + return name; + } + +} \ No newline at end of file diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java new file mode 100644 index 0000000..e896997 --- /dev/null +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.aggregates; + +import java.io.Serializable; +import java.util.Optional; + +import javax.validation.constraints.NotNull; + +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.ddd4j.ddd.AbstractAggregateRoot; +import org.fuin.ddd4j.ddd.ApplyEvent; +import org.fuin.ddd4j.ddd.EntityType; +import org.fuin.objects4j.common.Contract; + +/** + * Represents a natural person. + */ +public class Person extends AbstractAggregateRoot implements Serializable { + + private static final long serialVersionUID = 1000L; + + @NotNull + private PersonId id; + + /** + * Default constructor that is mandatory for aggregate roots. + */ + public Person() { + super(); + } + + /** + * Constructor with all data. + * + * @param id + * Unique identifier of the person. + * @param name + * Unique name of the person. + * @param service + * Service required by the method. + * + * @throws DuplicatePersonNameException + * The name already exists for another person. + */ + public Person(@NotNull final PersonId id, @NotNull final PersonName name, final CreatePersonService service) + throws DuplicatePersonNameException { + super(); + + // VERIFY PRECONDITIONS + Contract.requireArgNotNull("id", id); + Contract.requireArgNotNull("name", name); + + // VERIFY BUSINESS RULES + + // Rule 1: The name of the person must be unique + final Optional otherId = service.loadPersonIdByName(name); + if (otherId.isPresent()) { + throw new DuplicatePersonNameException(otherId.get(), name); + } + + // CREATE EVENT + apply(new PersonCreatedEvent(id, name)); + + } + + @Override + public PersonId getId() { + return id; + } + + @Override + public EntityType getType() { + return PersonId.TYPE; + } + + @ApplyEvent + public void applyEvent(final PersonCreatedEvent event) { + this.id = event.getEntityId(); + } + + /** + * Service for the constructor. + */ + public static interface CreatePersonService { + + /** + * Loads the person's identifier for a given name. + * + * @param name + * Person's name. + * + * @return Office identifier or empty if not found. + */ + public Optional loadPersonIdByName(@NotNull PersonName name); + + } + +} diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java new file mode 100644 index 0000000..ed6dad6 --- /dev/null +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.aggregates; + +import javax.annotation.concurrent.NotThreadSafe; +import javax.validation.constraints.NotNull; + +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.ddd4j.ddd.EntityType; +import org.fuin.ddd4j.esrepo.EventStoreRepository; +import org.fuin.esc.api.EventStore; + +/** + * Event sourced repository for storing a {@link Person} aggregate. + */ +@NotThreadSafe +public class PersonRepository extends EventStoreRepository { + + /** + * Constructor all mandatory data. + * + * @param eventStore + * Event store. + */ + public PersonRepository(final EventStore eventStore) { + super(eventStore); + } + + @Override + @NotNull + public Class getAggregateClass() { + return Person.class; + } + + @Override + @NotNull + public EntityType getAggregateType() { + return PersonId.TYPE; + } + + @Override + @NotNull + public Person create() { + return new Person(); + } + + @Override + @NotNull + public String getIdParamName() { + return "personId"; + } + +} \ No newline at end of file diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java new file mode 100644 index 0000000..f486708 --- /dev/null +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.aggregates; + +/** + * Domain specific code like aggregate root classes and their repositories. + */ diff --git a/aggregates/src/main/resources/.gitkeep b/aggregates/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java b/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java new file mode 100644 index 0000000..a3752f5 --- /dev/null +++ b/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java @@ -0,0 +1,57 @@ +package org.fuin.cqrs4j.example.aggregates; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import java.util.Optional; +import java.util.UUID; + +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.cqrs4j.example.shared.PersonName; +import org.junit.Test; + +/** + * Test for the {@link Person} class. + */ +public class PersonTest { + + @Test + public final void testCreateOK() throws DuplicatePersonNameException { + + // PREPARE + final PersonId personId = new PersonId(UUID.fromString("f645969a-402d-41a9-882b-d2d8000d0f43")); + final PersonName personName = new PersonName("Peter Parker"); + + // TEST + final Person testee = new Person(personId, personName, pid -> { return Optional.empty(); }) ; + + // VERIFY + assertThat(testee.getUncommittedChanges()).hasSize(1); + assertThat(testee.getUncommittedChanges().get(0)).isInstanceOf(PersonCreatedEvent.class); + final PersonCreatedEvent event = (PersonCreatedEvent) testee.getUncommittedChanges().get(0); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getName()).isEqualTo(personName); + + } + + + @Test + public final void testCreateDuplicateName() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Peter Parker"); + final PersonId otherId = new PersonId(UUID.randomUUID()); + + // TEST & VERIFY + try { + new Person(personId, personName, pid -> { return Optional.of(otherId); }) ; + fail("Excpected duplicate name exception"); + } catch (final DuplicatePersonNameException ex) { + assertThat(ex.getMessage()).isEqualTo("The name 'Peter Parker' already exists: " + otherId); + } + + } + +} diff --git a/aggregates/src/test/resources/.gitkeep b/aggregates/src/test/resources/.gitkeep new file mode 100644 index 0000000..e69de29