From c9cd1c282ef657693c70833aaa3c7c827ea8a38b Mon Sep 17 00:00:00 2001 From: Michael Schnell Date: Tue, 23 Apr 2019 16:40:49 +0200 Subject: [PATCH] Initial commit --- .gitignore | 6 + README.md | 68 ++++- logback.xml | 25 ++ pom.xml | 267 ++++++++++++++++++ .../cmd/app/CmdExampleApp.java | 101 +++++++ .../cmd/app/package-info.java | 17 ++ .../cmd/basics/package-info.java | 17 ++ .../domain/DuplicatePersonNameException.java | 54 ++++ .../dddcqrs4jexample/cmd/domain/Person.java | 101 +++++++ .../cmd/domain/PersonRepository.java | 51 ++++ .../cmd/domain/PersonRepositoryFactory.java | 29 ++ .../cmd/domain/package-info.java | 17 ++ .../dddcqrs4jexample/cmd/package-info.java | 17 ++ .../qry/app/QryCheckForViewUpdatesEvent.java | 8 + .../qry/app/QryEntityManagerProducer.java | 31 ++ .../qry/app/QryExampleApp.java | 86 ++++++ .../QryProjectionAdminEventStoreFactory.java | 62 ++++ .../qry/app/QryScheduledExecutorService.java | 22 ++ .../qry/app/QryThreadFactoryFactory.java | 21 ++ .../dddcqrs4jexample/qry/app/QryTimer.java | 20 ++ .../qry/app/package-info.java | 17 ++ .../qry/basics/package-info.java | 17 ++ .../qry/domain/QryPerson.java | 96 +++++++ .../qry/domain/QryPersonRepository.java | 12 + .../qry/domain/package-info.java | 17 ++ .../handler/PersonCreatedEventHandler.java | 43 +++ .../qry/handler/QryEventChunkHandler.java | 50 ++++ .../handler/QryEventDispatcherFactory.java | 21 ++ .../handler/QryPersonProjectionPosition.java | 88 ++++++ ...QryPersonProjectionPositionRepository.java | 50 ++++ .../qry/handler/QryProjector.java | 87 ++++++ .../qry/handler/package-info.java | 17 ++ .../dddcqrs4jexample/qry/package-info.java | 17 ++ .../shared/app/SharedConfig.java | 132 +++++++++ .../shared/app/SharedEntityIdFactory.java | 58 ++++ .../app/SharedEsjcEventStoreFactory.java | 37 +++ .../shared/app/SharedEventStoreFactory.java | 52 ++++ .../app/SharedExecutorServiceFactory.java | 33 +++ .../SharedSerDeserializerRegistryFactory.java | 34 +++ .../shared/app/SharedUtils.java | 170 +++++++++++ .../shared/app/package-info.java | 17 ++ .../shared/domain/PersonCreatedEvent.java | 87 ++++++ .../shared/domain/PersonId.java | 132 +++++++++ .../shared/domain/PersonName.java | 216 ++++++++++++++ .../shared/domain/package-info.java | 17 ++ .../dddcqrs4jexample/shared/package-info.java | 17 ++ src/main/resources/META-INF/beans.xml | 10 + src/main/resources/META-INF/persistence.xml | 27 ++ 48 files changed, 2590 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 logback.xml create mode 100644 pom.xml create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/app/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/basics/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/DuplicatePersonNameException.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepository.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepositoryFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/cmd/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryCheckForViewUpdatesEvent.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryEntityManagerProducer.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryProjectionAdminEventStoreFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryScheduledExecutorService.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryThreadFactoryFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/app/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/basics/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPerson.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPersonRepository.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/domain/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/PersonCreatedEventHandler.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventChunkHandler.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventDispatcherFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPosition.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPositionRepository.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryProjector.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/handler/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/qry/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedConfig.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEntityIdFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEsjcEventStoreFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEventStoreFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedExecutorServiceFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedSerDeserializerRegistryFactory.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedUtils.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/app/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonId.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonName.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/domain/package-info.java create mode 100644 src/main/java/org/fuin/dddcqrs4jexample/shared/package-info.java create mode 100644 src/main/resources/META-INF/beans.xml create mode 100644 src/main/resources/META-INF/persistence.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6c7569 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.* +!.gitignore +!.gitkeep +!.mvn +target +*.log diff --git a/README.md b/README.md index 05647ee..14f78cc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,68 @@ # ddd-cqrs-4-java-example -Minimal standalone example application that uses the ddd-4-java and cqrs-4-java libraries +Minimal standalone example application that uses the [ddd-4-java](https://github.com/fuinorg/ddd-4-java) and [cqrs-4-java](https://github.com/fuinorg/cqrs-4-java) libraries and an [EventStore](https://eventstore.org/) to store the events (Event Sourcing). + +## Starting the demo + +1. Start an EventStore locally on your PC + * You can use the [EventStore Docker Image](https://hub.docker.com/r/eventstore/eventstore/): ```docker run --name eventstore-node -p 2113:2113 -p 1113:1113 --rm eventstore/eventstore``` + * Or simply start an instance (See [Install and run Event Store](https://eventstore.org/docs/server/index.html?tabs=tabid-1) for more information) +2. Run the [QryExampleApp](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java) to make the view/read part listen to new events + * You should see something like this on the console: ```INFO o.f.d.qry.handler.QryProjector - Create projection 'qry-person-stream' with events: [PersonCreatedEvent]``` + * If you open the event store UI (http://localhost:2113/web/index.html#/projections) you should see a projection name 'qry-person-stream' +3. Run the [CmdExampleApp](src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java) to create a [PersonCreatedEvent](src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java) + * You should see something like this on the console: ```INFO o.f.d.cmd.app.CmdExampleApp - Updated event store...``` + * There should also be an update on the 'QryExampleApp' console: ```INFO o.f.d.q.h.PersonCreatedEventHandler - Handle Person 'Peter Parker Inc.' was created``` + * If you open the event store UI (http://localhost:2113/web/index.html#/projections) and open the projection details for 'qry-person-stream' it should show 'Events processed = 1' +4. Kill the [QryExampleApp](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java) +5. Stop the event store + +## What is in the application? +The application is splitted into three packages: 'shared', 'cmd' and 'qry'. + +### shared +Has code shared between both modules 'cmd' and 'qry'. + +#### Sub package 'app' +There are several configuration classes used for CDI in the [app](src/main/java/org/fuin/dddcqrs4jexample/shared/app) package + +#### Sub package 'domain' +There are three DDD domain model related classes +* The [PersonId](src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonId.java) that is the unique identifier for the [Person](/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java) aggregate (Value Object) +* The [PersonName](src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonName.java) that is the name for the [Person](/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java) aggregate (Value Object) +* The [PersonCreatedEvent](src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java) that is fired if a new [Person](/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java) aggregate was created (Event / Value Object) + + +### cmd +Code for the 'command' or 'write' side of the application. + +#### Sub package 'app' +Contains mainly the command line application. +* The command line application [CmdExampleApp](src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java) that creates a new [Person](/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java) aggregate and saves the state as a series of events in the [EventStore](https://eventstore.org/) (Event Sourcing). + +#### Sub package 'domain' +There are three DDD domain model related classes +* The [Person](/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java) aggregate (Aggregate Root). +* The [PersonRepository](src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepository.java) used to store the aggregate root in the event store. + +### Package 'qry' +Code for the 'query' or 'read' side of the application. + +#### Sub package 'app' +There are several configuration classes used for CDI in the [app](src/main/java/org/fuin/dddcqrs4jexample/qry/app) package and the main query application. + +* The command line application [QryExampleApp](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java) that reads events from the [EventStore](https://eventstore.org/) (Event Sourcing) and updates it's internal view model. +* The [QryTimer](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java) class fires an asynchronous CDI event called [QryCheckForViewUpdatesEvent](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryCheckForViewUpdatesEvent.java) + + +#### Sub package 'domain' +The view data will be stored in an in-memory HSQL database using JPA +* The [QryPerson](src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPerson.java) class is an [@javax.persistence.Entity](https://javaee.github.io/javaee-spec/javadocs/javax/persistence/Entity.html) class +* The [QryPersonRepository](src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPersonRepository.java) class is used for the [Apache DeltaSpike Data Module](https://deltaspike.apache.org/documentation/data.html) entity repository to simplifying the database access. (See [EntityRepository](https://deltaspike.apache.org/javadoc/1.7.2/index.html?org/apache/deltaspike/data/api/EntityRepository.html)) + +#### Sub package 'handler' +The query side has to listen for new events. This is done by polling the Event Store every second. + +* The [QryProjector](src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryProjector.java) receives the event issued by the [QryTimer](src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java) and starts reading a from a projection that collects all necessary events required for one view. +* The [QryEventChunkHandler](src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventChunkHandler.java) is used to read all events of a single projection and dispatch them to the event handler that want to have them. It also updates the [QryPersonProjectionPosition](src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPosition.java) in the database. This is done to remember, what the last event was that was read from the event store's event stream (projection). It uses a helper class called [SimpleEventDispatcher](https://github.com/fuinorg/cqrs-4-java/blob/master/src/main/java/org/fuin/cqrs4j/SimpleEventDispatcher.java) that is aware of all known events and capable to handle the 'dispatch' operation. +* The [PersonCreatedEventHandler](src/main/java/org/fuin/dddcqrs4jexample/qry/handler/PersonCreatedEventHandler.java) class is an example of an event handler that updates the local view database using a single event. + diff --git a/logback.xml b/logback.xml new file mode 100644 index 0000000..710d60b --- /dev/null +++ b/logback.xml @@ -0,0 +1,25 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + ${log_path}/logback.log + + logback.%d{yyyy-MM-dd}.log + 30 + + true + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5e06442 --- /dev/null +++ b/pom.xml @@ -0,0 +1,267 @@ + + + + 4.0.0 + + + org.fuin + pom + 1.6.0 + + + ddd-cqrs-4-java-example + 0.1.0-SNAPSHOT + Minimal standalone example application that uses the 'ddd-4-java' and 'ddd-cqrs-4-java-example' libraries + + + https://github.com/fuinorg/ddd-cqrs-4-java-example/ + scm:git:git://github.com/fuinorg/ddd-cqrs-4-java-example.git + scm:git:git@github.com:fuinorg/ddd-cqrs-4-java-example.git + + + + GitHub Issues + https://github.com/fuinorg/ddd-cqrs-4-java-example/issues + + + + UTF-8 + 1.8 + 1.8 + 0.3.1-SNAPSHOT + + + + + + + + org.apache.deltaspike.distribution + distributions-bom + 1.9.0 + import + pom + + + + + + + + + + + + org.fuin + ddd-4-java + 0.2.1-SNAPSHOT + + + + org.fuin + cqrs-4-java + 0.2.1-SNAPSHOT + + + + org.fuin.esc + esc-spi + ${esc.version} + + + + org.fuin.esc + esc-esjc + ${esc.version} + + + + org.fuin.esc + esc-eshttp + ${esc.version} + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + + + + org.apache.httpcomponents + httpclient + 4.5 + + + + org.fuin + ext4logback + 0.2.0 + + + + javax.validation + validation-api + 2.0.1.Final + true + + + + org.apache.commons + commons-lang3 + 3.7 + + + + org.slf4j + slf4j-api + 1.7.25 + + + + ch.qos.logback + logback-classic + 1.1.8 + + + + javax.enterprise + cdi-api + 2.0 + + + + org.jboss.weld.se + weld-se-core + 3.0.4.Final + + + + org.eclipse.microprofile.config + microprofile-config-api + 1.3 + + + + io.smallrye + smallrye-config + 1.3.5 + + + + javax.json + javax.json-api + 1.1.4 + + + + org.glassfish + javax.json + 1.1.4 + + + + org.eclipse + yasson + 1.0.3 + + + + org.apache.deltaspike.modules + deltaspike-data-module-api + + + + org.apache.deltaspike.modules + deltaspike-data-module-impl + + + + org.hibernate.validator + hibernate-validator + 6.0.10.Final + + + jaxb-impl + com.sun.xml.bind + + + jaxb-api + javax.xml.bind + + + + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + 1.0.2 + + + + org.hibernate + hibernate-entitymanager + 5.4.2.Final + + + + commons-io + commons-io + 2.6 + + + + org.hsqldb + hsqldb + 2.4.1 + + + + + + org.fuin.esc + esc-mem + ${esc.version} + test + + + + junit + junit + test + + + + org.assertj + assertj-core + 3.10.0 + test + + + + org.fuin + units4j + 0.8.3 + test + + + + nl.jqno.equalsverifier + equalsverifier + 2.4.6 + test + + + + + + + + + + + + + diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java new file mode 100644 index 0000000..ba7b2c2 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/CmdExampleApp.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.cmd.app; + +import java.util.UUID; + +import javax.enterprise.context.control.ActivateRequestContext; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.inject.Inject; + +import org.fuin.dddcqrs4jexample.cmd.domain.Person; +import org.fuin.dddcqrs4jexample.cmd.domain.PersonRepository; +import org.fuin.dddcqrs4jexample.cmd.domain.PersonRepositoryFactory; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.dddcqrs4jexample.shared.domain.PersonName; +import org.fuin.esc.api.EventStore; +import org.fuin.ext4logback.LogbackStandalone; +import org.fuin.ext4logback.NewLogConfigFileParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Minimal example command (write) application. + */ +public class CmdExampleApp { + + private static final Logger LOG = LoggerFactory.getLogger(CmdExampleApp.class); + + @Inject + private Instance eventStoreInstance; + + /** + * Executes the application. + */ + @ActivateRequestContext + public void execute() { + + LOG.info("Executing..."); + + try (final EventStore eventStore = eventStoreInstance.get()) { + + final PersonId id = new PersonId(UUID.fromString("f645969a-402d-41a9-882b-d2d8000d0f43")); + final PersonName name = new PersonName("Peter Parker Inc."); + final Person person = new Person(id, name, (dn) -> { + return null; + }); + final PersonRepository repo = new PersonRepositoryFactory().create(eventStore); + + repo.update(person); + + LOG.info("Updated event store..."); + + } catch (final Exception ex) { + throw new RuntimeException("Error saving person aggregate into event store", ex); + } + + } + + /** + * Main entry point to the command line application. + * + * @param args + * Not used. + */ + public static void main(final String[] args) { + + try { + + new LogbackStandalone().init(args, new NewLogConfigFileParams("org.fuin.dddcqrs4jexample", "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); + } + + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/package-info.java new file mode 100644 index 0000000..7cee4ff --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/app/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.cmd.app; + +/** + * Command application specific code. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/basics/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/basics/package-info.java new file mode 100644 index 0000000..72aafd7 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/basics/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.cmd.basics; + +/** + * General helper classes used by all other 'cmd' packages. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/DuplicatePersonNameException.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/DuplicatePersonNameException.java new file mode 100644 index 0000000..fe965fc --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/DuplicatePersonNameException.java @@ -0,0 +1,54 @@ +package org.fuin.dddcqrs4jexample.cmd.domain; + +import javax.validation.constraints.NotNull; + +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.dddcqrs4jexample.shared.domain.PersonName; +import org.fuin.objects4j.common.Contract; + +/** + * A name that should be unique does already exist. + */ +public final class DuplicatePersonNameException extends Exception { + + private static final long serialVersionUID = 1000L; + + private PersonId personId; + + private PersonName name; + + /** + * Constructor with mandatory data. + * + * @param personId + * Identifier of the resource that caused the problem. + * @param name + * Name of the resource that caused the problem. + */ + public DuplicatePersonNameException(@NotNull final PersonId personId, @NotNull final PersonName name) { + super("The name '" + name + "' already exists: " + personId.asString()); + Contract.requireArgNotNull("personId", personId); + Contract.requireArgNotNull("name", name); + this.personId = personId; + this.name = name; + } + + /** + * Returns the identifier of the entity that has the name. + * + * @return Identifier. + */ + public final PersonId getPersonId() { + return personId; + } + + /** + * Returns the name that already exists. + * + * @return Name. + */ + public final PersonName getName() { + return name; + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java new file mode 100644 index 0000000..d4e7845 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/Person.java @@ -0,0 +1,101 @@ +package org.fuin.dddcqrs4jexample.cmd.domain; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.fuin.ddd4j.ddd.AbstractAggregateRoot; +import org.fuin.ddd4j.ddd.ApplyEvent; +import org.fuin.ddd4j.ddd.EntityType; +import org.fuin.dddcqrs4jexample.shared.domain.PersonCreatedEvent; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.dddcqrs4jexample.shared.domain.PersonName; +import org.fuin.objects4j.common.Contract; + +/** + * A medical practitioner most likely also holder of an accredited academic degree. + */ +public class Person extends AbstractAggregateRoot implements Serializable { + + private static final long serialVersionUID = 1000L; + + @NotNull + private PersonId id; + + @NotNull + private PersonName name; + + /** + * Default constructor that is mandatory for aggregate roots. + */ + public Person() { + super(); + } + + /** + * Constructor with all data. + * + * @param id + * Unique identifier of the person. + * @param name + * Unique name of the person. + * @param service + * Service required by the method. + * + * @throws DuplicatePersonNameException + * The name already exists for another person. + */ + public Person(@NotNull final PersonId id, @NotNull final PersonName name, final PersonService service) throws DuplicatePersonNameException { + super(); + + // VERIFY PRECONDITIONS + Contract.requireArgNotNull("id", id); + Contract.requireArgNotNull("name", name); + + // VERIFY BUSINESS RULES + + // Rule 1: The name of the person must be unique + final PersonId otherId = service.loadPersonIdByName(name); + if (otherId != null) { + throw new DuplicatePersonNameException(otherId, name); + } + + // CREATE EVENT + apply(new PersonCreatedEvent(id, name)); + + } + + @Override + public PersonId getId() { + return id; + } + + @Override + public EntityType getType() { + return PersonId.TYPE; + } + + @ApplyEvent + public void applyEvent(final PersonCreatedEvent event) { + this.id = event.getEntityId(); + this.name = event.getName(); + } + + /** + * Service for the constructor. + */ + public static interface PersonService { + + /** + * Loads the person's identifier for a given name. + * + * @param name + * Person's name. + * + * @return Office identifier or {@literal null} if not found. + */ + public PersonId loadPersonIdByName(@NotNull PersonName name); + + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepository.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepository.java new file mode 100644 index 0000000..3d691e0 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepository.java @@ -0,0 +1,51 @@ +package org.fuin.dddcqrs4jexample.cmd.domain; + +import javax.annotation.concurrent.NotThreadSafe; +import javax.validation.constraints.NotNull; + +import org.fuin.ddd4j.ddd.EntityType; +import org.fuin.ddd4j.esrepo.EventStoreRepository; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.esc.api.EventStore; + +/** + * Event sourced repository for storing a {@link Person} aggregate. + */ +@NotThreadSafe +public class PersonRepository extends EventStoreRepository { + + /** + * Constructor all mandatory data. + * + * @param eventStore + * Event store. + */ + public PersonRepository(final EventStore eventStore) { + super(eventStore); + } + + @Override + @NotNull + public Class getAggregateClass() { + return Person.class; + } + + @Override + @NotNull + public EntityType getAggregateType() { + return PersonId.TYPE; + } + + @Override + @NotNull + public Person create() { + return new Person(); + } + + @Override + @NotNull + public String getIdParamName() { + return "personId"; + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepositoryFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepositoryFactory.java new file mode 100644 index 0000000..1d9aa0a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/PersonRepositoryFactory.java @@ -0,0 +1,29 @@ +package org.fuin.dddcqrs4jexample.cmd.domain; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; + +import org.fuin.esc.api.EventStore; + +/** + * CDI factory that creates an event store connection and repositories. + */ +@ApplicationScoped +public class PersonRepositoryFactory { + + /** + * Creates a repository. + * + * @param eventStore + * Event store implementation. + * + * @return Dependent scope project repository. + */ + @Produces + @Dependent + public PersonRepository create(final EventStore eventStore) { + return new PersonRepository(eventStore); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/package-info.java new file mode 100644 index 0000000..de80907 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/domain/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.cmd.domain; + +/** + * Domain specific code like aggregate root classes and their repositories. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/cmd/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/cmd/package-info.java new file mode 100644 index 0000000..cac9665 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/cmd/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.cmd; + +/** + * Code used for the command (write) side of the application. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryCheckForViewUpdatesEvent.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryCheckForViewUpdatesEvent.java new file mode 100644 index 0000000..738e19b --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryCheckForViewUpdatesEvent.java @@ -0,0 +1,8 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +/** + * Notifies the projectors to check for view updates. + */ +public class QryCheckForViewUpdatesEvent { + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryEntityManagerProducer.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryEntityManagerProducer.java new file mode 100644 index 0000000..574a8bb --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryEntityManagerProducer.java @@ -0,0 +1,31 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.deltaspike.jpa.api.transaction.TransactionScoped; + +@ApplicationScoped +public class QryEntityManagerProducer { + + private EntityManagerFactory emf; + + @Produces + @TransactionScoped + public EntityManager create() { + if (emf == null) { + emf = Persistence.createEntityManagerFactory("QueryDb"); + } + return emf.createEntityManager(); + } + + public void close(@Disposes EntityManager em) { + if (em.isOpen()) { + em.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java new file mode 100644 index 0000000..e902d96 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryExampleApp.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry.app; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.inject.Inject; + +import org.fuin.ext4logback.LogbackStandalone; +import org.fuin.ext4logback.NewLogConfigFileParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Minimal example command (write) application. + */ +public class QryExampleApp { + + private static final Logger LOG = LoggerFactory.getLogger(QryExampleApp.class); + + @Inject + private ScheduledExecutorService scheduledExecutorService; + + @Inject + private QryTimer qryTimer; + + /** + * Executes the application. + */ + public void execute() { + LOG.info("Started execution..."); + + final ScheduledFuture future = scheduledExecutorService.scheduleAtFixedRate(qryTimer, 5, 1, TimeUnit.SECONDS); + try { + future.get(); + } catch (InterruptedException | ExecutionException ex) { + throw new RuntimeException(ex); + } + + LOG.info("Ended execution..."); + } + + /** + * Main entry point to the command line application. + * + * @param args + * Not used. + */ + public static void main(final String[] args) { + + try { + + new LogbackStandalone().init(args, new NewLogConfigFileParams("org.fuin.dddcqrs4jexample", "logback")); + + LOG.info("Started query example"); + + try (final SeContainer container = SeContainerInitializer.newInstance().initialize()) { + final QryExampleApp app = container.select(QryExampleApp.class).get(); + app.execute(); + } + + LOG.info("Query example is running until you kill it..."); + + } catch (final RuntimeException ex) { + ex.printStackTrace(System.err); + System.exit(1); + } + + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryProjectionAdminEventStoreFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryProjectionAdminEventStoreFactory.java new file mode 100644 index 0000000..4a98f86 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryProjectionAdminEventStoreFactory.java @@ -0,0 +1,62 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.ThreadFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.fuin.dddcqrs4jexample.shared.app.SharedConfig; +import org.fuin.esc.api.ProjectionAdminEventStore; +import org.fuin.esc.eshttp.ESEnvelopeType; +import org.fuin.esc.eshttp.ESHttpEventStore; +import org.fuin.esc.spi.SimpleSerializerDeserializerRegistry; + +/** + * CDI producer that creates an {@link ProjectionAdminEventStore}. + */ +@ApplicationScoped +public class QryProjectionAdminEventStoreFactory { + + /** + * Creates an event store that is capable of administering projections. + * + * @return Dependent scope projection admin event store. + */ + @Produces + @Dependent + public ProjectionAdminEventStore createProjectionAdminEventStore(final SharedConfig config, final ThreadFactory threadFactory) { + final String url = "http://" + config.getEventStoreHost() + ":" + config.getEventStoreHttpPort(); + try { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(config.getEventStoreUser(), + config.getEventStorePassword()); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + final SimpleSerializerDeserializerRegistry registry = new SimpleSerializerDeserializerRegistry(); + final ProjectionAdminEventStore es = new ESHttpEventStore(threadFactory, new URL(url), ESEnvelopeType.JSON, registry, registry, + credentialsProvider); + es.open(); + return es; + } catch (final MalformedURLException ex) { + throw new RuntimeException("Failed to create URL: " + url, ex); + } + } + + /** + * Closes the projection admin event store when the context is disposed. + * + * @param es + * Projection admin event store to close. + */ + public void closeProjectionAdminEventStore(@Disposes ProjectionAdminEventStore es) { + es.close(); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryScheduledExecutorService.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryScheduledExecutorService.java new file mode 100644 index 0000000..246cca9 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryScheduledExecutorService.java @@ -0,0 +1,22 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +/** + * CDI producer that creates an {@link ScheduledExecutorService}. + */ +@ApplicationScoped +public class QryScheduledExecutorService { + + @Produces + @ApplicationScoped + public ScheduledExecutorService create(ThreadFactory threadFactory) { + return new ScheduledThreadPoolExecutor(1, threadFactory, (runnable, executor) -> { System.out.println("Execution blocked"); }); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryThreadFactoryFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryThreadFactoryFactory.java new file mode 100644 index 0000000..8581d8a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryThreadFactoryFactory.java @@ -0,0 +1,21 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +/** + * Creates an thread factory. + */ +@ApplicationScoped +public class QryThreadFactoryFactory { + + @ApplicationScoped + @Produces + public ThreadFactory create() { + return Executors.defaultThreadFactory(); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java new file mode 100644 index 0000000..baa4f1a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/QryTimer.java @@ -0,0 +1,20 @@ +package org.fuin.dddcqrs4jexample.qry.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; +import javax.inject.Inject; + +@ApplicationScoped +public class QryTimer implements Runnable { + + @Inject + private Event checkForViewUpdates; + + /** + * Notifies all listeners to check for view updates. + */ + public void run() { + checkForViewUpdates.fireAsync(new QryCheckForViewUpdatesEvent()); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/app/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/package-info.java new file mode 100644 index 0000000..61b68ac --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/app/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry.app; + +/** + * Query application specific code. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/basics/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/basics/package-info.java new file mode 100644 index 0000000..0966bff --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/basics/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry.basics; + +/** + * General helper classes used by all other 'qry' packages. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPerson.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPerson.java new file mode 100644 index 0000000..2f08f34 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPerson.java @@ -0,0 +1,96 @@ +package org.fuin.dddcqrs4jexample.qry.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.dddcqrs4jexample.shared.domain.PersonName; +import org.fuin.objects4j.common.Contract; + +/** + * Represents a person that will be stored in the database. + */ +@Entity +@Table(name = "QRY_PERSON") +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "qry-person") +public class QryPerson { + + @Id + @Column(name = "ID", nullable = false, length = 36, updatable = false) + @NotNull + @XmlElement(name = "id") + private String id; + + @Column(name = "NAME", nullable = false, length = PersonName.MAX_LENGTH, updatable = true) + @NotNull + @XmlElement(name = "name") + private String name; + + /** + * JAX-B constructor. + */ + protected QryPerson() { + super(); + } + + /** + * Constructor with all data. + * + * @param id + * Unique aggregate identifier. + * @param name + * Name of the created person + */ + public QryPerson(@NotNull final PersonId id, @NotNull final PersonName name) { + super(); + Contract.requireArgNotNull("id", id); + Contract.requireArgNotNull("name", name); + this.id = id.asString(); + this.name = name.asString(); + } + + /** + * Returns the unique person identifier. + * + * @return Aggregate ID. + */ + @NotNull + public PersonId getId() { + return PersonId.valueOf(id); + } + + /** + * Returns the name of the person to create. + * + * @return the Person name + */ + @NotNull + public PersonName getName() { + return new PersonName(name); + } + + /** + * Sets the name of the person. + * + * @param name + * Name to set. + */ + public void setName(@NotNull final PersonName name) { + Contract.requireArgNotNull("name", name); + this.name = name.asString(); + } + + @Override + public String toString() { + return "QryPerson [id=" + id + ", name=" + name + "]"; + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPersonRepository.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPersonRepository.java new file mode 100644 index 0000000..1953b1b --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/QryPersonRepository.java @@ -0,0 +1,12 @@ +package org.fuin.dddcqrs4jexample.qry.domain; + +import org.apache.deltaspike.data.api.EntityRepository; +import org.apache.deltaspike.data.api.Repository; + +/** + * Database service for persons. + */ +@Repository(forEntity = QryPerson.class) +public interface QryPersonRepository extends EntityRepository { + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/package-info.java new file mode 100644 index 0000000..6bb8f04 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/domain/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry.domain; + +/** + * Domain specific code like view objects. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/PersonCreatedEventHandler.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/PersonCreatedEventHandler.java new file mode 100644 index 0000000..87152d3 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/PersonCreatedEventHandler.java @@ -0,0 +1,43 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.deltaspike.jpa.api.transaction.Transactional; +import org.fuin.cqrs4j.EventHandler; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.dddcqrs4jexample.qry.domain.QryPerson; +import org.fuin.dddcqrs4jexample.qry.domain.QryPersonRepository; +import org.fuin.dddcqrs4jexample.shared.domain.PersonCreatedEvent; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the {@link PersonCreatedEvent}. + */ +@ApplicationScoped +public class PersonCreatedEventHandler implements EventHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PersonCreatedEventHandler.class); + + @Inject + private QryPersonRepository repo; + + @Override + public EventType getEventType() { + return PersonCreatedEvent.TYPE; + } + + @Override + @Transactional + public void handle(final PersonCreatedEvent event) { + LOG.info("Handle " + event); + final PersonId personId = event.getEntityId(); + if (repo.findBy(personId.asString()) == null) { + repo.save(new QryPerson(personId, event.getName())); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventChunkHandler.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventChunkHandler.java new file mode 100644 index 0000000..2ed0efc --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventChunkHandler.java @@ -0,0 +1,50 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.deltaspike.jpa.api.transaction.Transactional; +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.esc.api.ProjectionStreamId; +import org.fuin.esc.api.StreamEventsSlice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +@Transactional +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"); + + @Inject + private EventDispatcher dispatcher; + + @Inject + private ProjectionService projectionService; + + /** + * 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. + */ + public void handleChunk(final StreamEventsSlice currentSlice) { + LOG.debug("Handle chunk: {}", currentSlice); + dispatcher.dispatchCommonEvents(currentSlice.getEvents()); + projectionService.updateProjectionPosition(PROJECTION_STREAM_ID, currentSlice.getNextEventNumber()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventDispatcherFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventDispatcherFactory.java new file mode 100644 index 0000000..51ec60a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryEventDispatcherFactory.java @@ -0,0 +1,21 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.cqrs4j.SimpleEventDispatcher; + +/** + * Create an {@link EventDispatcher}. + */ +@ApplicationScoped +public class QryEventDispatcherFactory { + + @Produces + @ApplicationScoped + public EventDispatcher createDispatcher(final PersonCreatedEventHandler createdHandler) { + return new SimpleEventDispatcher(createdHandler); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPosition.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPosition.java new file mode 100644 index 0000000..fa99ad7 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPosition.java @@ -0,0 +1,88 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +import org.fuin.esc.api.SimpleStreamId; +import org.fuin.esc.api.StreamId; +import org.fuin.objects4j.common.Contract; + +/** + * Stores the next position to read from the projection in the event store. + */ +@Entity +@Table(name = "QRY_PERSON_PROJECTION_POS") +public class QryPersonProjectionPosition { + + @Id + @Column(name = "STREAM_ID", nullable = false, length = 250, updatable = false) + @NotNull + private String streamId; + + @Column(name = "NEXT_POS", nullable = false, updatable = true) + @NotNull + private Long nextPos; + + /** + * JPA constructor. + */ + protected QryPersonProjectionPosition() { + super(); + } + + /** + * Constructor with mandatory data. + * + * @param streamId + * Unique stream identifier. + * @param nextPos + * Next position from the stream to read. + */ + public QryPersonProjectionPosition(@NotNull final StreamId streamId, @NotNull final Long nextPos) { + super(); + Contract.requireArgNotNull("streamId", streamId); + Contract.requireArgNotNull("nextPos", nextPos); + this.streamId = streamId.asString(); + this.nextPos = nextPos; + } + + /** + * Returns the unique stream identifier. + * + * @return Stream ID. + */ + @NotNull + public StreamId getStreamId() { + return new SimpleStreamId(streamId); + } + + /** + * Returns the next position read from the stream. + * + * @return Position to read next time. + */ + @NotNull + public Long getNextPos() { + return nextPos; + } + + /** + * Sets the next position read from the stream. + * + * @param nextPos + * New position to set. + */ + public void setNextPosition(@NotNull final Long nextPos) { + Contract.requireArgNotNull("nextPos", nextPos); + this.nextPos = nextPos; + } + + @Override + public String toString() { + return "QryPersonHandlerPosition [streamId=" + streamId + ", nextPos=" + nextPos + "]"; + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPositionRepository.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPositionRepository.java new file mode 100644 index 0000000..9fff18a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryPersonProjectionPositionRepository.java @@ -0,0 +1,50 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import javax.validation.constraints.NotNull; + +import org.apache.deltaspike.data.api.AbstractEntityRepository; +import org.apache.deltaspike.data.api.Repository; +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.esc.api.StreamId; +import org.fuin.objects4j.common.Contract; + +/** + * Repository that contains the position of the stream. + */ +@Repository(forEntity = QryPersonProjectionPosition.class) +public abstract class QryPersonProjectionPositionRepository extends AbstractEntityRepository + implements ProjectionService { + + @Override + public void resetProjectionPosition(@NotNull final StreamId streamId) { + Contract.requireArgNotNull("streamId", streamId); + final QryPersonProjectionPosition pos = findBy(streamId.asString()); + if (pos != null) { + pos.setNextPosition(0L); + } + } + + @Override + public Long readProjectionPosition(@NotNull StreamId streamId) { + Contract.requireArgNotNull("streamId", streamId); + final QryPersonProjectionPosition pos = findBy(streamId.asString()); + if (pos == null) { + return 0L; + } + return pos.getNextPos(); + } + + @Override + public void updateProjectionPosition(@NotNull StreamId streamId, @NotNull Long nextEventNumber) { + Contract.requireArgNotNull("streamId", streamId); + Contract.requireArgNotNull("nextEventNumber", nextEventNumber); + QryPersonProjectionPosition pos = findBy(streamId.asString()); + if (pos == null) { + pos = new QryPersonProjectionPosition(streamId, nextEventNumber); + } else { + pos.setNextPosition(nextEventNumber); + } + save(pos); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryProjector.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryProjector.java new file mode 100644 index 0000000..def92b4 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/QryProjector.java @@ -0,0 +1,87 @@ +package org.fuin.dddcqrs4jexample.qry.handler; + +import static org.fuin.cqrs4j.Cqrs4JUtils.tryLocked; +import static org.fuin.dddcqrs4jexample.qry.handler.QryEventChunkHandler.PROJECTION_STREAM_ID; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + +import javax.enterprise.event.ObservesAsync; +import javax.inject.Inject; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.dddcqrs4jexample.qry.app.QryCheckForViewUpdatesEvent; +import org.fuin.esc.api.EventStore; +import org.fuin.esc.api.ProjectionAdminEventStore; +import org.fuin.esc.api.TypeName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Reads incoming events from an attached event store and dispatches them to the appropriate event handlers. + */ +public class QryProjector { + + private static final Logger LOG = LoggerFactory.getLogger(QryProjector.class); + + /** Prevents more than one projector thread running at a time. */ + private static final Semaphore LOCK = new Semaphore(1); + + // The following beans are NOT thread safe! + // Above LOCK prevents multithreaded access + + @Inject + private ProjectionAdminEventStore eventstore; + + @Inject + private EventStore eventStore; + + @Inject + private QryEventChunkHandler chunkHandler; + + @Inject + private EventDispatcher dispatcher; + + /** + * Listens for timer events. If a second timer event occurs while the previous call is still being executed, the method will simply be + * skipped. + * + * @param event + * Timer event. + */ + public void onEvent(@ObservesAsync final QryCheckForViewUpdatesEvent event) { + tryLocked(LOCK, () -> { + try { + readStreamEvents(); + } catch (final RuntimeException ex) { + LOG.error("Error reading events from stream", ex); + } + }); + } + + private void readStreamEvents() { + + // TODO Make sure a projection with the correct events exists + // We must update the projection if new events are defined or some are removed! + if (!eventstore.projectionExists(PROJECTION_STREAM_ID)) { + final Set eventTypes = dispatcher.getAllTypes(); + final List typeNames = new ArrayList<>(); + for (final EventType eventType : eventTypes) { + typeNames.add(new TypeName(eventType.asBaseType())); + } + LOG.info("Create projection '{}' with events: {}", PROJECTION_STREAM_ID, typeNames); + eventstore.createProjection(PROJECTION_STREAM_ID, true, typeNames); + } + + // Read and dispatch events + final Long nextEventNumber = chunkHandler.readNextEventNumber(); + eventStore.readAllEventsForward(PROJECTION_STREAM_ID, nextEventNumber, 100, (currentSlice) -> { + chunkHandler.handleChunk(currentSlice); + }); + + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/package-info.java new file mode 100644 index 0000000..8515136 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/handler/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry.handler; + +/** + * Code related to event handlers. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/qry/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/qry/package-info.java new file mode 100644 index 0000000..978c5c6 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/qry/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.qry; + +/** + * Code used for the query (read) side of the application. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedConfig.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedConfig.java new file mode 100644 index 0000000..3cc9bfd --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedConfig.java @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +/** + * Application configuration. + */ +@ApplicationScoped +public class SharedConfig { + + private static final String EVENT_STORE_HOST = "127.0.0.1"; + + 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"; + + @Inject + @ConfigProperty(name = "EVENT_STORE_HOST", defaultValue = EVENT_STORE_HOST) + private String eventStoreHost; + + @Inject + @ConfigProperty(name = "EVENT_STORE_HTTP_PORT", defaultValue = "" + EVENT_STORE_HTTP_PORT) + private int eventStoreHttpPort; + + @Inject + @ConfigProperty(name = "EVENT_STORE_TCP_PORT", defaultValue = "" + EVENT_STORE_TCP_PORT) + private int eventStoreTcpPort; + + @Inject + @ConfigProperty(name = "EVENT_STORE_USER", defaultValue = EVENT_STORE_USER) + private String eventStoreUser; + + @Inject + @ConfigProperty(name = "EVENT_STORE_PASSWORD", defaultValue = EVENT_STORE_PASSWORD) + private String eventStorePassword; + + /** + * Constructor using default values internally. + */ + public SharedConfig() { + super(); + 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 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) { + super(); + this.eventStoreHost = eventStoreHost; + this.eventStoreHttpPort = eventStoreHttpPort; + this.eventStoreTcpPort = eventStoreTcpPort; + this.eventStoreUser = eventStoreUser; + this.eventStorePassword = eventStorePassword; + } + + /** + * 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; + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEntityIdFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEntityIdFactory.java new file mode 100644 index 0000000..4b15be8 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEntityIdFactory.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +import java.util.HashMap; +import java.util.Map; + +import org.fuin.ddd4j.ddd.EntityId; +import org.fuin.ddd4j.ddd.EntityIdFactory; +import org.fuin.ddd4j.ddd.SingleEntityIdFactory; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; + +/** + * Factory that creates entity identifier instances based on the type. + */ +public final class SharedEntityIdFactory implements EntityIdFactory { + + private Map map; + + /** + * Default constructor. + */ + public SharedEntityIdFactory() { + super(); + map = new HashMap(); + map.put(PersonId.TYPE.asString(), PersonId::valueOf); + } + + @Override + public EntityId createEntityId(final String type, final String id) { + final SingleEntityIdFactory factory = map.get(type); + if (factory == null) { + throw new IllegalArgumentException("Unknown type: " + type); + } + return factory.createEntityId(id); + } + + @Override + public boolean containsType(final String type) { + return map.containsKey(type); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEsjcEventStoreFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEsjcEventStoreFactory.java new file mode 100644 index 0000000..c4a61f0 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEsjcEventStoreFactory.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import com.github.msemys.esjc.EventStore; +import com.github.msemys.esjc.EventStoreBuilder; + +/** + * CDI producer that creates a {@link EventStore}. + */ +@ApplicationScoped +public class SharedEsjcEventStoreFactory { + + @Produces + @ApplicationScoped + public EventStore createESJC(final SharedConfig config) { + return EventStoreBuilder.newBuilder().singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort()) + .executor(Executors.newFixedThreadPool(10)).userCredentials(config.getEventStoreUser(), config.getEventStorePassword()) + .build(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEventStoreFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEventStoreFactory.java new file mode 100644 index 0000000..9a780fa --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedEventStoreFactory.java @@ -0,0 +1,52 @@ +package org.fuin.dddcqrs4jexample.shared.app; + +import java.nio.charset.Charset; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.fuin.esc.api.EventStore; +import org.fuin.esc.esjc.ESJCEventStore; +import org.fuin.esc.spi.EnhancedMimeType; +import org.fuin.esc.spi.SerDeserializerRegistry; + +/** + * CDI factory that creates an event store connection and repositories. + */ +@ApplicationScoped +public class SharedEventStoreFactory { + + /** + * Creates an event store.
+ *
+ * CAUTION: The returned event store instance is NOT thread safe. + * + * @param es Native event store API. + * @param registry Serialization registry. + * + * @return Dependent scope event store. + */ + @Produces + @RequestScoped + public EventStore createEventStore(final com.github.msemys.esjc.EventStore es, + final SerDeserializerRegistry registry) { + + final EventStore eventstore = new ESJCEventStore(es, registry, registry, + EnhancedMimeType.create("application", "json", Charset.forName("utf-8"))); + eventstore.open(); + return eventstore; + + } + + /** + * Closes the event store when the context is disposed. + * + * @param es Event store to close. + */ + public void closeEventStore(@Disposes final EventStore es) { + es.close(); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedExecutorServiceFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedExecutorServiceFactory.java new file mode 100644 index 0000000..b830465 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedExecutorServiceFactory.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +/** + * Creates an executor service. + */ +@ApplicationScoped +public class SharedExecutorServiceFactory { + + @ApplicationScoped + @Produces + public ExecutorService createExecutorService() { + return Executors.newFixedThreadPool(10); + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedSerDeserializerRegistryFactory.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedSerDeserializerRegistryFactory.java new file mode 100644 index 0000000..e598c0b --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedSerDeserializerRegistryFactory.java @@ -0,0 +1,34 @@ +package org.fuin.dddcqrs4jexample.shared.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.fuin.esc.spi.JsonbDeSerializer; +import org.fuin.esc.spi.SerDeserializerRegistry; +import org.fuin.esc.spi.SerializedDataTypeRegistry; + +/** + * CDI bean that creates a {@link SerDeserializerRegistry}. + */ +@ApplicationScoped +public class SharedSerDeserializerRegistryFactory { + + @Produces + @ApplicationScoped + public SerDeserializerRegistry createRegistry() { + + // Knows about all types for usage with JSON-B + final SerializedDataTypeRegistry typeRegistry = SharedUtils.createTypeRegistry(); + + // Does the actual marshalling/unmarshalling + 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; + + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedUtils.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedUtils.java new file mode 100644 index 0000000..e8c711a --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/SharedUtils.java @@ -0,0 +1,170 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +import java.nio.charset.Charset; + +import javax.json.bind.adapter.JsonbAdapter; + +import org.eclipse.yasson.FieldAccessStrategy; +import org.fuin.ddd4j.ddd.EntityIdPathConverter; +import org.fuin.ddd4j.ddd.EventIdConverter; +import org.fuin.dddcqrs4jexample.shared.domain.PersonCreatedEvent; +import org.fuin.dddcqrs4jexample.shared.domain.PersonId; +import org.fuin.dddcqrs4jexample.shared.domain.PersonName; +import org.fuin.esc.spi.Base64Data; +import org.fuin.esc.spi.EscEvent; +import org.fuin.esc.spi.EscEvents; +import org.fuin.esc.spi.EscMeta; +import org.fuin.esc.spi.EscSpiUtils; +import org.fuin.esc.spi.JsonbDeSerializer; +import org.fuin.esc.spi.SerDeserializerRegistry; +import org.fuin.esc.spi.SerializedDataType; +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. + */ +public final class SharedUtils { + + /** All types that will be written into and read from the event store. */ + private static 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(), + 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. + * + * @return New instance. + */ + public static SerializedDataTypeRegistry createTypeRegistry() { + + // Contains all types for usage with JSON-B + final SimpleSerializedDataTypeRegistry typeRegistry = new SimpleSerializedDataTypeRegistry(); + + // Base types always needed + typeRegistry.add(EscEvent.SER_TYPE, EscEvent.class); + typeRegistry.add(EscEvents.SER_TYPE, EscEvents.class); + typeRegistry.add(EscMeta.SER_TYPE, EscMeta.class); + typeRegistry.add(Base64Data.SER_TYPE, Base64Data.class); + + // User defined types + for (final TypeClass tc : USER_DEFINED_TYPES) { + typeRegistry.add(tc.getType(), tc.getClasz()); + } + return typeRegistry; + + } + + /** + * 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. + * + * @return New instance. + */ + public static SerDeserializerRegistry createSerDeserializerRegistry(final SerializedDataTypeRegistry typeRegistry, + final JsonbDeSerializer jsonbDeSer) { + + 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); + + // User defined types + for (final TypeClass tc : USER_DEFINED_TYPES) { + registry.add(tc.getType(), "application/json", jsonbDeSer); + } + jsonbDeSer.init(typeRegistry, registry, registry); + + return registry; + } + + /** + * Creates an instance of the JSON-B serializer/deserializer. + * + * @return New instance that is fully initialized with al necessary settings. + */ + 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(); + + } + + /** + * Helper class for type/class combination. + */ + private static final class TypeClass { + + private final SerializedDataType type; + + private final Class clasz; + + /** + * Constructor with all data. + * + * @param type Type. + * @param clasz Class. + */ + public TypeClass(final SerializedDataType type, final Class clasz) { + super(); + this.type = type; + this.clasz = clasz; + } + + /** + * Returns the type. + * + * @return Type. + */ + public SerializedDataType getType() { + return type; + } + + /** + * Returns the class. + * + * @return Class. + */ + public Class getClasz() { + return clasz; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/app/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/package-info.java new file mode 100644 index 0000000..4e41ecc --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/app/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.app; + +/** + * Application specific code to be shared between all modules. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java new file mode 100644 index 0000000..8eb3d1c --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonCreatedEvent.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.domain; + +import javax.annotation.concurrent.Immutable; +import javax.json.bind.annotation.JsonbProperty; +import javax.validation.constraints.NotNull; + +import org.fuin.ddd4j.ddd.AbstractDomainEvent; +import org.fuin.ddd4j.ddd.EntityIdPath; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.spi.SerializedDataType; +import org.fuin.objects4j.common.Contract; + +/** + * A new person was created in the system. + */ +@Immutable +public final class PersonCreatedEvent extends AbstractDomainEvent { + + private static final long serialVersionUID = 1000L; + + /** Never changing unique event type name. */ + public static final EventType TYPE = new EventType("PersonCreatedEvent"); + + /** Unique name used for marshalling/unmarshalling the event. */ + public static final SerializedDataType SER_TYPE = new SerializedDataType(PersonCreatedEvent.TYPE.asBaseType()); + + @NotNull + @JsonbProperty("name") + private PersonName name; + + /** + * Protected default constructor for deserialization. + */ + protected PersonCreatedEvent() { + super(); + } + + /** + * A new person was created in the system. + * + * @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)); + Contract.requireArgNotNull("name", name); + this.name = name; + } + + @Override + public final EventType getEventType() { + return PersonCreatedEvent.TYPE; + } + + /** + * Returns: Name of a person. + * + * @return Current value. + */ + @NotNull + public final PersonName getName() { + return name; + } + + @Override + public final String toString() { + return "Person '" + name + "' was created"; + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonId.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonId.java new file mode 100644 index 0000000..a40d447 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonId.java @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.domain; + +import java.util.UUID; + +import javax.annotation.concurrent.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.ui.Label; +import org.fuin.objects4j.ui.ShortLabel; +import org.fuin.objects4j.ui.Tooltip; +import org.fuin.objects4j.vo.ValueObjectConverter; + +/** + * Identifies uniquely a person. + */ +@ShortLabel(bundle = "ddd-cqrs-4-java-example", key = "PersonId.slabel", value = "PID") +@Label(bundle = "ddd-cqrs-4-java-example", key = "PersonId.label", value = "Person's ID") +@Tooltip(bundle = "ddd-cqrs-4-java-example", key = "PersonId.tooltip", value = "Unique identifier of a person") +@Immutable +public final class PersonId extends AggregateRootUuid { + + private static final long serialVersionUID = 1000L; + + /** Unique name of the aggregate this identifier refers to. */ + public static final EntityType TYPE = new StringBasedEntityType("PERSON"); + + /** + * Default constructor. + */ + protected PersonId() { + super(PersonId.TYPE); + } + + /** + * Constructor with all data. + * + * @param value Persistent value. + */ + public PersonId(@NotNull final UUID value) { + super(PersonId.TYPE, value); + } + + /** + * Parses a given string and returns a new instance of PersonId. + * + * @param value String with valid UUID to convert. A null value + * returns null. + * + * @return Converted value. + */ + public static PersonId valueOf(final String value) { + if (value == null) { + return null; + } + AggregateRootUuid.requireArgValid("value", value); + return new PersonId(UUID.fromString(value)); + } + + /** + * Converts the value object from/to UUID. + */ + public static final class Converter implements ValueObjectConverter, JsonbAdapter { + + // Attribute Converter + + @Override + public final Class getBaseTypeClass() { + return UUID.class; + } + + @Override + public final Class getValueObjectClass() { + return PersonId.class; + } + + @Override + public boolean isValid(final UUID value) { + return true; + } + + @Override + public final PersonId toVO(final UUID value) { + if (value == null) { + return null; + } + return new PersonId(value); + } + + @Override + public final UUID fromVO(final PersonId value) { + if (value == null) { + return null; + } + return value.asBaseType(); + } + + // JSONB Adapter + + @Override + public final UUID adaptToJson(final PersonId obj) throws Exception { + return fromVO(obj); + } + + @Override + public final PersonId adaptFromJson(final UUID value) throws Exception { + return toVO(value); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonName.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonName.java new file mode 100644 index 0000000..84eaea6 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/PersonName.java @@ -0,0 +1,216 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. + * http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.domain; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.concurrent.Immutable; +import javax.json.bind.adapter.JsonbAdapter; +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; +import javax.validation.constraints.NotNull; + +import org.fuin.objects4j.common.ConstraintViolationException; +import org.fuin.objects4j.ui.Label; +import org.fuin.objects4j.ui.ShortLabel; +import org.fuin.objects4j.ui.Tooltip; +import org.fuin.objects4j.vo.AbstractStringValueObject; +import org.fuin.objects4j.vo.ValueObjectConverter; + +/** + * Name of a person. + */ +@ShortLabel(bundle = "ddd-cqrs-4-java-example", key = "PersonName.slabel", value = "PNAME") +@Label(bundle = "ddd-cqrs-4-java-example", key = "PersonName.label", value = "Person's name") +@Tooltip(bundle = "ddd-cqrs-4-java-example", key = "PersonName.tooltip", value = "Name of a person") +@Immutable +public final class PersonName extends AbstractStringValueObject { + + private static final long serialVersionUID = 1000L; + + /** Max length of a person's name. */ + public static final int MAX_LENGTH = 100; + + @NotNull + @PersonNameStr + private String value; + + /** + * Protected default constructor for deserialization. + */ + protected PersonName() { + super(); + } + + /** + * Constructor with mandatory data. + * + * @param value Value. + */ + public PersonName(final String value) { + super(); + PersonName.requireArgValid("value", value); + this.value = value; + } + + @Override + public final String asBaseType() { + return value; + } + + @Override + public final String toString() { + return value; + } + + /** + * Verifies that a given string can be converted into the type. + * + * @param value Value to validate. + * + * @return Returns true if it's a valid type else + * false. + */ + public static boolean isValid(final String value) { + if (value == null) { + return true; + } + if (value.length() == 0) { + return false; + } + final String trimmed = value.trim(); + if (trimmed.length() > PersonName.MAX_LENGTH) { + return false; + } + return true; + } + + /** + * 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. + * + * @throws ConstraintViolationException The value was not valid. + */ + 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 + "'"); + } + + } + + /** + * Ensures that the string can be converted into the type. + */ + @Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Constraint(validatedBy = { Validator.class }) + @Documented + public static @interface PersonNameStr { + + String message() + + default "{org.fuin.dddcqrs4jexample.PersonName.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + } + + /** + * Validates if a string is compliant with the type. + */ + public static final class Validator implements ConstraintValidator { + + @Override + public final void initialize(final PersonNameStr annotation) { + // Not used + } + + @Override + public final boolean isValid(final String value, final ConstraintValidatorContext context) { + return PersonName.isValid(value); + } + + } + + /** + * Converts the value object from/to string. + */ + public static final class Converter + implements ValueObjectConverter, JsonbAdapter { + + // Attribute Converter + + @Override + public final Class getBaseTypeClass() { + return String.class; + } + + @Override + public final Class getValueObjectClass() { + return PersonName.class; + } + + @Override + public boolean isValid(final String value) { + return PersonName.isValid(value); + } + + @Override + public final PersonName toVO(final String value) { + if (value == null) { + return null; + } + return new PersonName(value); + } + + @Override + public final String fromVO(final PersonName value) { + if (value == null) { + return null; + } + return value.asBaseType(); + } + + // JSONB Adapter + + @Override + public final String adaptToJson(final PersonName obj) throws Exception { + return fromVO(obj); + } + + @Override + public final PersonName adaptFromJson(final String str) throws Exception { + return toVO(str); + } + + } + +} diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/package-info.java new file mode 100644 index 0000000..fa51332 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/domain/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared.domain; + +/** + * Domain specific code to be shared between all modules. + */ diff --git a/src/main/java/org/fuin/dddcqrs4jexample/shared/package-info.java b/src/main/java/org/fuin/dddcqrs4jexample/shared/package-info.java new file mode 100644 index 0000000..2261b52 --- /dev/null +++ b/src/main/java/org/fuin/dddcqrs4jexample/shared/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.dddcqrs4jexample.shared; + +/** + * Code shared between command (write) and query (read) modules. + */ diff --git a/src/main/resources/META-INF/beans.xml b/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000..b009592 --- /dev/null +++ b/src/main/resources/META-INF/beans.xml @@ -0,0 +1,10 @@ + + + + + org.apache.deltaspike.jpa.impl.transaction.BeanManagedUserTransactionStrategy + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..f2bbfb1 --- /dev/null +++ b/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,27 @@ + + + + + org.fuin.dddcqrs4jexample.qry.handler.QryPersonProjectionPosition + org.fuin.dddcqrs4jexample.qry.domain.QryPerson + true + + + + + + + + + + + + + + + + + +