Refactored module and package structure

This commit is contained in:
Michael Schnell
2019-12-31 08:21:44 +01:00
parent 61db5c0bbe
commit eb71c77197
42 changed files with 347 additions and 1984 deletions

View File

@@ -8,10 +8,11 @@ import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import org.eclipse.yasson.FieldAccessStrategy;
import org.fuin.cqrs4j.example.spring.command.domain.PersonRepository;
import org.fuin.cqrs4j.example.spring.shared.SharedUtils;
import org.fuin.esc.api.EventStore;
import org.fuin.cqrs4j.example.aggregates.PersonRepository;
import org.fuin.cqrs4j.example.shared.SharedUtils;
import org.fuin.cqrs4j.example.spring.shared.Config;
import org.fuin.esc.esjc.ESJCEventStore;
import org.fuin.esc.esjc.IESJCEventStore;
import org.fuin.esc.spi.EnhancedMimeType;
import org.fuin.esc.spi.SerDeserializerRegistry;
import org.springframework.boot.SpringApplication;
@@ -21,7 +22,8 @@ import org.springframework.web.context.annotation.RequestScope;
import com.github.msemys.esjc.EventStoreBuilder;
@SpringBootApplication(scanBasePackages = "org.fuin.cqrs4j.example.spring.command")
@SpringBootApplication(scanBasePackages = { "org.fuin.cqrs4j.example.spring.command.app",
"org.fuin.cqrs4j.example.spring.command.controller", "org.fuin.cqrs4j.example.spring.shared" })
public class CmdApplication {
/**
@@ -30,49 +32,48 @@ public class CmdApplication {
* @return Fully configured instance.
*/
@Bean
public Jsonb createJsonb() {
final JsonbConfig config = new JsonbConfig()
.withAdapters(SharedUtils.JSONB_ADAPTERS)
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
final Jsonb jsonb = JsonbBuilder.create(config);
return jsonb;
}
/**
* Creates a TCP based event store connection.
*
* @param config Configuration to use.
*
* @return New event store instance.
*/
@Bean(destroyMethod = "shutdown")
public com.github.msemys.esjc.EventStore getESHttpEventStore(final CmdConfig config) {
return EventStoreBuilder.newBuilder().singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort())
.executor(Executors.newFixedThreadPool(10)).userCredentials(config.getEventStoreUser(), config.getEventStorePassword())
.build();
public Jsonb createJsonb() {
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
final Jsonb jsonb = JsonbBuilder.create(config);
return jsonb;
}
/**
* Creates an event store connection.
* Creates a TCP based event store connection.
*
* @param config Configuration to use.
*
* @return New event store instance.
*/
*/
@Bean(destroyMethod = "shutdown")
public com.github.msemys.esjc.EventStore getESHttpEventStore(final Config config) {
return EventStoreBuilder.newBuilder()
.singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort())
.executor(Executors.newFixedThreadPool(10))
.userCredentials(config.getEventStoreUser(), config.getEventStorePassword()).build();
}
/**
* Creates an event store connection.
*
* @param config Configuration to use.
*
* @return New event store instance.
*/
@Bean(destroyMethod = "close")
public EventStore getEventStore(final com.github.msemys.esjc.EventStore es) {
public IESJCEventStore getEventStore(final com.github.msemys.esjc.EventStore es) {
final SerDeserializerRegistry registry = SharedUtils.createRegistry();
final EventStore eventstore = new ESJCEventStore(es, registry, registry,
EnhancedMimeType.create("application", "json", Charset.forName("utf-8")));
eventstore.open();
return eventstore;
final IESJCEventStore eventstore = new ESJCEventStore(es, registry, registry,
EnhancedMimeType.create("application", "json", Charset.forName("utf-8")));
eventstore.open();
return eventstore;
}
}
/**
* Creates an event sourced repository that can store a person.
* Creates an event sourced repository that can store a person.
*
* @param eventStore Event store to use.
*
@@ -80,11 +81,11 @@ public class CmdApplication {
*/
@Bean
@RequestScope
public PersonRepository create(final EventStore eventStore) {
return new PersonRepository(eventStore);
}
public static void main(String[] args) {
public PersonRepository create(final IESJCEventStore eventStore) {
return new PersonRepository(eventStore);
}
public static void main(String[] args) {
SpringApplication.run(CmdApplication.class, args);
}

View File

@@ -1,127 +0,0 @@
package org.fuin.cqrs4j.example.spring.command.app;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class CmdConfig {
private static final String EVENT_STORE_PROTOCOL = "http";
private static final String EVENT_STORE_HOST = "localhost";
private static final int EVENT_STORE_HTTP_PORT = 2113;
private static final int EVENT_STORE_TCP_PORT = 1113;
private static final String EVENT_STORE_USER = "admin";
private static final String EVENT_STORE_PASSWORD = "changeit";
@Value("${EVENT_STORE_PROTOCOL:http}")
private String eventStoreProtocol;
@Value("${EVENT_STORE_HOST:localhost}")
private String eventStoreHost;
@Value("${EVENT_STORE_HTTP_PORT:2113}")
private int eventStoreHttpPort;
@Value("${EVENT_STORE_TCP_PORT:1113}")
private int eventStoreTcpPort;
@Value("${EVENT_STORE_USER:admin}")
private String eventStoreUser;
@Value("${EVENT_STORE_PASSWORD:changeit}")
private String eventStorePassword;
/**
* Constructor using default values internally.
*/
public CmdConfig() {
super();
this.eventStoreProtocol = EVENT_STORE_PROTOCOL;
this.eventStoreHost = EVENT_STORE_HOST;
this.eventStoreHttpPort = EVENT_STORE_HTTP_PORT;
this.eventStoreTcpPort = EVENT_STORE_TCP_PORT;
this.eventStoreUser = EVENT_STORE_USER;
this.eventStorePassword = EVENT_STORE_PASSWORD;
}
/**
* Constructor with all data.
*
* @param eventStoreProtocol Protocol.
* @param eventStoreHost Host.
* @param eventStoreHttpPort HTTP port
* @param eventStoreTcpPort TCP port.
* @param eventStoreUser User.
* @param eventStorePassword Password.
*/
public CmdConfig(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
final int eventStoreTcpPort, final String eventStoreUser, final String eventStorePassword) {
super();
this.eventStoreProtocol = eventStoreProtocol;
this.eventStoreHost = eventStoreHost;
this.eventStoreHttpPort = eventStoreHttpPort;
this.eventStoreTcpPort = eventStoreTcpPort;
this.eventStoreUser = eventStoreUser;
this.eventStorePassword = eventStorePassword;
}
/**
* Returns the protocol of the event store.
*
* @return Either http or https.
*/
public String getEventStoreProtocol() {
return eventStoreProtocol;
}
/**
* Returns the host name of the event store.
*
* @return Name.
*/
public String getEventStoreHost() {
return eventStoreHost;
}
/**
* Returns the HTTP port of the event store.
*
* @return Port.
*/
public int getEventStoreHttpPort() {
return eventStoreHttpPort;
}
/**
* Returns the TCP port of the event store.
*
* @return Port.
*/
public int getEventStoreTcpPort() {
return eventStoreTcpPort;
}
/**
* Returns the username of the event store.
*
* @return Username.
*/
public String getEventStoreUser() {
return eventStoreUser;
}
/**
* Returns the password of the event store.
*
* @return Password.
*/
public String getEventStorePassword() {
return eventStorePassword;
}
}

View File

@@ -1,160 +0,0 @@
/**
* 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.spring.command.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.fuin.cqrs4j.CommandExecutionFailedException;
import org.fuin.cqrs4j.SimpleResult;
import org.fuin.cqrs4j.example.spring.command.domain.DuplicatePersonNameException;
import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException;
import org.fuin.ddd4j.ddd.AggregateDeletedException;
import org.fuin.ddd4j.ddd.AggregateNotFoundException;
import org.fuin.ddd4j.ddd.AggregateVersionConflictException;
import org.fuin.ddd4j.ddd.AggregateVersionNotFoundException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.ExceptionShortIdentifable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Maps the exceptions into a HTTP status.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final String CONSTRAINT_VIOLATION = "CONSTRAINT_VIOLATION";
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = AggregateAlreadyExistsException.class)
public ResponseEntity<SimpleResult> exception(final AggregateAlreadyExistsException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.CONFLICT);
}
@ExceptionHandler(value = AggregateDeletedException.class)
public ResponseEntity<SimpleResult> exception(final AggregateDeletedException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.GONE);
}
@ExceptionHandler(value = AggregateNotFoundException.class)
public ResponseEntity<SimpleResult> exception(final AggregateNotFoundException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(value = AggregateVersionConflictException.class)
public ResponseEntity<SimpleResult> exception(final AggregateVersionConflictException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.CONFLICT);
}
@ExceptionHandler(value = AggregateVersionNotFoundException.class)
public ResponseEntity<SimpleResult> exception(final AggregateVersionNotFoundException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(value = CommandExecutionFailedException.class)
public ResponseEntity<SimpleResult> exception(final CommandExecutionFailedException ex) {
final String shortId;
if (ex.getCause() instanceof ExceptionShortIdentifable) {
final ExceptionShortIdentifable esi = (ExceptionShortIdentifable) ex.getCause();
shortId = esi.getShortId();
} else {
shortId = ex.getCause().getClass().getName();
}
LOG.info("{} {}", shortId, ex.getCause().getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(shortId, ex.getMessage()), headers, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = DuplicatePersonNameException.class)
public ResponseEntity<SimpleResult> exception(final DuplicatePersonNameException ex) {
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(ex.getShortId(), ex.getMessage()), headers, HttpStatus.CONFLICT);
}
@ExceptionHandler(value = ConstraintViolationException.class)
public ResponseEntity<SimpleResult> exception(final ConstraintViolationException ex) {
LOG.info("{} {}", CONSTRAINT_VIOLATION, "ConstraintViolationException");
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(SimpleResult.error(CONSTRAINT_VIOLATION, asString(ex.getConstraintViolations())), headers, HttpStatus.BAD_REQUEST);
}
private static String asString(@Nullable final Set<ConstraintViolation<?>> constraintViolations) {
if (constraintViolations == null || constraintViolations.size() == 0) {
return "";
}
final List<String> list = new ArrayList<>();
for (final ConstraintViolation<?> constraintViolation : constraintViolations) {
list.add(Contract.asString(constraintViolation));
}
return list.toString();
}
}

View File

@@ -20,10 +20,10 @@ import javax.validation.Validator;
import org.fuin.cqrs4j.CommandExecutionFailedException;
import org.fuin.cqrs4j.SimpleResult;
import org.fuin.cqrs4j.example.spring.command.domain.DuplicatePersonNameException;
import org.fuin.cqrs4j.example.spring.command.domain.Person;
import org.fuin.cqrs4j.example.spring.command.domain.PersonRepository;
import org.fuin.cqrs4j.example.spring.shared.CreatePersonCommand;
import org.fuin.cqrs4j.example.aggregates.DuplicatePersonNameException;
import org.fuin.cqrs4j.example.aggregates.Person;
import org.fuin.cqrs4j.example.aggregates.PersonRepository;
import org.fuin.cqrs4j.example.shared.CreatePersonCommand;
import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException;
import org.fuin.ddd4j.ddd.AggregateDeletedException;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,72 +0,0 @@
/**
* 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.spring.command.domain;
import javax.validation.constraints.NotNull;
import org.fuin.cqrs4j.example.spring.shared.PersonId;
import org.fuin.cqrs4j.example.spring.shared.PersonName;
import org.fuin.objects4j.common.ExceptionShortIdentifable;
/**
* A name that should be unique does already exist.
*/
public final class DuplicatePersonNameException extends Exception implements ExceptionShortIdentifable {
private static final long serialVersionUID = 1000L;
private String sid;
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.sid = "DUPLICATE_PERSON_NAME";
this.personId = personId;
this.name = name;
}
@Override
public final String getShortId() {
return sid;
}
/**
* 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

@@ -1,111 +0,0 @@
/**
* 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.spring.command.domain;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import org.fuin.cqrs4j.example.spring.shared.PersonCreatedEvent;
import org.fuin.cqrs4j.example.spring.shared.PersonId;
import org.fuin.cqrs4j.example.spring.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;
/**
* A medical practitioner most likely also holder of an accredited academic
* degree.
*/
public class Person extends AbstractAggregateRoot<PersonId> implements Serializable {
private static final long serialVersionUID = 1000L;
@NotNull
private PersonId id;
@NotNull
private PersonName name;
/**
* 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 PersonService 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 PersonId otherId = service.loadPersonIdByName(name);
if (otherId != null) {
throw new DuplicatePersonNameException(otherId, 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();
this.name = event.getName();
}
/**
* Service for the constructor.
*/
public static interface PersonService {
/**
* Loads the person's identifier for a given name.
*
* @param name Person's name.
*
* @return Office identifier or {@literal null} if not found.
*/
public PersonId loadPersonIdByName(@NotNull PersonName name);
}
}

View File

@@ -1,51 +0,0 @@
package org.fuin.cqrs4j.example.spring.command.domain;
import javax.annotation.concurrent.NotThreadSafe;
import javax.validation.constraints.NotNull;
import org.fuin.cqrs4j.example.spring.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

@@ -15,11 +15,11 @@ import javax.json.bind.Jsonb;
import org.fuin.cqrs4j.ResultType;
import org.fuin.cqrs4j.SimpleResult;
import org.fuin.cqrs4j.example.shared.CreatePersonCommand;
import org.fuin.cqrs4j.example.shared.PersonCreatedEvent;
import org.fuin.cqrs4j.example.shared.PersonId;
import org.fuin.cqrs4j.example.shared.PersonName;
import org.fuin.cqrs4j.example.spring.command.app.CmdApplication;
import org.fuin.cqrs4j.example.spring.shared.CreatePersonCommand;
import org.fuin.cqrs4j.example.spring.shared.PersonCreatedEvent;
import org.fuin.cqrs4j.example.spring.shared.PersonId;
import org.fuin.cqrs4j.example.spring.shared.PersonName;
import org.fuin.esc.api.CommonEvent;
import org.fuin.esc.api.EventStore;
import org.fuin.esc.api.SimpleStreamId;