diff --git a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java
index 282eb0c..3c75f17 100644
--- a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java
+++ b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java
@@ -46,7 +46,7 @@ public class CmdApplication {
* @return New event store instance.
*/
@Bean(destroyMethod = "shutdown")
- public com.github.msemys.esjc.EventStore getESHttpEventStore(final Config config) {
+ public com.github.msemys.esjc.EventStore getESHttpEventStore(final CmdConfig config) {
return EventStoreBuilder.newBuilder().singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort())
.executor(Executors.newFixedThreadPool(10)).userCredentials(config.getEventStoreUser(), config.getEventStorePassword())
.build();
diff --git a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/Config.java b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdConfig.java
similarity index 94%
rename from spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/Config.java
rename to spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdConfig.java
index 87832a4..ce8e41a 100644
--- a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/Config.java
+++ b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdConfig.java
@@ -4,7 +4,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
-public class Config {
+public class CmdConfig {
private static final String EVENT_STORE_PROTOCOL = "http";
@@ -39,7 +39,7 @@ public class Config {
/**
* Constructor using default values internally.
*/
- public Config() {
+ public CmdConfig() {
super();
this.eventStoreProtocol = EVENT_STORE_PROTOCOL;
this.eventStoreHost = EVENT_STORE_HOST;
@@ -59,7 +59,7 @@ public class Config {
* @param eventStoreUser User.
* @param eventStorePassword Password.
*/
- public Config(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
+ public CmdConfig(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
final int eventStoreTcpPort, final String eventStoreUser, final String eventStorePassword) {
super();
this.eventStoreProtocol = eventStoreProtocol;
diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml
index 265c219..f2125a7 100644
--- a/spring-boot/pom.xml
+++ b/spring-boot/pom.xml
@@ -11,7 +11,7 @@
shared
query
-
+ command
diff --git a/spring-boot/query/pom.xml b/spring-boot/query/pom.xml
index 7503fe8..1cd243b 100644
--- a/spring-boot/query/pom.xml
+++ b/spring-boot/query/pom.xml
@@ -22,6 +22,8 @@
1.8
1.8
0.3.1-SNAPSHOT
+ 2.22.1
+ 2.22.2
@@ -87,12 +89,6 @@
true
-
- com.h2database
- h2
- runtime
-
-
@@ -100,6 +96,10 @@
spring-boot-starter-test
test
+
+ junit
+ junit
+
org.junit.vintage
junit-vintage-engine
@@ -107,15 +107,156 @@
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+ io.rest-assured
+ spring-mock-mvc
+ test
+
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+
+
org.springframework.boot
spring-boot-maven-plugin
+
+
+ maven-failsafe-plugin
+ ${failsafe-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+ 0.31.0
+
+
+
+
+ eventstore/eventstore:release-4.1.3
+
+
+ bridge
+
+
+ 1113:1113
+ 2113:2113
+
+
+ false
+
+
+
+ http://localhost:2113/web/index.html#/
+ GET
+
+
+
+
+
+
+
+ mariadb:10.4
+
+
+ bridge
+
+
+ 3306:3306
+
+
+ false
+
+
+ 1
+ xyz
+ querydb
+ mary
+ abc
+
+
+
+
+
+
+
+
+
+
+
+
+ start-images
+ pre-integration-test
+
+ start
+
+
+
+ stop-images
+ post-integration-test
+
+ stop
+
+
+
+
+
+
+
+
+
+
+ sonatype.oss.snapshots
+ Sonatype OSS Snapshot Repository
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ false
+
+
+ always
+ true
+
+
+
+
+
diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/Config.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/Config.java
deleted file mode 100644
index 9730873..0000000
--- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/Config.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.fuin.cqrs4j.example.spring.query.app;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-@Component
-public class Config {
-
- private static final String EVENT_STORE_PROTOCOL = "http";
-
- private static final String EVENT_STORE_HOST = "localhost";
-
- private static final int EVENT_STORE_HTTP_PORT = 2113;
-
- private static final int EVENT_STORE_TCP_PORT = 1113;
-
- private static final String EVENT_STORE_USER = "admin";
-
- private static final String EVENT_STORE_PASSWORD = "changeit";
-
- @Value("${EVENT_STORE_PROTOCOL:http}")
- private String eventStoreProtocol;
-
- @Value("${EVENT_STORE_HOST:localhost}")
- private String eventStoreHost;
-
- @Value("${EVENT_STORE_HTTP_PORT:2113}")
- private int eventStoreHttpPort;
-
- @Value("${EVENT_STORE_TCP_PORT:1113}")
- private int eventStoreTcpPort;
-
- @Value("${EVENT_STORE_USER:admin}")
- private String eventStoreUser;
-
- @Value("${EVENT_STORE_PASSWORD:changeit}")
- private String eventStorePassword;
-
- /**
- * Constructor using default values internally.
- */
- public 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;
- }
-
- /**
- * Returns the protocol of the event store.
- *
- * @return Either http or https.
- */
- public String getEventStoreProtocol() {
- return eventStoreProtocol;
- }
-
- /**
- * Returns the host name of the event store.
- *
- * @return Name.
- */
- public String getEventStoreHost() {
- return eventStoreHost;
- }
-
- /**
- * Returns the HTTP port of the event store.
- *
- * @return Port.
- */
- public int getEventStoreHttpPort() {
- return eventStoreHttpPort;
- }
-
- /**
- * Returns the TCP port of the event store.
- *
- * @return Port.
- */
- public int getEventStoreTcpPort() {
- return eventStoreTcpPort;
- }
-
- /**
- * Returns the username of the event store.
- *
- * @return Username.
- */
- public String getEventStoreUser() {
- return eventStoreUser;
- }
-
- /**
- * Returns the password of the event store.
- *
- * @return Password.
- */
- public String getEventStorePassword() {
- return eventStorePassword;
- }
-
-}
-
diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java
index 82c937b..7a30ff6 100644
--- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java
+++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java
@@ -32,7 +32,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
"org.fuin.cqrs4j.example.spring.query.controller", "org.fuin.cqrs4j.example.spring.query.domain",
"org.fuin.cqrs4j.example.spring.query.handler" })
@EnableJpaRepositories("org.fuin.cqrs4j.example.spring.query.domain")
-@EntityScan({"org.fuin.cqrs4j.example.spring.query.domain", "org.fuin.cqrs4j.example.spring.query.handler"})
+@EntityScan({ "org.fuin.cqrs4j.example.spring.query.domain", "org.fuin.cqrs4j.example.spring.query.handler" })
@EnableScheduling
@EnableAsync
public class QryApplication {
@@ -43,14 +43,13 @@ public class QryApplication {
* @return Fully configured instance.
*/
@Bean
- public Jsonb createJsonb() {
- final JsonbConfig config = new JsonbConfig()
- .withAdapters(SharedUtils.JSONB_ADAPTERS)
- .withPropertyVisibilityStrategy(new FieldAccessStrategy());
- final Jsonb jsonb = JsonbBuilder.create(config);
- return jsonb;
- }
-
+ public Jsonb createJsonb() {
+ final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
+ .withPropertyVisibilityStrategy(new FieldAccessStrategy());
+ final Jsonb jsonb = JsonbBuilder.create(config);
+ return jsonb;
+ }
+
/**
* Creates a HTTP based event store connection.
*
@@ -59,7 +58,7 @@ public class QryApplication {
* @return New event store instance.
*/
@Bean(destroyMethod = "close")
- public IESHttpEventStore getESHttpEventStore(final Config config) {
+ public IESHttpEventStore getESHttpEventStore(final QryConfig config) {
final String url = config.getEventStoreProtocol() + "://" + config.getEventStoreHost() + ":"
+ config.getEventStoreHttpPort();
try {
diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryConfig.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryConfig.java
new file mode 100644
index 0000000..bc48bea
--- /dev/null
+++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryConfig.java
@@ -0,0 +1,134 @@
+package org.fuin.cqrs4j.example.spring.query.app;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class QryConfig {
+
+ private static final String EVENT_STORE_PROTOCOL = "http";
+
+ private static final String EVENT_STORE_HOST = "localhost";
+
+ private static final int EVENT_STORE_HTTP_PORT = 2113;
+
+ private static final int EVENT_STORE_TCP_PORT = 1113;
+
+ private static final String EVENT_STORE_USER = "admin";
+
+ private static final String EVENT_STORE_PASSWORD = "changeit";
+
+ @Value("${EVENT_STORE_PROTOCOL:http}")
+ private String eventStoreProtocol;
+
+ @Value("${EVENT_STORE_HOST:localhost}")
+ private String eventStoreHost;
+
+ @Value("${EVENT_STORE_HTTP_PORT:2113}")
+ private int eventStoreHttpPort;
+
+ @Value("${EVENT_STORE_TCP_PORT:1113}")
+ private int eventStoreTcpPort;
+
+ @Value("${EVENT_STORE_USER:admin}")
+ private String eventStoreUser;
+
+ @Value("${EVENT_STORE_PASSWORD:changeit}")
+ private String eventStorePassword;
+
+ /**
+ * Constructor using default values internally.
+ */
+ public QryConfig() {
+ 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 QryConfig(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
+ final int eventStoreTcpPort, final String eventStoreUser, final String eventStorePassword) {
+ super();
+ this.eventStoreProtocol = eventStoreProtocol;
+ this.eventStoreHost = eventStoreHost;
+ this.eventStoreHttpPort = eventStoreHttpPort;
+ this.eventStoreTcpPort = eventStoreTcpPort;
+ this.eventStoreUser = eventStoreUser;
+ this.eventStorePassword = eventStorePassword;
+ }
+
+ /**
+ * Returns the protocol of the event store.
+ *
+ * @return Either http or https.
+ */
+ public String getEventStoreProtocol() {
+ return eventStoreProtocol;
+ }
+
+ /**
+ * Returns the host name of the event store.
+ *
+ * @return Name.
+ */
+ public String getEventStoreHost() {
+ return eventStoreHost;
+ }
+
+ /**
+ * Returns the HTTP port of the event store.
+ *
+ * @return Port.
+ */
+ public int getEventStoreHttpPort() {
+ return eventStoreHttpPort;
+ }
+
+ /**
+ * Returns the TCP port of the event store.
+ *
+ * @return Port.
+ */
+ public int getEventStoreTcpPort() {
+ return eventStoreTcpPort;
+ }
+
+ /**
+ * Returns the username of the event store.
+ *
+ * @return Username.
+ */
+ public String getEventStoreUser() {
+ return eventStoreUser;
+ }
+
+ /**
+ * Returns the password of the event store.
+ *
+ * @return Password.
+ */
+ public String getEventStorePassword() {
+ return eventStorePassword;
+ }
+
+ @Override
+ public String toString() {
+ return "QryConfig [eventStoreProtocol=" + eventStoreProtocol + ", eventStoreHost=" + eventStoreHost
+ + ", eventStoreHttpPort=" + eventStoreHttpPort + ", eventStoreTcpPort=" + eventStoreTcpPort
+ + ", eventStoreUser=" + eventStoreUser + "]";
+ }
+
+}
diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/handler/PersonProjector.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/handler/PersonProjector.java
index c2446b1..9524642 100644
--- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/handler/PersonProjector.java
+++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/handler/PersonProjector.java
@@ -9,6 +9,7 @@ import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.PreDestroy;
import javax.annotation.concurrent.ThreadSafe;
import org.fuin.ddd4j.ddd.EventType;
@@ -82,6 +83,12 @@ public class PersonProjector {
APP_STARTED.set(true);
}
+ @PreDestroy
+ public void destroy() {
+ APP_STARTED.set(false);
+ LOG.info("Application stopped");
+ }
+
private void readStreamEvents() {
// TODO Make sure a projection with the correct events exists
diff --git a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/Cqrs4jSpringQueryExampleApplicationTests.java b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/Cqrs4jSpringQueryExampleApplicationTests.java
deleted file mode 100644
index 3bc0d68..0000000
--- a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/Cqrs4jSpringQueryExampleApplicationTests.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.fuin.cqrs4j.example.spring.query;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class Cqrs4jSpringQueryExampleApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}
diff --git a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java
new file mode 100644
index 0000000..903569c
--- /dev/null
+++ b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java
@@ -0,0 +1,109 @@
+package org.fuin.cqrs4j.example.spring.query.api;
+
+import static io.restassured.RestAssured.given;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.persistence.EntityManager;
+
+import org.fuin.cqrs4j.example.spring.query.app.QryApplication;
+import org.fuin.cqrs4j.example.spring.query.app.QryConfig;
+import org.fuin.cqrs4j.example.spring.query.controller.PersonController;
+import org.fuin.cqrs4j.example.spring.query.domain.QryPerson;
+import org.fuin.cqrs4j.example.spring.shared.PersonCreatedEvent;
+import org.fuin.cqrs4j.example.spring.shared.PersonId;
+import org.fuin.cqrs4j.example.spring.shared.PersonName;
+import org.fuin.esc.api.CommonEvent;
+import org.fuin.esc.api.EventId;
+import org.fuin.esc.api.EventStore;
+import org.fuin.esc.api.SimpleCommonEvent;
+import org.fuin.esc.api.SimpleStreamId;
+import org.fuin.esc.api.TypeName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+import io.restassured.RestAssured;
+import io.restassured.module.mockmvc.RestAssuredMockMvc;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ContextConfiguration(classes = QryApplication.class)
+public class PersonControllerIT {
+
+ @LocalServerPort
+ int port;
+
+ @Autowired
+ WebApplicationContext wac;
+
+ @Autowired
+ EventStore eventStore;
+
+ @Autowired
+ EntityManager em;
+
+ @Autowired
+ PersonController testee;
+
+ @Autowired
+ QryConfig config;
+
+ @BeforeEach
+ public void initRestAssuredMockMvcStandalone() {
+ RestAssured.port = port;
+ RestAssuredMockMvc.webAppContextSetup(wac);
+ }
+
+ @Test
+ public void testGetByIdNotFound() {
+ given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404);
+ }
+
+ public boolean findPerson(final PersonId personId) {
+ return em.find(QryPerson.class, personId.asString()) != null;
+ }
+
+ @Test
+ public void testGetByIdOK() {
+
+ // 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);
+
+ await().atMost(5, SECONDS).until(() -> findPerson(personId));
+
+ // TEST & VERIFY
+
+ final QryPerson person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then()
+ .statusCode(200).extract().as(QryPerson.class);
+ assertThat(person.getId(), is(equalTo(personId)));
+ assertThat(person.getName(), is(equalTo(personName)));
+
+ final QryPerson[] persons = given().when().get("/persons").then().statusCode(200).extract()
+ .as(QryPerson[].class);
+
+ assertThat(Arrays.asList(persons), is(not(empty())));
+ final QryPerson person0 = persons[0];
+ assertThat(person0.getId(), is(equalTo(personId)));
+ assertThat(person0.getName(), is(equalTo(personName)));
+
+ }
+
+}