Cleanup Sonar issues, added Github build and formatted source

This commit is contained in:
Michael Schnell
2021-09-17 17:22:05 +02:00
parent a3fe604856
commit 6bab4790af
58 changed files with 726 additions and 712 deletions

37
.github/workflows/maven.yml vendored Normal file
View 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
View File

@@ -2,5 +2,6 @@
!.gitignore
!.gitkeep
!.mvn
!.github
target
*.log

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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