diff --git a/README.md b/README.md index db3d5d1..5a2d00f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ Here is an overview of how such an application looks like: - **[Aggregates](aggregates)** - DDD related code for all demo applications (aggregates, entities and business exceptions). - **[Quarkus](quarkus)** - Two microservices (Command & Query) based on [Quarkus](https://quarkus.io/). - **[Spring Boot](spring-boot)** - Two microservices (Command & Query) based on [Spring Boot](https://spring.io/projects/spring-boot/). -- **[Java SE + CDI](java-se-cdi)** - Two standalone applications (Command & Query) using CDI for dependency injection. ## Getting started The following instructions are tested on Linux (Ubuntu 22) @@ -108,31 +107,69 @@ For more details see [spring-boot/command](spring-boot/command). You should see a projection named "qry-person-stream" when you click on "Projections" in the top menu. 2. Opening [http://localhost:8080/persons](http://localhost:8080/persons) should show an empty JSON array -### Execute a test command (Console window 4) -Change into the demo directory and execute a command using cURL (See [shell script](demo/create-person-command.sh) and [command](demo/create-person-command.json)) +### Execute some create commands (Console window 4) +Change into the demo directory and execute the command using cURL (See [shell script](demo/create-persons.sh) and JSON files with commands in [demo](demo)) ``` cd ddd-cqrs-4-java-example/demo -./create-person-command.sh +./create-persons.sh ``` Command service (Console window 3) should show something like ``` +Update aggregate: id=PERSON 954177c4-aeb7-4d1e-b6d7-3e02fe9432cb, version=-1, nextVersion=0 +Update aggregate: id=PERSON 568df38c-fdc3-4f60-81aa-d3cce9ebfd7b, version=-1, nextVersion=0 Update aggregate: id=PERSON 84565d62-115e-4502-b7c9-38ad69c64b05, version=-1, nextVersion=0 ``` Query service (Console window 2) should show something like ``` -PersonCreatedEventHandler ... Handle PersonCreatedEvent: Person 'Peter Parker' was created +Handle PersonCreatedEvent: Person 'Harry Osborn' (954177c4-aeb7-4d1e-b6d7-3e02fe9432cb) was created +Handle PersonCreatedEvent: Person 'Mary Jane Watson' (568df38c-fdc3-4f60-81aa-d3cce9ebfd7b) was created +Handle PersonCreatedEvent: Person 'Peter Parker' (84565d62-115e-4502-b7c9-38ad69c64b05) was created ``` ### Verify the query data was updated 1. Refreshing [http://localhost:8080/persons](http://localhost:8080/persons) should show ```json - [{"id":"84565d62-115e-4502-b7c9-38ad69c64b05","name":"Peter Parker"}] + [ + { + "id": "568df38c-fdc3-4f60-81aa-d3cce9ebfd7b", + "name": "Mary Jane Watson" + }, + { + "id": "84565d62-115e-4502-b7c9-38ad69c64b05", + "name": "Peter Parker" + }, + { + "id": "954177c4-aeb7-4d1e-b6d7-3e02fe9432cb", + "name": "Harry Osborn" + } + ] ``` 2. Opening [http://localhost:8080/persons/84565d62-115e-4502-b7c9-38ad69c64b05](http://localhost:8080/persons/84565d62-115e-4502-b7c9-38ad69c64b05) should show ```json {"id":"84565d62-115e-4502-b7c9-38ad69c64b05","name":"Peter Parker"} 3. The event sourced data of the person aggregate could be found in a stream named [PERSON-84565d62-115e-4502-b7c9-38ad69c64b05](http://localhost:2113/web/index.html#/streams/PERSON-84565d62-115e-4502-b7c9-38ad69c64b05) +### Execute a delete command (Console window 4) +Change into the demo directory and execute the command using cURL (See [shell script](demo/create-persons.sh) and JSON files with commands in [demo](demo)) +``` +cd ddd-cqrs-4-java-example/demo +./delete-harry-osborn.sh +``` +### Verify the query data was updated +1. Refreshing [http://localhost:8080/persons](http://localhost:8080/persons) should show + ```json + [ + { + "id": "568df38c-fdc3-4f60-81aa-d3cce9ebfd7b", + "name": "Mary Jane Watson" + }, + { + "id": "84565d62-115e-4502-b7c9-38ad69c64b05", + "name": "Peter Parker" + } + ] + ``` + "Harry Osborn" should no longer be present in the list. ### Stop Event Store and Maria DB and clean up 1. Stop Docker Compose (Ubuntu shortcut = ctrl c) diff --git a/aggregates/pom.xml b/aggregates/pom.xml index 47a0a45..48d6219 100644 --- a/aggregates/pom.xml +++ b/aggregates/pom.xml @@ -7,7 +7,7 @@ org.fuin.cqrs4j.example cqrs4j-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -23,7 +23,7 @@ org.fuin.cqrs4j.example cqrs4j-example-shared - 0.3.0 + 0.4.0-SNAPSHOT diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java index 44787e8..99fec92 100644 --- a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/DuplicatePersonNameException.java @@ -1,19 +1,6 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.aggregates; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.cqrs4j.example.shared.PersonName; import org.fuin.objects4j.common.ExceptionShortIdentifable; diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepository.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/EventStorePersonRepository.java similarity index 79% rename from java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepository.java rename to aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/EventStorePersonRepository.java index 1e21026..e9cec0d 100644 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepository.java +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/EventStorePersonRepository.java @@ -1,26 +1,25 @@ -package org.fuin.cqrs4j.example.javasecdi.cmd.domain; +package org.fuin.cqrs4j.example.aggregates; -import org.fuin.objects4j.common.NotThreadSafe; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.ddd4j.ddd.EntityType; import org.fuin.ddd4j.esrepo.EventStoreRepository; import org.fuin.esc.api.EventStore; +import org.fuin.objects4j.common.NotThreadSafe; /** * Event sourced repository for storing a {@link Person} aggregate. */ @NotThreadSafe -public class PersonRepository extends EventStoreRepository { +public class EventStorePersonRepository extends EventStoreRepository implements PersonRepository { /** * Constructor all mandatory data. - * + * * @param eventStore * Event store. */ - public PersonRepository(final EventStore eventStore) { + public EventStorePersonRepository(final EventStore eventStore) { super(eventStore); } diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java index 9784e5d..9be0c0f 100644 --- a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/Person.java @@ -1,30 +1,19 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.aggregates; -import java.io.Serializable; -import java.util.Optional; - import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.cqrs4j.example.shared.PersonName; import org.fuin.ddd4j.ddd.AbstractAggregateRoot; +import org.fuin.ddd4j.ddd.AggregateDeletedException; import org.fuin.ddd4j.ddd.ApplyEvent; import org.fuin.ddd4j.ddd.EntityType; import org.fuin.objects4j.common.Contract; +import java.io.Serializable; +import java.util.Optional; + /** * Represents a natural person. */ @@ -35,6 +24,11 @@ public class Person extends AbstractAggregateRoot implements Serializa @NotNull private PersonId id; + @NotNull + private PersonName name; + + private boolean deleted; + /** * Default constructor that is mandatory for aggregate roots. */ @@ -72,10 +66,22 @@ public Person(@NotNull final PersonId id, @NotNull final PersonName name, final } // CREATE EVENT - apply(new PersonCreatedEvent(id, name)); + apply(new PersonCreatedEvent.Builder().id(id).name(name).version(getNextVersion() + 1).build()); } + /** + * Deletes the person. + * + * @throws AggregateDeletedException The aggregate was already deleted. + */ + public void delete() throws AggregateDeletedException { + if (deleted) { + throw new AggregateDeletedException(PersonId.TYPE, id); + } + apply(new PersonDeletedEvent.Builder().id(id).name(name).version(getNextVersion() + 1).build()); + } + @Override public PersonId getId() { return id; @@ -89,6 +95,12 @@ public EntityType getType() { @ApplyEvent public void applyEvent(final PersonCreatedEvent event) { this.id = event.getEntityId(); + this.name = event.getName(); + } + + @ApplyEvent + public void applyEvent(final PersonDeletedEvent event) { + this.deleted = true; } /** diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java index b2430c4..3a81964 100644 --- a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/PersonRepository.java @@ -1,63 +1,11 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.aggregates; -import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.ddd4j.ddd.EntityType; -import org.fuin.ddd4j.esrepo.EventStoreRepository; -import org.fuin.esc.api.EventStore; -import org.fuin.objects4j.common.NotThreadSafe; +import org.fuin.ddd4j.ddd.Repository; /** * 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"; - } +public interface PersonRepository extends Repository { } \ No newline at end of file diff --git a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java index f486708..110cb9f 100644 --- a/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java +++ b/aggregates/src/main/java/org/fuin/cqrs4j/example/aggregates/package-info.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.aggregates; /** diff --git a/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java b/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java index 4b28124..f03107a 100644 --- a/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java +++ b/aggregates/src/test/java/org/fuin/cqrs4j/example/aggregates/PersonTest.java @@ -1,16 +1,18 @@ package org.fuin.cqrs4j.example.aggregates; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.Optional; -import java.util.UUID; - import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.ddd4j.ddd.AggregateDeletedException; import org.junit.jupiter.api.Test; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + /** * Test for the {@link Person} class. */ @@ -33,6 +35,7 @@ public final void testCreateOK() throws DuplicatePersonNameException { assertThat(testee.getUncommittedChanges().get(0)).isInstanceOf(PersonCreatedEvent.class); final PersonCreatedEvent event = (PersonCreatedEvent) testee.getUncommittedChanges().get(0); assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(0); assertThat(event.getName()).isEqualTo(personName); } @@ -57,4 +60,29 @@ public final void testCreateDuplicateName() { } + @Test + public void testDeleteOK() throws DuplicatePersonNameException, AggregateDeletedException { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Peter Parker"); + final PersonId otherId = new PersonId(UUID.randomUUID()); + final Person testee = new Person(personId, personName, pid -> { + return Optional.empty(); + }); + testee.markChangesAsCommitted(); + + // TEST + testee.delete(); + + //VERIFY + assertThat(testee.getUncommittedChanges()).hasSize(1); + assertThat(testee.getUncommittedChanges().get(0)).isInstanceOf(PersonDeletedEvent.class); + final PersonDeletedEvent event = (PersonDeletedEvent) testee.getUncommittedChanges().get(0); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(1); + assertThat(event.getName()).isEqualTo(personName); + + } + } diff --git a/demo/create-harry-osborn-command.json b/demo/create-harry-osborn-command.json new file mode 100644 index 0000000..31cade2 --- /dev/null +++ b/demo/create-harry-osborn-command.json @@ -0,0 +1,6 @@ +{ + "event-id": "5601097d-6e2e-4df1-a7b2-ecc4f443c068", + "event-timestamp": "2024-01-07T10:00:00.000+01:00[Europe/Berlin]", + "entity-id-path": "PERSON 954177c4-aeb7-4d1e-b6d7-3e02fe9432cb", + "name": "Harry Osborn" +} \ No newline at end of file diff --git a/demo/create-mary-jane-watson-command.json b/demo/create-mary-jane-watson-command.json new file mode 100644 index 0000000..578682f --- /dev/null +++ b/demo/create-mary-jane-watson-command.json @@ -0,0 +1,6 @@ +{ + "event-id": "4bf5bb56-4fe8-47a3-8358-25144e15497d", + "event-timestamp": "2024-01-07T09:00:00.000+01:00[Europe/Berlin]", + "entity-id-path": "PERSON 568df38c-fdc3-4f60-81aa-d3cce9ebfd7b", + "name": "Mary Jane Watson" +} \ No newline at end of file diff --git a/demo/create-person-command.sh b/demo/create-person-command.sh deleted file mode 100755 index f79f149..0000000 --- a/demo/create-person-command.sh +++ /dev/null @@ -1,4 +0,0 @@ -curl -i \ - -H "Content-Type:application/json" \ - -d "@create-person-command.json" \ - "http://localhost:8081/persons/create" diff --git a/demo/create-persons.sh b/demo/create-persons.sh new file mode 100644 index 0000000..eb0d8e8 --- /dev/null +++ b/demo/create-persons.sh @@ -0,0 +1,7 @@ +for file in ./create-*-command.json +do + curl -i \ + -H "Content-Type:application/json" \ + -d "@$file" \ + "http://localhost:8081/persons/create" +done diff --git a/demo/create-person-command.json b/demo/create-peter-parker-command.json similarity index 100% rename from demo/create-person-command.json rename to demo/create-peter-parker-command.json diff --git a/spring-boot/shared/src/test/resources/commands/CreatePersonCommand.json b/demo/delete-harry-osborn-command.json similarity index 52% rename from spring-boot/shared/src/test/resources/commands/CreatePersonCommand.json rename to demo/delete-harry-osborn-command.json index 92ceb71..d736c05 100644 --- a/spring-boot/shared/src/test/resources/commands/CreatePersonCommand.json +++ b/demo/delete-harry-osborn-command.json @@ -1,6 +1,7 @@ { "event-id": "109a77b2-1de2-46fc-aee1-97fa7740a552", "event-timestamp": "2019-11-17T10:27:13.183+01:00[Europe/Berlin]", - "entity-id-path": "PERSON 84565d62-115e-4502-b7c9-38ad69c64b05", - "name": "Peter Parker" + "entity-id-path": "PERSON 954177c4-aeb7-4d1e-b6d7-3e02fe9432cb", + "aggregate-version": 0, + "name": "Harry Osborn" } \ No newline at end of file diff --git a/demo/delete-harry-osborn.sh b/demo/delete-harry-osborn.sh new file mode 100644 index 0000000..359d1f8 --- /dev/null +++ b/demo/delete-harry-osborn.sh @@ -0,0 +1,5 @@ +curl -i \ + -X DELETE \ + -H "Content-Type:application/json" \ + -d "@delete-harry-osborn-command.json" \ + "http://localhost:8081/persons/954177c4-aeb7-4d1e-b6d7-3e02fe9432cb" diff --git a/java-se-cdi/README.md b/java-se-cdi/README.md deleted file mode 100644 index 16fee64..0000000 --- a/java-se-cdi/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# ddd-cqrs-4-java-example -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). The query data is stored in-memory using a [HSQLDB](http://hsqldb.org/). - -> [!CAUTION] -> [Apache Deltaspike](https://deltaspike.apache.org/) does not support the **jakarta** namespace yet. -> Therefore the project currently does not work correctly! (Applications fail to start...) - -## Starting the demo - -1. Start an EventStore locally on your PC - * Use the [docker-compose.yml](../docker-compose.yml) script: `docker-compose up` - * 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/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/cmd/app/CmdExampleApp.java) to create a [PersonCreatedEvent](src/main/java/org/fuin/cqrs4j/example/javasecdi/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) (User 'admin' / Password 'changeit') and open the projection details for 'qry-person-stream' it should show 'Events processed = 1' - * Look at the person's aggregate event stream: ```http://localhost:2113/web/index.html#/streams/PERSON-00000000-0000-0000-0000-000000000000`` - Replace the zero UUID with the one shown in the event handler message 'Person 'Peter Parker Inc.' (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) was created' -4. Kill the [QryExampleApp](src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryExampleApp.java) -5. Stop the event store (Pressing CTRL+C keys) - * Use the [docker-compose.yml](../docker-compose.yml) script: `docker-compose rm` to clean up the images if you used Docker Compose - -## 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/cqrs4j/example/javasecdi/shared/app) package - -### 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/cqrs4j/example/javasecdi/cmd/app/CmdExampleApp.java) that creates a new [Person](src/main/java/org/fuin/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/cmd/domain/Person.java) aggregate (Aggregate Root). -* The [PersonRepository](src/main/java/org/fuin/cqrs4j/example/javasecdi/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. - -#### Overview -![Overview](https://raw.github.com/fuinorg/ddd-cqrs-4-java-example/master/java-se-cdi/doc/ddd-cqrs-4-java-example.png) - -#### Sub package 'app' -There are several configuration classes used for CDI in the [app](src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app) package and the main query application. - -* The command line application [QryExampleApp](src/main/java/org/fuin/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/qry/app/QryTimer.java) class fires an asynchronous CDI event called [QryCheckForViewUpdatesEvent](src/main/java/org/fuin/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/qry/domain/QryPerson.java) class is an [@jakarta.persistence.Entity](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entity) class -* The [QryPersonRepository](src/main/java/org/fuin/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/qry/handler/QryProjector.java) receives the event issued by the [QryTimer](src/main/java/org/fuin/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/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/cqrs4j/example/javasecdi/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/java-se-cdi/doc/ddd-cqrs-4-java-example.png b/java-se-cdi/doc/ddd-cqrs-4-java-example.png deleted file mode 100644 index efc7ded..0000000 Binary files a/java-se-cdi/doc/ddd-cqrs-4-java-example.png and /dev/null differ diff --git a/java-se-cdi/logback.xml b/java-se-cdi/logback.xml deleted file mode 100644 index 47c4bc7..0000000 --- a/java-se-cdi/logback.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - %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/java-se-cdi/pom.xml b/java-se-cdi/pom.xml deleted file mode 100644 index 0ecd396..0000000 --- a/java-se-cdi/pom.xml +++ /dev/null @@ -1,224 +0,0 @@ - - - - 4.0.0 - - - org.fuin.cqrs4j.example - cqrs4j-example-root - 0.3.0 - ../pom.xml - - - org.fuin.cqrs4j.example.javasecdi - cqrs4j-javasecdi-example - 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 - - - - - - - - org.apache.deltaspike.distribution - distributions-bom - 1.9.6 - import - pom - - - - - - - - - - - - org.fuin.cqrs4j.example - cqrs4j-example-shared - 0.3.0 - - - - org.fuin - ddd-4-java - - - - org.fuin - cqrs-4-java - - - - org.fuin.esc - esc-spi - - - - org.fuin.esc - esc-esgrpc - - - - org.fuin.esc - esc-http-admin - - - - org.apache.httpcomponents - httpasyncclient - - - - org.apache.httpcomponents - httpclient - - - - org.fuin - ext4logback - - - - jakarta.validation - jakarta.validation-api - - - - org.apache.commons - commons-lang3 - - - - org.slf4j - slf4j-api - - - - ch.qos.logback - logback-classic - - - - jakarta.enterprise - jakarta.enterprise.cdi-api - 4.0.1 - - - - org.jboss.weld.se - weld-se-core - 5.1.2.Final - - - - org.eclipse.microprofile.config - microprofile-config-api - 3.1 - - - - io.smallrye.config - smallrye-config - 3.5.0 - - - - jakarta.json - jakarta.json-api - - - - org.glassfish - jakarta.json - - - - org.eclipse - yasson - - - - org.apache.deltaspike.modules - deltaspike-data-module-api - - - - org.apache.deltaspike.modules - deltaspike-data-module-impl - - - - org.hibernate.validator - hibernate-validator - - - - jakarta.persistence - jakarta.persistence-api - - - - org.hibernate - hibernate-entitymanager - - - - commons-io - commons-io - - - - org.hsqldb - hsqldb - - - - - - org.fuin.esc - esc-mem - test - - - - org.junit.jupiter - junit-jupiter - test - - - - org.assertj - assertj-core - test - - - - org.fuin - units4j - test - - - - nl.jqno.equalsverifier - equalsverifier - test - - - - - \ No newline at end of file diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/CmdExampleApp.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/CmdExampleApp.java deleted file mode 100644 index 5d8da70..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/CmdExampleApp.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.cmd.app; - -import java.util.UUID; - -import jakarta.enterprise.context.control.ActivateRequestContext; -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.se.SeContainer; -import jakarta.enterprise.inject.se.SeContainerInitializer; -import jakarta.inject.Inject; - -import org.fuin.cqrs4j.example.javasecdi.cmd.domain.Person; -import org.fuin.cqrs4j.example.javasecdi.cmd.domain.PersonRepository; -import org.fuin.cqrs4j.example.javasecdi.cmd.domain.PersonRepositoryFactory; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.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.cqrs4j.example.javasecdi", "logback")); - - LOG.info("Start example"); - - try (final SeContainer container = SeContainerInitializer.newInstance().initialize()) { - final CmdExampleApp app = container.select(CmdExampleApp.class).get(); - app.execute(); - } - - LOG.info("Finished example"); - - System.exit(0); - - } catch (final RuntimeException ex) { - ex.printStackTrace(System.err); - System.exit(1); - } - - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/package-info.java deleted file mode 100644 index 334af48..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/app/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.cmd.app; - -/** - * Command application specific code. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/basics/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/basics/package-info.java deleted file mode 100644 index f27ab06..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/basics/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.cmd.basics; - -/** - * General helper classes used by all other 'cmd' packages. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/DuplicatePersonNameException.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/DuplicatePersonNameException.java deleted file mode 100644 index f327a24..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/DuplicatePersonNameException.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.cmd.domain; - -import jakarta.validation.constraints.NotNull; - -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/Person.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/Person.java deleted file mode 100644 index 11e318b..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/Person.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.cmd.domain; - -import java.io.Serializable; - -import jakarta.validation.constraints.NotNull; - -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; -import org.fuin.ddd4j.ddd.AbstractAggregateRoot; -import org.fuin.ddd4j.ddd.ApplyEvent; -import org.fuin.ddd4j.ddd.EntityType; -import org.fuin.objects4j.common.Contract; - -/** - * A medical practitioner most likely also holder of an accredited academic degree. - */ -public class Person extends AbstractAggregateRoot 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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepositoryFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepositoryFactory.java deleted file mode 100644 index c3ec34a..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/PersonRepositoryFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.cmd.domain; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Dependent; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/package-info.java deleted file mode 100644 index b46aa4a..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/domain/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.cmd.domain; - -/** - * Domain specific code like aggregate root classes and their repositories. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/package-info.java deleted file mode 100644 index a0b9ee1..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/cmd/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.cmd; - -/** - * Code used for the command (write) side of the application. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/HttpClientFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/HttpClientFactory.java deleted file mode 100644 index f623720..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/HttpClientFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import org.fuin.cqrs4j.example.javasecdi.shared.app.SharedConfig; - -import java.net.Authenticator; -import java.net.PasswordAuthentication; -import java.net.http.HttpClient; -import java.time.Duration; -import java.time.temporal.ChronoUnit; - -/** - * CDI factory that creates a {@link HttpClient} instance. - */ -@ApplicationScoped -public class HttpClientFactory { - - @Produces - public HttpClient getHttpClient(final SharedConfig config) { - return HttpClient.newBuilder() - .authenticator(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(config.getEventStoreUser(), config.getEventStorePassword().toCharArray()); - } - }) - .connectTimeout(Duration.of(10, ChronoUnit.SECONDS)) - .build(); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryCheckForViewUpdatesEvent.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryCheckForViewUpdatesEvent.java deleted file mode 100644 index 2bf9e16..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryCheckForViewUpdatesEvent.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -/** - * Notifies the projectors to check for view updates. - */ -public class QryCheckForViewUpdatesEvent { - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryEntityManagerProducer.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryEntityManagerProducer.java deleted file mode 100644 index 106e255..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryEntityManagerProducer.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Disposes; -import jakarta.enterprise.inject.Produces; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryExampleApp.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryExampleApp.java deleted file mode 100644 index 865fd74..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryExampleApp.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import jakarta.enterprise.inject.se.SeContainer; -import jakarta.enterprise.inject.se.SeContainerInitializer; -import jakarta.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.cqrs4j.example.javasecdi", "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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryProjectionAdminEventStoreFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryProjectionAdminEventStoreFactory.java deleted file mode 100644 index b1decb1..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryProjectionAdminEventStoreFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Disposes; -import jakarta.enterprise.inject.Produces; -import org.fuin.cqrs4j.example.javasecdi.shared.app.SharedConfig; -import org.fuin.esc.admin.HttpProjectionAdminEventStore; -import org.fuin.esc.api.ProjectionAdminEventStore; - -import java.net.MalformedURLException; -import java.net.URL; -import java.net.http.HttpClient; - -/** - * CDI factory that creates a {@link ProjectionAdminEventStore} instance. - */ -@ApplicationScoped -public class QryProjectionAdminEventStoreFactory { - - @Produces - @ApplicationScoped - public ProjectionAdminEventStore getProjectionAdminEventStore(final SharedConfig config, final HttpClient httpClient) { - final String url = "http://" + config.getEventStoreHost() + ":" + config.getEventStoreHttpPort(); - try { - final ProjectionAdminEventStore es = new HttpProjectionAdminEventStore(httpClient, new URL(url)); - 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 Event store to close. - */ - public void closeProjectionAdminEventStore(@Disposes final ProjectionAdminEventStore es) { - es.close(); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryScheduledExecutorService.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryScheduledExecutorService.java deleted file mode 100644 index b7e5c61..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryScheduledExecutorService.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryThreadFactoryFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryThreadFactoryFactory.java deleted file mode 100644 index 12533ae..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryThreadFactoryFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -/** - * Creates an thread factory. - */ -@ApplicationScoped -public class QryThreadFactoryFactory { - - @ApplicationScoped - @Produces - public ThreadFactory create() { - return Executors.defaultThreadFactory(); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryTimer.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryTimer.java deleted file mode 100644 index 7f7710f..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/QryTimer.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Event; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/package-info.java deleted file mode 100644 index a1f78f7..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/app/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry.app; - -/** - * Query application specific code. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/basics/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/basics/package-info.java deleted file mode 100644 index 242503a..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/basics/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry.basics; - -/** - * General helper classes used by all other 'qry' packages. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPerson.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPerson.java deleted file mode 100644 index 116f9c4..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPerson.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; - -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPersonRepository.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPersonRepository.java deleted file mode 100644 index 40706c9..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/QryPersonRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/package-info.java deleted file mode 100644 index 92c9977..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/domain/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry.domain; - -/** - * Domain specific code like view objects. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/PersonCreatedEventHandler.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/PersonCreatedEventHandler.java deleted file mode 100644 index 61c5b5e..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/PersonCreatedEventHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.apache.deltaspike.jpa.api.transaction.Transactional; -import org.fuin.cqrs4j.EventHandler; -import org.fuin.cqrs4j.example.javasecdi.qry.domain.QryPerson; -import org.fuin.cqrs4j.example.javasecdi.qry.domain.QryPersonRepository; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.ddd4j.ddd.EventType; -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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventChunkHandler.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventChunkHandler.java deleted file mode 100644 index d9d2a60..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventChunkHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventDispatcherFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventDispatcherFactory.java deleted file mode 100644 index c5869fa..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryEventDispatcherFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPosition.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPosition.java deleted file mode 100644 index 3e8a856..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPosition.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPositionRepository.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPositionRepository.java deleted file mode 100644 index 57a1287..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryPersonProjectionPositionRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryProjector.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryProjector.java deleted file mode 100644 index 2cd56d6..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/QryProjector.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -import static org.fuin.cqrs4j.Cqrs4JUtils.tryLocked; -import static org.fuin.cqrs4j.example.javasecdi.qry.handler.QryEventChunkHandler.PROJECTION_STREAM_ID; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Semaphore; - -import jakarta.enterprise.event.ObservesAsync; -import jakarta.inject.Inject; - -import org.fuin.cqrs4j.EventDispatcher; -import org.fuin.ddd4j.ddd.EventType; -import org.fuin.cqrs4j.example.javasecdi.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/package-info.java deleted file mode 100644 index c3f777f..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/handler/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry.handler; - -/** - * Code related to event handlers. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/package-info.java deleted file mode 100644 index a17916e..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/qry/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.qry; - -/** - * Code used for the query (read) side of the application. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedConfig.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedConfig.java deleted file mode 100644 index 79c277f..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedConfig.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -/** - * 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 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_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.eventStoreUser = EVENT_STORE_USER; - this.eventStorePassword = EVENT_STORE_PASSWORD; - } - - /** - * Constructor with all data. - * - * @param eventStoreHost Host. - * @param eventStoreHttpPort HTTP port - * @param eventStoreUser User. - * @param eventStorePassword Password. - */ - public SharedConfig(final String eventStoreHost, final int eventStoreHttpPort, final String eventStoreUser, - final String eventStorePassword) { - super(); - this.eventStoreHost = eventStoreHost; - this.eventStoreHttpPort = eventStoreHttpPort; - 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 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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEntityIdFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEntityIdFactory.java deleted file mode 100644 index 0cbe085..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEntityIdFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.ddd4j.ddd.EntityId; -import org.fuin.ddd4j.ddd.EntityIdFactory; - -/** - * Factory that creates entity identifier instances based on the type. - */ -public final class SharedEntityIdFactory implements EntityIdFactory { - - private Map> valueOfMap; - - private Map> isValidMap; - - /** - * Default constructor. - */ - public SharedEntityIdFactory() { - super(); - valueOfMap = new HashMap<>(); - isValidMap = new HashMap<>(); - valueOfMap.put(PersonId.TYPE.asString(), PersonId::valueOf); - isValidMap.put(PersonId.TYPE.asString(), PersonId::isValid); - } - - @Override - public EntityId createEntityId(final String type, final String id) { - final Function factory = valueOfMap.get(type); - if (factory == null) { - throw new IllegalArgumentException("Unknown type: " + type); - } - return factory.apply(id); - } - - @Override - public boolean containsType(final String type) { - return valueOfMap.containsKey(type); - } - - @Override - public boolean isValid(String type, String id) { - final Function func = isValidMap.get(type); - if (func == null) { - return false; - } - return func.apply(id); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEventStoreFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEventStoreFactory.java deleted file mode 100644 index 99b2ac2..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedEventStoreFactory.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - *

- * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - *

- * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - *

- * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import com.eventstore.dbclient.EventStoreDBClient; -import com.eventstore.dbclient.EventStoreDBClientSettings; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Disposes; -import jakarta.enterprise.inject.Produces; -import org.fuin.esc.esgrpc.ESGrpcEventStore; -import org.fuin.esc.esgrpc.IESGrpcEventStore; -import org.fuin.esc.spi.EnhancedMimeType; -import org.fuin.esc.spi.SerDeserializerRegistry; - -import java.nio.charset.StandardCharsets; - -/** - * CDI factory that creates an event store connection. - */ -@ApplicationScoped -public class SharedEventStoreFactory { - - /** - * Creates an GRPC based event store.
- *
- * CAUTION: The returned event store instance is NOT thread safe. - * - * @param config - * Configuration to use. - * @param registry - * Serialization registry. - * - * @return Application scope event store. - */ - @Produces - @ApplicationScoped - public IESGrpcEventStore createEventStore(final SharedConfig config, final SerDeserializerRegistry registry) { - - final EventStoreDBClientSettings setts = EventStoreDBClientSettings.builder() - .addHost(config.getEventStoreHost(), config.getEventStoreHttpPort()) - .defaultCredentials(config.getEventStoreUser(), config.getEventStorePassword()) - .tls(false) - .buildConnectionSettings(); - - final EventStoreDBClient client = EventStoreDBClient.create(setts); - final IESGrpcEventStore eventstore = new ESGrpcEventStore.Builder().eventStore(client).serDesRegistry(registry) - .targetContentType(EnhancedMimeType.create("application", "json", StandardCharsets.UTF_8)) - .build(); - - eventstore.open(); - return eventstore; - - } - - /** - * Closes the GRPC based event store when the context is disposed. - * - * @param es - * Event store to close. - */ - public void closeEventStore(@Disposes final IESGrpcEventStore es) { - es.close(); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedExecutorServiceFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedExecutorServiceFactory.java deleted file mode 100644 index 2498621..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedExecutorServiceFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -/** - * Creates an executor service. - */ -@ApplicationScoped -public class SharedExecutorServiceFactory { - - @ApplicationScoped - @Produces - public ExecutorService createExecutorService() { - return Executors.newFixedThreadPool(10); - } - -} diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedSerDeserializerRegistryFactory.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedSerDeserializerRegistryFactory.java deleted file mode 100644 index bf41865..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedSerDeserializerRegistryFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedUtils.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedUtils.java deleted file mode 100644 index 0a9f593..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/SharedUtils.java +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -import java.nio.charset.Charset; - -import jakarta.json.bind.adapter.JsonbAdapter; - -import org.eclipse.yasson.FieldAccessStrategy; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; -import org.fuin.ddd4j.ddd.EntityIdPathConverter; -import org.fuin.ddd4j.ddd.EventIdConverter; -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/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/package-info.java deleted file mode 100644 index 018ea17..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/app/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared.app; - -/** - * Application specific code to be shared between all modules. - */ diff --git a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/package-info.java b/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/package-info.java deleted file mode 100644 index 448ca1a..0000000 --- a/java-se-cdi/src/main/java/org/fuin/cqrs4j/example/javasecdi/shared/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.javasecdi.shared; - -/** - * Code shared between command (write) and query (read) modules. - */ diff --git a/java-se-cdi/src/main/resources/META-INF/beans.xml b/java-se-cdi/src/main/resources/META-INF/beans.xml deleted file mode 100644 index dba3375..0000000 --- a/java-se-cdi/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - org.apache.deltaspike.jpa.impl.transaction.BeanManagedUserTransactionStrategy - - - \ No newline at end of file diff --git a/java-se-cdi/src/main/resources/META-INF/persistence.xml b/java-se-cdi/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index 293b261..0000000 --- a/java-se-cdi/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - org.fuin.cqrs4j.example.javasecdi.qry.handler.QryPersonProjectionPosition - org.fuin.cqrs4j.example.javasecdi.qry.domain.QryPerson - true - - - - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index fd4297b..ad37cd0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.fuin.cqrs4j.example cqrs4j-example-root - 0.3.0 + 0.4.0-SNAPSHOT pom @@ -15,13 +15,12 @@ true UTF-8 UTF-8 - 0.7.0-SNAPSHOT + 0.7.0 shared aggregates - java-se-cdi quarkus spring-boot @@ -72,12 +71,6 @@ ${esc.version} - - org.fuin.esc - esc-http-admin - ${esc.version} - - org.apache.httpcomponents httpasyncclient diff --git a/quarkus/command/README.md b/quarkus/command/README.md index f8eef49..f16ba30 100644 --- a/quarkus/command/README.md +++ b/quarkus/command/README.md @@ -29,7 +29,7 @@ Make sure you installed everything as described [here](../../../../). ``` 4. Run the microservice ``` - ./target/cqrs4j-quarkus-example-command-0.3.0-runner \ + ./target/cqrs4j-quarkus-example-command-0.4.0-SNAPSHOT-runner \ -Djava.library.path=$GRAALVM_HOME/jre/lib/amd64 \ -Djavax.net.ssl.trustStore=$GRAALVM_HOME/jre/lib/security/cacerts ``` diff --git a/quarkus/command/pom.xml b/quarkus/command/pom.xml index 2c7a756..2415c6e 100644 --- a/quarkus/command/pom.xml +++ b/quarkus/command/pom.xml @@ -9,7 +9,7 @@ org.fuin.cqrs4j.example.quarkus cqrs4j-quarkus-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -57,13 +57,13 @@ org.fuin.cqrs4j.example cqrs4j-example-aggregates - 0.3.0 + 0.4.0-SNAPSHOT org.fuin.cqrs4j.example.quarkus cqrs4j-quarkus-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -94,6 +94,12 @@ test + + org.assertj + assertj-core + test + + io.rest-assured rest-assured diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateAlreadyExistsExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateAlreadyExistsExceptionMapper.java index 89d38b0..79131ce 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateAlreadyExistsExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateAlreadyExistsExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException; import org.slf4j.Logger; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateDeletedExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateDeletedExceptionMapper.java index ac987e0..c24cb16 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateDeletedExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateDeletedExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.ddd4j.ddd.AggregateDeletedException; import org.slf4j.Logger; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateNotFoundExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateNotFoundExceptionMapper.java index 5335cf0..d6862ba 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateNotFoundExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateNotFoundExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.ddd4j.ddd.AggregateNotFoundException; import org.slf4j.Logger; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionConflictExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionConflictExceptionMapper.java index 7f2b8ae..ab39821 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionConflictExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionConflictExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.ddd4j.ddd.AggregateVersionConflictException; import org.slf4j.Logger; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionNotFoundExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionNotFoundExceptionMapper.java index 871e110..972d3a2 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionNotFoundExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/AggregateVersionNotFoundExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.ddd4j.ddd.AggregateVersionNotFoundException; import org.slf4j.Logger; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/CommandExecutionFailedExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/CommandExecutionFailedExceptionMapper.java index f53c245..01a4968 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/CommandExecutionFailedExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/CommandExecutionFailedExceptionMapper.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; import jakarta.ws.rs.core.Context; @@ -18,7 +6,6 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.CommandExecutionFailedException; import org.fuin.cqrs4j.SimpleResult; import org.fuin.objects4j.common.ExceptionShortIdentifable; diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/ConstraintViolationExceptionMapper.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/ConstraintViolationExceptionMapper.java index e6e04fd..cdb169c 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/ConstraintViolationExceptionMapper.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/ConstraintViolationExceptionMapper.java @@ -1,22 +1,6 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.fuin.objects4j.common.Nullable; +import jakarta.annotation.Nullable; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.ws.rs.core.Context; @@ -25,12 +9,15 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; - import org.fuin.cqrs4j.SimpleResult; import org.fuin.objects4j.common.Contract; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + /** * Maps the exceptions into a HTTP status. */ diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResource.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResource.java index a44db1a..df56f21 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResource.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResource.java @@ -1,41 +1,26 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.api; -import java.util.Optional; -import java.util.Set; - import jakarta.inject.Inject; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; - import org.fuin.cqrs4j.CommandExecutionFailedException; import org.fuin.cqrs4j.SimpleResult; import org.fuin.cqrs4j.example.aggregates.DuplicatePersonNameException; import org.fuin.cqrs4j.example.aggregates.Person; import org.fuin.cqrs4j.example.aggregates.PersonRepository; import org.fuin.cqrs4j.example.shared.CreatePersonCommand; -import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException; -import org.fuin.ddd4j.ddd.AggregateDeletedException; +import org.fuin.cqrs4j.example.shared.DeletePersonCommand; +import org.fuin.ddd4j.ddd.*; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; @Path("/persons") public class PersonResource { @@ -78,4 +63,35 @@ public Response create(final CreatePersonCommand cmd) } + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/{personId}") + public Response delete(@PathParam("personId") final UUID personId, final DeletePersonCommand cmd) + throws AggregateVersionConflictException, AggregateVersionNotFoundException, + AggregateDeletedException, AggregateNotFoundException { + + // Verify preconditions + final Set> violations = validator.validate(cmd); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + // Read last known entity version + final Person person = repo.read(cmd.getAggregateRootId(), cmd.getAggregateVersionInteger()); + + // Try to delete the aggregate + // Internally just sets a 'deleted' flag + person.delete(); + + // Write resulting events back to the repository + // DO NOT call "repo.delete(..)! If you would do, you would never see a "deleted" event... + // The repository "delete" really removes the stream and is more like a "purge". + repo.update(person); + + // Send OK response + return Response.ok(SimpleResult.ok()).build(); + + } + } diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/app/CmdApp.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/app/CmdApp.java new file mode 100644 index 0000000..8623001 --- /dev/null +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/app/CmdApp.java @@ -0,0 +1,21 @@ +package org.fuin.cqrs4j.example.quarkus.command.app; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; + +/** + * Represents the (custom) entry point, most likely used to the Quarkus application in the IDE. + */ +@QuarkusMain +public class CmdApp { + + /** + * Main method to start the app. + * + * @param args Arguments from the command line. + */ + public static void main(String[] args) { + Quarkus.run(args); + } + +} \ No newline at end of file diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/PersonRepositoryFactory.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/PersonRepositoryFactory.java index b4bd5ee..feade5c 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/PersonRepositoryFactory.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/PersonRepositoryFactory.java @@ -1,20 +1,9 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.domain; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.Produces; +import org.fuin.cqrs4j.example.aggregates.EventStorePersonRepository; import org.fuin.cqrs4j.example.aggregates.PersonRepository; import org.fuin.esc.esgrpc.IESGrpcEventStore; @@ -35,7 +24,7 @@ public class PersonRepositoryFactory { @Produces @Dependent public PersonRepository create(final IESGrpcEventStore eventStore) { - return new PersonRepository(eventStore); + return new EventStorePersonRepository(eventStore); } } diff --git a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/package-info.java b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/package-info.java index 040b9c5..d2a905c 100644 --- a/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/package-info.java +++ b/quarkus/command/src/main/java/org/fuin/cqrs4j/example/quarkus/command/domain/package-info.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.command.domain; /** diff --git a/quarkus/command/src/test/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResourceIT.java b/quarkus/command/src/test/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResourceIT.java index 55be502..a207e73 100644 --- a/quarkus/command/src/test/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResourceIT.java +++ b/quarkus/command/src/test/java/org/fuin/cqrs4j/example/quarkus/command/api/PersonResourceIT.java @@ -6,10 +6,7 @@ import jakarta.json.bind.Jsonb; import org.fuin.cqrs4j.ResultType; import org.fuin.cqrs4j.SimpleResult; -import org.fuin.cqrs4j.example.shared.CreatePersonCommand; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.cqrs4j.example.shared.*; import org.fuin.esc.api.CommonEvent; import org.fuin.esc.api.SimpleStreamId; import org.fuin.esc.api.StreamEventsSlice; @@ -17,13 +14,11 @@ import org.fuin.esc.esgrpc.IESGrpcEventStore; import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.List; import java.util.UUID; import static io.restassured.RestAssured.given; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.assertj.core.api.Assertions.assertThat; @QuarkusTest class PersonResourceIT { @@ -40,23 +35,82 @@ void testCreate() { // PREPARE final PersonId personId = new PersonId(UUID.randomUUID()); final PersonName personName = new PersonName("Peter Parker"); - final CreatePersonCommand cmd = new CreatePersonCommand(personId, personName); + final CreatePersonCommand cmd = new CreatePersonCommand.Builder().id(personId).name(personName).build(); final String json = jsonb.toJson(cmd); // TEST & VERIFY - final SimpleResult result = given().accept(ContentType.JSON).contentType(ContentType.JSON).body(json).when().post("/persons/create") - .then().statusCode(200).extract().as(SimpleResult.class); - assertThat(result.getType(), is(equalTo(ResultType.OK))); + final SimpleResult result = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(json) + .when() + .post("/persons/create") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(result.getType()).isEqualTo(ResultType.OK); final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 0, 1); final List events = slice.getEvents(); - assertThat(Arrays.asList(events), is(not(empty()))); + assertThat(events).isNotEmpty(); final CommonEvent ce = events.get(0); - assertThat(ce.getDataType(), is(equalTo(new TypeName(PersonCreatedEvent.TYPE.asBaseType())))); + assertThat(ce.getDataType()).isEqualTo(new TypeName(PersonCreatedEvent.TYPE.asBaseType())); final PersonCreatedEvent event = (PersonCreatedEvent) ce.getData(); - assertThat(event.getEntityId(), is(equalTo(personId))); - assertThat(event.getName(), is(equalTo(personName))); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(0); + assertThat(event.getName()).isEqualTo(personName); + + } + + @Test + void testDelete() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Delete Me"); + final CreatePersonCommand createCmd = new CreatePersonCommand.Builder().id(personId).name(personName).build(); + final String jsonCreateCmd = jsonb.toJson(createCmd); + final SimpleResult createResult = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(jsonCreateCmd) + .when() + .post("/persons/create") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(createResult.getType()).isEqualTo(ResultType.OK); + + final DeletePersonCommand deleteCmd = new DeletePersonCommand.Builder().id(personId).name(personName).version(0).build(); + final String jsonDeleteCmd = jsonb.toJson(deleteCmd); + + // TEST & VERIFY + final SimpleResult deleteResult = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(jsonDeleteCmd) + .when() + .pathParam("personId", personId.asString()) + .delete("/persons/{personId}") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(deleteResult.getType()).isEqualTo(ResultType.OK); + + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 1, 1); + final List events = slice.getEvents(); + assertThat(events).isNotEmpty(); + final CommonEvent ce = events.get(0); + assertThat(ce.getDataType()).isEqualTo(new TypeName(PersonDeletedEvent.TYPE.asBaseType())); + final PersonDeletedEvent event = (PersonDeletedEvent) ce.getData(); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(1); + assertThat(event.getName()).isEqualTo(personName); } diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 0539d1f..ffd4035 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -7,7 +7,7 @@ org.fuin.cqrs4j.example cqrs4j-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml diff --git a/quarkus/query/README.md b/quarkus/query/README.md index 7266960..3e25c9d 100644 --- a/quarkus/query/README.md +++ b/quarkus/query/README.md @@ -35,7 +35,7 @@ In case you want to run the integration test inside your IDE (Eclipse or other), ``` 4. Run the microservice ``` - ./target/cqrs4j-quarkus-example-query-0.3.0-runner \ + ./target/cqrs4j-quarkus-example-query-0.4.0-SNAPSHOT-runner \ -Djava.library.path=$GRAALVM_HOME/jre/lib/amd64 \ -Djavax.net.ssl.trustStore=$GRAALVM_HOME/jre/lib/security/cacerts ``` diff --git a/quarkus/query/pom.xml b/quarkus/query/pom.xml index 39788e8..857574b 100644 --- a/quarkus/query/pom.xml +++ b/quarkus/query/pom.xml @@ -9,7 +9,7 @@ org.fuin.cqrs4j.example.quarkus cqrs4j-quarkus-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -57,13 +57,13 @@ org.fuin.cqrs4j.example cqrs4j-example-aggregates - 0.3.0 + 0.4.0-SNAPSHOT org.fuin.cqrs4j.example.quarkus cqrs4j-quarkus-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -71,11 +71,6 @@ esc-esgrpc - - org.fuin.esc - esc-http-admin - - com.sun.mail jakarta.mail @@ -95,6 +90,12 @@ test + + org.assertj + assertj-core + test + + org.awaitility awaitility diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryApp.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryApp.java new file mode 100644 index 0000000..9990efe --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryApp.java @@ -0,0 +1,21 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; + +/** + * Represents the (custom) entry point, most likely used to the Quarkus application in the IDE. + */ +@QuarkusMain +public class QryApp { + + /** + * Main method to start the app. + * + * @param args Arguments from the command line. + */ + public static void main(String[] args) { + Quarkus.run(args); + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java index ddf6ca4..a2d0ed9 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.app; /** diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java index de78077..9bedf45 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java @@ -1,23 +1,10 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.app; +import io.quarkus.scheduler.Scheduled; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Event; import jakarta.inject.Inject; -import io.quarkus.scheduler.Scheduled; - @ApplicationScoped public class QryScheduler { diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPosition.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPosition.java index 1ea1d21..e773711 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPosition.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPosition.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.common; import jakarta.persistence.Column; @@ -17,7 +5,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; - import org.fuin.esc.api.SimpleStreamId; import org.fuin.esc.api.StreamId; import org.fuin.objects4j.common.Contract; diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPositionRepository.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPositionRepository.java index 332710c..c47bff1 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPositionRepository.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QryProjectionPositionRepository.java @@ -1,22 +1,9 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.common; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.ProjectionService; import org.fuin.esc.api.StreamId; import org.fuin.objects4j.common.Contract; diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QuarkusViewManager.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QuarkusViewManager.java new file mode 100644 index 0000000..1b05498 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/common/QuarkusViewManager.java @@ -0,0 +1,186 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.common; + +import io.quarkus.arc.All; +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.runtime.Shutdown; +import io.quarkus.runtime.Startup; +import io.quarkus.scheduler.Scheduler; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.cqrs4j.example.shared.SharedUtils; +import org.fuin.cqrs4j.example.shared.View; +import org.fuin.ddd4j.ddd.Event; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.api.*; +import org.fuin.esc.esgrpc.IESGrpcEventStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + +import static org.fuin.cqrs4j.Cqrs4JUtils.tryLocked; + +/** + * Creates scheduler update jobs for all classes implementing the {@link View} interface. + * Avoids boilerplate code: Instead of having a separated "Projector", "EventDispatcher" + * and a "ChunkHandler" class for each view, there is only one simplified "View" class now. + */ +@ApplicationScoped +public class QuarkusViewManager { + + private static final Logger LOG = LoggerFactory.getLogger(QuarkusViewManager.class); + + @Inject + Scheduler scheduler; + + @Inject + @All + List rawViews; + + @Inject + IESGrpcEventStore eventstore; + + @Inject + ProjectionAdminEventStore admin; + + @Inject + ProjectionService projectionService; + + private List views; + + @Startup + void createViews() { + LOG.info("Create views..."); + views = rawViews.stream().map(ViewExt::new).toList(); + for (final ViewExt view : views) { + LOG.info("Create: {}", view.getName()); + scheduler.newJob(view.getName()) + .setCron(view.getCron()) + .setTask(executionContext -> updateView(view)) + .schedule(); + } + } + + @Shutdown + void shutdownViews() { + LOG.info("Shutdown views..."); + for (final ViewExt view : views) { + LOG.info("Shutdown: {}", view.getName()); + scheduler.unscheduleJob(view.getName()); + } + } + + private void updateView(final ViewExt view) { + tryLocked(view.getLock(), () -> { + new Thread(() -> { + try { + QuarkusTransaction.requiringNew() + .timeout(10) + .call(() -> { + readStreamEvents(view); + return 0; + }); + } catch (final RuntimeException ex) { + LOG.error("Error reading events from stream", ex); + } + } + ).start(); + }); + } + + private void readStreamEvents(final ViewExt view) { + + // Create an event store projection if it does not exist. + if (!admin.projectionExists(view.getProjectionStreamId())) { + final List typeNames = asTypeNames(view.getEventTypes()); + LOG.info("Create projection '{}' with events: {}", view.getProjectionStreamId(), typeNames); + admin.createProjection(view.getProjectionStreamId(), true, typeNames); + } + + // Read and dispatch events + final Long nextEventNumber = projectionService.readProjectionPosition(view.getProjectionStreamId()); + eventstore.readAllEventsForward(view.getProjectionStreamId(), nextEventNumber, view.getChunkSize(), + currentSlice -> handleChunk(view, currentSlice)); + + } + + private List asTypeNames(Set eventTypes) { + return eventTypes.stream().map(eventType -> new TypeName((eventType.asString()))).toList(); + } + + private void handleChunk(final ViewExt view, final StreamEventsSlice currentSlice) { + QuarkusTransaction.requiringNew() + .timeout(10) + .call(() -> { + LOG.debug("Handle chunk: {}", currentSlice); + view.handleEvents(asEvents(currentSlice.getEvents())); + projectionService.updateProjectionPosition(view.getProjectionStreamId(), currentSlice.getNextEventNumber()); + return 0; + }); + } + + private List asEvents(List events) { + return events.stream().map(event -> (Event) event.getData()).toList(); + } + + /** + * Extends the view with some necessary values used only by this class. + */ + private static class ViewExt implements View { + + private final View delegate; + + private final ProjectionStreamId projectionStreamId; + + private final Semaphore lock; + + public ViewExt(final View delegate) { + this.delegate = delegate; + + final Set eventTypes = delegate.getEventTypes(); + final String name = delegate.getName() + "-" + SharedUtils.calculateChecksum(eventTypes); + projectionStreamId = new ProjectionStreamId(name); + + this.lock = new Semaphore(1); + + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getCron() { + return delegate.getCron(); + } + + @Override + public Set getEventTypes() { + return delegate.getEventTypes(); + } + + @Override + public int getChunkSize() { + return delegate.getChunkSize(); + } + + @Override + public void handleEvents(List events) { + delegate.handleEvents(events); + } + + public ProjectionStreamId getProjectionStreamId() { + return projectionStreamId; + } + + public Semaphore getLock() { + return lock; + } + + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/package-info.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/package-info.java index 52a73e0..62c02cf 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/package-info.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/package-info.java @@ -1,18 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views; /** - * Contains the views used in this query application. A view never uses code of another view, means all views are completely independent of - * each other. As an exception, the 'commons' package has some small classes that are not view specific. + * Contains the views used in this query application. A view never uses code of another view, + * means all views are completely independent of each other. As an exception, the 'commons' package + * has some small classes that are not view specific. */ diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonCreatedEventHandler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonCreatedEventHandler.java index 293234b..c984603 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonCreatedEventHandler.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonCreatedEventHandler.java @@ -1,21 +1,8 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.personlist; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; - import org.fuin.cqrs4j.EventHandler; import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; import org.fuin.cqrs4j.example.shared.PersonId; diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonDeletedEventHandler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonDeletedEventHandler.java new file mode 100644 index 0000000..93e6dc3 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonDeletedEventHandler.java @@ -0,0 +1,39 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.personlist; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.EventHandler; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.ddd4j.ddd.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the {@link PersonDeletedEvent}. + */ +@ApplicationScoped +public class PersonDeletedEventHandler implements EventHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PersonDeletedEventHandler.class); + + @Inject + EntityManager em; + + @Override + public EventType getEventType() { + return PersonDeletedEvent.TYPE; + } + + @Override + public void handle(final PersonDeletedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final PersonId personId = event.getEntityId(); + final PersonListEntry entity = em.find(PersonListEntry.class, personId.asString()); + if (entity != null) { + em.remove(entity); + } + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEntry.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEntry.java index c6a220c..391aa52 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEntry.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEntry.java @@ -1,34 +1,16 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.personlist; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; - import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.cqrs4j.example.shared.PersonName; import org.fuin.objects4j.common.Contract; -import io.quarkus.runtime.annotations.RegisterForReflection; - /** * Represents a person that will be stored in the database. */ diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventChunkHandler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventChunkHandler.java index 8174e7b..bbabdf9 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventChunkHandler.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventChunkHandler.java @@ -1,23 +1,8 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.personlist; -import java.util.Set; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; - import org.fuin.cqrs4j.ProjectionService; import org.fuin.cqrs4j.example.shared.SharedUtils; import org.fuin.ddd4j.ddd.EventType; @@ -26,6 +11,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Set; + @ApplicationScoped @Transactional public class PersonListEventChunkHandler { diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventDispatcher.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventDispatcher.java index e0bb231..1416ea3 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventDispatcher.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListEventDispatcher.java @@ -1,29 +1,16 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.personlist; -import java.util.List; -import java.util.Set; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.EventDispatcher; import org.fuin.cqrs4j.SimpleEventDispatcher; import org.fuin.ddd4j.ddd.Event; import org.fuin.ddd4j.ddd.EventType; import org.fuin.esc.api.CommonEvent; +import java.util.List; +import java.util.Set; + /** * Dispatches events for the person list view. */ @@ -37,10 +24,13 @@ public class PersonListEventDispatcher implements EventDispatcher { * * @param createdHandler * PersonCreatedEventHandler. + * @param deletedHandler + * PersonDeletedEventHandler. */ - public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler) { + public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler, + final PersonDeletedEventHandler deletedHandler) { super(); - this.delegate = new SimpleEventDispatcher(createdHandler); + this.delegate = new SimpleEventDispatcher(createdHandler, deletedHandler); } @Override diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListProjector.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListProjector.java index 59d067f..07ea60d 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListProjector.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListProjector.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.query.views.personlist; import jakarta.enterprise.context.ApplicationScoped; diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResource.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResource.java similarity index 56% rename from quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResource.java rename to quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResource.java index 98da44b..864a357 100644 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResource.java +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResource.java @@ -1,18 +1,4 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.quarkus.query.api; - -import java.util.List; +package org.fuin.cqrs4j.example.quarkus.query.views.personlist; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; @@ -22,12 +8,15 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; - -import org.fuin.cqrs4j.example.quarkus.query.views.personlist.PersonListEntry; import org.fuin.objects4j.vo.UUIDStrValidator; +import java.util.List; + +/** + * REST resource providing the persons. + */ @Path("/persons") -public class QryPersonResource { +public class PersonListResource { @Inject EntityManager em; diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/package-info.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/package-info.java deleted file mode 100644 index ec84117..0000000 --- a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.quarkus.query.views.personlist; - -/** - * Classes building the 'person list' view. - */ diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/EntityType.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/EntityType.java new file mode 100644 index 0000000..661f5ae --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/EntityType.java @@ -0,0 +1,47 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.statistic; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotEmpty; + +import java.util.Objects; + +/** + * Defines the name of a type of entity. + * + * @param name Unique name. Will be converted to lowercase internally - Minimum 1 character, maximum 30 characters. + */ +public record EntityType(String name) { + + /** + * Maximum allowed length of the name. + */ + public static final int MAX_LENGTH = 30; + + public EntityType(@NotEmpty @Max(MAX_LENGTH) String name) { + this.name = Objects.requireNonNull(name, "name==null").toLowerCase(); + if (name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty"); + } + if (name.length() > MAX_LENGTH) { + throw new IllegalArgumentException("Name has a length of " + name.length() + + ", but max allowed is " + MAX_LENGTH + " characters: '" + name + "'"); + } + } + + @Override + public String toString() { + return name; + } + + /** + * Determines if the name is valid. + * + * @param name Name to be verified. + * @return {@literal true} if the given name can be converted into an instance of this class. + */ + public static boolean isValid(String name) { + return name != null && !name.isEmpty() && name.length() <= MAX_LENGTH; + } + + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/QryStatisticResource.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/QryStatisticResource.java new file mode 100644 index 0000000..1e286ca --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/QryStatisticResource.java @@ -0,0 +1,49 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.statistic; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.fuin.cqrs4j.example.quarkus.query.views.personlist.PersonListEntry; +import org.fuin.cqrs4j.example.quarkus.query.views.statistic.EntityType; +import org.fuin.cqrs4j.example.quarkus.query.views.statistic.Statistic; +import org.fuin.cqrs4j.example.quarkus.query.views.statistic.StatisticEntity; +import org.fuin.objects4j.vo.UUIDStrValidator; + +import java.util.List; + +/** + * REST resource providing the statistics. + */ +@Path("/statistics") +public class QryStatisticResource { + + @Inject + EntityManager em; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getAll() { + final List statistics = em.createNamedQuery(StatisticEntity.FIND_ALL, Statistic.class).getResultList(); + return Response.ok(statistics).build(); + } + + @GET + @Path("{name}") + @Produces(MediaType.APPLICATION_JSON) + public Response getByName(@PathParam("name") String name) { + if (!EntityType.isValid(name)) { + return Response.status(Response.Status.BAD_REQUEST).entity("Invalid entity type name").build(); + } + final StatisticEntity entity = em.find(StatisticEntity.class, name); + if (entity == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(entity.toDto()).build(); + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/Statistic.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/Statistic.java new file mode 100644 index 0000000..d0ccdd1 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/Statistic.java @@ -0,0 +1,19 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.statistic; + +import jakarta.json.bind.annotation.JsonbCreator; + +/** + * DTO class sent back to the client. We could also serialize the entity instead. + * In case you want to provide some Java library for the client, this class can be + * moved to the API module without making JPA entity details public. + * + * @param type Unique type name. + * @param count Number of instances the type currently has. + */ +public record Statistic(String type, int count) { + + @JsonbCreator + public Statistic { + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticEntity.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticEntity.java new file mode 100644 index 0000000..abcad86 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticEntity.java @@ -0,0 +1,86 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.statistic; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import org.fuin.cqrs4j.example.quarkus.query.views.personlist.PersonListEntry; + +import java.util.Objects; + +/** + * Represents a statistic record that will be stored in the database. + */ +@Entity +@Table(name = "QUARKUS_STATISTIC") +@NamedQuery(name = StatisticEntity.FIND_ALL, + query = "SELECT new org.fuin.cqrs4j.example.quarkus.query.views.statistic.Statistic(s.type, s.count) FROM StatisticEntity s") +public class StatisticEntity { + + public static final String FIND_ALL = "StatisticEntity.findAll"; + + @Id + @Column(name = "TYPE", nullable = false, length = EntityType.MAX_LENGTH, updatable = false) + @NotNull + private String type; + + @Column(name = "COUNT", updatable = true) + private int count; + + /** + * JPA default constructor. + */ + protected StatisticEntity() { + } + + /** + * Constrcutor with a given type that sets the number of instances to one. + * + * @param type Unique type ID. + */ + public StatisticEntity(@NotNull EntityType type) { + this.type = Objects.requireNonNull(type, "type==null").name(); + this.count = 1; + } + + public Statistic toDto() { + return new Statistic(type, count); + } + + /** + * Increases the number of entries for the type by one. + */ + public void inc() { + this.count++; + } + + /** + * Decreases the number of entries for the type by one. + */ + public void dec() { + this.count--; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StatisticEntity that = (StatisticEntity) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + return "StatisticEntity{" + + "type='" + type + '\'' + + ", count=" + count + + '}'; + } +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticView.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticView.java new file mode 100644 index 0000000..2735b6e --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/views/statistic/StatisticView.java @@ -0,0 +1,77 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.statistic; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.View; +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.ddd4j.ddd.Event; +import org.fuin.ddd4j.ddd.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; + +/** + * Handles the events required to maintain the statistic database view. + */ +@ApplicationScoped +public class StatisticView implements View { + + private static final Logger LOG = LoggerFactory.getLogger(StatisticView.class); + + private static final EntityType PERSON = new EntityType("person"); + + @Inject + EntityManager em; + + @Override + public String getName() { + return "quarkus-qry-statistic"; + } + + @Override + public String getCron() { + // Every second + return "* * * * * ? *"; + } + + @Override + public Set getEventTypes() { + return Set.of(PersonCreatedEvent.TYPE, PersonDeletedEvent.TYPE); + } + + @Override + public void handleEvents(final List events) { + for (final Event event : events) { + if (event instanceof PersonCreatedEvent ev) { + handlePersonCreatedEvent(ev); + } else if (event instanceof PersonDeletedEvent ev) { + handlePersonDeletedEvent(ev); + } else { + throw new RuntimeException("Cannot handle event: " + event); + } + } + } + + private void handlePersonCreatedEvent(final PersonCreatedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final StatisticEntity entity = em.find(StatisticEntity.class, PERSON.name()); + if (entity == null) { + em.persist(new StatisticEntity(PERSON)); + } else { + entity.inc(); + } + } + + private void handlePersonDeletedEvent(final PersonDeletedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final StatisticEntity entity = em.find(StatisticEntity.class, PERSON.name()); + if (entity != null) { + entity.dec(); + } + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/resources/META-INF/resources/index.html b/quarkus/query/src/main/resources/META-INF/resources/index.html index fe2f63e..944ff0e 100644 --- a/quarkus/query/src/main/resources/META-INF/resources/index.html +++ b/quarkus/query/src/main/resources/META-INF/resources/index.html @@ -9,8 +9,14 @@

CQRS Quarkus Example Query Module

This page is served by Quarkus. The source is in src/main/resources/META-INF/resources/index.html.

Try + diff --git a/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResourceIT.java b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResourceIT.java deleted file mode 100644 index 71e0642..0000000 --- a/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/api/QryPersonResourceIT.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.fuin.cqrs4j.example.quarkus.query.api; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.enterprise.context.control.ActivateRequestContext; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.fuin.cqrs4j.example.quarkus.query.views.personlist.PersonListEntry; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; -import org.fuin.esc.api.*; -import org.fuin.esc.esgrpc.IESGrpcEventStore; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.UUID; - -import static io.restassured.RestAssured.given; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -@QuarkusTest -class QryPersonResourceIT { - - @Inject - IESGrpcEventStore eventStore; - - @Inject - EntityManager em; - - @Test - void testGetByIdNotFound() { - given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404); - } - - @ActivateRequestContext - public boolean findPerson(final PersonId personId) { - return em.find(PersonListEntry.class, personId.asString()) != null; - } - - @Test - void testGetByIdOK() { - - // PREPARE - final PersonId personId = new PersonId(UUID.randomUUID()); - final PersonName personName = new PersonName("Peter Parker"); - final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); - final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName); - final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()), - new TypeName(event.getEventType().asBaseType()), event); - eventStore.appendToStream(personStreamId, ce); - - await().atMost(5, SECONDS).until(() -> findPerson(personId)); - - // TEST & VERIFY - - final PersonListEntry person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then().statusCode(200) - .extract().as(PersonListEntry.class); - assertThat(person.getId(), is(equalTo(personId))); - assertThat(person.getName(), is(equalTo(personName))); - - final PersonListEntry[] persons = given().when().get("/persons").then().statusCode(200).extract().as(PersonListEntry[].class); - - assertThat(Arrays.asList(persons), is(not(empty()))); - final PersonListEntry person0 = persons[0]; - assertThat(person0.getId(), is(equalTo(personId))); - assertThat(person0.getName(), is(equalTo(personName))); - - } - -} diff --git a/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResourceIT.java b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResourceIT.java new file mode 100644 index 0000000..689c05e --- /dev/null +++ b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/views/personlist/PersonListResourceIT.java @@ -0,0 +1,124 @@ +package org.fuin.cqrs4j.example.quarkus.query.views.personlist; + +import io.quarkus.test.junit.QuarkusTest; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.example.quarkus.query.views.personlist.PersonListEntry; +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.esc.api.*; +import org.fuin.esc.esgrpc.IESGrpcEventStore; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@QuarkusTest +class PersonListResourceIT { + + @Inject + IESGrpcEventStore eventStore; + + @Inject + EntityManager em; + + @Test + void testGetByIdNotFound() { + given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404); + } + + @ActivateRequestContext + public boolean findPerson(final PersonId personId) { + return em.find(PersonListEntry.class, personId.asString()) != null; + } + + @Test + void testGetByIdOK() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Peter Parker"); + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + final PersonCreatedEvent event = new PersonCreatedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()), + new TypeName(event.getEventType().asBaseType()), event); + eventStore.appendToStream(personStreamId, ce); + + await().atMost(5, SECONDS).until(() -> findPerson(personId)); + + // TEST & VERIFY + + final PersonListEntry person = + given() + .pathParam("id", personId.asString()) + .when() + .get("/persons/{id}") + .then() + .statusCode(200) + .extract().as(PersonListEntry.class); + assertThat(person.getId()).isEqualTo(personId); + assertThat(person.getName()).isEqualTo(personName); + + final PersonListEntry[] persons = + given() + .when() + .get("/persons") + .then() + .statusCode(200) + .extract().as(PersonListEntry[].class); + + assertThat(persons).isNotEmpty(); + final PersonListEntry person0 = persons[0]; + assertThat(person0.getId()).isEqualTo(personId); + assertThat(person0.getName()).isEqualTo(personName); + + } + + @Test + void testDelete() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Delete Me Man"); + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + + final PersonCreatedEvent createdEvent = new PersonCreatedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent commonCreatedEvent = new SimpleCommonEvent(new EventId(createdEvent.getEventId().asBaseType()), + new TypeName(createdEvent.getEventType().asBaseType()), createdEvent); + eventStore.appendToStream(personStreamId, commonCreatedEvent); + await().atMost(5, SECONDS).until(() -> findPerson(personId)); + + final PersonDeletedEvent deletedEvent = new PersonDeletedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent commonDeletedEvent = new SimpleCommonEvent(new EventId(deletedEvent.getEventId().asBaseType()), + new TypeName(deletedEvent.getEventType().asBaseType()), deletedEvent); + eventStore.appendToStream(personStreamId, commonDeletedEvent); + await().atMost(5, SECONDS).until(() -> !findPerson(personId)); + + // TEST & VERIFY + given() + .pathParam("id", personId.asString()) + .when() + .get("/persons/{id}") + .then() + .statusCode(404); + + final PersonListEntry[] persons = + given() + .when() + .get("/persons") + .then() + .statusCode(200) + .extract().as(PersonListEntry[].class); + + assertThat(persons).doesNotContain(new PersonListEntry(personId, personName)); + + } + +} diff --git a/quarkus/shared/pom.xml b/quarkus/shared/pom.xml index 8d829cf..87abef4 100644 --- a/quarkus/shared/pom.xml +++ b/quarkus/shared/pom.xml @@ -7,7 +7,7 @@ org.fuin.cqrs4j.example.quarkus cqrs4j-quarkus-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -22,7 +22,7 @@ org.fuin.cqrs4j.example cqrs4j-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -45,11 +45,6 @@ esc-esgrpc - - org.fuin.esc - esc-http-admin - - org.slf4j slf4j-api diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/Config.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/Config.java index d6804fd..02ff6c3 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/Config.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/Config.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - *

- * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - *

- * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - *

- * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.shared; import jakarta.enterprise.context.ApplicationScoped; diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/EventStoreFactory.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/EventStoreFactory.java index 0d8d3b4..ec2ac4a 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/EventStoreFactory.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/EventStoreFactory.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - *

- * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - *

- * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - *

- * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.shared; import com.eventstore.dbclient.EventStoreDBClient; diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/JsonbFactory.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/JsonbFactory.java index 596ec37..69200d5 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/JsonbFactory.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/JsonbFactory.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.shared; import jakarta.enterprise.context.ApplicationScoped; @@ -17,7 +5,6 @@ import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; - import org.eclipse.yasson.FieldAccessStrategy; import org.fuin.cqrs4j.example.shared.SharedUtils; diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/ProjectionAdminEventStoreFactory.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/ProjectionAdminEventStoreFactory.java index 3c0969c..e088e97 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/ProjectionAdminEventStoreFactory.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/ProjectionAdminEventStoreFactory.java @@ -1,14 +1,12 @@ package org.fuin.cqrs4j.example.quarkus.shared; +import com.eventstore.dbclient.EventStoreDBClientSettings; +import com.eventstore.dbclient.EventStoreDBProjectionManagementClient; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; -import org.fuin.esc.admin.HttpProjectionAdminEventStore; import org.fuin.esc.api.ProjectionAdminEventStore; - -import java.net.MalformedURLException; -import java.net.URL; -import java.net.http.HttpClient; +import org.fuin.esc.esgrpc.GrpcProjectionAdminEventStore; /** * CDI factory that creates a {@link ProjectionAdminEventStore} instance. @@ -18,15 +16,16 @@ public class ProjectionAdminEventStoreFactory { @Produces @ApplicationScoped - public ProjectionAdminEventStore getProjectionAdminEventStore(final Config config, final HttpClient httpClient) { - final String url = config.getEventStoreProtocol() + "://" + config.getEventStoreHost() + ":" + config.getEventStoreHttpPort(); - try { - final ProjectionAdminEventStore es = new HttpProjectionAdminEventStore(httpClient, new URL(url)); - es.open(); - return es; - } catch (final MalformedURLException ex) { - throw new RuntimeException("Failed to create URL: " + url, ex); - } + public ProjectionAdminEventStore getProjectionAdminEventStore(final Config config) { + + final EventStoreDBClientSettings settings = EventStoreDBClientSettings.builder() + .addHost(config.getEventStoreHost(), config.getEventStoreHttpPort()) + .defaultCredentials(config.getEventStoreUser(), config.getEventStorePassword()) + .tls(false) + .buildConnectionSettings(); + final EventStoreDBProjectionManagementClient client = EventStoreDBProjectionManagementClient.create(settings); + return new GrpcProjectionAdminEventStore(client).open(); + } /** diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/SerDeserializerRegistryFactory.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/SerDeserializerRegistryFactory.java index 2e10eeb..47b5fa5 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/SerDeserializerRegistryFactory.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/SerDeserializerRegistryFactory.java @@ -1,20 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.shared; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; - import org.fuin.cqrs4j.example.shared.SharedUtils; import org.fuin.esc.spi.JsonbDeSerializer; import org.fuin.esc.spi.SerDeserializerRegistry; diff --git a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/package-info.java b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/package-info.java index 3c3c81c..ac7fa1d 100644 --- a/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/package-info.java +++ b/quarkus/shared/src/main/java/org/fuin/cqrs4j/example/quarkus/shared/package-info.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.quarkus.shared; /** diff --git a/quarkus/shared/src/test/resources/events/PersonCreatedEvent.json b/quarkus/shared/src/test/resources/events/PersonCreatedEvent.json deleted file mode 100644 index fd283ba..0000000 --- a/quarkus/shared/src/test/resources/events/PersonCreatedEvent.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "event-id": "a7b88543-ce32-40eb-a3fe-f49aec39b570", - "event-timestamp": "2019-11-02T09:56:40.669Z[Etc/UTC]", - "entity-id-path": "PERSON f645969a-402d-41a9-882b-d2d8000d0f43", - "name": "Peter Parker" -} diff --git a/shared/pom.xml b/shared/pom.xml index 2d48e31..8d82233 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -7,7 +7,7 @@ org.fuin.cqrs4j.example cqrs4j-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/CreatePersonCommand.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/CreatePersonCommand.java index 5c342da..626e253 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/CreatePersonCommand.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/CreatePersonCommand.java @@ -1,30 +1,11 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; import jakarta.json.bind.annotation.JsonbProperty; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.AbstractAggregateCommand; import org.fuin.ddd4j.ddd.DomainEventExpectedEntityIdPath; import org.fuin.ddd4j.ddd.EventType; import org.fuin.esc.spi.SerializedDataType; -import org.fuin.objects4j.common.Contract; import org.fuin.objects4j.common.Immutable; /** @@ -36,10 +17,14 @@ public final class CreatePersonCommand extends AbstractAggregateCommand { + + private CreatePersonCommand delegate; + + public Builder() { + super(new CreatePersonCommand()); + delegate = delegate(); + } + + public Builder id(PersonId personId) { + entityIdPath(personId); + return this; + } + + public Builder name(String name) { + delegate.name = new PersonName(name); + return this; + } + + public Builder name(PersonName name) { + delegate.name = name; + return this; + } + + public CreatePersonCommand build() { + ensureBuildableAbstractAggregateCommand(); + ensureNotNull("name", delegate.name); + + final CreatePersonCommand result = delegate; + delegate = new CreatePersonCommand(); + resetAbstractAggregateCommand(delegate); + return result; + } + + } + } \ No newline at end of file diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/DeletePersonCommand.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/DeletePersonCommand.java new file mode 100644 index 0000000..8d778a5 --- /dev/null +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/DeletePersonCommand.java @@ -0,0 +1,106 @@ +package org.fuin.cqrs4j.example.shared; + +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.validation.constraints.NotNull; +import org.fuin.cqrs4j.AbstractAggregateCommand; +import org.fuin.ddd4j.ddd.AggregateVersion; +import org.fuin.ddd4j.ddd.DomainEventExpectedEntityIdPath; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.spi.SerializedDataType; +import org.fuin.objects4j.common.Immutable; + +/** + * A new person should be deleted from the system. + */ +@Immutable +@DomainEventExpectedEntityIdPath(PersonId.class) +public final class DeletePersonCommand extends AbstractAggregateCommand { + + private static final long serialVersionUID = 1000L; + + /** + * Never changing unique event type name. + */ + public static final EventType TYPE = new EventType("DeletePersonCommand"); + + /** + * Unique name used for marshalling/unmarshalling the event. + */ + public static final SerializedDataType SER_TYPE = new SerializedDataType(DeletePersonCommand.TYPE.asBaseType()); + + @NotNull + @JsonbProperty("name") + private PersonName name; + + /** + * Protected default constructor for deserialization. + */ + protected DeletePersonCommand() { + super(); + } + + @Override + public final EventType getEventType() { + return DeletePersonCommand.TYPE; + } + + /** + * Returns: Name of a person. + * + * @return Current value. + */ + @NotNull + public final PersonName getName() { + return name; + } + + @Override + public final String toString() { + return "Delete person '" + name + "' with identifier '" + getAggregateRootId() + "'"; + } + + /** + * Builds an instance of the outer class. + */ + public static final class Builder extends AbstractAggregateCommand.Builder { + + private DeletePersonCommand delegate; + + public Builder() { + super(new DeletePersonCommand()); + delegate = delegate(); + } + + public DeletePersonCommand.Builder id(PersonId personId) { + entityIdPath(personId); + return this; + } + + public DeletePersonCommand.Builder name(String name) { + delegate.name = new PersonName(name); + return this; + } + + public DeletePersonCommand.Builder name(PersonName name) { + delegate.name = name; + return this; + } + + public DeletePersonCommand build() { + ensureBuildableAbstractAggregateCommand(); + ensureNotNull("name", delegate.name); + + final DeletePersonCommand result = delegate; + delegate = new DeletePersonCommand(); + resetAbstractAggregateCommand(delegate); + return result; + } + + public Builder version(int version) { + aggregateVersion(AggregateVersion.valueOf(version)); + return this; + } + + } + +} \ No newline at end of file diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonCreatedEvent.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonCreatedEvent.java index 01fca4c..70f4705 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonCreatedEvent.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonCreatedEvent.java @@ -1,30 +1,11 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; import jakarta.json.bind.annotation.JsonbProperty; import jakarta.validation.constraints.NotNull; - import org.fuin.ddd4j.ddd.AbstractDomainEvent; -import org.fuin.ddd4j.ddd.EntityIdPath; +import org.fuin.ddd4j.ddd.AggregateVersion; import org.fuin.ddd4j.ddd.EventType; import org.fuin.esc.spi.SerializedDataType; -import org.fuin.objects4j.common.Contract; import org.fuin.objects4j.common.Immutable; /** @@ -35,10 +16,14 @@ public final class PersonCreatedEvent extends AbstractDomainEvent { private static final long serialVersionUID = 1000L; - /** Never changing unique event type name. */ + /** + * Never changing unique event type name. + */ public static final EventType TYPE = new EventType("PersonCreatedEvent"); - /** Unique name used for marshalling/unmarshalling the event. */ + /** + * Unique name used for marshalling/unmarshalling the event. + */ public static final SerializedDataType SER_TYPE = new SerializedDataType(PersonCreatedEvent.TYPE.asBaseType()); @NotNull @@ -52,20 +37,6 @@ 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; @@ -86,4 +57,48 @@ public final String toString() { return "Person '" + name + "' (" + getEntityId() + ") was created [Event " + getEventId() + "]"; } + /** + * Builds an instance of the outer class. + */ + public static final class Builder extends AbstractDomainEvent.Builder { + + private PersonCreatedEvent delegate; + + public Builder() { + super(new PersonCreatedEvent()); + delegate = delegate(); + } + + public Builder id(PersonId personId) { + entityIdPath(personId); + return this; + } + + public Builder name(String name) { + delegate.name = new PersonName(name); + return this; + } + + public Builder name(PersonName name) { + delegate.name = name; + return this; + } + + public Builder version(int version) { + aggregateVersion(AggregateVersion.valueOf(version)); + return this; + } + + public PersonCreatedEvent build() { + ensureBuildableAbstractDomainEvent(); + ensureNotNull("name", delegate.name); + + final PersonCreatedEvent result = delegate; + delegate = new PersonCreatedEvent(); + resetAbstractDomainEvent(delegate); + return result; + } + + } + } diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonDeletedEvent.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonDeletedEvent.java new file mode 100644 index 0000000..3e07d23 --- /dev/null +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonDeletedEvent.java @@ -0,0 +1,106 @@ +package org.fuin.cqrs4j.example.shared; + +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.validation.constraints.NotNull; +import org.fuin.ddd4j.ddd.AbstractDomainEvent; +import org.fuin.ddd4j.ddd.AggregateVersion; +import org.fuin.ddd4j.ddd.EntityIdPath; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.spi.SerializedDataType; +import org.fuin.objects4j.common.Contract; +import org.fuin.objects4j.common.Immutable; + +/** + * A person was deleted from the system. + */ +@Immutable +public final class PersonDeletedEvent extends AbstractDomainEvent { + + private static final long serialVersionUID = 1000L; + + /** + * Never changing unique event type name. + */ + public static final EventType TYPE = new EventType("PersonDeletedEvent"); + + /** + * Unique name used for marshalling/unmarshalling the event. + */ + public static final SerializedDataType SER_TYPE = new SerializedDataType(PersonDeletedEvent.TYPE.asBaseType()); + + @NotNull + @JsonbProperty("name") + private PersonName name; + + /** + * Protected default constructor for deserialization. + */ + protected PersonDeletedEvent() { + super(); + } + + @Override + public final EventType getEventType() { + return PersonDeletedEvent.TYPE; + } + + /** + * Returns: Name of a person. + * + * @return Current value. + */ + @NotNull + public final PersonName getName() { + return name; + } + + @Override + public final String toString() { + return "Deleted person '" + name + "' (" + getEntityId() + ") [Event " + getEventId() + "]"; + } + + /** + * Builds an instance of the outer class. + */ + public static final class Builder extends AbstractDomainEvent.Builder { + + private PersonDeletedEvent delegate; + + public Builder() { + super(new PersonDeletedEvent()); + delegate = delegate(); + } + + public Builder id(PersonId personId) { + entityIdPath(personId); + return this; + } + + public Builder name(String name) { + delegate.name = new PersonName(name); + return this; + } + + public Builder name(PersonName name) { + delegate.name = name; + return this; + } + + public Builder version(int version) { + aggregateVersion(AggregateVersion.valueOf(version)); + return this; + } + + public PersonDeletedEvent build() { + ensureBuildableAbstractDomainEvent(); + ensureNotNull("name", delegate.name); + + final PersonDeletedEvent result = delegate; + delegate = new PersonDeletedEvent(); + resetAbstractDomainEvent(delegate); + return result; + } + + } + +} diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonId.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonId.java index df43039..fafcf8e 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonId.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonId.java @@ -1,27 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import java.util.UUID; - import jakarta.json.bind.adapter.JsonbAdapter; import jakarta.validation.constraints.NotNull; - import org.fuin.ddd4j.ddd.AggregateRootUuid; import org.fuin.ddd4j.ddd.EntityType; import org.fuin.ddd4j.ddd.StringBasedEntityType; @@ -31,6 +11,8 @@ import org.fuin.objects4j.ui.Tooltip; import org.fuin.objects4j.vo.ValueObjectConverter; +import java.util.UUID; + /** * Identifies uniquely a person. */ diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonName.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonName.java index b28b2e6..b59f6c5 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonName.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/PersonName.java @@ -1,35 +1,11 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -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 jakarta.json.bind.adapter.JsonbAdapter; import jakarta.validation.Constraint; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.Payload; import jakarta.validation.constraints.NotNull; - import org.fuin.objects4j.common.ConstraintViolationException; import org.fuin.objects4j.common.Immutable; import org.fuin.objects4j.ui.Label; @@ -38,6 +14,8 @@ import org.fuin.objects4j.vo.AbstractStringValueObject; import org.fuin.objects4j.vo.ValueObjectConverter; +import java.lang.annotation.*; + /** * Name of a person. */ diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedEntityIdFactory.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedEntityIdFactory.java index a2456d9..55e14b5 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedEntityIdFactory.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedEntityIdFactory.java @@ -1,29 +1,12 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; +import org.fuin.ddd4j.ddd.EntityId; +import org.fuin.ddd4j.ddd.EntityIdFactory; + import java.util.HashMap; import java.util.Map; import java.util.function.Function; -import org.fuin.ddd4j.ddd.EntityId; -import org.fuin.ddd4j.ddd.EntityIdFactory; - /** * Factory that creates entity identifier instances based on the type. */ diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedUtils.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedUtils.java index 2a0ee0e..9b83ef5 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedUtils.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/SharedUtils.java @@ -1,48 +1,16 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; +import jakarta.json.bind.adapter.JsonbAdapter; +import jakarta.json.bind.config.PropertyVisibilityStrategy; +import org.fuin.ddd4j.ddd.*; +import org.fuin.esc.spi.*; + import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.zip.Adler32; -import jakarta.json.bind.adapter.JsonbAdapter; -import jakarta.json.bind.config.PropertyVisibilityStrategy; - -import org.fuin.ddd4j.ddd.AggregateVersionConverter; -import org.fuin.ddd4j.ddd.EntityIdConverter; -import org.fuin.ddd4j.ddd.EntityIdPathConverter; -import org.fuin.ddd4j.ddd.EventIdConverter; -import org.fuin.ddd4j.ddd.EventType; -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. */ @@ -52,7 +20,9 @@ public final class SharedUtils { /** All types that will be written into and read from the event store. */ private static final TypeClass[] USER_DEFINED_TYPES = new TypeClass[] { - new TypeClass(PersonCreatedEvent.SER_TYPE, PersonCreatedEvent.class) }; + new TypeClass(PersonCreatedEvent.SER_TYPE, PersonCreatedEvent.class), + new TypeClass(PersonDeletedEvent.SER_TYPE, PersonDeletedEvent.class) + }; /** All JSON-B adapters from this module. */ public static final JsonbAdapter[] JSONB_ADAPTERS = new JsonbAdapter[] { new EventIdConverter(), diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/View.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/View.java new file mode 100644 index 0000000..c8cdcb3 --- /dev/null +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/View.java @@ -0,0 +1,56 @@ +package org.fuin.cqrs4j.example.shared; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.ddd4j.ddd.Event; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.api.ProjectionStreamId; +import org.fuin.esc.api.TypeName; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + +/** + * Defines a unit that projects events read from the event store into another representation. + * The view is updated regularly by using a scheduler. + */ +public interface View { + + /** + * Unique name of the view. + * + * @return Name that is unique in this program instance. + */ + String getName(); + + /** + * Returns the CRON expression defining how often the view should be updated. + * + * @return Spring Quartz CRON expression + */ + String getCron(); + + /** + * Returns the type of events the view is interested in. + * + * @return List of events. + */ + Set getEventTypes(); + + /** + * Number of events to read and handle in one transaction. + * + * @return Number of events (defaults to 100). + */ + default int getChunkSize() { + return 100; + } + + /** + * Events to handle by the view. + * + * @param events Events used to update the view. + */ + void handleEvents(List events); + +} diff --git a/shared/src/main/java/org/fuin/cqrs4j/example/shared/package-info.java b/shared/src/main/java/org/fuin/cqrs4j/example/shared/package-info.java index 1ba4d68..0ac3364 100644 --- a/shared/src/main/java/org/fuin/cqrs4j/example/shared/package-info.java +++ b/shared/src/main/java/org/fuin/cqrs4j/example/shared/package-info.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; /** diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/CreatePersonCommandTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/CreatePersonCommandTest.java index b7d22be..ba632c2 100644 --- a/shared/src/test/java/org/fuin/cqrs4j/example/shared/CreatePersonCommandTest.java +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/CreatePersonCommandTest.java @@ -1,38 +1,20 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.time.ZonedDateTime; -import java.util.UUID; - import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; - import org.apache.commons.io.IOUtils; import org.eclipse.yasson.FieldAccessStrategy; import org.fuin.utils4j.Utils4J; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.nio.charset.Charset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + // CHECKSTYLE:OFF public final class CreatePersonCommandTest { @@ -99,7 +81,7 @@ public final void testUnmarshalJsonFromFile() throws IOException { private CreatePersonCommand createTestee() { final PersonId personId = new PersonId(UUID.fromString(PERSON_UUID)); final PersonName personName = new PersonName("Peter Parker"); - return new CreatePersonCommand(personId, personName); + return new CreatePersonCommand.Builder().id(personId).name(personName).build(); } } diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/DeletePersonCommandTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/DeletePersonCommandTest.java new file mode 100644 index 0000000..5a194ee --- /dev/null +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/DeletePersonCommandTest.java @@ -0,0 +1,92 @@ +package org.fuin.cqrs4j.example.shared; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; +import org.apache.commons.io.IOUtils; +import org.eclipse.yasson.FieldAccessStrategy; +import org.fuin.ddd4j.ddd.AggregateVersion; +import org.fuin.utils4j.Utils4J; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +// CHECKSTYLE:OFF +public final class DeletePersonCommandTest { + + private static final String PERSON_UUID = "84565d62-115e-4502-b7c9-38ad69c64b05"; + + @Test + public final void testSerializeDeserialize() { + + // PREPARE + final DeletePersonCommand original = createTestee(); + + // TEST + final DeletePersonCommand copy = Utils4J.deserialize(Utils4J.serialize(original)); + + // VERIFY + assertThat(copy).isEqualTo(original); + assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId()); + assertThat(copy.getAggregateVersionInteger()).isEqualTo(0L); + assertThat(copy.getName()).isEqualTo(original.getName()); + + } + + @Test + public final void testMarshalUnmarshalJson() { + + // PREPARE + final DeletePersonCommand original = createTestee(); + + final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS) + .withPropertyVisibilityStrategy(new FieldAccessStrategy()); + final Jsonb jsonb = JsonbBuilder.create(config); + + // TEST + final String json = jsonb.toJson(original, DeletePersonCommand.class); + final DeletePersonCommand copy = jsonb.fromJson(json, DeletePersonCommand.class); + + // VERIFY + assertThat(copy).isEqualTo(original); + assertThat(copy.getAggregateRootId()).isEqualTo(original.getAggregateRootId()); + assertThat(copy.getAggregateVersionInteger()).isEqualTo(0L); + assertThat(copy.getName()).isEqualTo(original.getName()); + + } + + @Test + public final void testUnmarshalJsonFromFile() throws IOException { + + // PREPARE + final String json = IOUtils.toString(this.getClass().getResourceAsStream("/commands/DeletePersonCommand.json"), + Charset.forName("utf-8")); + final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS) + .withPropertyVisibilityStrategy(new FieldAccessStrategy()); + final Jsonb jsonb = JsonbBuilder.create(config); + + // TEST + final DeletePersonCommand copy = jsonb.fromJson(json, DeletePersonCommand.class); + + // VERIFY + assertThat(copy.getEventId().asBaseType()).isEqualTo(UUID.fromString("109a77b2-1de2-46fc-aee1-97fa7740a552")); + assertThat(copy.getEventTimestamp()).isEqualTo(ZonedDateTime.parse("2019-11-17T10:27:13.183+01:00[Europe/Berlin]")); + assertThat(copy.getAggregateRootId().asString()).isEqualTo(PERSON_UUID); + assertThat(copy.getAggregateVersionInteger()).isEqualTo(0L); + assertThat(copy.getName().asString()).isEqualTo("Peter Parker"); + + } + + private DeletePersonCommand createTestee() { + final PersonId personId = new PersonId(UUID.fromString(PERSON_UUID)); + final PersonName personName = new PersonName("Peter Parker"); + return new DeletePersonCommand.Builder().id(personId).name(personName).version(0).build(); + } + +} +// CHECKSTYLE:ON diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonCreatedEventTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonCreatedEventTest.java index 06f9350..895f2cd 100644 --- a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonCreatedEventTest.java +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonCreatedEventTest.java @@ -1,38 +1,20 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import static org.assertj.core.api.Assertions.assertThat; -import static org.fuin.utils4j.Utils4J.deserialize; -import static org.fuin.utils4j.Utils4J.serialize; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.UUID; - import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; - import org.apache.commons.io.IOUtils; import org.eclipse.yasson.FieldAccessStrategy; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.fuin.utils4j.Utils4J.deserialize; +import static org.fuin.utils4j.Utils4J.serialize; + // CHECKSTYLE:OFF public final class PersonCreatedEventTest { @@ -101,7 +83,7 @@ public final void testToString() { private PersonCreatedEvent createTestee() { final PersonId personId = new PersonId(UUID.fromString("f645969a-402d-41a9-882b-d2d8000d0f43")); final PersonName personName = new PersonName("Peter Parker"); - return new PersonCreatedEvent(personId, personName); + return new PersonCreatedEvent.Builder().id(personId).name(personName).version(0).build(); } } diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonDeletedEventTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonDeletedEventTest.java new file mode 100644 index 0000000..1be6a4a --- /dev/null +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonDeletedEventTest.java @@ -0,0 +1,91 @@ +package org.fuin.cqrs4j.example.shared; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; +import org.apache.commons.io.IOUtils; +import org.eclipse.yasson.FieldAccessStrategy; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.fuin.utils4j.Utils4J.deserialize; +import static org.fuin.utils4j.Utils4J.serialize; + +// CHECKSTYLE:OFF +public final class PersonDeletedEventTest { + + @Test + public final void testSerializeDeserialize() { + + // PREPARE + final PersonDeletedEvent original = createTestee(); + + // TEST + final PersonDeletedEvent copy = deserialize(serialize(original)); + + // VERIFY + assertThat(copy).isEqualTo(original); + assertThat(copy.getName()).isEqualTo(original.getName()); + + } + + @Test + public final void testMarshalUnmarshalJson() { + + // PREPARE + final PersonDeletedEvent original = createTestee(); + + final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS) + .withPropertyVisibilityStrategy(new FieldAccessStrategy()); + final Jsonb jsonb = JsonbBuilder.create(config); + + // TEST + final String json = jsonb.toJson(original, PersonDeletedEvent.class); + final PersonDeletedEvent copy = jsonb.fromJson(json, PersonDeletedEvent.class); + + // VERIFY + assertThat(copy).isEqualTo(original); + assertThat(copy.getName()).isEqualTo(original.getName()); + assertThat(copy.getAggregateVersionInteger()).isEqualTo(1L); + + } + + @Test + public final void testUnmarshalJson() throws IOException { + + // PREPARE + final PersonDeletedEvent original = createTestee(); + final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS) + .withPropertyVisibilityStrategy(new FieldAccessStrategy()); + final Jsonb jsonb = JsonbBuilder.create(config); + + // TEST + final String json = IOUtils.toString(this.getClass().getResourceAsStream("/events/PersonDeletedEvent.json"), + Charset.forName("utf-8")); + final PersonDeletedEvent copy = jsonb.fromJson(json, PersonDeletedEvent.class); + + // VERIFY + assertThat(copy.getEntityIdPath()).isEqualTo(original.getEntityIdPath()); + assertThat(copy.getName()).isEqualTo(original.getName()); + assertThat(copy.getAggregateVersionInteger()).isEqualTo(1L); + + } + + @Test + public final void testToString() { + final PersonDeletedEvent testee = createTestee(); + assertThat(testee) + .hasToString("Deleted person 'Peter Parker' (" + testee.getEntityId() + ") [Event " + testee.getEventId() + "]"); + } + + private PersonDeletedEvent createTestee() { + final PersonId personId = new PersonId(UUID.fromString("f645969a-402d-41a9-882b-d2d8000d0f43")); + final PersonName personName = new PersonName("Peter Parker"); + return new PersonDeletedEvent.Builder().id(personId).name(personName).version(1).build(); + } + +} diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonIdTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonIdTest.java index 1c2a035..fff18c9 100644 --- a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonIdTest.java +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonIdTest.java @@ -1,34 +1,16 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.UUID; - +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; import org.fuin.ddd4j.ddd.EntityType; import org.fuin.ddd4j.ddd.StringBasedEntityType; import org.fuin.objects4j.common.ConstraintViolationException; import org.junit.jupiter.api.Test; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; /** * Test for {@link PersonId}. diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonNameTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonNameTest.java index cabfac8..53f95a4 100644 --- a/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonNameTest.java +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/PersonNameTest.java @@ -1,31 +1,13 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; import org.fuin.objects4j.common.ConstraintViolationException; import org.fuin.utils4j.Utils4J; import org.junit.jupiter.api.Test; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; // CHECKSTYLE:OFF public final class PersonNameTest { diff --git a/shared/src/test/java/org/fuin/cqrs4j/example/shared/SharedUtilsTest.java b/shared/src/test/java/org/fuin/cqrs4j/example/shared/SharedUtilsTest.java index bad0cf9..bc15d2c 100644 --- a/shared/src/test/java/org/fuin/cqrs4j/example/shared/SharedUtilsTest.java +++ b/shared/src/test/java/org/fuin/cqrs4j/example/shared/SharedUtilsTest.java @@ -1,29 +1,12 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. - * http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) any - * later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.shared; -import static org.assertj.core.api.Assertions.assertThat; +import org.fuin.ddd4j.ddd.EventType; +import org.junit.jupiter.api.Test; import java.util.HashSet; import java.util.Set; -import org.fuin.ddd4j.ddd.EventType; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for the {@link SharedUtils} class. diff --git a/quarkus/shared/src/test/resources/commands/CreatePersonCommand.json b/shared/src/test/resources/commands/DeletePersonCommand.json similarity index 89% rename from quarkus/shared/src/test/resources/commands/CreatePersonCommand.json rename to shared/src/test/resources/commands/DeletePersonCommand.json index 92ceb71..11341f0 100644 --- a/quarkus/shared/src/test/resources/commands/CreatePersonCommand.json +++ b/shared/src/test/resources/commands/DeletePersonCommand.json @@ -2,5 +2,6 @@ "event-id": "109a77b2-1de2-46fc-aee1-97fa7740a552", "event-timestamp": "2019-11-17T10:27:13.183+01:00[Europe/Berlin]", "entity-id-path": "PERSON 84565d62-115e-4502-b7c9-38ad69c64b05", + "aggregate-version": 0, "name": "Peter Parker" } \ No newline at end of file diff --git a/shared/src/test/resources/events/PersonCreatedEvent.json b/shared/src/test/resources/events/PersonCreatedEvent.json index fd283ba..8f23f1c 100644 --- a/shared/src/test/resources/events/PersonCreatedEvent.json +++ b/shared/src/test/resources/events/PersonCreatedEvent.json @@ -2,5 +2,6 @@ "event-id": "a7b88543-ce32-40eb-a3fe-f49aec39b570", "event-timestamp": "2019-11-02T09:56:40.669Z[Etc/UTC]", "entity-id-path": "PERSON f645969a-402d-41a9-882b-d2d8000d0f43", + "aggregate-version": 0, "name": "Peter Parker" } diff --git a/spring-boot/shared/src/test/resources/events/PersonCreatedEvent.json b/shared/src/test/resources/events/PersonDeletedEvent.json similarity index 89% rename from spring-boot/shared/src/test/resources/events/PersonCreatedEvent.json rename to shared/src/test/resources/events/PersonDeletedEvent.json index fd283ba..e8af5c3 100644 --- a/spring-boot/shared/src/test/resources/events/PersonCreatedEvent.json +++ b/shared/src/test/resources/events/PersonDeletedEvent.json @@ -2,5 +2,6 @@ "event-id": "a7b88543-ce32-40eb-a3fe-f49aec39b570", "event-timestamp": "2019-11-02T09:56:40.669Z[Etc/UTC]", "entity-id-path": "PERSON f645969a-402d-41a9-882b-d2d8000d0f43", + "aggregate-version": 1, "name": "Peter Parker" } diff --git a/spring-boot/command/pom.xml b/spring-boot/command/pom.xml index efa89d0..c764626 100644 --- a/spring-boot/command/pom.xml +++ b/spring-boot/command/pom.xml @@ -8,7 +8,7 @@ org.fuin.cqrs4j.example.spring cqrs4j-spring-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -23,19 +23,19 @@ org.fuin.cqrs4j.example cqrs4j-example-shared - 0.3.0 + 0.4.0-SNAPSHOT org.fuin.cqrs4j.example cqrs4j-example-aggregates - 0.3.0 + 0.4.0-SNAPSHOT org.fuin.cqrs4j.example.spring cqrs4j-spring-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -98,6 +98,12 @@ test + + org.assertj + assertj-core + test + + io.rest-assured rest-assured diff --git a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java index 5e9094c..50466c9 100644 --- a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java +++ b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/app/CmdApplication.java @@ -1,5 +1,6 @@ package org.fuin.cqrs4j.example.spring.command.app; +import org.fuin.cqrs4j.example.aggregates.EventStorePersonRepository; import org.fuin.cqrs4j.example.aggregates.PersonRepository; import org.fuin.esc.esgrpc.IESGrpcEventStore; import org.springframework.boot.SpringApplication; @@ -22,7 +23,7 @@ public class CmdApplication { @Bean @RequestScope public PersonRepository create(final IESGrpcEventStore eventStore) { - return new PersonRepository(eventStore); + return new EventStorePersonRepository(eventStore); } public static void main(String[] args) { diff --git a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/controller/PersonController.java b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/controller/PersonController.java index 74e7b76..b1201df 100644 --- a/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/controller/PersonController.java +++ b/spring-boot/command/src/main/java/org/fuin/cqrs4j/example/spring/command/controller/PersonController.java @@ -1,52 +1,41 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.spring.command.controller; -import java.util.Optional; -import java.util.Set; - import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; - import org.fuin.cqrs4j.CommandExecutionFailedException; import org.fuin.cqrs4j.SimpleResult; import org.fuin.cqrs4j.example.aggregates.DuplicatePersonNameException; import org.fuin.cqrs4j.example.aggregates.Person; import org.fuin.cqrs4j.example.aggregates.PersonRepository; import org.fuin.cqrs4j.example.shared.CreatePersonCommand; -import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException; -import org.fuin.ddd4j.ddd.AggregateDeletedException; -import org.springframework.beans.factory.annotation.Autowired; +import org.fuin.cqrs4j.example.shared.DeletePersonCommand; +import org.fuin.ddd4j.ddd.*; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; @RestController @RequestMapping("/persons") -public class PersonController { +public final class PersonController { + + private final PersonRepository repo; - @Autowired - private PersonRepository repo; + private final Validator validator; - @Autowired - private Validator validator; + public PersonController(PersonRepository repo, Validator validator) { + this.repo = repo; + this.validator = validator; + } - @PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = "/create", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity create(@RequestBody final CreatePersonCommand cmd) throws AggregateAlreadyExistsException, AggregateDeletedException, CommandExecutionFailedException, DuplicatePersonNameException { @@ -68,4 +57,34 @@ public ResponseEntity create(@RequestBody final CreatePersonComman } + @DeleteMapping(path = "/{personId}", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity delete(@PathVariable("personId") final UUID personId, @RequestBody final DeletePersonCommand cmd) + throws AggregateVersionConflictException, AggregateVersionNotFoundException, + AggregateDeletedException, AggregateNotFoundException { + + // Verify preconditions + final Set> violations = validator.validate(cmd); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + // Read last known entity version + final Person person = repo.read(cmd.getAggregateRootId(), cmd.getAggregateVersionInteger()); + + // Try to delete the aggregate + // Internally just sets a 'deleted' flag + person.delete(); + + // Write resulting events back to the repository + // DO NOT call "repo.delete(..)! If you would do, you would never see a "deleted" event... + // The repository "delete" really removes the stream and is more like a "purge". + repo.update(person); + + // Send OK response + return new ResponseEntity<>(SimpleResult.ok(), HttpStatus.OK); + + } + } diff --git a/spring-boot/command/src/test/java/org/fuin/cqrs4j/example/spring/command/api/PersonControllerIT.java b/spring-boot/command/src/test/java/org/fuin/cqrs4j/example/spring/command/api/PersonControllerIT.java index 94920d9..a1e6c64 100644 --- a/spring-boot/command/src/test/java/org/fuin/cqrs4j/example/spring/command/api/PersonControllerIT.java +++ b/spring-boot/command/src/test/java/org/fuin/cqrs4j/example/spring/command/api/PersonControllerIT.java @@ -6,10 +6,7 @@ import jakarta.json.bind.Jsonb; import org.fuin.cqrs4j.ResultType; import org.fuin.cqrs4j.SimpleResult; -import org.fuin.cqrs4j.example.shared.CreatePersonCommand; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.cqrs4j.example.shared.*; import org.fuin.cqrs4j.example.spring.command.app.CmdApplication; import org.fuin.esc.api.CommonEvent; import org.fuin.esc.api.SimpleStreamId; @@ -24,13 +21,11 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.web.context.WebApplicationContext; -import java.util.Arrays; import java.util.List; import java.util.UUID; import static io.restassured.RestAssured.given; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration(classes = CmdApplication.class) @@ -60,24 +55,84 @@ void testCreate() { // PREPARE final PersonId personId = new PersonId(UUID.randomUUID()); final PersonName personName = new PersonName("Peter Parker"); - final CreatePersonCommand cmd = new CreatePersonCommand(personId, personName); + final CreatePersonCommand cmd = new CreatePersonCommand.Builder().id(personId).name(personName).build(); final String json = jsonb.toJson(cmd); // TEST & VERIFY - final SimpleResult result = given().accept(ContentType.JSON).contentType(ContentType.JSON).body(json).when().post("/persons/create") - .then().statusCode(200).extract().as(SimpleResult.class); - assertThat(result.getType(), is(equalTo(ResultType.OK))); + final SimpleResult result = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(json) + .when() + .post("/persons/create") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(result.getType()).isEqualTo(ResultType.OK); final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 0, 1); final List events = slice.getEvents(); - assertThat(Arrays.asList(events), is(not(empty()))); + assertThat(events).isNotEmpty(); final CommonEvent ce = events.get(0); - assertThat(ce.getDataType(), is(equalTo(new TypeName(PersonCreatedEvent.TYPE.asBaseType())))); + assertThat(ce.getDataType()).isEqualTo(new TypeName(PersonCreatedEvent.TYPE.asBaseType())); final PersonCreatedEvent event = (PersonCreatedEvent) ce.getData(); - assertThat(event.getEntityId(), is(equalTo(personId))); - assertThat(event.getName(), is(equalTo(personName))); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(0); + assertThat(event.getName()).isEqualTo(personName); } + @Test + void testDelete() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Delete Me"); + final CreatePersonCommand createCmd = new CreatePersonCommand.Builder().id(personId).name(personName).build(); + final String jsonCreateCmd = jsonb.toJson(createCmd); + final SimpleResult createResult = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(jsonCreateCmd) + .when() + .post("/persons/create") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(createResult.getType()).isEqualTo(ResultType.OK); + + final DeletePersonCommand deleteCmd = new DeletePersonCommand.Builder().id(personId).name(personName).version(0).build(); + final String jsonDeleteCmd = jsonb.toJson(deleteCmd); + + // TEST & VERIFY + final SimpleResult deleteResult = + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .body(jsonDeleteCmd) + .when() + .pathParam("personId", personId.asString()) + .delete("/persons/{personId}") + .then() + .statusCode(200) + .extract().as(SimpleResult.class); + assertThat(deleteResult.getType()).isEqualTo(ResultType.OK); + + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + final StreamEventsSlice slice = eventStore.readEventsForward(personStreamId, 1, 1); + final List events = slice.getEvents(); + assertThat(events).isNotEmpty(); + final CommonEvent ce = events.get(0); + assertThat(ce.getDataType()).isEqualTo(new TypeName(PersonDeletedEvent.TYPE.asBaseType())); + final PersonDeletedEvent event = (PersonDeletedEvent) ce.getData(); + assertThat(event.getEntityId()).isEqualTo(personId); + assertThat(event.getAggregateVersionInteger()).isEqualTo(1); + assertThat(event.getName()).isEqualTo(personName); + + } + + } \ No newline at end of file diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index b4807c3..dad44f0 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -12,7 +12,7 @@ org.fuin.cqrs4j.example.spring cqrs4j-spring-example-root - 0.3.0 + 0.4.0-SNAPSHOT pom @@ -21,7 +21,7 @@ true UTF-8 UTF-8 - 0.7.0-SNAPSHOT + 0.7.0 21.10.1-bionic @@ -65,12 +65,6 @@ ${esc.version} - - org.fuin.esc - esc-http-admin - ${esc.version} - - org.glassfish jakarta.json diff --git a/spring-boot/query/pom.xml b/spring-boot/query/pom.xml index 2db073b..5bf0407 100644 --- a/spring-boot/query/pom.xml +++ b/spring-boot/query/pom.xml @@ -8,7 +8,7 @@ org.fuin.cqrs4j.example.spring cqrs4j-spring-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -23,13 +23,13 @@ org.fuin.cqrs4j.example cqrs4j-example-shared - 0.3.0 + 0.4.0-SNAPSHOT org.fuin.cqrs4j.example.spring cqrs4j-spring-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -67,11 +67,6 @@ esc-esgrpc - - org.fuin.esc - esc-http-admin - - diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java index 106af79..e749ff5 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/app/QryApplication.java @@ -1,21 +1,26 @@ package org.fuin.cqrs4j.example.spring.query.app; -import java.util.concurrent.Executor; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; -@SpringBootApplication(scanBasePackages = { "org.fuin.cqrs4j.example.spring.query.app", "org.fuin.cqrs4j.example.spring.query.controller", - "org.fuin.cqrs4j.example.spring.query.views.common", "org.fuin.cqrs4j.example.spring.query.views.personlist", - "org.fuin.cqrs4j.example.spring.shared" }) +@SpringBootApplication(scanBasePackages = { + "org.fuin.cqrs4j.example.spring.query.app", + "org.fuin.cqrs4j.example.spring.shared", + "org.fuin.cqrs4j.example.spring.query.views" +}) @EnableJpaRepositories("org.fuin.cqrs4j.example.spring.query.views.common") -@EntityScan({ "org.fuin.cqrs4j.example.spring.query.views.common", "org.fuin.cqrs4j.example.spring.query.views.personlist" }) +@EntityScan({"org.fuin.cqrs4j.example.spring.query.views"}) @EnableScheduling @EnableAsync public class QryApplication { @@ -31,6 +36,20 @@ public Executor taskExecutor() { return executor; } + @Bean + public ScheduledTaskRegistrar scheduledTaskRegistrar(TaskScheduler taskScheduler) { + final ScheduledTaskRegistrar scheduledTaskRegistrar = new ScheduledTaskRegistrar(); + scheduledTaskRegistrar.setScheduler(taskScheduler); + return scheduledTaskRegistrar; + } + + @Bean + public TaskScheduler threadPoolTaskScheduler() { + final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(20); + return scheduler; + } + public static void main(String[] args) { SpringApplication.run(QryApplication.class, args); } diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionPosition.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionPosition.java index d09edd8..9c2010c 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionPosition.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionPosition.java @@ -5,7 +5,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; - import org.fuin.esc.api.SimpleStreamId; import org.fuin.esc.api.StreamId; import org.fuin.objects4j.common.Contract; diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionService.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionService.java index 811d97c..3241faa 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionService.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/QryProjectionService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.ProjectionService; import org.fuin.esc.api.StreamId; import org.fuin.objects4j.common.Contract; diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/SpringViewManager.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/SpringViewManager.java new file mode 100644 index 0000000..a790cfd --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/common/SpringViewManager.java @@ -0,0 +1,235 @@ +package org.fuin.cqrs4j.example.spring.query.views.common; + +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.cqrs4j.example.shared.SharedUtils; +import org.fuin.cqrs4j.example.shared.View; +import org.fuin.cqrs4j.example.spring.query.views.statistic.StatisticView; +import org.fuin.ddd4j.ddd.Event; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.api.*; +import org.fuin.esc.esgrpc.IESGrpcEventStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.annotation.Order; +import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.CronTask; +import org.springframework.scheduling.config.ScheduledTask; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + +import static org.fuin.cqrs4j.Cqrs4JUtils.tryLocked; + +/** + * Creates scheduler update tasks for all classes implementing the {@link View} interface. + * Avoids boilerplate code: Instead of having a separated "Projector", "EventDispatcher" + * and a "ChunkHandler" class for each view, there is only one simplified "View" class now. + */ +@Component +@Order(0) +public class SpringViewManager implements ApplicationListener, SchedulingConfigurer { + + private static final Logger LOG = LoggerFactory.getLogger(SpringViewManager.class); + + private final ScheduledAnnotationBeanPostProcessor postProcessor; + + private final List rawViews; + + private final IESGrpcEventStore eventstore; + + private final ProjectionAdminEventStore admin; + + private final ProjectionService projectionService; + + private final TransactionTemplate requiresNewTransaction; + + private List views; + + public SpringViewManager( + final ScheduledAnnotationBeanPostProcessor postProcessor, + final List rawViews, + final IESGrpcEventStore eventstore, + final ProjectionAdminEventStore admin, + final ProjectionService projectionService, + final PlatformTransactionManager transactionManager, + final StatisticView sv) { + this.postProcessor = postProcessor; + this.rawViews = rawViews; + this.eventstore = eventstore; + this.admin = admin; + this.projectionService = projectionService; + this.requiresNewTransaction = new TransactionTemplate(transactionManager); + this.requiresNewTransaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.requiresNewTransaction.setTimeout(10); + LOG.info("SV={}", sv); + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + createViews(taskRegistrar); + } + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + shutdownViews(); + } + + private void createViews(ScheduledTaskRegistrar taskRegistrar) { + LOG.info("Create {} views...", rawViews == null ? 0 : rawViews.size()); + views = rawViews.stream().map(ViewExt::new).toList(); + for (final ViewExt view : views) { + LOG.info("Create view: {}", view.getName()); + view.setCronTask(new CronTask(() -> updateView(view), view.getCron())); + taskRegistrar.addCronTask(view.getCronTask()); + } + } + + private void shutdownViews() { + LOG.info("Shutdown {} views...", views == null ? 0 : views.size()); + final Set scheduledTasks = postProcessor.getScheduledTasks(); + for (final ViewExt view : views) { + LOG.info("Shutdown view: {}", view.getName()); + scheduledTasks.stream() + .filter(scheduled -> scheduled.getTask() == view.getCronTask()) + .findFirst() + .ifPresent(ScheduledTask::cancel); + } + } + + + private void updateView(final ViewExt view) { + tryLocked(view.getLock(), () -> { + new Thread(() -> { + try { + readStreamEvents(view); + } catch (final RuntimeException ex) { + LOG.error("Error reading events from stream", ex); + } + } + ).start(); + }); + } + + private void readStreamEvents(final ViewExt view) { + + // Create an event store projection if it does not exist. + if (!admin.projectionExists(view.getProjectionStreamId())) { + final List typeNames = asTypeNames(view.getEventTypes()); + LOG.info("Create projection '{}' with events: {}", view.getProjectionStreamId(), typeNames); + admin.createProjection(view.getProjectionStreamId(), true, typeNames); + } + + // Read and dispatch events + final Long nextEventNumber = projectionService.readProjectionPosition(view.getProjectionStreamId()); + eventstore.readAllEventsForward(view.getProjectionStreamId(), nextEventNumber, view.getChunkSize(), + currentSlice -> handleChunk(view, currentSlice)); + + } + + private List asTypeNames(Set eventTypes) { + return eventTypes.stream().map(eventType -> new TypeName((eventType.asString()))).toList(); + } + + private void handleChunk(final ViewExt view, final StreamEventsSlice currentSlice) { + requiresNewTransaction.execute(new TransactionCallbackWithoutResult() { + public void doInTransactionWithoutResult(TransactionStatus status) { + LOG.debug("Handle chunk: {}", currentSlice); + view.handleEvents(asEvents(currentSlice.getEvents())); + projectionService.updateProjectionPosition(view.getProjectionStreamId(), currentSlice.getNextEventNumber()); + } + }); + } + + private List asEvents(List events) { + return events.stream().map(event -> (Event) event.getData()).toList(); + } + + /** + * Extends the view with some necessary values used only by this class. + */ + private static class ViewExt implements View { + + private final View delegate; + + private final ProjectionStreamId projectionStreamId; + + private final Semaphore lock; + + private CronTask cronTask; + + public ViewExt(final View delegate) { + this.delegate = delegate; + + final Set eventTypes = delegate.getEventTypes(); + final String name = delegate.getName() + "-" + SharedUtils.calculateChecksum(eventTypes); + projectionStreamId = new ProjectionStreamId(name); + + this.lock = new Semaphore(1); + + } + + /** + * Returns the task used. + * + * @return Task. + */ + public CronTask getCronTask() { + return cronTask; + } + + /** + * Sets the task to use. + * + * @param cronTask Task. + */ + public void setCronTask(CronTask cronTask) { + this.cronTask = cronTask; + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getCron() { + return delegate.getCron(); + } + + @Override + public Set getEventTypes() { + return delegate.getEventTypes(); + } + + @Override + public int getChunkSize() { + return delegate.getChunkSize(); + } + + @Override + public void handleEvents(List events) { + delegate.handleEvents(events); + } + + public ProjectionStreamId getProjectionStreamId() { + return projectionStreamId; + } + + public Semaphore getLock() { + return lock; + } + + } + +} diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/package-info.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/package-info.java index fde056c..540187f 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/package-info.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/package-info.java @@ -1,18 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.spring.query.views; /** - * Contains the views used in this query application. A view never uses code of another view, means all views are completely independent of - * each other. As an exception, the 'commons' package has some small classes that are not view specific. + * Contains the views used in this query application. A view never uses code of another view, + * means all views are completely independent of each other. As an exception, the 'commons' package + * has some small classes that are not view specific. */ diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonCreatedEventHandler.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonCreatedEventHandler.java index 6d9ef01..08bda72 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonCreatedEventHandler.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonCreatedEventHandler.java @@ -1,7 +1,6 @@ package org.fuin.cqrs4j.example.spring.query.views.personlist; import jakarta.persistence.EntityManager; - import org.fuin.cqrs4j.EventHandler; import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; import org.fuin.cqrs4j.example.shared.PersonId; diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonDeletedEventHandler.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonDeletedEventHandler.java new file mode 100644 index 0000000..463688f --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonDeletedEventHandler.java @@ -0,0 +1,39 @@ +package org.fuin.cqrs4j.example.spring.query.views.personlist; + +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.EventHandler; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.ddd4j.ddd.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Handles the {@link PersonDeletedEvent}. + */ +@Component +public class PersonDeletedEventHandler implements EventHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PersonDeletedEventHandler.class); + + @Autowired + private EntityManager em; + + @Override + public EventType getEventType() { + return PersonDeletedEvent.TYPE; + } + + @Override + public void handle(final PersonDeletedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final PersonId personId = event.getEntityId(); + final PersonListEntry entity = em.find(PersonListEntry.class, personId.asString()); + if (entity != null) { + em.remove(entity); + } + } + +} \ No newline at end of file diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/controller/PersonController.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListController.java similarity index 70% rename from spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/controller/PersonController.java rename to spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListController.java index f5ba870..854a363 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/controller/PersonController.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListController.java @@ -1,24 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.spring.query.controller; - -import java.util.List; -import java.util.UUID; +package org.fuin.cqrs4j.example.spring.query.views.personlist; import jakarta.persistence.EntityManager; - import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.spring.query.views.personlist.PersonListEntry; import org.fuin.ddd4j.ddd.AggregateNotFoundException; import org.fuin.objects4j.vo.UUIDStr; import org.slf4j.Logger; @@ -32,12 +15,18 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; + +/** + * REST controller providing the persons. + */ @RestController @RequestMapping("/persons") @Transactional(readOnly = true) -public class PersonController { +public class PersonListController { - private static final Logger LOG = LoggerFactory.getLogger(PersonController.class); + private static final Logger LOG = LoggerFactory.getLogger(PersonListController.class); @Autowired private EntityManager em; diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEntry.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEntry.java index 1cb464a..6711ca4 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEntry.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEntry.java @@ -1,24 +1,7 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.spring.query.views.personlist; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.example.shared.PersonId; import org.fuin.cqrs4j.example.shared.PersonName; import org.fuin.objects4j.common.Contract; diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventChunkHandler.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventChunkHandler.java index e88918b..3f5d2cb 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventChunkHandler.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventChunkHandler.java @@ -1,7 +1,5 @@ package org.fuin.cqrs4j.example.spring.query.views.personlist; -import java.util.Set; - import org.fuin.cqrs4j.ProjectionService; import org.fuin.cqrs4j.example.shared.SharedUtils; import org.fuin.ddd4j.ddd.EventType; @@ -14,6 +12,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.util.Set; + /** * Dispatches the events to the event handlers that will update the database and stores the next event position in the database (All in the * same transaction). diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventDispatcher.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventDispatcher.java index 82e8e74..bad5c34 100644 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventDispatcher.java +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListEventDispatcher.java @@ -1,10 +1,6 @@ package org.fuin.cqrs4j.example.spring.query.views.personlist; -import java.util.List; -import java.util.Set; - import jakarta.validation.constraints.NotNull; - import org.fuin.cqrs4j.EventDispatcher; import org.fuin.cqrs4j.SimpleEventDispatcher; import org.fuin.ddd4j.ddd.Event; @@ -13,6 +9,9 @@ import org.fuin.objects4j.common.NotThreadSafe; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Set; + /** * Dispatches events that relate to the {@link PersonListEntry} entity to the appropriate event handers. */ @@ -25,9 +24,10 @@ public class PersonListEventDispatcher implements EventDispatcher { /** * Default constructor. */ - public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler) { + public PersonListEventDispatcher(final PersonCreatedEventHandler createdHandler, + final PersonDeletedEventHandler deletedHandler) { super(); - delegate = new SimpleEventDispatcher(createdHandler); + delegate = new SimpleEventDispatcher(createdHandler, deletedHandler); } @Override diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/package-info.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/package-info.java deleted file mode 100644 index 87ca2be..0000000 --- a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/personlist/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ -package org.fuin.cqrs4j.example.spring.query.views.personlist; - -/** - * Classes building the 'person list' view. - */ diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/EntityType.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/EntityType.java new file mode 100644 index 0000000..8e8b246 --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/EntityType.java @@ -0,0 +1,47 @@ +package org.fuin.cqrs4j.example.spring.query.views.statistic; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotEmpty; + +import java.util.Objects; + +/** + * Defines the name of a type of entity. + * + * @param name Unique name. Will be converted to lowercase internally - Minimum 1 character, maximum 30 characters. + */ +public record EntityType(String name) { + + /** + * Maximum allowed length of the name. + */ + public static final int MAX_LENGTH = 30; + + public EntityType(@NotEmpty @Max(MAX_LENGTH) String name) { + this.name = Objects.requireNonNull(name, "name==null").toLowerCase(); + if (name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty"); + } + if (name.length() > MAX_LENGTH) { + throw new IllegalArgumentException("Name has a length of " + name.length() + + ", but max allowed is " + MAX_LENGTH + " characters: '" + name + "'"); + } + } + + @Override + public String toString() { + return name; + } + + /** + * Determines if the name is valid. + * + * @param name Name to be verified. + * @return {@literal true} if the given name can be converted into an instance of this class. + */ + public static boolean isValid(String name) { + return name != null && !name.isEmpty() && name.length() <= MAX_LENGTH; + } + + +} diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/QryStatisticResource.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/QryStatisticResource.java new file mode 100644 index 0000000..bd7127d --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/QryStatisticResource.java @@ -0,0 +1,49 @@ +package org.fuin.cqrs4j.example.spring.query.views.statistic; + +import jakarta.persistence.EntityManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * REST controller providing the statistics. + */ +@RestController +@RequestMapping("/statistics") +@Transactional(readOnly = true) +public class QryStatisticResource { + + private static final Logger LOG = LoggerFactory.getLogger(QryStatisticResource.class); + + @Autowired + EntityManager em; + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + final List statistics = em.createNamedQuery(StatisticEntity.FIND_ALL, Statistic.class).getResultList(); + LOG.info("getAll() = {}", statistics.size()); + return statistics; + } + + @GetMapping(path = "/{name}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getByName(@PathVariable(value = "name") String name) { + if (!EntityType.isValid(name)) { + return ResponseEntity.badRequest().body("Invalid entity type name"); + } + final StatisticEntity entity = em.find(StatisticEntity.class, name); + if (entity == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok().body(entity.toDto()); + } + +} diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/Statistic.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/Statistic.java new file mode 100644 index 0000000..36165b5 --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/Statistic.java @@ -0,0 +1,19 @@ +package org.fuin.cqrs4j.example.spring.query.views.statistic; + +import jakarta.json.bind.annotation.JsonbCreator; + +/** + * DTO class sent back to the client. We could also serialize the entity instead. + * In case you want to provide some Java library for the client, this class can be + * moved to the API module without making JPA entity details public. + * + * @param type Unique type name. + * @param count Number of instances the type currently has. + */ +public record Statistic(String type, int count) { + + @JsonbCreator + public Statistic { + } + +} diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticEntity.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticEntity.java new file mode 100644 index 0000000..d7a3b2e --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticEntity.java @@ -0,0 +1,81 @@ +package org.fuin.cqrs4j.example.spring.query.views.statistic; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; + +import java.util.Objects; + +/** + * Represents a statistic record that will be stored in the database. + */ +@Entity +@Table(name = "SPRING_STATISTIC") +@NamedQuery(name = StatisticEntity.FIND_ALL, + query = "SELECT new org.fuin.cqrs4j.example.spring.query.views.statistic.Statistic(s.type, s.count) FROM StatisticEntity s") +public class StatisticEntity { + + public static final String FIND_ALL = "StatisticEntity.findAll"; + + @Id + @Column(name = "TYPE", nullable = false, length = EntityType.MAX_LENGTH, updatable = false) + @NotNull + private String type; + + @Column(name = "COUNT", updatable = true) + private int count; + + /** + * JPA default constructor. + */ + protected StatisticEntity() { + } + + /** + * Constrcutor with a given type that sets the number of instances to one. + * + * @param type Unique type ID. + */ + public StatisticEntity(@NotNull EntityType type) { + this.type = Objects.requireNonNull(type, "type==null").name(); + this.count = 1; + } + + public Statistic toDto() { + return new Statistic(type, count); + } + + /** + * Increases the number of entries for the type by one. + */ + public void inc() { + this.count++; + } + + /** + * Decreases the number of entries for the type by one. + */ + public void dec() { + this.count--; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StatisticEntity that = (StatisticEntity) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + return "StatisticEntity{" + + "type='" + type + '\'' + + ", count=" + count + + '}'; + } +} \ No newline at end of file diff --git a/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticView.java b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticView.java new file mode 100644 index 0000000..58e3034 --- /dev/null +++ b/spring-boot/query/src/main/java/org/fuin/cqrs4j/example/spring/query/views/statistic/StatisticView.java @@ -0,0 +1,81 @@ +package org.fuin.cqrs4j.example.spring.query.views.statistic; + +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.View; +import org.fuin.ddd4j.ddd.Event; +import org.fuin.ddd4j.ddd.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Handles the events required to maintain the statistic database view. + */ +@Component +public class StatisticView implements View { + + private static final Logger LOG = LoggerFactory.getLogger(StatisticView.class); + + private static final EntityType PERSON = new EntityType("person"); + + private final EntityManager em; + + public StatisticView(EntityManager em) { + this.em = Objects.requireNonNull(em, "em==null"); + } + + @Override + public String getName() { + return "spring-qry-statistic"; + } + + @Override + public String getCron() { + // Every second + return "* * * * * *"; + } + + @Override + public Set getEventTypes() { + return Set.of(PersonCreatedEvent.TYPE, PersonDeletedEvent.TYPE); + } + + @Override + public void handleEvents(final List events) { + for (final Event event : events) { + if (event instanceof PersonCreatedEvent ev) { + handlePersonCreatedEvent(ev); + } else if (event instanceof PersonDeletedEvent ev) { + handlePersonDeletedEvent(ev); + } else { + throw new RuntimeException("Cannot handle event: " + event); + } + } + } + + private void handlePersonCreatedEvent(final PersonCreatedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final StatisticEntity entity = em.find(StatisticEntity.class, PERSON.name()); + if (entity == null) { + em.persist(new StatisticEntity(PERSON)); + } else { + entity.inc(); + } + } + + private void handlePersonDeletedEvent(final PersonDeletedEvent event) { + LOG.info("Handle {}: {}", event.getClass().getSimpleName(), event); + final StatisticEntity entity = em.find(StatisticEntity.class, PERSON.name()); + if (entity != null) { + entity.dec(); + } + } + +} \ No newline at end of file diff --git a/spring-boot/query/src/main/resources/public/index.html b/spring-boot/query/src/main/resources/public/index.html index 2be4577..3e4f87a 100644 --- a/spring-boot/query/src/main/resources/public/index.html +++ b/spring-boot/query/src/main/resources/public/index.html @@ -9,8 +9,14 @@

CQRS Spring Boot Example Query Module

This page is served by Spring Boot. The source is in src/main/resources/public/index.html.

Try + diff --git a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java deleted file mode 100644 index 9ef454b..0000000 --- a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/api/PersonControllerIT.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.fuin.cqrs4j.example.spring.query.api; - -import io.restassured.RestAssured; -import io.restassured.module.mockmvc.RestAssuredMockMvc; -import jakarta.persistence.EntityManager; -import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; -import org.fuin.cqrs4j.example.shared.PersonId; -import org.fuin.cqrs4j.example.shared.PersonName; -import org.fuin.cqrs4j.example.spring.query.app.QryApplication; -import org.fuin.cqrs4j.example.spring.query.controller.PersonController; -import org.fuin.cqrs4j.example.spring.query.views.personlist.PersonListEntry; -import org.fuin.cqrs4j.example.spring.shared.Config; -import org.fuin.esc.api.*; -import org.fuin.esc.esgrpc.IESGrpcEventStore; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.context.WebApplicationContext; - -import java.util.Arrays; -import java.util.UUID; - -import static io.restassured.RestAssured.given; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = QryApplication.class) -class PersonControllerIT { - - @LocalServerPort - int port; - - @Autowired - WebApplicationContext wac; - - @Autowired - IESGrpcEventStore eventStore; - - @Autowired - EntityManager em; - - @Autowired - PersonController testee; - - @Autowired - Config config; - - @BeforeEach - public void initRestAssuredMockMvcStandalone() { - RestAssured.port = port; - RestAssuredMockMvc.webAppContextSetup(wac); - } - - @Test - void testGetByIdNotFound() { - given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404); - } - - public boolean findPerson(final PersonId personId) { - return em.find(PersonListEntry.class, personId.asString()) != null; - } - - @Test - void testGetByIdOK() { - - // PREPARE - final PersonId personId = new PersonId(UUID.randomUUID()); - final PersonName personName = new PersonName("Peter Parker " + personId); - final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); - final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName); - final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()), - new TypeName(event.getEventType().asBaseType()), event); - eventStore.appendToStream(personStreamId, ce); - - await().atMost(5, SECONDS).until(() -> findPerson(personId)); - - // TEST & VERIFY - - // given().pathParam("id", - // personId.asString()).when().get("/persons/{id}").then().log().all(true); - - final PersonListEntry person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then().statusCode(200) - .extract().as(PersonListEntry.class); - assertThat(person.getId(), is(equalTo(personId))); - assertThat(person.getName(), is(equalTo(personName))); - - final PersonListEntry[] persons = given().when().get("/persons").then().statusCode(200).extract().as(PersonListEntry[].class); - - assertThat(Arrays.asList(persons), is(not(empty()))); - final PersonListEntry person0 = persons[0]; - assertThat(person0.getId(), is(equalTo(personId))); - assertThat(person0.getName(), is(equalTo(personName))); - - } - -} diff --git a/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListControllerIT.java b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListControllerIT.java new file mode 100644 index 0000000..cd15f24 --- /dev/null +++ b/spring-boot/query/src/test/java/org/fuin/cqrs4j/example/spring/query/views/personlist/PersonListControllerIT.java @@ -0,0 +1,148 @@ +package org.fuin.cqrs4j.example.spring.query.views.personlist; + +import io.restassured.RestAssured; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import jakarta.persistence.EntityManager; +import org.fuin.cqrs4j.example.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.shared.PersonDeletedEvent; +import org.fuin.cqrs4j.example.shared.PersonId; +import org.fuin.cqrs4j.example.shared.PersonName; +import org.fuin.cqrs4j.example.spring.query.app.QryApplication; +import org.fuin.cqrs4j.example.spring.shared.Config; +import org.fuin.esc.api.*; +import org.fuin.esc.esgrpc.IESGrpcEventStore; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(classes = QryApplication.class) +class PersonListControllerIT { + + @LocalServerPort + int port; + + @Autowired + WebApplicationContext wac; + + @Autowired + IESGrpcEventStore eventStore; + + @Autowired + EntityManager em; + + @Autowired + PersonListController testee; + + @Autowired + Config config; + + @BeforeEach + public void initRestAssuredMockMvcStandalone() { + RestAssured.port = port; + RestAssuredMockMvc.webAppContextSetup(wac); + } + + @Test + void testGetByIdNotFound() { + given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404); + } + + public boolean findPerson(final PersonId personId) { + return em.find(PersonListEntry.class, personId.asString()) != null; + } + + @Test + void testGetByIdOK() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Peter Parker"); + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + final PersonCreatedEvent event = new PersonCreatedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()), + new TypeName(event.getEventType().asBaseType()), event); + eventStore.appendToStream(personStreamId, ce); + + await().atMost(5, SECONDS).until(() -> findPerson(personId)); + + // TEST & VERIFY + + final PersonListEntry person = + given() + .pathParam("id", personId.asString()) + .when() + .get("/persons/{id}") + .then() + .statusCode(200) + .extract().as(PersonListEntry.class); + assertThat(person.getId()).isEqualTo(personId); + assertThat(person.getName()).isEqualTo(personName); + + final PersonListEntry[] persons = + given() + .when() + .get("/persons") + .then() + .statusCode(200) + .extract().as(PersonListEntry[].class); + + assertThat(persons).isNotEmpty(); + final PersonListEntry person0 = persons[0]; + assertThat(person0.getId()).isEqualTo(personId); + assertThat(person0.getName()).isEqualTo(personName); + + } + + @Test + void testDelete() { + + // PREPARE + final PersonId personId = new PersonId(UUID.randomUUID()); + final PersonName personName = new PersonName("Delete Me Man"); + final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId); + + final PersonCreatedEvent createdEvent = new PersonCreatedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent commonCreatedEvent = new SimpleCommonEvent(new EventId(createdEvent.getEventId().asBaseType()), + new TypeName(createdEvent.getEventType().asBaseType()), createdEvent); + eventStore.appendToStream(personStreamId, commonCreatedEvent); + await().atMost(5, SECONDS).until(() -> findPerson(personId)); + + final PersonDeletedEvent deletedEvent = new PersonDeletedEvent.Builder().id(personId).name(personName).version(0).build(); + final CommonEvent commonDeletedEvent = new SimpleCommonEvent(new EventId(deletedEvent.getEventId().asBaseType()), + new TypeName(deletedEvent.getEventType().asBaseType()), deletedEvent); + eventStore.appendToStream(personStreamId, commonDeletedEvent); + await().atMost(5, SECONDS).until(() -> !findPerson(personId)); + + // TEST & VERIFY + given() + .pathParam("id", personId.asString()) + .when() + .get("/persons/{id}") + .then() + .statusCode(404); + + final PersonListEntry[] persons = + given() + .when() + .get("/persons") + .then() + .statusCode(200) + .extract().as(PersonListEntry[].class); + + assertThat(persons).doesNotContain(new PersonListEntry(personId, personName)); + + } + +} diff --git a/spring-boot/shared/pom.xml b/spring-boot/shared/pom.xml index 99a2ee1..9765394 100644 --- a/spring-boot/shared/pom.xml +++ b/spring-boot/shared/pom.xml @@ -7,7 +7,7 @@ org.fuin.cqrs4j.example.spring cqrs4j-spring-example-root - 0.3.0 + 0.4.0-SNAPSHOT ../pom.xml @@ -22,7 +22,7 @@ org.fuin.cqrs4j.example cqrs4j-example-shared - 0.3.0 + 0.4.0-SNAPSHOT @@ -60,11 +60,6 @@ esc-esgrpc - - org.fuin.esc - esc-http-admin - - org.slf4j slf4j-api diff --git a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/BeanFactory.java b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/BeanFactory.java index 8378b8c..76b12ea 100644 --- a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/BeanFactory.java +++ b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/BeanFactory.java @@ -2,14 +2,15 @@ import com.eventstore.dbclient.EventStoreDBClient; import com.eventstore.dbclient.EventStoreDBClientSettings; +import com.eventstore.dbclient.EventStoreDBProjectionManagementClient; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; import org.eclipse.yasson.FieldAccessStrategy; import org.fuin.cqrs4j.example.shared.SharedUtils; -import org.fuin.esc.admin.HttpProjectionAdminEventStore; import org.fuin.esc.api.ProjectionAdminEventStore; import org.fuin.esc.esgrpc.ESGrpcEventStore; +import org.fuin.esc.esgrpc.GrpcProjectionAdminEventStore; import org.fuin.esc.esgrpc.IESGrpcEventStore; import org.fuin.esc.spi.EnhancedMimeType; import org.fuin.esc.spi.SerDeserializerRegistry; @@ -17,9 +18,7 @@ import org.springframework.stereotype.Component; import java.net.Authenticator; -import java.net.MalformedURLException; import java.net.PasswordAuthentication; -import java.net.URL; import java.net.http.HttpClient; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -40,30 +39,42 @@ public Jsonb createJsonb() { return JsonbBuilder.create(config); } + + @Bean + public EventStoreDBClient createEventStoreDBClient(final Config config) { + final EventStoreDBClientSettings settings = EventStoreDBClientSettings.builder() + .addHost(config.getEventStoreHost(), config.getEventStoreHttpPort()) + .defaultCredentials(config.getEventStoreUser(), config.getEventStorePassword()) + .tls(false) + .buildConnectionSettings(); + return EventStoreDBClient.create(settings); + } + + @Bean + public EventStoreDBProjectionManagementClient createEventStoreDBProjectionManagementClient(final Config config) { + final EventStoreDBClientSettings settings = EventStoreDBClientSettings.builder() + .addHost(config.getEventStoreHost(), config.getEventStoreHttpPort()) + .defaultCredentials(config.getEventStoreUser(), config.getEventStorePassword()) + .tls(false) + .buildConnectionSettings(); + return EventStoreDBProjectionManagementClient.create(settings); + } + + /** * Creates an event store connection. * - * @param config Configuration to use. + * @param client Client to use. * @return New event store instance. */ @Bean(destroyMethod = "close") - public IESGrpcEventStore getESJCEventStore(final Config config) { + public IESGrpcEventStore getESJCEventStore(final EventStoreDBClient client) { final SerDeserializerRegistry registry = SharedUtils.createRegistry(); - final EventStoreDBClientSettings setts = EventStoreDBClientSettings.builder() - .addHost(config.getEventStoreHost(), config.getEventStoreHttpPort()) - .defaultCredentials(config.getEventStoreUser(), config.getEventStorePassword()) - .tls(false) - .buildConnectionSettings(); - - final EventStoreDBClient client = EventStoreDBClient.create(setts); - final IESGrpcEventStore eventstore = new ESGrpcEventStore.Builder().eventStore(client).serDesRegistry(registry) + return new ESGrpcEventStore.Builder().eventStore(client).serDesRegistry(registry) .targetContentType(EnhancedMimeType.create("application", "json", StandardCharsets.UTF_8)) - .build(); - - eventstore.open(); - return eventstore; + .build().open(); } @@ -82,22 +93,14 @@ protected PasswordAuthentication getPasswordAuthentication() { /** - * Creates an HTTP based projection admin event store. + * Creates an GRPC based projection admin event store. * - * @param config Configuration to use. - * @param httpClient Client to use. + * @param client Client to use. * @return New event store instance. */ @Bean(destroyMethod = "close") - public ProjectionAdminEventStore getProjectionAdminEventStore(final Config config, final HttpClient httpClient) { - final String url = config.getEventStoreProtocol() + "://" + config.getEventStoreHost() + ":" + config.getEventStoreHttpPort(); - try { - final ProjectionAdminEventStore es = new HttpProjectionAdminEventStore(httpClient, new URL(url)); - es.open(); - return es; - } catch (final MalformedURLException ex) { - throw new RuntimeException("Failed to create URL: " + url, ex); - } + public ProjectionAdminEventStore getProjectionAdminEventStore(final EventStoreDBProjectionManagementClient client) { + return new GrpcProjectionAdminEventStore(client).open(); } } diff --git a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/GlobalExceptionHandler.java b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/GlobalExceptionHandler.java index c0f7566..f05ac35 100644 --- a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/GlobalExceptionHandler.java +++ b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/GlobalExceptionHandler.java @@ -1,31 +1,10 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.spring.shared; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; - import org.fuin.cqrs4j.CommandExecutionFailedException; import org.fuin.cqrs4j.SimpleResult; -import org.fuin.ddd4j.ddd.AggregateAlreadyExistsException; -import org.fuin.ddd4j.ddd.AggregateDeletedException; -import org.fuin.ddd4j.ddd.AggregateNotFoundException; -import org.fuin.ddd4j.ddd.AggregateVersionConflictException; -import org.fuin.ddd4j.ddd.AggregateVersionNotFoundException; +import org.fuin.ddd4j.ddd.*; import org.fuin.objects4j.common.Contract; import org.fuin.objects4j.common.ExceptionShortIdentifable; import org.fuin.objects4j.common.Nullable; @@ -38,6 +17,10 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + /** * Maps the exceptions into a HTTP status. */ diff --git a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/package-info.java b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/package-info.java index 635443f..9d38ce6 100644 --- a/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/package-info.java +++ b/spring-boot/shared/src/main/java/org/fuin/cqrs4j/example/spring/shared/package-info.java @@ -1,15 +1,3 @@ -/** - * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ - * - * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see - * http://www.gnu.org/licenses/. - */ package org.fuin.cqrs4j.example.spring.shared; /**