Cleanup Sonar issues, added Github build and formatted source
This commit is contained in:
37
.github/workflows/maven.yml
vendored
Normal file
37
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Java Maven Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Show versions
|
||||
run: ./mvnw -version
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'zulu'
|
||||
cache: maven
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
|
||||
- name: Build with Maven
|
||||
run: ./mvnw clean verify -U -B --file pom.xml
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,5 +2,6 @@
|
||||
!.gitignore
|
||||
!.gitkeep
|
||||
!.mvn
|
||||
!.github
|
||||
target
|
||||
*.log
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.fuin.cqrs4j.example.shared.PersonId;
|
||||
import org.fuin.cqrs4j.example.shared.PersonName;
|
||||
import org.fuin.objects4j.common.ExceptionShortIdentifable;
|
||||
|
||||
|
||||
/**
|
||||
* A name that should be unique does already exist.
|
||||
*/
|
||||
@@ -26,11 +25,11 @@ public final class DuplicatePersonNameException extends Exception implements Exc
|
||||
|
||||
private static final long serialVersionUID = 1000L;
|
||||
|
||||
private static final String SHORT_ID = "DUPLICATE_PERSON_NAME";
|
||||
|
||||
private PersonId personId;
|
||||
private static final String SHORT_ID = "DUPLICATE_PERSON_NAME";
|
||||
|
||||
private PersonName name;
|
||||
private final PersonId personId;
|
||||
|
||||
private final PersonName name;
|
||||
|
||||
/**
|
||||
* Constructor with mandatory data.
|
||||
@@ -64,9 +63,9 @@ public final class DuplicatePersonNameException extends Exception implements Exc
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getShortId() {
|
||||
return SHORT_ID;
|
||||
}
|
||||
@Override
|
||||
public final String getShortId() {
|
||||
return SHORT_ID;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,13 +12,13 @@
|
||||
*/
|
||||
package org.fuin.cqrs4j.example.aggregates;
|
||||
|
||||
import org.fuin.objects4j.common.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;
|
||||
import org.fuin.objects4j.common.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* Event sourced repository for storing a {@link Person} aggregate.
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.fuin.cqrs4j.example.shared.PersonName;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test for the {@link Person} class.
|
||||
* Test for the {@link Person} class.
|
||||
*/
|
||||
public class PersonTest {
|
||||
|
||||
@@ -24,7 +24,9 @@ public class PersonTest {
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
|
||||
// TEST
|
||||
final Person testee = new Person(personId, personName, pid -> { return Optional.empty(); }) ;
|
||||
final Person testee = new Person(personId, personName, pid -> {
|
||||
return Optional.empty();
|
||||
});
|
||||
|
||||
// VERIFY
|
||||
assertThat(testee.getUncommittedChanges()).hasSize(1);
|
||||
@@ -34,8 +36,7 @@ public class PersonTest {
|
||||
assertThat(event.getName()).isEqualTo(personName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public final void testCreateDuplicateName() {
|
||||
|
||||
@@ -46,12 +47,14 @@ public class PersonTest {
|
||||
|
||||
// TEST & VERIFY
|
||||
try {
|
||||
new Person(personId, personName, pid -> { return Optional.of(otherId); }) ;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class CmdExampleApp {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CmdExampleApp.class);
|
||||
|
||||
|
||||
@Inject
|
||||
private Instance<EventStore> eventStoreInstance;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class CmdExampleApp {
|
||||
public void execute() {
|
||||
|
||||
LOG.info("Executing...");
|
||||
|
||||
|
||||
try (final EventStore eventStore = eventStoreInstance.get()) {
|
||||
|
||||
final PersonId id = new PersonId(UUID.fromString("f645969a-402d-41a9-882b-d2d8000d0f43"));
|
||||
@@ -61,7 +61,7 @@ public class CmdExampleApp {
|
||||
repo.update(person);
|
||||
|
||||
LOG.info("Updated event store...");
|
||||
|
||||
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("Error saving person aggregate into event store", ex);
|
||||
}
|
||||
@@ -81,16 +81,16 @@ public class CmdExampleApp {
|
||||
new LogbackStandalone().init(args, new NewLogConfigFileParams("org.fuin.cqrs4j.example.javasecdi", "logback"));
|
||||
|
||||
LOG.info("Start example");
|
||||
|
||||
|
||||
try (final SeContainer container = SeContainerInitializer.newInstance().initialize()) {
|
||||
final CmdExampleApp app = container.select(CmdExampleApp.class).get();
|
||||
app.execute();
|
||||
}
|
||||
|
||||
LOG.info("Finished example");
|
||||
|
||||
|
||||
System.exit(0);
|
||||
|
||||
|
||||
} catch (final RuntimeException ex) {
|
||||
ex.printStackTrace(System.err);
|
||||
System.exit(1);
|
||||
|
||||
@@ -45,7 +45,8 @@ public class Person extends AbstractAggregateRoot<PersonId> implements Serializa
|
||||
* @throws DuplicatePersonNameException
|
||||
* The name already exists for another person.
|
||||
*/
|
||||
public Person(@NotNull final PersonId id, @NotNull final PersonName name, final PersonService service) throws DuplicatePersonNameException {
|
||||
public Person(@NotNull final PersonId id, @NotNull final PersonName name, final PersonService service)
|
||||
throws DuplicatePersonNameException {
|
||||
super();
|
||||
|
||||
// VERIFY PRECONDITIONS
|
||||
|
||||
@@ -11,10 +11,10 @@ import org.apache.deltaspike.jpa.api.transaction.TransactionScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class QryEntityManagerProducer {
|
||||
|
||||
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
@Produces
|
||||
@Produces
|
||||
@TransactionScoped
|
||||
public EntityManager create() {
|
||||
if (emf == null) {
|
||||
|
||||
@@ -16,7 +16,9 @@ public class QryScheduledExecutorService {
|
||||
@Produces
|
||||
@ApplicationScoped
|
||||
public ScheduledExecutorService create(ThreadFactory threadFactory) {
|
||||
return new ScheduledThreadPoolExecutor(1, threadFactory, (runnable, executor) -> { System.out.println("Execution blocked"); });
|
||||
return new ScheduledThreadPoolExecutor(1, threadFactory, (runnable, executor) -> {
|
||||
System.out.println("Execution blocked");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PersonCreatedEventHandler implements EventHandler<PersonCreatedEvent> {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonCreatedEventHandler.class);
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class QryEventChunkHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(QryEventChunkHandler.class);
|
||||
|
||||
|
||||
/** Unique name of the event store projection that is used. */
|
||||
public static final ProjectionStreamId PROJECTION_STREAM_ID = new ProjectionStreamId("qry-person-stream");
|
||||
|
||||
|
||||
@@ -68,14 +68,19 @@ public class SharedConfig {
|
||||
/**
|
||||
* Constructor with all data.
|
||||
*
|
||||
* @param eventStoreHost Host.
|
||||
* @param eventStoreHttpPort HTTP port
|
||||
* @param eventStoreTcpPort TCP port.
|
||||
* @param eventStoreUser User.
|
||||
* @param eventStorePassword Password.
|
||||
* @param eventStoreHost
|
||||
* Host.
|
||||
* @param eventStoreHttpPort
|
||||
* HTTP port
|
||||
* @param eventStoreTcpPort
|
||||
* TCP port.
|
||||
* @param eventStoreUser
|
||||
* User.
|
||||
* @param eventStorePassword
|
||||
* Password.
|
||||
*/
|
||||
public SharedConfig(final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort,
|
||||
final String eventStoreUser, final String eventStorePassword) {
|
||||
public SharedConfig(final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort, final String eventStoreUser,
|
||||
final String eventStorePassword) {
|
||||
super();
|
||||
this.eventStoreHost = eventStoreHost;
|
||||
this.eventStoreHttpPort = eventStoreHttpPort;
|
||||
|
||||
@@ -24,8 +24,7 @@ public class SharedSerDeserializerRegistryFactory {
|
||||
final JsonbDeSerializer jsonbDeSer = SharedUtils.createJsonbDeSerializer();
|
||||
|
||||
// Registry connects the type with the appropriate serializer and de-serializer
|
||||
final SerDeserializerRegistry serDeserRegistry = SharedUtils.createSerDeserializerRegistry(typeRegistry,
|
||||
jsonbDeSer);
|
||||
final SerDeserializerRegistry serDeserRegistry = SharedUtils.createSerDeserializerRegistry(typeRegistry, jsonbDeSer);
|
||||
|
||||
return serDeserRegistry;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.fuin.esc.spi.SerializedDataTypeRegistry;
|
||||
import org.fuin.esc.spi.SimpleSerializedDataTypeRegistry;
|
||||
import org.fuin.esc.spi.SimpleSerializerDeserializerRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Utility code shared between command (write) and query (read) module.
|
||||
*/
|
||||
@@ -51,16 +50,14 @@ public final class SharedUtils {
|
||||
|
||||
/** All JSON-B adapters from this module. */
|
||||
public static JsonbAdapter<?, ?>[] JSONB_ADAPTERS = new JsonbAdapter<?, ?>[] { new EventIdConverter(),
|
||||
new EntityIdPathConverter(new SharedEntityIdFactory()), new PersonId.Converter(),
|
||||
new PersonName.Converter() };
|
||||
new EntityIdPathConverter(new SharedEntityIdFactory()), new PersonId.Converter(), new PersonName.Converter() };
|
||||
|
||||
private SharedUtils() {
|
||||
throw new UnsupportedOperationException("It is not allowed to create an instance of a utiliy class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a registry that allows finding types (classes) based on their unique
|
||||
* type name.
|
||||
* Create a registry that allows finding types (classes) based on their unique type name.
|
||||
*
|
||||
* @return New instance.
|
||||
*/
|
||||
@@ -84,11 +81,12 @@ public final class SharedUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a registry that connects the type with the appropriate serializer and
|
||||
* de-serializer.
|
||||
* Creates a registry that connects the type with the appropriate serializer and de-serializer.
|
||||
*
|
||||
* @param typeRegistry Type registry (Mapping from type name to class).
|
||||
* @param jsonbDeSer JSON-B serializer/deserializer to use.
|
||||
* @param typeRegistry
|
||||
* Type registry (Mapping from type name to class).
|
||||
* @param jsonbDeSer
|
||||
* JSON-B serializer/deserializer to use.
|
||||
*
|
||||
* @return New instance.
|
||||
*/
|
||||
@@ -120,9 +118,8 @@ public final class SharedUtils {
|
||||
public static JsonbDeSerializer createJsonbDeSerializer() {
|
||||
|
||||
return JsonbDeSerializer.builder().withSerializers(EscSpiUtils.createEscJsonbSerializers())
|
||||
.withDeserializers(EscSpiUtils.createEscJsonbDeserializers())
|
||||
.withAdapters(JSONB_ADAPTERS).withPropertyVisibilityStrategy(new FieldAccessStrategy())
|
||||
.withEncoding(Charset.forName("utf-8")).build();
|
||||
.withDeserializers(EscSpiUtils.createEscJsonbDeserializers()).withAdapters(JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy()).withEncoding(Charset.forName("utf-8")).build();
|
||||
|
||||
}
|
||||
|
||||
@@ -138,8 +135,10 @@ public final class SharedUtils {
|
||||
/**
|
||||
* Constructor with all data.
|
||||
*
|
||||
* @param type Type.
|
||||
* @param clasz Class.
|
||||
* @param type
|
||||
* Type.
|
||||
* @param clasz
|
||||
* Class.
|
||||
*/
|
||||
public TypeClass(final SerializedDataType type, final Class<?> clasz) {
|
||||
super();
|
||||
|
||||
@@ -40,8 +40,8 @@ public class AggregateAlreadyExistsExceptionMapper implements ExceptionMapper<Ag
|
||||
|
||||
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
|
||||
|
||||
return Response.status(Status.CONFLICT).entity(SimpleResult.error(ex.getShortId(), ex.getMessage()))
|
||||
.type(headers.getMediaType()).build();
|
||||
return Response.status(Status.CONFLICT).entity(SimpleResult.error(ex.getShortId(), ex.getMessage())).type(headers.getMediaType())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class AggregateDeletedExceptionMapper implements ExceptionMapper<Aggregat
|
||||
|
||||
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
|
||||
|
||||
return Response.status(Status.GONE).entity(SimpleResult.error(ex.getShortId(), ex.getMessage()))
|
||||
.type(headers.getMediaType()).build();
|
||||
return Response.status(Status.GONE).entity(SimpleResult.error(ex.getShortId(), ex.getMessage())).type(headers.getMediaType())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class AggregateNotFoundExceptionMapper implements ExceptionMapper<Aggrega
|
||||
|
||||
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
|
||||
|
||||
return Response.status(Status.NOT_FOUND).entity(SimpleResult.error(ex.getShortId(), ex.getMessage()))
|
||||
.type(headers.getMediaType()).build();
|
||||
return Response.status(Status.NOT_FOUND).entity(SimpleResult.error(ex.getShortId(), ex.getMessage())).type(headers.getMediaType())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class AggregateVersionConflictExceptionMapper implements ExceptionMapper<
|
||||
|
||||
LOG.info("{} {}", ex.getShortId(), ex.getMessage());
|
||||
|
||||
return Response.status(Status.CONFLICT).entity(SimpleResult.error(ex.getShortId(), ex.getMessage()))
|
||||
.type(headers.getMediaType()).build();
|
||||
return Response.status(Status.CONFLICT).entity(SimpleResult.error(ex.getShortId(), ex.getMessage())).type(headers.getMediaType())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ public class AggregateVersionNotFoundExceptionMapper implements ExceptionMapper<
|
||||
@Context
|
||||
private HttpHeaders headers;
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public Response toResponse(final AggregateVersionNotFoundException ex) {
|
||||
|
||||
LOG.error("{} {}", ex.getShortId(), ex.getMessage());
|
||||
|
||||
return Response.status(Status.NOT_FOUND).entity(SimpleResult.error(ex.getShortId(), ex.getMessage()))
|
||||
.type(headers.getMediaType()).build();
|
||||
return Response.status(Status.NOT_FOUND).entity(SimpleResult.error(ex.getShortId(), ex.getMessage())).type(headers.getMediaType())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ConstraintViolationExceptionMapper implements ExceptionMapper<Const
|
||||
}
|
||||
|
||||
private static String asString(@Nullable final Set<ConstraintViolation<?>> constraintViolations) {
|
||||
if (constraintViolations == null || constraintViolations.size() == 0) {
|
||||
if (constraintViolations == null || constraintViolations.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
final List<String> list = new ArrayList<>();
|
||||
|
||||
@@ -48,7 +48,7 @@ public class PersonResource {
|
||||
|
||||
@Context
|
||||
UriInfo uriInfo;
|
||||
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -64,18 +64,18 @@ public class PersonResource {
|
||||
|
||||
try {
|
||||
// Create aggregate
|
||||
final Person person = new Person(cmd.getAggregateRootId(), cmd.getName(), (name) -> {
|
||||
final Person person = new Person(cmd.getAggregateRootId(), cmd.getName(), name -> {
|
||||
// TODO Execute a call to the query side to verify if the name already exists
|
||||
return Optional.empty();
|
||||
});
|
||||
repo.add(person);
|
||||
|
||||
// Send OK response
|
||||
return Response.ok(SimpleResult.ok()).build();
|
||||
return Response.ok(SimpleResult.ok()).build();
|
||||
} catch (final DuplicatePersonNameException ex) {
|
||||
throw new CommandExecutionFailedException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -31,37 +31,28 @@ import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
|
||||
@QuarkusTest
|
||||
public class PersonResourceIT {
|
||||
class PersonResourceIT {
|
||||
|
||||
@Inject
|
||||
IESJCEventStore eventStore;
|
||||
|
||||
|
||||
@Inject
|
||||
Jsonb jsonb;
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
|
||||
void testCreate() {
|
||||
|
||||
// PREPARE
|
||||
final PersonId personId = new PersonId(UUID.randomUUID());
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
final CreatePersonCommand cmd = new CreatePersonCommand(personId, personName);
|
||||
final String json = jsonb.toJson(cmd);
|
||||
|
||||
|
||||
// TEST & VERIFY
|
||||
final SimpleResult result =
|
||||
given()
|
||||
.accept(ContentType.JSON)
|
||||
.contentType(ContentType.JSON)
|
||||
.body(json)
|
||||
.when()
|
||||
.post("/persons/create")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract()
|
||||
.as(SimpleResult.class);
|
||||
final SimpleResult result = given().accept(ContentType.JSON).contentType(ContentType.JSON).body(json).when().post("/persons/create")
|
||||
.then().statusCode(200).extract().as(SimpleResult.class);
|
||||
assertThat(result.getType(), is(equalTo(ResultType.OK)));
|
||||
|
||||
|
||||
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
|
||||
final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 0, 1);
|
||||
final List<CommonEvent> events = slice.getEvents();
|
||||
@@ -71,7 +62,7 @@ public class PersonResourceIT {
|
||||
final PersonCreatedEvent event = (PersonCreatedEvent) ce.getData();
|
||||
assertThat(event.getEntityId(), is(equalTo(personId)));
|
||||
assertThat(event.getName(), is(equalTo(personName)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,12 +27,13 @@ import org.fuin.objects4j.common.Contract;
|
||||
@ApplicationScoped
|
||||
public class QryProjectionPositionRepository implements ProjectionService {
|
||||
|
||||
private static final String ARG_STREAM_ID = "streamId";
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
|
||||
@Override
|
||||
public void resetProjectionPosition(@NotNull final StreamId streamId) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
final QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos != null) {
|
||||
pos.setNextPosition(0L);
|
||||
@@ -41,7 +42,7 @@ public class QryProjectionPositionRepository implements ProjectionService {
|
||||
|
||||
@Override
|
||||
public Long readProjectionPosition(@NotNull StreamId streamId) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
final QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos == null) {
|
||||
return 0L;
|
||||
@@ -51,7 +52,7 @@ public class QryProjectionPositionRepository implements ProjectionService {
|
||||
|
||||
@Override
|
||||
public void updateProjectionPosition(@NotNull StreamId streamId, @NotNull Long nextEventNumber) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
Contract.requireArgNotNull("nextEventNumber", nextEventNumber);
|
||||
final QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos == null) {
|
||||
|
||||
@@ -14,5 +14,5 @@ package org.fuin.cqrs4j.example.quarkus.query.views;
|
||||
|
||||
/**
|
||||
* Contains the views used in this query application. A view never uses code of another view, means all views are completely independent of
|
||||
* each other. As an exception, the 'commons' package has some small classes that are not view specific.
|
||||
* each other. As an exception, the 'commons' package has some small classes that are not view specific.
|
||||
*/
|
||||
|
||||
@@ -28,12 +28,12 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PersonCreatedEventHandler implements EventHandler<PersonCreatedEvent> {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonCreatedEventHandler.class);
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
|
||||
@Override
|
||||
public EventType getEventType() {
|
||||
return PersonCreatedEvent.TYPE;
|
||||
@@ -41,7 +41,7 @@ public class PersonCreatedEventHandler implements EventHandler<PersonCreatedEven
|
||||
|
||||
@Override
|
||||
public void handle(final PersonCreatedEvent event) {
|
||||
LOG.info("Handle {}: {}", event.getClass().getSimpleName() , event);
|
||||
LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event);
|
||||
final PersonId personId = event.getEntityId();
|
||||
if (em.find(PersonListEntry.class, personId.asString()) == null) {
|
||||
em.persist(new PersonListEntry(personId, event.getName()));
|
||||
|
||||
@@ -33,7 +33,7 @@ public class PersonListEventDispatcher implements EventDispatcher {
|
||||
private final SimpleEventDispatcher delegate;
|
||||
|
||||
/**
|
||||
* Constructor with all events to be dispatched.
|
||||
* Constructor with all events to be dispatched.
|
||||
*
|
||||
* @param createdHandler
|
||||
* PersonCreatedEventHandler.
|
||||
|
||||
@@ -81,9 +81,8 @@ public class PersonListProjector {
|
||||
|
||||
// Read and dispatch events
|
||||
final Long nextEventNumber = chunkHandler.readNextEventNumber();
|
||||
eventstore.readAllEventsForward(chunkHandler.getProjectionStreamId(), nextEventNumber, 100, (currentSlice) -> {
|
||||
chunkHandler.handleChunk(currentSlice);
|
||||
});
|
||||
eventstore.readAllEventsForward(chunkHandler.getProjectionStreamId(), nextEventNumber, 100,
|
||||
currentSlice -> chunkHandler.handleChunk(currentSlice));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -31,71 +31,52 @@ import org.junit.jupiter.api.Test;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
|
||||
@QuarkusTest
|
||||
public class QryPersonResourceIT {
|
||||
|
||||
class QryPersonResourceIT {
|
||||
|
||||
@Inject
|
||||
IESHttpEventStore eventStore;
|
||||
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
@Test
|
||||
public void testGetByIdNotFound() {
|
||||
given()
|
||||
.pathParam("id", UUID.randomUUID())
|
||||
.when()
|
||||
.get("/persons/{id}")
|
||||
.then()
|
||||
.statusCode(404);
|
||||
void testGetByIdNotFound() {
|
||||
given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404);
|
||||
}
|
||||
|
||||
@ActivateRequestContext
|
||||
public boolean findPerson(final PersonId personId) {
|
||||
return em.find(PersonListEntry.class, personId.asString()) != null;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetByIdOK() {
|
||||
|
||||
void testGetByIdOK() {
|
||||
|
||||
// PREPARE
|
||||
final PersonId personId = new PersonId(UUID.randomUUID());
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
|
||||
final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName);
|
||||
final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()),
|
||||
new TypeName(event.getEventType().asBaseType()), event);
|
||||
final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()),
|
||||
new TypeName(event.getEventType().asBaseType()), event);
|
||||
eventStore.appendToStream(personStreamId, ce);
|
||||
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> findPerson(personId));
|
||||
|
||||
|
||||
// TEST & VERIFY
|
||||
|
||||
final PersonListEntry person =
|
||||
given()
|
||||
.pathParam("id", personId.asString())
|
||||
.when()
|
||||
.get("/persons/{id}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract()
|
||||
.as(PersonListEntry.class);
|
||||
|
||||
final PersonListEntry person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then().statusCode(200)
|
||||
.extract().as(PersonListEntry.class);
|
||||
assertThat(person.getId(), is(equalTo(personId)));
|
||||
assertThat(person.getName(), is(equalTo(personName)));
|
||||
|
||||
final PersonListEntry[] persons =
|
||||
given()
|
||||
.when()
|
||||
.get("/persons")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract()
|
||||
.as(PersonListEntry[].class);
|
||||
final PersonListEntry[] persons = given().when().get("/persons").then().statusCode(200).extract().as(PersonListEntry[].class);
|
||||
|
||||
assertThat(Arrays.asList(persons), is(not(empty())));
|
||||
final PersonListEntry person0 = persons[0];
|
||||
assertThat(person0.getId(), is(equalTo(personId)));
|
||||
assertThat(person0.getName(), is(equalTo(personName)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,14 +71,19 @@ public class Config {
|
||||
/**
|
||||
* Constructor with all data.
|
||||
*
|
||||
* @param eventStoreHost Host.
|
||||
* @param eventStoreHttpPort HTTP port
|
||||
* @param eventStoreTcpPort TCP port.
|
||||
* @param eventStoreUser User.
|
||||
* @param eventStorePassword Password.
|
||||
* @param eventStoreHost
|
||||
* Host.
|
||||
* @param eventStoreHttpPort
|
||||
* HTTP port
|
||||
* @param eventStoreTcpPort
|
||||
* TCP port.
|
||||
* @param eventStoreUser
|
||||
* User.
|
||||
* @param eventStorePassword
|
||||
* Password.
|
||||
*/
|
||||
public Config(final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort,
|
||||
final String eventStoreUser, final String eventStorePassword) {
|
||||
public Config(final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort, final String eventStoreUser,
|
||||
final String eventStorePassword) {
|
||||
super();
|
||||
this.eventStoreHost = eventStoreHost;
|
||||
this.eventStoreHttpPort = eventStoreHttpPort;
|
||||
|
||||
@@ -30,8 +30,7 @@ public class EsjcEventStoreFactory {
|
||||
@ApplicationScoped
|
||||
public EventStore createESJC(final ManagedExecutor executor, final Config config) {
|
||||
return EventStoreBuilder.newBuilder().singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort())
|
||||
.executor(executor).userCredentials(config.getEventStoreUser(), config.getEventStorePassword())
|
||||
.build();
|
||||
.executor(executor).userCredentials(config.getEventStoreUser(), config.getEventStorePassword()).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.fuin.cqrs4j.example.quarkus.shared;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class EventStoreFactory {
|
||||
public IESJCEventStore createEventStore(final com.github.msemys.esjc.EventStore es, final SerDeserializerRegistry registry) {
|
||||
|
||||
final IESJCEventStore eventstore = new ESJCEventStore.Builder().eventStore(es).serDesRegistry(registry)
|
||||
.targetContentType(EnhancedMimeType.create("application", "json", Charset.forName("utf-8"))).build();
|
||||
.targetContentType(EnhancedMimeType.create("application", "json", StandardCharsets.UTF_8)).build();
|
||||
|
||||
eventstore.open();
|
||||
return eventstore;
|
||||
|
||||
@@ -34,8 +34,7 @@ public class JsonbFactory {
|
||||
*/
|
||||
@Produces
|
||||
public Jsonb createJsonb() {
|
||||
final JsonbConfig config = new JsonbConfig()
|
||||
.withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
return JsonbBuilder.create(config);
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ public class SerDeserializerRegistryFactory {
|
||||
final JsonbDeSerializer jsonbDeSer = SharedUtils.createJsonbDeSerializer();
|
||||
|
||||
// Registry connects the type with the appropriate serializer and de-serializer
|
||||
return SharedUtils.createSerDeserializerRegistry(typeRegistry,
|
||||
jsonbDeSer);
|
||||
return SharedUtils.createSerDeserializerRegistry(typeRegistry, jsonbDeSer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,10 @@ public final class CreatePersonCommand extends AbstractAggregateCommand<PersonId
|
||||
/**
|
||||
* A new person was created in the system.
|
||||
*
|
||||
* @param id Identifies uniquely a person.
|
||||
* @param name Name of a person.
|
||||
* @param id
|
||||
* Identifies uniquely a person.
|
||||
* @param name
|
||||
* Name of a person.
|
||||
*/
|
||||
public CreatePersonCommand(@NotNull final PersonId id, @NotNull final PersonName name) {
|
||||
super(id, null);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.fuin.cqrs4j.example.shared;
|
||||
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
import javax.json.bind.annotation.JsonbProperty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@@ -26,6 +25,7 @@ import org.fuin.ddd4j.ddd.EntityIdPath;
|
||||
import org.fuin.ddd4j.ddd.EventType;
|
||||
import org.fuin.esc.spi.SerializedDataType;
|
||||
import org.fuin.objects4j.common.Contract;
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
|
||||
/**
|
||||
* A new person was created in the system.
|
||||
@@ -55,8 +55,10 @@ public final class PersonCreatedEvent extends AbstractDomainEvent<PersonId> {
|
||||
/**
|
||||
* A new person was created in the system.
|
||||
*
|
||||
* @param id Identifies uniquely a person.
|
||||
* @param name Name of a person.
|
||||
* @param id
|
||||
* Identifies uniquely a person.
|
||||
* @param name
|
||||
* Name of a person.
|
||||
*/
|
||||
public PersonCreatedEvent(@NotNull final PersonId id, @NotNull final PersonName name) {
|
||||
super(new EntityIdPath(id));
|
||||
|
||||
@@ -19,13 +19,13 @@ package org.fuin.cqrs4j.example.shared;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
import javax.json.bind.adapter.JsonbAdapter;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.fuin.ddd4j.ddd.AggregateRootUuid;
|
||||
import org.fuin.ddd4j.ddd.EntityType;
|
||||
import org.fuin.ddd4j.ddd.StringBasedEntityType;
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
import org.fuin.objects4j.ui.Label;
|
||||
import org.fuin.objects4j.ui.ShortLabel;
|
||||
import org.fuin.objects4j.ui.Tooltip;
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
import javax.json.bind.adapter.JsonbAdapter;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintValidator;
|
||||
@@ -32,6 +31,7 @@ import javax.validation.Payload;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.fuin.objects4j.common.ConstraintViolationException;
|
||||
import org.fuin.objects4j.common.Immutable;
|
||||
import org.fuin.objects4j.ui.Label;
|
||||
import org.fuin.objects4j.ui.ShortLabel;
|
||||
import org.fuin.objects4j.ui.Tooltip;
|
||||
@@ -66,7 +66,8 @@ public final class PersonName extends AbstractStringValueObject {
|
||||
/**
|
||||
* Constructor with mandatory data.
|
||||
*
|
||||
* @param value Value.
|
||||
* @param value
|
||||
* Value.
|
||||
*/
|
||||
public PersonName(final String value) {
|
||||
super();
|
||||
@@ -83,14 +84,14 @@ public final class PersonName extends AbstractStringValueObject {
|
||||
public final String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that a given string can be converted into the type.
|
||||
*
|
||||
* @param value Value to validate.
|
||||
* @param value
|
||||
* Value to validate.
|
||||
*
|
||||
* @return Returns <code>true</code> if it's a valid type else
|
||||
* <code>false</code>.
|
||||
* @return Returns <code>true</code> if it's a valid type else <code>false</code>.
|
||||
*/
|
||||
public static boolean isValid(final String value) {
|
||||
if (value == null) {
|
||||
@@ -107,16 +108,17 @@ public final class PersonName extends AbstractStringValueObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the argument is valid and throws an exception if this is not the
|
||||
* case.
|
||||
* Verifies if the argument is valid and throws an exception if this is not the case.
|
||||
*
|
||||
* @param name Name of the value for a possible error message.
|
||||
* @param value Value to check.
|
||||
* @param name
|
||||
* Name of the value for a possible error message.
|
||||
* @param value
|
||||
* Value to check.
|
||||
*
|
||||
* @throws ConstraintViolationException The value was not valid.
|
||||
* @throws ConstraintViolationException
|
||||
* The value was not valid.
|
||||
*/
|
||||
public static void requireArgValid(@NotNull final String name, @NotNull final String value)
|
||||
throws ConstraintViolationException {
|
||||
public static void requireArgValid(@NotNull final String name, @NotNull final String value) throws ConstraintViolationException {
|
||||
|
||||
if (!PersonName.isValid(value)) {
|
||||
throw new ConstraintViolationException("The argument '" + name + "' is not valid: '" + value + "'");
|
||||
@@ -163,8 +165,7 @@ public final class PersonName extends AbstractStringValueObject {
|
||||
/**
|
||||
* Converts the value object from/to string.
|
||||
*/
|
||||
public static final class Converter
|
||||
implements ValueObjectConverter<String, PersonName>, JsonbAdapter<PersonName, String> {
|
||||
public static final class Converter implements ValueObjectConverter<String, PersonName>, JsonbAdapter<PersonName, String> {
|
||||
|
||||
// Attribute Converter
|
||||
|
||||
|
||||
@@ -29,42 +29,42 @@ import org.fuin.ddd4j.ddd.EntityIdFactory;
|
||||
*/
|
||||
public final class SharedEntityIdFactory implements EntityIdFactory {
|
||||
|
||||
private Map<String, Function<String, EntityId>> valueOfMap;
|
||||
private Map<String, Function<String, EntityId>> valueOfMap;
|
||||
|
||||
private Map<String, Function<String, Boolean>> isValidMap;
|
||||
private Map<String, Function<String, Boolean>> isValidMap;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SharedEntityIdFactory() {
|
||||
super();
|
||||
valueOfMap = new HashMap<>();
|
||||
isValidMap = new HashMap<>();
|
||||
valueOfMap.put(PersonId.TYPE.asString(), PersonId::valueOf);
|
||||
isValidMap.put(PersonId.TYPE.asString(), PersonId::isValid);
|
||||
}
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SharedEntityIdFactory() {
|
||||
super();
|
||||
valueOfMap = new HashMap<>();
|
||||
isValidMap = new HashMap<>();
|
||||
valueOfMap.put(PersonId.TYPE.asString(), PersonId::valueOf);
|
||||
isValidMap.put(PersonId.TYPE.asString(), PersonId::isValid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityId createEntityId(final String type, final String id) {
|
||||
final Function<String, EntityId> factory = valueOfMap.get(type);
|
||||
if (factory == null) {
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
return factory.apply(id);
|
||||
}
|
||||
@Override
|
||||
public EntityId createEntityId(final String type, final String id) {
|
||||
final Function<String, EntityId> factory = valueOfMap.get(type);
|
||||
if (factory == null) {
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
return factory.apply(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsType(final String type) {
|
||||
return valueOfMap.containsKey(type);
|
||||
}
|
||||
@Override
|
||||
public boolean containsType(final String type) {
|
||||
return valueOfMap.containsKey(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String type, String id) {
|
||||
final Function<String, Boolean> func = isValidMap.get(type);
|
||||
if (func == null) {
|
||||
return false;
|
||||
}
|
||||
return func.apply(id);
|
||||
}
|
||||
@Override
|
||||
public boolean isValid(String type, String id) {
|
||||
final Function<String, Boolean> func = isValidMap.get(type);
|
||||
if (func == null) {
|
||||
return false;
|
||||
}
|
||||
return func.apply(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.fuin.cqrs4j.example.shared;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.zip.Adler32;
|
||||
|
||||
@@ -48,12 +48,14 @@ import org.fuin.esc.spi.SimpleSerializerDeserializerRegistry;
|
||||
*/
|
||||
public final class SharedUtils {
|
||||
|
||||
private static final String APPLICATION_JSON = "application/json";
|
||||
|
||||
/** All types that will be written into and read from the event store. */
|
||||
private static TypeClass[] USER_DEFINED_TYPES = new TypeClass[] {
|
||||
private static final TypeClass[] USER_DEFINED_TYPES = new TypeClass[] {
|
||||
new TypeClass(PersonCreatedEvent.SER_TYPE, PersonCreatedEvent.class) };
|
||||
|
||||
/** All JSON-B adapters from this module. */
|
||||
public static JsonbAdapter<?, ?>[] JSONB_ADAPTERS = new JsonbAdapter<?, ?>[] { new EventIdConverter(),
|
||||
public static final JsonbAdapter<?, ?>[] JSONB_ADAPTERS = new JsonbAdapter<?, ?>[] { new EventIdConverter(),
|
||||
new EntityIdPathConverter(new SharedEntityIdFactory()), new EntityIdConverter(new SharedEntityIdFactory()),
|
||||
new AggregateVersionConverter(), new PersonId.Converter(), new PersonName.Converter() };
|
||||
|
||||
@@ -101,14 +103,14 @@ public final class SharedUtils {
|
||||
final SimpleSerializerDeserializerRegistry registry = new SimpleSerializerDeserializerRegistry();
|
||||
|
||||
// Base types always needed
|
||||
registry.add(EscEvents.SER_TYPE, "application/json", jsonbDeSer);
|
||||
registry.add(EscEvent.SER_TYPE, "application/json", jsonbDeSer);
|
||||
registry.add(EscMeta.SER_TYPE, "application/json", jsonbDeSer);
|
||||
registry.add(Base64Data.SER_TYPE, "application/json", jsonbDeSer);
|
||||
registry.add(EscEvents.SER_TYPE, APPLICATION_JSON, jsonbDeSer);
|
||||
registry.add(EscEvent.SER_TYPE, APPLICATION_JSON, jsonbDeSer);
|
||||
registry.add(EscMeta.SER_TYPE, APPLICATION_JSON, jsonbDeSer);
|
||||
registry.add(Base64Data.SER_TYPE, APPLICATION_JSON, jsonbDeSer);
|
||||
|
||||
// User defined types
|
||||
for (final TypeClass tc : USER_DEFINED_TYPES) {
|
||||
registry.add(tc.getType(), "application/json", jsonbDeSer);
|
||||
registry.add(tc.getType(), APPLICATION_JSON, jsonbDeSer);
|
||||
}
|
||||
jsonbDeSer.init(typeRegistry, registry, registry);
|
||||
|
||||
@@ -129,9 +131,7 @@ public final class SharedUtils {
|
||||
final JsonbDeSerializer jsonbDeSer = SharedUtils.createJsonbDeSerializer();
|
||||
|
||||
// Registry connects the type with the appropriate serializer and de-serializer
|
||||
final SerDeserializerRegistry serDeserRegistry = SharedUtils.createSerDeserializerRegistry(typeRegistry, jsonbDeSer);
|
||||
|
||||
return serDeserRegistry;
|
||||
return SharedUtils.createSerDeserializerRegistry(typeRegistry, jsonbDeSer);
|
||||
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public final class SharedUtils {
|
||||
|
||||
return JsonbDeSerializer.builder().withSerializers(EscSpiUtils.createEscJsonbSerializers())
|
||||
.withDeserializers(EscSpiUtils.createEscJsonbDeserializers()).withAdapters(JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy()).withEncoding(Charset.forName("utf-8")).build();
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy()).withEncoding(StandardCharsets.UTF_8).build();
|
||||
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public final class SharedUtils {
|
||||
public static long calculateChecksum(final Collection<EventType> eventTypes) {
|
||||
final Adler32 checksum = new Adler32();
|
||||
for (final EventType eventType : eventTypes) {
|
||||
checksum.update(eventType.asBaseType().getBytes(Charset.forName("ascii")));
|
||||
checksum.update(eventType.asBaseType().getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
return checksum.getValue();
|
||||
}
|
||||
|
||||
@@ -35,74 +35,72 @@ import org.junit.Test;
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
public final class CreatePersonCommandTest {
|
||||
|
||||
private static final String PERSON_UUID = "84565d62-115e-4502-b7c9-38ad69c64b05";
|
||||
|
||||
private static final String PERSON_UUID = "84565d62-115e-4502-b7c9-38ad69c64b05";
|
||||
|
||||
@Test
|
||||
public final void testSerializeDeserialize() {
|
||||
@Test
|
||||
public final void testSerializeDeserialize() {
|
||||
|
||||
// PREPARE
|
||||
final CreatePersonCommand original = createTestee();
|
||||
// PREPARE
|
||||
final CreatePersonCommand original = createTestee();
|
||||
|
||||
// TEST
|
||||
final CreatePersonCommand copy = Utils4J.deserialize(Utils4J.serialize(original));
|
||||
// TEST
|
||||
final CreatePersonCommand copy = Utils4J.deserialize(Utils4J.serialize(original));
|
||||
|
||||
// VERIFY
|
||||
assertThat(copy).isEqualTo(original);
|
||||
assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId());
|
||||
assertThat(copy.getName()).isEqualTo(original.getName());
|
||||
// VERIFY
|
||||
assertThat(copy).isEqualTo(original);
|
||||
assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId());
|
||||
assertThat(copy.getName()).isEqualTo(original.getName());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void testMarshalUnmarshalJson() {
|
||||
@Test
|
||||
public final void testMarshalUnmarshalJson() {
|
||||
|
||||
// PREPARE
|
||||
final CreatePersonCommand original = createTestee();
|
||||
// PREPARE
|
||||
final CreatePersonCommand original = createTestee();
|
||||
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
final Jsonb jsonb = JsonbBuilder.create(config);
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
final Jsonb jsonb = JsonbBuilder.create(config);
|
||||
|
||||
// TEST
|
||||
final String json = jsonb.toJson(original, CreatePersonCommand.class);
|
||||
final CreatePersonCommand copy = jsonb.fromJson(json, CreatePersonCommand.class);
|
||||
// TEST
|
||||
final String json = jsonb.toJson(original, CreatePersonCommand.class);
|
||||
final CreatePersonCommand copy = jsonb.fromJson(json, CreatePersonCommand.class);
|
||||
|
||||
// VERIFY
|
||||
assertThat(copy).isEqualTo(original);
|
||||
assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId());
|
||||
assertThat(copy.getName()).isEqualTo(original.getName());
|
||||
// VERIFY
|
||||
assertThat(copy).isEqualTo(original);
|
||||
assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId());
|
||||
assertThat(copy.getName()).isEqualTo(original.getName());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void testUnmarshalJsonFromFile() throws IOException {
|
||||
@Test
|
||||
public final void testUnmarshalJsonFromFile() throws IOException {
|
||||
|
||||
// PREPARE
|
||||
// PREPARE
|
||||
final String json = IOUtils.toString(this.getClass().getResourceAsStream("/commands/CreatePersonCommand.json"),
|
||||
Charset.forName("utf-8"));
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
final Jsonb jsonb = JsonbBuilder.create(config);
|
||||
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
final Jsonb jsonb = JsonbBuilder.create(config);
|
||||
|
||||
// TEST
|
||||
final CreatePersonCommand copy = jsonb.fromJson(json, CreatePersonCommand.class);
|
||||
// TEST
|
||||
final CreatePersonCommand copy = jsonb.fromJson(json, CreatePersonCommand.class);
|
||||
|
||||
// VERIFY
|
||||
assertThat(copy.getEventId().asBaseType()).isEqualTo(UUID.fromString("109a77b2-1de2-46fc-aee1-97fa7740a552"));
|
||||
assertThat(copy.getEventTimestamp()).isEqualTo(ZonedDateTime.parse("2019-11-17T10:27:13.183+01:00[Europe/Berlin]"));
|
||||
assertThat(copy.getAggregateRootId().asString()).isEqualTo(PERSON_UUID);
|
||||
assertThat(copy.getName().asString()).isEqualTo("Peter Parker");
|
||||
// VERIFY
|
||||
assertThat(copy.getEventId().asBaseType()).isEqualTo(UUID.fromString("109a77b2-1de2-46fc-aee1-97fa7740a552"));
|
||||
assertThat(copy.getEventTimestamp()).isEqualTo(ZonedDateTime.parse("2019-11-17T10:27:13.183+01:00[Europe/Berlin]"));
|
||||
assertThat(copy.getAggregateRootId().asString()).isEqualTo(PERSON_UUID);
|
||||
assertThat(copy.getName().asString()).isEqualTo("Peter Parker");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private CreatePersonCommand createTestee() {
|
||||
final PersonId personId = new PersonId(UUID.fromString(PERSON_UUID));
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
return new CreatePersonCommand(personId, personName);
|
||||
}
|
||||
private CreatePersonCommand createTestee() {
|
||||
final PersonId personId = new PersonId(UUID.fromString(PERSON_UUID));
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
return new CreatePersonCommand(personId, personName);
|
||||
}
|
||||
|
||||
}
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.yasson.FieldAccessStrategy;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
public final class PersonCreatedEventTest {
|
||||
|
||||
@@ -71,7 +70,7 @@ public final class PersonCreatedEventTest {
|
||||
assertThat(copy.getName()).isEqualTo(original.getName());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public final void testUnmarshalJson() throws IOException {
|
||||
|
||||
@@ -92,12 +91,11 @@ public final class PersonCreatedEventTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public final void testToString() {
|
||||
final PersonCreatedEvent testee = createTestee();
|
||||
assertThat(testee.toString())
|
||||
.isEqualTo("Person 'Peter Parker' (" + testee.getEntityId() + ") was created [Event " + testee.getEventId() + "]");
|
||||
assertThat(testee)
|
||||
.hasToString("Person 'Peter Parker' (" + testee.getEntityId() + ") was created [Event " + testee.getEventId() + "]");
|
||||
}
|
||||
|
||||
private PersonCreatedEvent createTestee() {
|
||||
|
||||
@@ -35,57 +35,55 @@ import nl.jqno.equalsverifier.Warning;
|
||||
*/
|
||||
public final class PersonIdTest {
|
||||
|
||||
private static final String PERSON_UUID = "84565d62-115e-4502-b7c9-38ad69c64b05";
|
||||
private static final String PERSON_UUID = "84565d62-115e-4502-b7c9-38ad69c64b05";
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
EqualsVerifier.forClass(PersonId.class).suppress(Warning.NONFINAL_FIELDS)
|
||||
.withNonnullFields("entityType", "uuid")
|
||||
.withPrefabValues(EntityType.class, new StringBasedEntityType("A"), new StringBasedEntityType("B"))
|
||||
.verify();
|
||||
}
|
||||
@Test
|
||||
public void testEquals() {
|
||||
EqualsVerifier.forClass(PersonId.class).suppress(Warning.NONFINAL_FIELDS).withNonnullFields("entityType", "uuid")
|
||||
.withPrefabValues(EntityType.class, new StringBasedEntityType("A"), new StringBasedEntityType("B")).verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueOf() {
|
||||
final PersonId personId = PersonId.valueOf(PERSON_UUID);
|
||||
@Test
|
||||
public void testValueOf() {
|
||||
final PersonId personId = PersonId.valueOf(PERSON_UUID);
|
||||
|
||||
assertThat(personId.asString()).isEqualTo(PERSON_UUID);
|
||||
assertThat(personId.asString()).isEqualTo(PERSON_UUID);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueOfIllegalArgumentCharacter() {
|
||||
try {
|
||||
PersonId.valueOf("abc");
|
||||
fail();
|
||||
} catch (final ConstraintViolationException ex) {
|
||||
assertThat(ex.getMessage()).isEqualTo("The argument 'value' is not valid: 'abc'");
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void testValueOfIllegalArgumentCharacter() {
|
||||
try {
|
||||
PersonId.valueOf("abc");
|
||||
fail();
|
||||
} catch (final ConstraintViolationException ex) {
|
||||
assertThat(ex.getMessage()).isEqualTo("The argument 'value' is not valid: 'abc'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void testConverterUnmarshal() throws Exception {
|
||||
@Test
|
||||
public final void testConverterUnmarshal() throws Exception {
|
||||
|
||||
// PREPARE
|
||||
final String personIdValue = PERSON_UUID;
|
||||
// PREPARE
|
||||
final String personIdValue = PERSON_UUID;
|
||||
|
||||
// TEST
|
||||
final PersonId personId = new PersonId.Converter().adaptFromJson(UUID.fromString(PERSON_UUID));
|
||||
// TEST
|
||||
final PersonId personId = new PersonId.Converter().adaptFromJson(UUID.fromString(PERSON_UUID));
|
||||
|
||||
// VERIFY
|
||||
assertThat(personId.asString()).isEqualTo(personIdValue);
|
||||
}
|
||||
// VERIFY
|
||||
assertThat(personId.asString()).isEqualTo(personIdValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConverterMarshal() throws Exception {
|
||||
@Test
|
||||
public void testConverterMarshal() throws Exception {
|
||||
|
||||
final PersonId personId = PersonId.valueOf(PERSON_UUID);
|
||||
final PersonId personId = PersonId.valueOf(PERSON_UUID);
|
||||
|
||||
// TEST
|
||||
final UUID uuid = new PersonId.Converter().adaptToJson(personId);
|
||||
// TEST
|
||||
final UUID uuid = new PersonId.Converter().adaptToJson(personId);
|
||||
|
||||
// VERIFY
|
||||
assertThat(uuid).isEqualTo(UUID.fromString(PERSON_UUID));
|
||||
}
|
||||
// VERIFY
|
||||
assertThat(uuid).isEqualTo(UUID.fromString(PERSON_UUID));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.fuin.cqrs4j.example.shared;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.fuin.objects4j.common.ConstraintViolationException;
|
||||
@@ -76,8 +75,9 @@ public final class PersonNameTest {
|
||||
assertThat(PersonName.isValid("Peter Parker")).isTrue();
|
||||
|
||||
assertThat(PersonName.isValid("")).isFalse();
|
||||
assertThat(PersonName.isValid("123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345")).isFalse();
|
||||
assertThat(PersonName.isValid(
|
||||
"123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "12345"))
|
||||
.isFalse();
|
||||
|
||||
}
|
||||
|
||||
@@ -95,13 +95,12 @@ public final class PersonNameTest {
|
||||
}
|
||||
|
||||
try {
|
||||
PersonName.requireArgValid("d", "123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345");
|
||||
PersonName.requireArgValid("d",
|
||||
"123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "12345");
|
||||
Assert.fail();
|
||||
} catch (final ConstraintViolationException ex) {
|
||||
assertThat(ex.getMessage())
|
||||
.isEqualTo("The argument 'd' is not valid: '" + "123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345" + "'");
|
||||
assertThat(ex.getMessage()).isEqualTo("The argument 'd' is not valid: '" + "123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345" + "'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,8 +112,9 @@ public final class PersonNameTest {
|
||||
assertThat(new PersonName.Validator().isValid("Peter Parker", null)).isTrue();
|
||||
|
||||
assertThat(new PersonName.Validator().isValid("", null)).isFalse();
|
||||
assertThat(new PersonName.Validator().isValid("123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345", null)).isFalse();
|
||||
assertThat(new PersonName.Validator().isValid(
|
||||
"123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "12345",
|
||||
null)).isFalse();
|
||||
|
||||
}
|
||||
|
||||
@@ -126,8 +126,9 @@ public final class PersonNameTest {
|
||||
assertThat(new PersonName.Converter().isValid(null)).isTrue();
|
||||
assertThat(new PersonName.Converter().isValid("Peter Parker")).isTrue();
|
||||
|
||||
assertThat(new PersonName.Converter().isValid("123456789.123456789.123456789.123456789.123456789."
|
||||
+ "123456789.123456789.123456789.123456789.123456789." + "12345")).isFalse();
|
||||
assertThat(new PersonName.Converter().isValid(
|
||||
"123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "12345"))
|
||||
.isFalse();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,24 +8,25 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.context.annotation.RequestScope;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = { "org.fuin.cqrs4j.example.spring.command.app",
|
||||
"org.fuin.cqrs4j.example.spring.command.controller", "org.fuin.cqrs4j.example.spring.shared" })
|
||||
"org.fuin.cqrs4j.example.spring.command.controller", "org.fuin.cqrs4j.example.spring.shared" })
|
||||
public class CmdApplication {
|
||||
|
||||
/**
|
||||
* Creates an event sourced repository that can store a person.
|
||||
*
|
||||
* @param eventStore Event store to use.
|
||||
*
|
||||
* @return Repository only valid for the current request.
|
||||
*/
|
||||
@Bean
|
||||
@RequestScope
|
||||
public PersonRepository create(final IESJCEventStore eventStore) {
|
||||
return new PersonRepository(eventStore);
|
||||
}
|
||||
/**
|
||||
* Creates an event sourced repository that can store a person.
|
||||
*
|
||||
* @param eventStore
|
||||
* Event store to use.
|
||||
*
|
||||
* @return Repository only valid for the current request.
|
||||
*/
|
||||
@Bean
|
||||
@RequestScope
|
||||
public PersonRepository create(final IESJCEventStore eventStore) {
|
||||
return new PersonRepository(eventStore);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CmdApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CmdApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,33 +40,32 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/persons")
|
||||
public class PersonController {
|
||||
|
||||
@Autowired
|
||||
private PersonRepository repo;
|
||||
@Autowired
|
||||
private PersonRepository repo;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<SimpleResult> create(@RequestBody final CreatePersonCommand cmd)
|
||||
throws AggregateAlreadyExistsException, AggregateDeletedException, CommandExecutionFailedException,
|
||||
DuplicatePersonNameException {
|
||||
@PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<SimpleResult> create(@RequestBody final CreatePersonCommand cmd) throws AggregateAlreadyExistsException,
|
||||
AggregateDeletedException, CommandExecutionFailedException, DuplicatePersonNameException {
|
||||
|
||||
// Verify preconditions
|
||||
final Set<ConstraintViolation<CreatePersonCommand>> violations = validator.validate(cmd);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
// Verify preconditions
|
||||
final Set<ConstraintViolation<CreatePersonCommand>> violations = validator.validate(cmd);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
|
||||
// Create aggregate
|
||||
final Person person = new Person(cmd.getAggregateRootId(), cmd.getName(), (name) -> {
|
||||
// TODO Execute a call to the query side to verify if the name already exists
|
||||
return Optional.empty();
|
||||
});
|
||||
repo.add(person);
|
||||
// Create aggregate
|
||||
final Person person = new Person(cmd.getAggregateRootId(), cmd.getName(), name -> {
|
||||
// TODO Execute a call to the query side to verify if the name already exists
|
||||
return Optional.empty();
|
||||
});
|
||||
repo.add(person);
|
||||
|
||||
// Send OK response
|
||||
return new ResponseEntity<>(SimpleResult.ok(), HttpStatus.OK);
|
||||
// Send OK response
|
||||
return new ResponseEntity<>(SimpleResult.ok(), HttpStatus.OK);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,49 +39,40 @@ import io.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(classes = CmdApplication.class)
|
||||
public class PersonControllerIT {
|
||||
class PersonControllerIT {
|
||||
|
||||
@LocalServerPort
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext wac;
|
||||
|
||||
@Autowired
|
||||
IESJCEventStore eventStore;
|
||||
|
||||
@Autowired
|
||||
@Autowired
|
||||
WebApplicationContext wac;
|
||||
|
||||
@Autowired
|
||||
IESJCEventStore eventStore;
|
||||
|
||||
@Autowired
|
||||
Jsonb jsonb;
|
||||
|
||||
@BeforeEach
|
||||
public void initRestAssuredMockMvcStandalone() {
|
||||
RestAssured.port = port;
|
||||
RestAssuredMockMvc.webAppContextSetup(wac);
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void initRestAssuredMockMvcStandalone() {
|
||||
RestAssured.port = port;
|
||||
RestAssuredMockMvc.webAppContextSetup(wac);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
|
||||
void testCreate() {
|
||||
|
||||
// PREPARE
|
||||
final PersonId personId = new PersonId(UUID.randomUUID());
|
||||
final PersonName personName = new PersonName("Peter Parker");
|
||||
final CreatePersonCommand cmd = new CreatePersonCommand(personId, personName);
|
||||
final String json = jsonb.toJson(cmd);
|
||||
|
||||
|
||||
// TEST & VERIFY
|
||||
final SimpleResult result =
|
||||
given()
|
||||
.accept(ContentType.JSON)
|
||||
.contentType(ContentType.JSON)
|
||||
.body(json)
|
||||
.when()
|
||||
.post("/persons/create")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract()
|
||||
.as(SimpleResult.class);
|
||||
final SimpleResult result = given().accept(ContentType.JSON).contentType(ContentType.JSON).body(json).when().post("/persons/create")
|
||||
.then().statusCode(200).extract().as(SimpleResult.class);
|
||||
assertThat(result.getType(), is(equalTo(ResultType.OK)));
|
||||
|
||||
|
||||
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
|
||||
final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 0, 1);
|
||||
final List<CommonEvent> events = slice.getEvents();
|
||||
@@ -91,7 +82,7 @@ public class PersonControllerIT {
|
||||
final PersonCreatedEvent event = (PersonCreatedEvent) ce.getData();
|
||||
assertThat(event.getEntityId(), is(equalTo(personId)));
|
||||
assertThat(event.getName(), is(equalTo(personName)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,29 +11,28 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = { "org.fuin.cqrs4j.example.spring.query.app",
|
||||
"org.fuin.cqrs4j.example.spring.query.controller", "org.fuin.cqrs4j.example.spring.query.views.common",
|
||||
"org.fuin.cqrs4j.example.spring.query.views.personlist", "org.fuin.cqrs4j.example.spring.shared" })
|
||||
@SpringBootApplication(scanBasePackages = { "org.fuin.cqrs4j.example.spring.query.app", "org.fuin.cqrs4j.example.spring.query.controller",
|
||||
"org.fuin.cqrs4j.example.spring.query.views.common", "org.fuin.cqrs4j.example.spring.query.views.personlist",
|
||||
"org.fuin.cqrs4j.example.spring.shared" })
|
||||
@EnableJpaRepositories("org.fuin.cqrs4j.example.spring.query.views.common")
|
||||
@EntityScan({ "org.fuin.cqrs4j.example.spring.query.views.common",
|
||||
"org.fuin.cqrs4j.example.spring.query.views.personlist" })
|
||||
@EntityScan({ "org.fuin.cqrs4j.example.spring.query.views.common", "org.fuin.cqrs4j.example.spring.query.views.personlist" })
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
public class QryApplication {
|
||||
|
||||
@Bean("projectorExecutor")
|
||||
public Executor taskExecutor() {
|
||||
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(1);
|
||||
executor.setMaxPoolSize(5);
|
||||
executor.setQueueCapacity(500);
|
||||
executor.setThreadNamePrefix("person-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
@Bean("projectorExecutor")
|
||||
public Executor taskExecutor() {
|
||||
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(1);
|
||||
executor.setMaxPoolSize(5);
|
||||
executor.setQueueCapacity(500);
|
||||
executor.setThreadNamePrefix("person-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(QryApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(QryApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,44 +36,45 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/persons")
|
||||
@Transactional(readOnly = true)
|
||||
public class PersonController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonController.class);
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonController.class);
|
||||
|
||||
@Autowired
|
||||
private EntityManager em;
|
||||
|
||||
/**
|
||||
* Get all persons list.
|
||||
*
|
||||
* @return the list
|
||||
*/
|
||||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<PersonListEntry> getAllPersons() {
|
||||
/**
|
||||
* Get all persons list.
|
||||
*
|
||||
* @return the list
|
||||
*/
|
||||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<PersonListEntry> getAllPersons() {
|
||||
final List<PersonListEntry> persons = em.createNamedQuery(PersonListEntry.FIND_ALL, PersonListEntry.class).getResultList();
|
||||
LOG.info("getAllPersons() = {}", persons.size());
|
||||
return persons;
|
||||
}
|
||||
return persons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a person by it's universally unique aggregate UUID.
|
||||
*
|
||||
* @param personId
|
||||
* Person UUID.
|
||||
*
|
||||
* @return Person from database.
|
||||
*
|
||||
* @throws AggregateNotFoundException
|
||||
* A person with the given identifier is unknown.
|
||||
*/
|
||||
@GetMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<PersonListEntry> getPersonById(@PathVariable(value = "id") @UUIDStr String personId)
|
||||
throws AggregateNotFoundException {
|
||||
|
||||
/**
|
||||
* Reads a person by it's universally unique aggregate UUID.
|
||||
*
|
||||
* @param personId Person UUID.
|
||||
*
|
||||
* @return Person from database.
|
||||
*
|
||||
* @throws AggregateNotFoundException A person with the given identifier is
|
||||
* unknown.
|
||||
*/
|
||||
@GetMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<PersonListEntry> getPersonById(@PathVariable(value = "id") @UUIDStr String personId)
|
||||
throws AggregateNotFoundException {
|
||||
|
||||
final PersonListEntry person = em.find(PersonListEntry.class, personId);
|
||||
if (person == null) {
|
||||
throw new AggregateNotFoundException(PersonId.TYPE, new PersonId(UUID.fromString(personId)));
|
||||
throw new AggregateNotFoundException(PersonId.TYPE, new PersonId(UUID.fromString(personId)));
|
||||
}
|
||||
LOG.info("getPersonById({}) = {}", personId, person);
|
||||
return ResponseEntity.ok().body(person);
|
||||
}
|
||||
return ResponseEntity.ok().body(person);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,17 +11,18 @@ import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Service to read and persist the next position of a stream to read.
|
||||
* Service to read and persist the next position of a stream to read.
|
||||
*/
|
||||
@Repository
|
||||
public class QryProjectionService implements ProjectionService {
|
||||
|
||||
private static final String ARG_STREAM_ID = "streamId";
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
|
||||
@Override
|
||||
public void resetProjectionPosition(@NotNull final StreamId streamId) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
final QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos != null) {
|
||||
pos.setNextPosition(0L);
|
||||
@@ -31,7 +32,7 @@ public class QryProjectionService implements ProjectionService {
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Long readProjectionPosition(@NotNull StreamId streamId) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
final QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos == null) {
|
||||
return 0L;
|
||||
@@ -41,12 +42,12 @@ public class QryProjectionService implements ProjectionService {
|
||||
|
||||
@Override
|
||||
public void updateProjectionPosition(@NotNull StreamId streamId, @NotNull Long nextEventNumber) {
|
||||
Contract.requireArgNotNull("streamId", streamId);
|
||||
Contract.requireArgNotNull(ARG_STREAM_ID, streamId);
|
||||
Contract.requireArgNotNull("nextEventNumber", nextEventNumber);
|
||||
QryProjectionPosition pos = em.find(QryProjectionPosition.class, streamId.asString());
|
||||
if (pos == null) {
|
||||
pos = new QryProjectionPosition(streamId, nextEventNumber);
|
||||
em.persist(pos);
|
||||
em.persist(pos);
|
||||
} else {
|
||||
pos.setNextPosition(nextEventNumber);
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ package org.fuin.cqrs4j.example.spring.query.views;
|
||||
|
||||
/**
|
||||
* Contains the views used in this query application. A view never uses code of another view, means all views are completely independent of
|
||||
* each other. As an exception, the 'commons' package has some small classes that are not view specific.
|
||||
* each other. As an exception, the 'commons' package has some small classes that are not view specific.
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
public class PersonCreatedEventHandler implements EventHandler<PersonCreatedEvent> {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonCreatedEventHandler.class);
|
||||
|
||||
@Autowired
|
||||
@@ -27,9 +27,9 @@ public class PersonCreatedEventHandler implements EventHandler<PersonCreatedEven
|
||||
return PersonCreatedEvent.TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void handle(final PersonCreatedEvent event) {
|
||||
LOG.info("Handle " + event);
|
||||
LOG.info("Handle {}", event);
|
||||
final PersonId personId = event.getEntityId();
|
||||
if (em.find(PersonListEntry.class, personId.asString()) == null) {
|
||||
em.persist(new PersonListEntry(personId, event.getName()));
|
||||
|
||||
@@ -2,13 +2,12 @@ package org.fuin.cqrs4j.example.spring.query.views.personlist;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.fuin.objects4j.common.NotThreadSafe;
|
||||
|
||||
import org.fuin.cqrs4j.ProjectionService;
|
||||
import org.fuin.cqrs4j.example.shared.SharedUtils;
|
||||
import org.fuin.ddd4j.ddd.EventType;
|
||||
import org.fuin.esc.api.ProjectionStreamId;
|
||||
import org.fuin.esc.api.StreamEventsSlice;
|
||||
import org.fuin.objects4j.common.NotThreadSafe;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -16,23 +15,23 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Dispatches the events to the event handlers that will update the database and
|
||||
* stores the next event position in the database (All in the same transaction).
|
||||
* Dispatches the events to the event handlers that will update the database and stores the next event position in the database (All in the
|
||||
* same transaction).
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@Component
|
||||
public class PersonListEventChunkHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonListEventChunkHandler.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonListEventChunkHandler.class);
|
||||
|
||||
/** Unique name of the event store projection that is used. */
|
||||
public static final ProjectionStreamId PROJECTION_STREAM_ID = new ProjectionStreamId("spring-qry-person-stream");
|
||||
/** Unique name of the event store projection that is used. */
|
||||
public static final ProjectionStreamId PROJECTION_STREAM_ID = new ProjectionStreamId("spring-qry-person-stream");
|
||||
|
||||
@Autowired
|
||||
private PersonListEventDispatcher dispatcher;
|
||||
@Autowired
|
||||
private PersonListEventDispatcher dispatcher;
|
||||
|
||||
@Autowired
|
||||
private ProjectionService projectionService;
|
||||
@Autowired
|
||||
private ProjectionService projectionService;
|
||||
|
||||
private ProjectionStreamId streamId;
|
||||
|
||||
@@ -51,24 +50,25 @@ public class PersonListEventChunkHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next event position to read.
|
||||
*
|
||||
* @return Number of the next event to read.
|
||||
*/
|
||||
public Long readNextEventNumber() {
|
||||
return projectionService.readProjectionPosition(PROJECTION_STREAM_ID);
|
||||
}
|
||||
* Returns the next event position to read.
|
||||
*
|
||||
* @return Number of the next event to read.
|
||||
*/
|
||||
public Long readNextEventNumber() {
|
||||
return projectionService.readProjectionPosition(PROJECTION_STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the current slice as a single transaction.
|
||||
*
|
||||
* @param currentSlice Slice with events to dispatch.
|
||||
*/
|
||||
@Transactional
|
||||
public void handleChunk(final StreamEventsSlice currentSlice) {
|
||||
LOG.debug("Handle chunk: {}", currentSlice);
|
||||
dispatcher.dispatchCommonEvents(currentSlice.getEvents());
|
||||
projectionService.updateProjectionPosition(PROJECTION_STREAM_ID, currentSlice.getNextEventNumber());
|
||||
}
|
||||
/**
|
||||
* Handles the current slice as a single transaction.
|
||||
*
|
||||
* @param currentSlice
|
||||
* Slice with events to dispatch.
|
||||
*/
|
||||
@Transactional
|
||||
public void handleChunk(final StreamEventsSlice currentSlice) {
|
||||
LOG.debug("Handle chunk: {}", currentSlice);
|
||||
dispatcher.dispatchCommonEvents(currentSlice.getEvents());
|
||||
projectionService.updateProjectionPosition(PROJECTION_STREAM_ID, currentSlice.getNextEventNumber());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package org.fuin.cqrs4j.example.spring.query.views.personlist;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.fuin.objects4j.common.NotThreadSafe;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.fuin.cqrs4j.EventDispatcher;
|
||||
@@ -11,44 +10,44 @@ import org.fuin.cqrs4j.SimpleEventDispatcher;
|
||||
import org.fuin.ddd4j.ddd.Event;
|
||||
import org.fuin.ddd4j.ddd.EventType;
|
||||
import org.fuin.esc.api.CommonEvent;
|
||||
import org.fuin.objects4j.common.NotThreadSafe;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Dispatches events that relate to the {@link PersonListEntry} entity to the appropriate
|
||||
* event handers.
|
||||
* Dispatches events that relate to the {@link PersonListEntry} entity to the appropriate event handers.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@Component
|
||||
public class PersonListEventDispatcher implements EventDispatcher {
|
||||
|
||||
private SimpleEventDispatcher delegate;
|
||||
private SimpleEventDispatcher delegate;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler) {
|
||||
super();
|
||||
delegate = new SimpleEventDispatcher(createdHandler);
|
||||
}
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler) {
|
||||
super();
|
||||
delegate = new SimpleEventDispatcher(createdHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<EventType> getAllTypes() {
|
||||
return delegate.getAllTypes();
|
||||
}
|
||||
@Override
|
||||
public @NotNull Set<EventType> getAllTypes() {
|
||||
return delegate.getAllTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchCommonEvents(@NotNull List<CommonEvent> commonEvents) {
|
||||
delegate.dispatchCommonEvents(commonEvents);
|
||||
}
|
||||
@Override
|
||||
public void dispatchCommonEvents(@NotNull List<CommonEvent> commonEvents) {
|
||||
delegate.dispatchCommonEvents(commonEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchEvents(@NotNull List<Event> events) {
|
||||
delegate.dispatchEvents(events);
|
||||
}
|
||||
@Override
|
||||
public void dispatchEvents(@NotNull List<Event> events) {
|
||||
delegate.dispatchEvents(events);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(@NotNull Event event) {
|
||||
delegate.dispatchEvent(event);
|
||||
}
|
||||
@Override
|
||||
public void dispatchEvent(@NotNull Event event) {
|
||||
delegate.dispatchEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.fuin.objects4j.common.ThreadSafe;
|
||||
|
||||
import org.fuin.ddd4j.ddd.EventType;
|
||||
import org.fuin.esc.api.TypeName;
|
||||
import org.fuin.esc.eshttp.IESHttpEventStore;
|
||||
import org.fuin.objects4j.common.ThreadSafe;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,66 +24,64 @@ import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Reads incoming events from an event store projection and dispatches them to
|
||||
* the appropriate event handlers. The event store projection will be created by
|
||||
* this class, if it does not yet exist.
|
||||
* Reads incoming events from an event store projection and dispatches them to the appropriate event handlers. The event store projection
|
||||
* will be created by this class, if it does not yet exist.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@Component
|
||||
public class PersonListProjector {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonListProjector.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonListProjector.class);
|
||||
|
||||
/** Prevents more than one projector thread running at a time. */
|
||||
private static final Semaphore LOCK = new Semaphore(1);
|
||||
/** Prevents more than one projector thread running at a time. */
|
||||
private static final Semaphore LOCK = new Semaphore(1);
|
||||
|
||||
private static AtomicBoolean APP_STARTED = new AtomicBoolean(false);
|
||||
|
||||
// The following beans are NOT thread safe!
|
||||
// Above LOCK prevents multithreaded access
|
||||
private static final AtomicBoolean APP_STARTED = new AtomicBoolean(false);
|
||||
|
||||
@Autowired
|
||||
private IESHttpEventStore eventstore;
|
||||
// The following beans are NOT thread safe!
|
||||
// Above LOCK prevents multithreaded access
|
||||
|
||||
@Autowired
|
||||
private PersonListEventChunkHandler chunkHandler;
|
||||
@Autowired
|
||||
private IESHttpEventStore eventstore;
|
||||
|
||||
@Autowired
|
||||
private PersonListEventDispatcher dispatcher;
|
||||
@Autowired
|
||||
private PersonListEventChunkHandler chunkHandler;
|
||||
|
||||
/**
|
||||
* Runs triggered by the timer. If a second timer event occurs while the
|
||||
* previous call is still being executed, the method execution will simply be
|
||||
* skipped.
|
||||
*/
|
||||
@Scheduled(fixedRate = 100)
|
||||
@Async("projectorExecutor")
|
||||
public void execute() {
|
||||
if (!APP_STARTED.get()) {
|
||||
// Do nothing until application started
|
||||
return;
|
||||
}
|
||||
tryLocked(LOCK, () -> {
|
||||
try {
|
||||
readStreamEvents();
|
||||
} catch (final RuntimeException ex) {
|
||||
LOG.error("Error reading events from stream", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onAppStart() {
|
||||
LOG.info("Application started");
|
||||
APP_STARTED.set(true);
|
||||
}
|
||||
@Autowired
|
||||
private PersonListEventDispatcher dispatcher;
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
APP_STARTED.set(false);
|
||||
LOG.info("Application stopped");
|
||||
/**
|
||||
* Runs triggered by the timer. If a second timer event occurs while the previous call is still being executed, the method execution
|
||||
* will simply be skipped.
|
||||
*/
|
||||
@Scheduled(fixedRate = 100)
|
||||
@Async("projectorExecutor")
|
||||
public void execute() {
|
||||
if (!APP_STARTED.get()) {
|
||||
// Do nothing until application started
|
||||
return;
|
||||
}
|
||||
tryLocked(LOCK, () -> {
|
||||
try {
|
||||
readStreamEvents();
|
||||
} catch (final RuntimeException ex) {
|
||||
LOG.error("Error reading events from stream", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onAppStart() {
|
||||
LOG.info("Application started");
|
||||
APP_STARTED.set(true);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
APP_STARTED.set(false);
|
||||
LOG.info("Application stopped");
|
||||
}
|
||||
|
||||
private void readStreamEvents() {
|
||||
|
||||
// Create a projection if it does not exist.
|
||||
@@ -97,9 +95,8 @@ public class PersonListProjector {
|
||||
|
||||
// Read and dispatch events
|
||||
final Long nextEventNumber = chunkHandler.readNextEventNumber();
|
||||
eventstore.readAllEventsForward(chunkHandler.getProjectionStreamId(), nextEventNumber, 100, (currentSlice) -> {
|
||||
chunkHandler.handleChunk(currentSlice);
|
||||
});
|
||||
eventstore.readAllEventsForward(chunkHandler.getProjectionStreamId(), nextEventNumber, 100,
|
||||
currentSlice -> chunkHandler.handleChunk(currentSlice));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -40,72 +40,72 @@ import io.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(classes = QryApplication.class)
|
||||
public class PersonControllerIT {
|
||||
class PersonControllerIT {
|
||||
|
||||
@LocalServerPort
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext wac;
|
||||
|
||||
@Autowired
|
||||
IESHttpEventStore eventStore;
|
||||
|
||||
@Autowired
|
||||
EntityManager em;
|
||||
@Autowired
|
||||
WebApplicationContext wac;
|
||||
|
||||
@Autowired
|
||||
PersonController testee;
|
||||
@Autowired
|
||||
IESHttpEventStore eventStore;
|
||||
|
||||
@Autowired
|
||||
Config config;
|
||||
@Autowired
|
||||
EntityManager em;
|
||||
|
||||
@BeforeEach
|
||||
public void initRestAssuredMockMvcStandalone() {
|
||||
RestAssured.port = port;
|
||||
RestAssuredMockMvc.webAppContextSetup(wac);
|
||||
}
|
||||
@Autowired
|
||||
PersonController testee;
|
||||
|
||||
@Test
|
||||
public void testGetByIdNotFound() {
|
||||
given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404);
|
||||
}
|
||||
@Autowired
|
||||
Config config;
|
||||
|
||||
public boolean findPerson(final PersonId personId) {
|
||||
return em.find(PersonListEntry.class, personId.asString()) != null;
|
||||
}
|
||||
@BeforeEach
|
||||
public void initRestAssuredMockMvcStandalone() {
|
||||
RestAssured.port = port;
|
||||
RestAssuredMockMvc.webAppContextSetup(wac);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByIdOK() {
|
||||
@Test
|
||||
void testGetByIdNotFound() {
|
||||
given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404);
|
||||
}
|
||||
|
||||
// PREPARE
|
||||
final PersonId personId = new PersonId(UUID.randomUUID());
|
||||
final PersonName personName = new PersonName("Peter Parker " + personId);
|
||||
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
|
||||
final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName);
|
||||
final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()),
|
||||
new TypeName(event.getEventType().asBaseType()), event);
|
||||
eventStore.appendToStream(personStreamId, ce);
|
||||
public boolean findPerson(final PersonId personId) {
|
||||
return em.find(PersonListEntry.class, personId.asString()) != null;
|
||||
}
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> findPerson(personId));
|
||||
@Test
|
||||
void testGetByIdOK() {
|
||||
|
||||
// TEST & VERIFY
|
||||
// PREPARE
|
||||
final PersonId personId = new PersonId(UUID.randomUUID());
|
||||
final PersonName personName = new PersonName("Peter Parker " + personId);
|
||||
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
|
||||
final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName);
|
||||
final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()),
|
||||
new TypeName(event.getEventType().asBaseType()), event);
|
||||
eventStore.appendToStream(personStreamId, ce);
|
||||
|
||||
// given().pathParam("id", personId.asString()).when().get("/persons/{id}").then().log().all(true);
|
||||
|
||||
final PersonListEntry person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then()
|
||||
.statusCode(200).extract().as(PersonListEntry.class);
|
||||
assertThat(person.getId(), is(equalTo(personId)));
|
||||
assertThat(person.getName(), is(equalTo(personName)));
|
||||
await().atMost(5, SECONDS).until(() -> findPerson(personId));
|
||||
|
||||
final PersonListEntry[] persons = given().when().get("/persons").then().statusCode(200).extract()
|
||||
.as(PersonListEntry[].class);
|
||||
// TEST & VERIFY
|
||||
|
||||
assertThat(Arrays.asList(persons), is(not(empty())));
|
||||
final PersonListEntry person0 = persons[0];
|
||||
assertThat(person0.getId(), is(equalTo(personId)));
|
||||
assertThat(person0.getName(), is(equalTo(personName)));
|
||||
// given().pathParam("id",
|
||||
// personId.asString()).when().get("/persons/{id}").then().log().all(true);
|
||||
|
||||
}
|
||||
final PersonListEntry person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then().statusCode(200)
|
||||
.extract().as(PersonListEntry.class);
|
||||
assertThat(person.getId(), is(equalTo(personId)));
|
||||
assertThat(person.getName(), is(equalTo(personName)));
|
||||
|
||||
final PersonListEntry[] persons = given().when().get("/persons").then().statusCode(200).extract().as(PersonListEntry[].class);
|
||||
|
||||
assertThat(Arrays.asList(persons), is(not(empty())));
|
||||
final PersonListEntry person0 = persons[0];
|
||||
assertThat(person0.getId(), is(equalTo(personId)));
|
||||
assertThat(person0.getName(), is(equalTo(personName)));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.fuin.cqrs4j.example.spring.shared;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.json.bind.Jsonb;
|
||||
@@ -39,8 +39,7 @@ public class BeanFactory {
|
||||
public Jsonb createJsonb() {
|
||||
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
|
||||
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
|
||||
final Jsonb jsonb = JsonbBuilder.create(config);
|
||||
return jsonb;
|
||||
return JsonbBuilder.create(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +71,7 @@ public class BeanFactory {
|
||||
final SerDeserializerRegistry registry = SharedUtils.createRegistry();
|
||||
|
||||
final IESJCEventStore eventstore = new ESJCEventStore.Builder().eventStore(es).serDesRegistry(registry)
|
||||
.targetContentType(EnhancedMimeType.create("application", "json", Charset.forName("utf-8"))).build();
|
||||
.targetContentType(EnhancedMimeType.create("application", "json", StandardCharsets.UTF_8)).build();
|
||||
|
||||
eventstore.open();
|
||||
return eventstore;
|
||||
|
||||
@@ -6,122 +6,128 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class Config {
|
||||
|
||||
private static final String EVENT_STORE_PROTOCOL = "http";
|
||||
private static final String EVENT_STORE_PROTOCOL = "http";
|
||||
|
||||
private static final String EVENT_STORE_HOST = "localhost";
|
||||
private static final String EVENT_STORE_HOST = "localhost";
|
||||
|
||||
private static final int EVENT_STORE_HTTP_PORT = 2113;
|
||||
private static final int EVENT_STORE_HTTP_PORT = 2113;
|
||||
|
||||
private static final int EVENT_STORE_TCP_PORT = 1113;
|
||||
private static final int EVENT_STORE_TCP_PORT = 1113;
|
||||
|
||||
private static final String EVENT_STORE_USER = "admin";
|
||||
private static final String EVENT_STORE_USER = "admin";
|
||||
|
||||
private static final String EVENT_STORE_PASSWORD = "changeit";
|
||||
private static final String EVENT_STORE_PASSWORD = "changeit";
|
||||
|
||||
@Value("${EVENT_STORE_PROTOCOL:http}")
|
||||
private String eventStoreProtocol;
|
||||
@Value("${EVENT_STORE_PROTOCOL:http}")
|
||||
private String eventStoreProtocol;
|
||||
|
||||
@Value("${EVENT_STORE_HOST:localhost}")
|
||||
private String eventStoreHost;
|
||||
@Value("${EVENT_STORE_HOST:localhost}")
|
||||
private String eventStoreHost;
|
||||
|
||||
@Value("${EVENT_STORE_HTTP_PORT:2113}")
|
||||
private int eventStoreHttpPort;
|
||||
@Value("${EVENT_STORE_HTTP_PORT:2113}")
|
||||
private int eventStoreHttpPort;
|
||||
|
||||
@Value("${EVENT_STORE_TCP_PORT:1113}")
|
||||
private int eventStoreTcpPort;
|
||||
@Value("${EVENT_STORE_TCP_PORT:1113}")
|
||||
private int eventStoreTcpPort;
|
||||
|
||||
@Value("${EVENT_STORE_USER:admin}")
|
||||
private String eventStoreUser;
|
||||
@Value("${EVENT_STORE_USER:admin}")
|
||||
private String eventStoreUser;
|
||||
|
||||
@Value("${EVENT_STORE_PASSWORD:changeit}")
|
||||
private String eventStorePassword;
|
||||
@Value("${EVENT_STORE_PASSWORD:changeit}")
|
||||
private String eventStorePassword;
|
||||
|
||||
/**
|
||||
* Constructor using default values internally.
|
||||
*/
|
||||
public Config() {
|
||||
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 using default values internally.
|
||||
*/
|
||||
public Config() {
|
||||
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 Config(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;
|
||||
}
|
||||
/**
|
||||
* 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 Config(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 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 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 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 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 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;
|
||||
}
|
||||
/**
|
||||
* Returns the password of the event store.
|
||||
*
|
||||
* @return Password.
|
||||
*/
|
||||
public String getEventStorePassword() {
|
||||
return eventStorePassword;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.fuin.objects4j.common.Nullable;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
@@ -29,6 +28,7 @@ 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.fuin.objects4j.common.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -129,12 +129,13 @@ public class GlobalExceptionHandler {
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
return new ResponseEntity<>(SimpleResult.error(CONSTRAINT_VIOLATION, asString(ex.getConstraintViolations())), headers, HttpStatus.BAD_REQUEST);
|
||||
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) {
|
||||
if (constraintViolations == null || constraintViolations.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
final List<String> list = new ArrayList<>();
|
||||
@@ -143,5 +144,5 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
return list.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user