JAMES-2393 Introduce dedicated modules for event sourcing

This intend to ease future usage, external usage as well as allow a clear view.


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/189490a4
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/189490a4
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/189490a4

Branch: refs/heads/master
Commit: 189490a4eaabaa9876a0c73b3f5f035ec6565c65
Parents: c2b85a0
Author: benwa <btell...@linagora.com>
Authored: Thu May 10 11:29:21 2018 +0700
Committer: Matthieu Baechler <matth...@apache.org>
Committed: Thu May 17 09:50:00 2018 +0200

----------------------------------------------------------------------
 event-sourcing/event-sourcing-core/pom.xml      |  90 +++++++
 .../james/eventsourcing/CommandDispatcher.java  | 110 ++++++++
 .../james/eventsourcing/CommandHandler.java     |  29 +++
 .../apache/james/eventsourcing/EventBus.java    |  63 +++++
 .../eventsourcing/EventSourcingSystem.java      |  37 +++
 .../apache/james/eventsourcing/Subscriber.java  |  24 ++
 .../eventsourcing/DataCollectorSubscriber.java  |  46 ++++
 .../eventsourcing/EventSourcingSystemTest.java  | 252 +++++++++++++++++++
 event-sourcing/event-sourcing-pojo/pom.xml      |  67 +++++
 .../apache/james/eventsourcing/AggregateId.java |  24 ++
 .../org/apache/james/eventsourcing/Command.java |  23 ++
 .../org/apache/james/eventsourcing/Event.java   |  43 ++++
 .../org/apache/james/eventsourcing/EventId.java |  86 +++++++
 .../apache/james/eventsourcing/EventIdTest.java |  85 +++++++
 .../james/eventsourcing/TestAggregateId.java    |  68 +++++
 .../apache/james/eventsourcing/TestEvent.java   |  82 ++++++
 event-sourcing/event-store-api/pom.xml          |  77 ++++++
 .../eventsourcing/eventstore/EventStore.java    |  47 ++++
 .../eventstore/EventStoreFailedException.java   |  23 ++
 .../james/eventsourcing/eventstore/History.java |  94 +++++++
 .../eventstore/EventStoreTest.java              |  88 +++++++
 .../eventsourcing/eventstore/HistoryTest.java   |  91 +++++++
 event-sourcing/event-store-cassandra/pom.xml    | 131 ++++++++++
 .../cassandra/CassandraEventStore.java          |  64 +++++
 .../cassandra/CassandraEventStoreModule.java    |  62 +++++
 .../cassandra/CassandraEventStoreTable.java     |  27 ++
 .../eventstore/cassandra/EventStoreDao.java     | 122 +++++++++
 .../cassandra/JsonEventSerializer.java          |  98 ++++++++
 .../eventstore/cassandra/dto/EventDTO.java      |  26 ++
 .../cassandra/dto/EventDTOModule.java           |  32 +++
 .../CassandraEventSourcingSystemTest.java       |  28 +++
 .../cassandra/CassandraEventStoreExtension.java |  30 +++
 .../cassandra/CassandraEventStoreTest.java      |  28 +++
 .../CassandraGenericEventStoreExtension.java    |  87 +++++++
 .../cassandra/JsonEventSerializerTest.java      | 102 ++++++++
 .../eventstore/cassandra/dto/OtherEvent.java    |  50 ++++
 .../cassandra/dto/OtherTestEventDTO.java        |  72 ++++++
 .../cassandra/dto/OtherTestEventDTOModule.java  |  55 ++++
 .../eventstore/cassandra/dto/TestEventDTO.java  |  73 ++++++
 .../cassandra/dto/TestEventDTOModule.java       |  56 +++++
 event-sourcing/event-store-memory/pom.xml       |  86 +++++++
 .../eventstore/memory/InMemoryEventStore.java   |  99 ++++++++
 .../memory/InMemoryEventSourcingSystemTest.java |  28 +++
 .../memory/InMemoryEventStoreExtension.java     |  39 +++
 .../memory/InMemoryEventStoreTest.java          |  28 +++
 event-sourcing/pom.xml                          |  44 ++++
 mailbox/plugin/quota-mailing-cassandra/pom.xml  |  21 ++
 .../cassandra/CassandraEventStore.java          |  62 -----
 .../cassandra/CassandraEventStoreModule.java    |  62 -----
 .../cassandra/CassandraEventStoreTable.java     |  27 --
 .../eventsourcing/cassandra/EventStoreDao.java  | 122 ---------
 .../cassandra/JsonEventSerializer.java          |  98 --------
 .../eventsourcing/cassandra/dto/EventDTO.java   |  26 --
 .../cassandra/dto/EventDTOModule.java           |  32 ---
 .../dto/QuotaThresholdChangedEventDTO.java      |   2 +-
 .../QuotaThresholdChangedEventDTOModule.java    |   4 +-
 .../CassandraEventSourcingSystemTest.java       |  28 ---
 .../cassandra/CassandraEventStoreExtension.java |  86 -------
 .../cassandra/CassandraEventStoreTest.java      |  28 ---
 .../cassandra/JsonEventSerializerTest.java      | 102 --------
 .../eventsourcing/cassandra/dto/OtherEvent.java |  50 ----
 .../cassandra/dto/OtherTestEventDTO.java        |  72 ------
 .../cassandra/dto/OtherTestEventDTOModule.java  |  55 ----
 .../cassandra/dto/TestEventDTO.java             |  73 ------
 .../cassandra/dto/TestEventDTOModule.java       |  56 -----
 .../mailbox/quota/cassandra/dto/DTOTest.java    |  30 +--
 .../listeners/CassandraEventStoreExtension.java |  32 +++
 ...draQuotaMailingListenersIntegrationTest.java |   1 -
 mailbox/plugin/quota-mailing-memory/pom.xml     |  21 ++
 .../james/eventsource/InMemoryEventStore.java   |  97 -------
 .../InMemoryEventSourcingSystemTest.java        |  27 --
 .../InMemoryEventStoreExtension.java            |  39 ---
 .../eventsourcing/InMemoryEventStoreTest.java   |  27 --
 ...oryQuotaMailingListenersIntegrationTest.java |   2 +-
 ...yQuotaThresholdConfigurationChangesTest.java |   2 +-
 mailbox/plugin/quota-mailing/pom.xml            |  14 ++
 .../apache/james/eventsourcing/AggregateId.java |  24 --
 .../james/eventsourcing/CommandDispatcher.java  | 117 ---------
 .../org/apache/james/eventsourcing/Event.java   |  43 ----
 .../apache/james/eventsourcing/EventBus.java    |  61 -----
 .../org/apache/james/eventsourcing/EventId.java |  86 -------
 .../eventsourcing/EventSourcingSystem.java      |  36 ---
 .../apache/james/eventsourcing/EventStore.java  | 114 ---------
 .../apache/james/eventsourcing/Subscriber.java  |  24 --
 .../mailing/aggregates/UserQuotaThresholds.java |   8 +-
 .../commands/DetectThresholdCrossing.java       |   4 +-
 .../DetectThresholdCrossingHandler.java         |   9 +-
 .../eventsourcing/DataCollectorSubscriber.java  |  46 ----
 .../apache/james/eventsourcing/EventIdTest.java |  85 -------
 .../eventsourcing/EventSourcingSystemTest.java  | 250 ------------------
 .../james/eventsourcing/EventStoreTest.java     |  75 ------
 .../apache/james/eventsourcing/HistoryTest.java |  87 -------
 .../james/eventsourcing/TestAggregateId.java    |  68 -----
 .../apache/james/eventsourcing/TestEvent.java   |  82 ------
 .../QuotaThresholdConfigurationChangesTest.java |   2 +-
 .../QuotaThresholdListenersTestSystem.java      |   2 +-
 .../QuotaThresholdMailingIntegrationTest.java   |   2 +-
 pom.xml                                         |  61 +++++
 98 files changed, 3199 insertions(+), 2401 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/pom.xml
----------------------------------------------------------------------
diff --git a/event-sourcing/event-sourcing-core/pom.xml 
b/event-sourcing/event-sourcing-core/pom.xml
new file mode 100644
index 0000000..7b7c6e1
--- /dev/null
+++ b/event-sourcing/event-sourcing-core/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.james</groupId>
+        <artifactId>event-sourcing</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>event-sourcing-core</artifactId>
+
+    <name>Apache James :: Event sourcing :: core</name>
+    <description>James Event Sourcing system</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-event-store-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-pojo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-pojo</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.steveash.guavate</groupId>
+            <artifactId>guavate</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
new file mode 100644
index 0000000..15d128c
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandDispatcher.java
@@ -0,0 +1,110 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
+import javax.inject.Inject;
+
+import org.apache.james.eventsourcing.eventstore.EventStoreFailedException;
+
+import com.github.steveash.guavate.Guavate;
+
+public class CommandDispatcher {
+
+    private static final int MAX_RETRY = 10;
+
+    public class UnknownCommandException extends RuntimeException {
+        private final Command command;
+
+        public UnknownCommandException(Command command) {
+            super(String.format("Unknown command %s", command));
+            this.command = command;
+        }
+
+        public Command getCommand() {
+            return command;
+        }
+    }
+
+    public class TooManyRetries extends RuntimeException {
+        private final Command command;
+        private final int retries;
+
+
+        public TooManyRetries(Command command, int retries) {
+            super(String.format("Too much retries for command %s. Store 
failure after %d retries", command, retries));
+            this.command = command;
+            this.retries = retries;
+        }
+
+
+        public Command getCommand() {
+            return command;
+        }
+
+        public int getRetries() {
+            return retries;
+        }
+    }
+
+    private final EventBus eventBus;
+    @SuppressWarnings("rawtypes")
+    private final Map<Class, CommandHandler> handlers;
+
+    @Inject
+    public CommandDispatcher(EventBus eventBus, Set<CommandHandler<?>> 
handlers) {
+        this.eventBus = eventBus;
+        this.handlers = handlers.stream()
+            .collect(Guavate.toImmutableMap(CommandHandler::handledClass, 
handler -> handler));
+    }
+
+    public void dispatch(Command c) {
+        trySeveralTimes(() -> tryDispatch(c))
+            .orElseThrow(() -> new TooManyRetries(c, MAX_RETRY));
+    }
+
+    public Optional<Integer> trySeveralTimes(Supplier<Boolean> singleTry) {
+        return IntStream.range(0, MAX_RETRY)
+            .boxed()
+            .filter(any -> singleTry.get())
+            .findFirst();
+    }
+
+    @SuppressWarnings("unchecked")
+    private boolean tryDispatch(Command c) {
+        try {
+            List<Event> events =
+                Optional.ofNullable(handlers.get(c.getClass()))
+                    .map(f -> f.handle(c))
+                    .orElseThrow(() -> new UnknownCommandException(c));
+
+            eventBus.publish(events);
+            return true;
+        } catch (EventStoreFailedException e) {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandHandler.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandHandler.java
 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandHandler.java
new file mode 100644
index 0000000..6b8aacf
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/CommandHandler.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.List;
+
+public interface CommandHandler<C> {
+
+    Class<C> handledClass();
+
+    List<? extends Event> handle(C c);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventBus.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventBus.java
 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventBus.java
new file mode 100644
index 0000000..289433e
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventBus.java
@@ -0,0 +1,63 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.EventStoreFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+
+public class EventBus {
+
+    public static final Logger LOGGER = 
LoggerFactory.getLogger(EventBus.class);
+    private final EventStore eventStore;
+    private final Set<Subscriber> subscribers;
+
+    @Inject
+    public EventBus(EventStore eventStore, Set<Subscriber> subscribers) {
+        this.eventStore = eventStore;
+        this.subscribers = ImmutableSet.copyOf(subscribers);
+    }
+
+    public void publish(List<Event> events) throws EventStoreFailedException {
+        eventStore.appendAll(events);
+        events.stream()
+            .flatMap(event -> subscribers.stream().map(subscriber -> 
Pair.of(event, subscriber)))
+            .forEach(this::handle);
+    }
+
+    public void handle(Pair<Event, Subscriber> pair) {
+        Subscriber subscriber = pair.getRight();
+        Event event = pair.getLeft();
+        try {
+            subscriber.handle(event);
+        } catch (Exception e) {
+            LOGGER.error("Error while calling {} for {}", subscriber, event, 
e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
new file mode 100644
index 0000000..e077fe6
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/EventSourcingSystem.java
@@ -0,0 +1,37 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.Set;
+
+import org.apache.james.eventsourcing.eventstore.EventStore;
+
+public class EventSourcingSystem {
+    private final CommandDispatcher commandDispatcher;
+
+    public EventSourcingSystem(Set<CommandHandler<?>> handlers, 
Set<Subscriber> subscribers, EventStore eventStore) {
+        EventBus eventBus = new EventBus(eventStore, subscribers);
+        this.commandDispatcher = new CommandDispatcher(eventBus, handlers);
+    }
+
+    public void dispatch(Command c) {
+        commandDispatcher.dispatch(c);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/Subscriber.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/Subscriber.java
 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/Subscriber.java
new file mode 100644
index 0000000..42a804d
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/main/java/org/apache/james/eventsourcing/Subscriber.java
@@ -0,0 +1,24 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+public interface Subscriber {
+    void handle(Event event);
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/DataCollectorSubscriber.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/DataCollectorSubscriber.java
 
b/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/DataCollectorSubscriber.java
new file mode 100644
index 0000000..85fccd1
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/DataCollectorSubscriber.java
@@ -0,0 +1,46 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+public class DataCollectorSubscriber implements Subscriber {
+
+    private final List<String> data;
+
+    public DataCollectorSubscriber() {
+        data = new ArrayList<>();
+    }
+
+    @Override
+    public void handle(Event event) {
+        if (event instanceof TestEvent) {
+            data.add(((TestEvent) event).getData());
+        }
+    }
+
+
+    public List<String> getData() {
+        return ImmutableList.copyOf(data);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/EventSourcingSystemTest.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/EventSourcingSystemTest.java
 
b/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/EventSourcingSystemTest.java
new file mode 100644
index 0000000..02c9eec
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-core/src/test/java/org/apache/james/eventsourcing/EventSourcingSystemTest.java
@@ -0,0 +1,252 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import org.apache.james.eventsourcing.eventstore.EventStore;
+import org.apache.james.eventsourcing.eventstore.History;
+import org.junit.jupiter.api.Test;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public interface EventSourcingSystemTest {
+
+    String PAYLOAD_1 = "payload1";
+    String PAYLOAD_2 = "payload2";
+    TestAggregateId AGGREGATE_ID = TestAggregateId.testId(42);
+
+    class MyCommand implements Command {
+        private final String payload;
+
+        public MyCommand(String payload) {
+            this.payload = payload;
+        }
+
+        public String getPayload() {
+            return payload;
+        }
+    }
+
+    @Test
+    default void 
dispatchShouldApplyCommandHandlerThenCallSubscribers(EventStore eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_1));
+
+        assertThat(subscriber.getData()).containsExactly(PAYLOAD_1);
+    }
+
+    @Test
+    default void throwingSubscribersShouldNotAbortSubscriberChain(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(
+                events -> {
+                    throw new RuntimeException();
+                },
+                subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_1));
+
+        assertThat(subscriber.getData()).containsExactly(PAYLOAD_1);
+    }
+
+    @Test
+    default void throwingStoreShouldNotLeadToPusblishing() {
+        EventStore eventStore = mock(EventStore.class);
+        doThrow(new 
RuntimeException()).when(eventStore).appendAll(anyListOf(Event.class));
+        
when(eventStore.getEventsOfAggregate(any())).thenReturn(History.empty());
+
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(
+                events -> {
+                    throw new RuntimeException();
+                },
+                subscriber),
+            eventStore);
+
+        assertThatThrownBy(() -> eventSourcingSystem.dispatch(new 
MyCommand(PAYLOAD_1)))
+            .isInstanceOf(RuntimeException.class);
+
+        assertThat(subscriber.getData()).isEmpty();
+    }
+
+    @Test
+    default void 
dispatchShouldApplyCommandHandlerThenStoreGeneratedEvents(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_1));
+
+        TestEvent expectedEvent = new TestEvent(EventId.first(), AGGREGATE_ID, 
PAYLOAD_1);
+        assertThat(eventStore.getEventsOfAggregate(AGGREGATE_ID).getEvents())
+            .containsOnly(expectedEvent);
+    }
+
+    @Test
+    default void dispatchShouldCallSubscriberForSubsequentCommands(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_1));
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_2));
+
+        assertThat(subscriber.getData()).containsExactly(PAYLOAD_1, PAYLOAD_2);
+    }
+
+    @Test
+    default void dispatchShouldStoreEventsForSubsequentCommands(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(simpleDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_1));
+        eventSourcingSystem.dispatch(new MyCommand(PAYLOAD_2));
+
+        TestEvent expectedEvent1 = new TestEvent(EventId.first(), 
AGGREGATE_ID, PAYLOAD_1);
+        TestEvent expectedEvent2 = new 
TestEvent(expectedEvent1.eventId().next(), AGGREGATE_ID, PAYLOAD_2);
+        assertThat(eventStore.getEventsOfAggregate(AGGREGATE_ID).getEvents())
+            .containsOnly(expectedEvent1, expectedEvent2);
+    }
+
+    @Test
+    default void dispatcherShouldBeAbleToReturnSeveralEvents(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(wordCuttingDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        eventSourcingSystem.dispatch(new MyCommand("This is a test"));
+
+        assertThat(subscriber.getData()).containsExactly("This", "is", "a", 
"test");
+    }
+
+    @Test
+    default void unknownCommandsShouldBeIgnored(EventStore eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+        EventSourcingSystem eventSourcingSystem = new EventSourcingSystem(
+            ImmutableSet.of(wordCuttingDispatcher(eventStore)),
+            ImmutableSet.of(subscriber),
+            eventStore);
+
+        assertThatThrownBy(() -> eventSourcingSystem.dispatch(new Command() 
{}))
+            .isInstanceOf(CommandDispatcher.UnknownCommandException.class);
+    }
+
+    @Test
+    default void 
constructorShouldThrowWhenSeveralHandlersForTheSameCommand(EventStore 
eventStore) {
+        DataCollectorSubscriber subscriber = new DataCollectorSubscriber();
+
+        assertThatThrownBy(() ->
+            new EventSourcingSystem(
+                ImmutableSet.of(wordCuttingDispatcher(eventStore),
+                    simpleDispatcher(eventStore)),
+                ImmutableSet.of(subscriber),
+                eventStore))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    default CommandHandler<MyCommand> simpleDispatcher(EventStore eventStore) {
+        return new CommandHandler<MyCommand>() {
+            @Override
+            public Class<MyCommand> handledClass() {
+                return MyCommand.class;
+            }
+
+            @Override
+            public List<? extends Event> handle(MyCommand myCommand) {
+                History history = 
eventStore.getEventsOfAggregate(AGGREGATE_ID);
+
+                return ImmutableList.of(new TestEvent(
+                    history.getNextEventId(),
+                    AGGREGATE_ID,
+                    myCommand.getPayload()));
+            }
+        };
+    }
+
+    default CommandHandler<MyCommand> wordCuttingDispatcher(EventStore 
eventStore) {
+        return new CommandHandler<MyCommand>() {
+            @Override
+            public Class<MyCommand> handledClass() {
+                return MyCommand.class;
+            }
+
+            @Override
+            public List<? extends Event> handle(MyCommand myCommand) {
+                History history = 
eventStore.getEventsOfAggregate(AGGREGATE_ID);
+
+                EventIdIncrementer eventIdIncrementer = new 
EventIdIncrementer(history.getNextEventId());
+
+                return Splitter.on(" ")
+                    .splitToList(myCommand.getPayload())
+                    .stream()
+                    .map(word -> new TestEvent(
+                        eventIdIncrementer.next(),
+                        AGGREGATE_ID,
+                        word))
+                    .collect(Guavate.toImmutableList());
+            }
+        };
+    }
+
+    class EventIdIncrementer {
+        private EventId currentEventId;
+
+        public EventIdIncrementer(EventId base) {
+            this.currentEventId = base;
+        }
+
+        public EventId next() {
+            currentEventId = currentEventId.next();
+            return currentEventId;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/pom.xml
----------------------------------------------------------------------
diff --git a/event-sourcing/event-sourcing-pojo/pom.xml 
b/event-sourcing/event-sourcing-pojo/pom.xml
new file mode 100644
index 0000000..c27130f
--- /dev/null
+++ b/event-sourcing/event-sourcing-pojo/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.james</groupId>
+        <artifactId>event-sourcing</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>event-sourcing-pojo</artifactId>
+
+    <name>Apache James :: Event sourcing :: pojo</name>
+    <description>James event sourcing types</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.steveash.guavate</groupId>
+            <artifactId>guavate</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/AggregateId.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/AggregateId.java
 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/AggregateId.java
new file mode 100644
index 0000000..18c6224
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/AggregateId.java
@@ -0,0 +1,24 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+public interface AggregateId {
+    String asAggregateKey();
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Command.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Command.java
 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Command.java
new file mode 100644
index 0000000..dad8332
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Command.java
@@ -0,0 +1,23 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+public interface Command {
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Event.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Event.java
 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Event.java
new file mode 100644
index 0000000..2b31374
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Event.java
@@ -0,0 +1,43 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.List;
+
+public interface Event extends Comparable<Event> {
+
+    static boolean belongsToSameAggregate(List<? extends Event> events) {
+        return events.stream()
+            .map(Event::getAggregateId)
+            .distinct()
+            .limit(2)
+            .count() == 1;
+    }
+
+    EventId eventId();
+
+    AggregateId getAggregateId();
+
+    @Override
+    default int compareTo(Event o) {
+        return eventId().compareTo(o.eventId());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/EventId.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/EventId.java
 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/EventId.java
new file mode 100644
index 0000000..cb5bd1e
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/EventId.java
@@ -0,0 +1,86 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class EventId implements Comparable<EventId> {
+
+    public static EventId fromSerialized(int value) {
+        return new EventId(value);
+    }
+
+    public static EventId first() {
+        return new EventId(0);
+    }
+
+    private final int value;
+
+    private EventId(int value) {
+        Preconditions.checkArgument(value >= 0, "EventId can not be negative");
+        this.value = value;
+    }
+
+    public EventId next() {
+        return new EventId(value + 1);
+    }
+
+    public Optional<EventId> previous() {
+        if (value > 0) {
+            return Optional.of(new EventId(value - 1));
+        }
+        return Optional.empty();
+    }
+
+    @Override
+    public int compareTo(EventId o) {
+        return Long.compare(value, o.value);
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof EventId) {
+            EventId eventId = (EventId) o;
+
+            return Objects.equals(this.value, eventId.value);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(value);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("value", value)
+            .toString();
+    }
+
+    public int serialize() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/EventIdTest.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/EventIdTest.java
 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/EventIdTest.java
new file mode 100644
index 0000000..1b46fbf
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/EventIdTest.java
@@ -0,0 +1,85 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class EventIdTest {
+
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(EventId.class)
+            .allFieldsShouldBeUsed()
+            .verify();
+    }
+
+    @Test
+    void firstShouldReturnAConstant() {
+        assertThat(EventId.first())
+            .isEqualTo(EventId.first());
+    }
+
+    @Test
+    void previousShouldReturnEmptyWhenBeforeFirst() {
+        assertThat(EventId.first().previous())
+            .isEmpty();
+    }
+
+    @Test
+    void compareToShouldReturnNegativeWhenComparedToNext() {
+        assertThat(EventId.first())
+            .isLessThan(EventId.first().next());
+    }
+
+    @Test
+    void compareToShouldReturnNegativeWhenComparedToPrevious() {
+        assertThat(EventId.first().next())
+            .isGreaterThan(EventId.first());
+    }
+
+    @Test
+    void nextShouldAlwaysHaveTheSameIncrement() {
+        assertThat(EventId.first().next())
+            .isEqualTo(EventId.first().next());
+    }
+
+    @Test
+    void previousShouldRevertNext() {
+        assertThat(EventId.first().next().previous())
+            .contains(EventId.first());
+    }
+
+    @Test
+    void compareToShouldReturnNegativeWhenComparedToNextWithPreviousCall() {
+        assertThat(EventId.first().next().previous().get())
+            .isLessThan(EventId.first().next());
+    }
+
+    @Test
+    void compareToShouldReturnNegativeWhenComparedToPreviousWithPreviousCall() 
{
+        assertThat(EventId.first().next())
+            .isGreaterThan(EventId.first().next().previous().get());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestAggregateId.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestAggregateId.java
 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestAggregateId.java
new file mode 100644
index 0000000..b3ae78c
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestAggregateId.java
@@ -0,0 +1,68 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+public class TestAggregateId implements AggregateId  {
+
+    public static TestAggregateId testId(int id) {
+        return new TestAggregateId(id);
+    }
+
+    private final int id;
+
+    private TestAggregateId(int id) {
+        this.id = id;
+    }
+
+    @Override
+    public String asAggregateKey() {
+        return "TestAggregateId-" + id;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof TestAggregateId) {
+            TestAggregateId that = (TestAggregateId) o;
+
+            return Objects.equals(this.id, that.id);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("id", id)
+            .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestEvent.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestEvent.java
 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestEvent.java
new file mode 100644
index 0000000..c46f804
--- /dev/null
+++ 
b/event-sourcing/event-sourcing-pojo/src/test/java/org/apache/james/eventsourcing/TestEvent.java
@@ -0,0 +1,82 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+public class TestEvent implements Event {
+    private final EventId id;
+    private final TestAggregateId aggregateId;
+    private final String data;
+
+    public TestEvent(EventId id, TestAggregateId aggregateId, String data) {
+        this.id = id;
+        this.aggregateId = aggregateId;
+        this.data = data;
+    }
+
+    @Override
+    public EventId eventId() {
+        return id;
+    }
+
+    @Override
+    public TestAggregateId getAggregateId() {
+        return aggregateId;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    @Override
+    public int compareTo(Event o) {
+        return Comparator.<EventId>naturalOrder().compare(id, o.eventId());
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof TestEvent) {
+            TestEvent testEvent = (TestEvent) o;
+
+            return Objects.equals(this.id, testEvent.id)
+                && Objects.equals(this.aggregateId, testEvent.aggregateId)
+                && Objects.equals(this.data, testEvent.data);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, aggregateId, data);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("id", id)
+            .add("aggregateId", aggregateId)
+            .add("data", data)
+            .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/pom.xml
----------------------------------------------------------------------
diff --git a/event-sourcing/event-store-api/pom.xml 
b/event-sourcing/event-store-api/pom.xml
new file mode 100644
index 0000000..2cfd710
--- /dev/null
+++ b/event-sourcing/event-store-api/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.james</groupId>
+        <artifactId>event-sourcing</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>event-sourcing-event-store-api</artifactId>
+
+    <name>Apache James :: Event Sourcing :: Event Store :: API</name>
+    <description>James Event Sourcing interface for Event Store 
implementations</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-pojo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-pojo</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.steveash.guavate</groupId>
+            <artifactId>guavate</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStore.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStore.java
 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStore.java
new file mode 100644
index 0000000..3b3a95e
--- /dev/null
+++ 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStore.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing.eventstore;
+
+import java.util.List;
+
+import org.apache.james.eventsourcing.AggregateId;
+import org.apache.james.eventsourcing.Event;
+
+import com.google.common.collect.ImmutableList;
+
+public interface EventStore {
+
+    default void append(Event event) {
+        appendAll(ImmutableList.of(event));
+    }
+
+    default void appendAll(Event... events) {
+        appendAll(ImmutableList.copyOf(events));
+    }
+
+    /**
+     * This method should check that no input event has an id already stored 
and throw otherwise
+     * It should also check that all events belong to the same aggregate
+     */
+    void appendAll(List<Event> events);
+
+    History getEventsOfAggregate(AggregateId aggregateId);
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStoreFailedException.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStoreFailedException.java
 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStoreFailedException.java
new file mode 100644
index 0000000..93bcb2f
--- /dev/null
+++ 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/EventStoreFailedException.java
@@ -0,0 +1,23 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing.eventstore;
+
+public class EventStoreFailedException extends RuntimeException {
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/History.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/History.java
 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/History.java
new file mode 100644
index 0000000..5a91306
--- /dev/null
+++ 
b/event-sourcing/event-store-api/src/main/java/org/apache/james/eventsourcing/eventstore/History.java
@@ -0,0 +1,94 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing.eventstore;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import org.apache.james.eventsourcing.Event;
+import org.apache.james.eventsourcing.EventId;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+public class History {
+    public static History empty() {
+        return new History(ImmutableList.of());
+    }
+
+    public static History of(List<Event> events) {
+        return new History(ImmutableList.copyOf(events));
+    }
+
+    public static History of(Event... events) {
+        return of(ImmutableList.copyOf(events));
+    }
+
+    private final List<Event> events;
+
+    private History(List<Event> events) {
+        if (hasEventIdDuplicates(events)) {
+            throw new EventStoreFailedException();
+        }
+        this.events = events;
+    }
+
+    public boolean hasEventIdDuplicates(List<Event> events) {
+        Set<EventId> eventIds = events.stream()
+            .map(Event::eventId)
+            .collect(Guavate.toImmutableSet());
+
+        return eventIds.size() != events.size();
+    }
+
+    public Optional<EventId> getVersion() {
+        return events.stream()
+            .map(Event::eventId)
+            .max(Comparator.naturalOrder());
+    }
+
+    public List<Event> getEvents() {
+        return events;
+    }
+
+    public EventId getNextEventId() {
+        return getVersion()
+            .map(EventId::next)
+            .orElse(EventId.first());
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof History) {
+            History history = (History) o;
+
+            return Objects.equals(this.events, history.events);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(events);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/EventStoreTest.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/EventStoreTest.java
 
b/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/EventStoreTest.java
new file mode 100644
index 0000000..dc312d9
--- /dev/null
+++ 
b/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/EventStoreTest.java
@@ -0,0 +1,88 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing.eventstore;
+
+import static org.apache.james.eventsourcing.TestAggregateId.testId;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.eventsourcing.EventId;
+import org.apache.james.eventsourcing.TestAggregateId;
+import org.apache.james.eventsourcing.TestEvent;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public interface EventStoreTest {
+
+    TestAggregateId AGGREGATE_1 = testId(1);
+    TestAggregateId AGGREGATE_2 = testId(2);
+
+    @Test
+    default void getEventsOfAggregateShouldThrowOnNullAggregateId(EventStore 
testee) {
+        assertThatThrownBy(() -> testee.getEventsOfAggregate(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    default void appendShouldThrowWhenEventFromSeveralAggregates(EventStore 
testee) {
+        TestEvent event1 = new TestEvent(EventId.first(), AGGREGATE_1, 
"first");
+        TestEvent event2 = new TestEvent(event1.eventId().next(), AGGREGATE_2, 
"second");
+        assertThatThrownBy(() -> testee.appendAll(event1, 
event2)).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void appendShouldDoNothingOnEmptyEventList(EventStore testee) {
+        assertThatCode(testee::appendAll).doesNotThrowAnyException();
+    }
+
+    @Test
+    default void appendShouldThrowWhenTryingToRewriteHistory(EventStore 
testee) {
+        TestEvent event1 = new TestEvent(EventId.first(), AGGREGATE_1, 
"first");
+        testee.append(event1);
+        TestEvent event2 = new TestEvent(EventId.first(), AGGREGATE_1, 
"second");
+        assertThatThrownBy(() -> 
testee.append(event2)).isInstanceOf(EventStoreFailedException.class);
+    }
+
+    @Test
+    default void 
getEventsOfAggregateShouldReturnEmptyHistoryWhenUnknown(EventStore testee) {
+        
assertThat(testee.getEventsOfAggregate(AGGREGATE_1)).isEqualTo(History.empty());
+    }
+
+    @Test
+    default void getEventsOfAggregateShouldReturnAppendedEvent(EventStore 
testee) {
+        TestEvent event = new TestEvent(EventId.first(), AGGREGATE_1, "first");
+        testee.append(event);
+        assertThat(testee.getEventsOfAggregate(AGGREGATE_1))
+            .isEqualTo(History.of(ImmutableList.of(event)));
+    }
+
+    @Test
+    default void getEventsOfAggregateShouldReturnAppendedEvents(EventStore 
testee) {
+        TestEvent event1 = new TestEvent(EventId.first(), AGGREGATE_1, 
"first");
+        TestEvent event2 = new TestEvent(event1.eventId().next(), AGGREGATE_1, 
"second");
+        testee.append(event1);
+        testee.append(event2);
+        assertThat(testee.getEventsOfAggregate(AGGREGATE_1))
+            .isEqualTo(History.of(ImmutableList.of(event1, event2)));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/HistoryTest.java
----------------------------------------------------------------------
diff --git 
a/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/HistoryTest.java
 
b/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/HistoryTest.java
new file mode 100644
index 0000000..2945b92
--- /dev/null
+++ 
b/event-sourcing/event-store-api/src/test/java/org/apache/james/eventsourcing/eventstore/HistoryTest.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.eventsourcing.eventstore;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.eventsourcing.EventId;
+import org.apache.james.eventsourcing.TestAggregateId;
+import org.apache.james.eventsourcing.TestEvent;
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class HistoryTest {
+
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(History.class)
+            .allFieldsShouldBeUsed()
+            .verify();
+    }
+
+    @Test
+    void emptyShouldGenerateAnEmptyHistory() {
+        assertThat(History.empty())
+            .isEqualTo(History.of());
+    }
+
+    @Test
+    void getVersionShouldReturnEmptyWhenEmpty() {
+        assertThat(History.empty()
+            .getVersion())
+            .isEmpty();
+    }
+
+    @Test
+    void getVersionShouldReturnSingleEventIdWhenSingleEvent() {
+        assertThat(History
+            .of(new TestEvent(EventId.first(),
+                TestAggregateId.testId(42),
+                "any"))
+            .getVersion())
+            .contains(EventId.first());
+    }
+
+    @Test
+    void getVersionShouldReturnHighestEventId() {
+        TestEvent event1 = new TestEvent(EventId.first(),
+            TestAggregateId.testId(42),
+            "any");
+        TestEvent event2 = new TestEvent(event1.eventId().next(),
+            TestAggregateId.testId(42),
+            "any");
+
+        assertThat(History.of(event1, event2)
+            .getVersion())
+            .contains(event2.eventId());
+    }
+
+    @Test
+    void duplicateHistoryShouldThrow() {
+        TestEvent event1 = new TestEvent(EventId.first(),
+            TestAggregateId.testId(42),
+            "any");
+        TestEvent event2 = new TestEvent(EventId.first(),
+            TestAggregateId.testId(42),
+            "any");
+
+        assertThatThrownBy(() -> History.of(event1, event2))
+            .isInstanceOf(EventStoreFailedException.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/189490a4/event-sourcing/event-store-cassandra/pom.xml
----------------------------------------------------------------------
diff --git a/event-sourcing/event-store-cassandra/pom.xml 
b/event-sourcing/event-store-cassandra/pom.xml
new file mode 100644
index 0000000..cf2e6dc
--- /dev/null
+++ b/event-sourcing/event-store-cassandra/pom.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.james</groupId>
+        <artifactId>event-sourcing</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>event-sourcing-event-store-cassandra</artifactId>
+
+    <name>Apache James :: Event sourcing :: Event Store :: Cassandra</name>
+    <description>Cassandra implementation for James Event Store</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-backends-cassandra</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-backends-cassandra</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-event-store-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-event-store-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>event-sourcing-pojo</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jdk8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit</artifactId>
+            <version>1.5.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-fluent</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to