Initial commit of aggregates module

This commit is contained in:
Michael Schnell
2019-12-30 17:17:02 +01:00
parent e98f765eb4
commit 73c2ea230b
9 changed files with 456 additions and 0 deletions

3
aggregates/README.md Normal file
View File

@@ -0,0 +1,3 @@
# cqrs4j-example-aggregates
DDD related code for all demo applications (aggregates, entities and business exceptions).

141
aggregates/pom.xml Normal file
View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.fuin.cqrs4j.example</groupId>
<artifactId>cqrs4j-example-aggregates</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>cqrs4j-example-aggregates</name>
<description>DDD related code for all demo applications (aggregates, entities and business exceptions)</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<esc.version>0.3.1-SNAPSHOT</esc.version>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.fuin.cqrs4j.example</groupId>
<artifactId>cqrs4j-example-shared</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.fuin</groupId>
<artifactId>ddd-4-java</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>org.fuin</groupId>
<artifactId>cqrs-4-java</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>org.fuin</groupId>
<artifactId>objects4j</artifactId>
<version>0.6.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.4</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fuin</groupId>
<artifactId>units4j</artifactId>
<version>0.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>2.4.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>1.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>jaxb-impl</artifactId>
<groupId>com.sun.xml.bind</groupId>
</exclusion>
<exclusion>
<artifactId>jaxb-api</artifactId>
<groupId>javax.xml.bind</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<repositories>
<repository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@@ -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;
}
}

View File

@@ -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<PersonId> 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<PersonId> 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<PersonId> loadPersonIdByName(@NotNull PersonName name);
}
}

View File

@@ -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<PersonId, Person> {
/**
* Constructor all mandatory data.
*
* @param eventStore
* Event store.
*/
public PersonRepository(final EventStore eventStore) {
super(eventStore);
}
@Override
@NotNull
public Class<Person> 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";
}
}

View File

@@ -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.
*/

View File

View File

@@ -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);
}
}
}

View File