You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2019/07/19 12:28:20 UTC

[james-project] branch master updated (3d3d540 -> 2868b35)

This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from 3d3d540  JAMES-2829 extract BlobStore function call into a local variable in all BlobStore contracts for better clarity
     new 21f5793  JAMES-2813 Introduce Task serialization
     new 2868b35  JAMES-2813 Extract JsonEventSerializer to use it out of Events

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 event-sourcing/event-store-cassandra/pom.xml       |  20 +---
 .../eventstore/cassandra/JsonEventSerializer.java  |  68 +++---------
 .../eventstore/cassandra/dto/EventDTO.java         |   9 +-
 .../eventstore/cassandra/dto/EventDTOModule.java   |  79 ++------------
 .../cassandra/dto/OtherTestEventDTO.java           |   5 +-
 .../eventstore/cassandra/dto/TestEventDTO.java     |   4 +-
 .../cassandra/dto/TestEventDTOModules.java         |  16 +--
 .../event-sourcing-core => json}/pom.xml           |  49 +++++----
 json/src/main/java/org/apache/james/json/DTO.java  |   5 +
 .../main/java/org/apache/james/json/DTOModule.java | 114 +++++++++++++++++++++
 .../apache/james/json/JsonGenericSerializer.java   |  60 +++++------
 .../java/org/apache/JsonGenericSerializerTest.java | 114 +++++++++++++++++++++
 json/src/test/java/org/apache/dto/BaseType.java    |   4 +
 .../src/test/java/org/apache/dto/FirstDTO.java     |  50 +++++----
 .../java/org/apache/dto/FirstDomainObject.java     |  40 +++++---
 .../src/test/java/org/apache/dto/SecondDTO.java    |  46 ++++-----
 .../java/org/apache/dto/SecondDomainObject.java    |  32 +++---
 .../src/test/java/org/apache/dto/TestModule.java   |  15 ++-
 .../src/test/java/org/apache/dto/TestModules.java  |  44 ++++----
 .../quota/cassandra/dto/QuotaEventDTOModules.java  |   3 +-
 .../dto/QuotaThresholdChangedEventDTO.java         |   2 +-
 pom.xml                                            |  11 ++
 .../cassandra/DLPConfigurationItemAddedDTO.java    |   2 +-
 .../cassandra/DLPConfigurationItemsRemovedDTO.java |   2 +-
 .../cassandra/DLPConfigurationModules.java         |   6 +-
 .../FilteringRuleSetDefineDTOModules.java          |   3 +-
 .../filtering/FilteringRuleSetDefinedDTO.java      |   5 +-
 server/pom.xml                                     |   1 +
 .../CassandraMailQueueViewConfigurationModule.java |   3 +-
 .../configuration/ConfigurationChangedDTO.java     |   5 +-
 server/{blob/blob-memory => task-json}/pom.xml     |  32 +++---
 .../server/task/json/InvalidTaskTypeException.java |   5 +-
 .../james/server/task/json/TaskDeserializer.java   |  78 ++++++++++++++
 .../james/server/task/json/TaskSerializer.java}    |  23 +++--
 .../task/json/UnsupportedTypeException.java}       |  16 +--
 .../server/task/json/TaskDeserializerTest.java     |  88 ++++++++++++++++
 .../james/server/task/json/TaskSerializerTest.java |  27 +++--
 .../apache/james/server/task/json/TestTask.java    |  58 +++++------
 .../src/main/java/org/apache/james/task/Task.java  |   6 ++
 39 files changed, 738 insertions(+), 412 deletions(-)
 copy {event-sourcing/event-sourcing-core => json}/pom.xml (69%)
 create mode 100644 json/src/main/java/org/apache/james/json/DTO.java
 create mode 100644 json/src/main/java/org/apache/james/json/DTOModule.java
 copy event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java => json/src/main/java/org/apache/james/json/JsonGenericSerializer.java (61%)
 create mode 100644 json/src/test/java/org/apache/JsonGenericSerializerTest.java
 create mode 100644 json/src/test/java/org/apache/dto/BaseType.java
 copy event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java => json/src/test/java/org/apache/dto/FirstDTO.java (62%)
 copy server/task/src/main/java/org/apache/james/task/TaskWithId.java => json/src/test/java/org/apache/dto/FirstDomainObject.java (60%)
 copy event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java => json/src/test/java/org/apache/dto/SecondDTO.java (62%)
 copy server/task/src/main/java/org/apache/james/task/TaskWithId.java => json/src/test/java/org/apache/dto/SecondDomainObject.java (68%)
 copy backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderTest.java => json/src/test/java/org/apache/dto/TestModule.java (76%)
 copy mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/JpaConcurrentSessionsTest.java => json/src/test/java/org/apache/dto/TestModules.java (57%)
 copy server/{blob/blob-memory => task-json}/pom.xml (73%)
 copy event-sourcing/event-sourcing-pojo/src/main/java/org/apache/james/eventsourcing/Command.java => server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java (91%)
 create mode 100644 server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java
 copy server/{data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/tables/CassandraSieveClusterQuotaTable.java => task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java} (68%)
 copy server/{protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/jmx/LineHandlerResultJMXMonitor.java => task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java} (78%)
 create mode 100644 server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java
 copy mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathV2DAOTest.java => server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java (59%)
 copy backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/migration/MigrationTask.java => server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java (51%)


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


[james-project] 02/02: JAMES-2813 Extract JsonEventSerializer to use it out of Events

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 2868b35c201256044480b82a765cde57cb63fe47
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Mon Jul 15 18:27:43 2019 +0200

    JAMES-2813 Extract JsonEventSerializer to use it out of Events
---
 event-sourcing/event-store-cassandra/pom.xml       |  20 +---
 .../eventstore/cassandra/JsonEventSerializer.java  |  68 +++---------
 .../eventstore/cassandra/dto/EventDTO.java         |   9 +-
 .../eventstore/cassandra/dto/EventDTOModule.java   |  79 ++------------
 .../cassandra/dto/OtherTestEventDTO.java           |   5 +-
 .../eventstore/cassandra/dto/TestEventDTO.java     |   4 +-
 .../cassandra/dto/TestEventDTOModules.java         |  16 +--
 .../event-store-cassandra => json}/pom.xml         |  84 ++++-----------
 json/src/main/java/org/apache/james/json/DTO.java  |   5 +
 .../main/java/org/apache/james/json/DTOModule.java | 114 +++++++++++++++++++++
 .../apache/james/json/JsonGenericSerializer.java   |  60 +++++------
 .../java/org/apache/JsonGenericSerializerTest.java | 114 +++++++++++++++++++++
 json/src/test/java/org/apache/dto/BaseType.java    |   4 +
 .../src/test/java/org/apache/dto/FirstDTO.java     |  50 +++++----
 .../java/org/apache/dto/FirstDomainObject.java     |  54 +++++++---
 .../src/test/java/org/apache/dto/SecondDTO.java    |  46 ++++-----
 .../java/org/apache/dto/SecondDomainObject.java    |  41 ++++++--
 .../src/test/java/org/apache/dto/TestModule.java   |  12 ++-
 .../src/test/java/org/apache/dto/TestModules.java  |  42 ++++----
 .../quota/cassandra/dto/QuotaEventDTOModules.java  |   3 +-
 .../dto/QuotaThresholdChangedEventDTO.java         |   2 +-
 pom.xml                                            |   6 ++
 .../cassandra/DLPConfigurationItemAddedDTO.java    |   2 +-
 .../cassandra/DLPConfigurationItemsRemovedDTO.java |   2 +-
 .../cassandra/DLPConfigurationModules.java         |   6 +-
 .../FilteringRuleSetDefineDTOModules.java          |   3 +-
 .../filtering/FilteringRuleSetDefinedDTO.java      |   5 +-
 .../CassandraMailQueueViewConfigurationModule.java |   3 +-
 .../configuration/ConfigurationChangedDTO.java     |   5 +-
 29 files changed, 491 insertions(+), 373 deletions(-)

diff --git a/event-sourcing/event-store-cassandra/pom.xml b/event-sourcing/event-store-cassandra/pom.xml
index 12e4f21..d10d7fa 100644
--- a/event-sourcing/event-store-cassandra/pom.xml
+++ b/event-sourcing/event-store-cassandra/pom.xml
@@ -70,6 +70,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-json</artifactId>
+        </dependency>
+        <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <scope>test</scope>
@@ -80,22 +84,6 @@
             <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-guava</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.datatype</groupId>
-            <artifactId>jackson-datatype-jdk8</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.datatype</groupId>
-            <artifactId>jackson-datatype-jsr310</artifactId>
-        </dependency>
-        <dependency>
             <groupId>net.javacrumbs.json-unit</groupId>
             <artifactId>json-unit-assertj</artifactId>
             <scope>test</scope>
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
index 2ab9019..a179a9b 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
+++ b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
@@ -20,57 +20,31 @@
 package org.apache.james.eventsourcing.eventstore.cassandra;
 
 import java.io.IOException;
-import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
-import java.util.function.Function;
 
 import javax.inject.Inject;
 
 import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
+import org.apache.james.json.JsonGenericSerializer;
 
-import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.guava.GuavaModule;
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.github.steveash.guavate.Guavate;
 import com.google.common.collect.ImmutableSet;
 
 public class JsonEventSerializer {
+
     public static class UnknownEventException extends RuntimeException {
-        public UnknownEventException(String message) {
-            super(message);
+        public UnknownEventException(JsonGenericSerializer.UnknownTypeException original) {
+            super(original);
         }
     }
 
-    @SuppressWarnings("rawtypes")
-    private final Map<Class<? extends Event>, EventDTOModule> eventClassToModule;
-    @SuppressWarnings("rawtypes")
-    private final Map<String, EventDTOModule> typeToModule;
-    private final ObjectMapper objectMapper;
+    private JsonGenericSerializer<Event, EventDTO<Event>> jsonGenericSerializer;
 
     @Inject
     public JsonEventSerializer(@SuppressWarnings("rawtypes") Set<EventDTOModule> modules) {
-        objectMapper = new ObjectMapper();
-        objectMapper.registerModule(new Jdk8Module());
-        objectMapper.registerModule(new JavaTimeModule());
-        objectMapper.registerModule(new GuavaModule());
-        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
-
-        typeToModule = modules.stream()
-            .collect(Guavate.toImmutableMap(
-                EventDTOModule::getType,
-                Function.identity()));
-
-        eventClassToModule = modules.stream()
-            .collect(Guavate.toImmutableMap(
-                EventDTOModule::getEventClass,
-                Function.identity()));
+        jsonGenericSerializer = new JsonGenericSerializer(modules);
     }
     
     public JsonEventSerializer(@SuppressWarnings("rawtypes") EventDTOModule... modules) {
@@ -78,29 +52,19 @@ public class JsonEventSerializer {
     }
 
     public String serialize(Event event) throws JsonProcessingException {
-        @SuppressWarnings("unchecked")
-        EventDTO dto = Optional.ofNullable(eventClassToModule.get(event.getClass()))
-            .orElseThrow(() -> new UnknownEventException("unknown event class " + event.getClass()))
-            .toDTO(event);
-        return objectMapper.writeValueAsString(dto);
+        try {
+            return jsonGenericSerializer.serialize(event);
+        } catch (JsonGenericSerializer.UnknownTypeException e) {
+            throw new UnknownEventException(e);
+        }
     }
 
     public Event deserialize(String value) throws IOException {
-        JsonNode jsonNode = objectMapper.readTree(value);
-
-        String type = jsonNode.path("type").asText();
-
-        EventDTO dto = objectMapper.readValue(
-            objectMapper.treeAsTokens(jsonNode),
-            retrieveDTOClass(type));
-        return dto.toEvent();
-    }
-
-    @SuppressWarnings("unchecked")
-    public Class<? extends EventDTO> retrieveDTOClass(String type) {
-        return Optional.ofNullable(typeToModule.get(type))
-            .map(EventDTOModule::getDTOClass)
-            .orElseThrow(() -> new UnknownEventException("unknown event type " + type));
+        try {
+            return jsonGenericSerializer.deserialize(value);
+        } catch (JsonGenericSerializer.UnknownTypeException e) {
+            throw new UnknownEventException(e);
+        }
     }
 
 }
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java
index 461212a..eeb448a 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java
+++ b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java
@@ -20,7 +20,12 @@
 package org.apache.james.eventsourcing.eventstore.cassandra.dto;
 
 import org.apache.james.eventsourcing.Event;
+import org.apache.james.json.DTO;
 
-public interface EventDTO {
-    Event toEvent();
+public interface EventDTO<T extends Event> extends DTO<T> {
+    T toEvent();
+
+    default T toDomainObject() {
+        return toEvent();
+    }
 }
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTOModule.java b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTOModule.java
index ba29e91..f68d13a 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTOModule.java
+++ b/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTOModule.java
@@ -20,81 +20,20 @@
 package org.apache.james.eventsourcing.eventstore.cassandra.dto;
 
 import org.apache.james.eventsourcing.Event;
+import org.apache.james.json.DTOModule;
 
-public class EventDTOModule<T extends Event, U extends EventDTO> {
+public class EventDTOModule<T extends Event, U extends EventDTO<T>> extends DTOModule<T, U> {
 
-    public interface EventDTOConverter<T extends Event, U extends EventDTO> {
-        U convert(T event, String typeName);
+    public static <EventTypeT extends Event> DTOModule.Builder<EventTypeT> forEvent(Class<EventTypeT> eventType) {
+        return new DTOModule.Builder<>(eventType);
     }
 
-    public static <U extends Event> Builder<U> forEvent(Class<U> eventType) {
-        return new Builder<>(eventType);
+    public EventDTOModule(DTOConverter<T, U> converter, Class<T> domainObjectType, Class<U> dtoType, String typeName) {
+        super(converter, domainObjectType, dtoType, typeName);
     }
 
-    public static class Builder<T extends Event> {
-
-        private final Class<T> eventType;
-
-        private Builder(Class<T> eventType) {
-            this.eventType = eventType;
-        }
-
-        public <U extends EventDTO> RequireConversionFunctionBuilder<U> convertToDTO(Class<U> dtoType) {
-            return new RequireConversionFunctionBuilder<>(dtoType);
-        }
-
-        public class RequireConversionFunctionBuilder<U extends EventDTO> {
-
-            private final Class<U> dtoType;
-
-            private RequireConversionFunctionBuilder(Class<U> dtoType) {
-                this.dtoType = dtoType;
-            }
-
-            public RequireTypeNameBuilder convertWith(EventDTOConverter<T, U> converter) {
-                return new RequireTypeNameBuilder(converter);
-            }
-
-            public class RequireTypeNameBuilder {
-                private final EventDTOConverter<T, U> converter;
-
-                private RequireTypeNameBuilder(EventDTOConverter<T, U> converter) {
-                    this.converter = converter;
-                }
-
-                public EventDTOModule<T, U> typeName(String typeName) {
-                    return new EventDTOModule<>(converter, eventType, dtoType, typeName);
-                }
-            }
-        }
-
-    }
-
-    private final EventDTOConverter<T, U> converter;
-    private final Class<T> eventType;
-    private final Class<U> dtoType;
-    private final String typeName;
-
-    private EventDTOModule(EventDTOConverter<T, U> converter, Class<T> eventType, Class<U> dtoType, String typeName) {
-        this.converter = converter;
-        this.eventType = eventType;
-        this.dtoType = dtoType;
-        this.typeName = typeName;
-    }
-
-    public String getType() {
-        return typeName;
-    }
-
-    public Class<U> getDTOClass() {
-        return dtoType;
-    }
-
-    public Class<T> getEventClass() {
-        return eventType;
-    }
-
-    public EventDTO toDTO(T event) {
-        return converter.convert(event, typeName);
+    @Override
+    public U toDTO(T domainObject) {
+        return super.toDTO(domainObject);
     }
 }
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
index d4739f8..222c911 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
+++ b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.eventsourcing.eventstore.cassandra.dto;
 
-import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.EventId;
 import org.apache.james.eventsourcing.TestAggregateId;
 
@@ -27,7 +26,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class OtherTestEventDTO implements EventDTO {
+public class OtherTestEventDTO implements EventDTO<OtherEvent> {
     private final String type;
     private final long data;
     private final int eventId;
@@ -63,7 +62,7 @@ public class OtherTestEventDTO implements EventDTO {
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
+    public OtherEvent toEvent() {
         return new OtherEvent(
             EventId.fromSerialized(eventId),
             TestAggregateId.testId(aggregate),
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTO.java b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTO.java
index 709c709..17d191a 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTO.java
+++ b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTO.java
@@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class TestEventDTO implements EventDTO {
+public class TestEventDTO implements EventDTO<TestEvent> {
     private final String type;
     private final String data;
     private final int eventId;
@@ -64,7 +64,7 @@ public class TestEventDTO implements EventDTO {
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
+    public TestEvent toEvent() {
         return new TestEvent(
             EventId.fromSerialized(eventId),
             TestAggregateId.testId(aggregate),
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java
index 1040380..2aed742 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java
+++ b/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java
@@ -23,8 +23,8 @@ import org.apache.james.eventsourcing.TestEvent;
 
 public interface TestEventDTOModules {
 
-    EventDTOModule<TestEvent, TestEventDTO> TEST_TYPE =
-        EventDTOModule
+
+    EventDTOModule TEST_TYPE = EventDTOModule
             .forEvent(TestEvent.class)
             .convertToDTO(TestEventDTO.class)
             .convertWith((event, typeName) -> new TestEventDTO(
@@ -32,16 +32,18 @@ public interface TestEventDTOModules {
                 event.getData(),
                 event.eventId().serialize(),
                 event.getAggregateId().getId()))
-            .typeName("TestType");
+            .typeName("TestType")
+            .withFactory(EventDTOModule::new);
 
-    EventDTOModule<OtherEvent, OtherTestEventDTO> OTHER_TEST_TYPE =
+    EventDTOModule OTHER_TEST_TYPE =
         EventDTOModule
             .forEvent(OtherEvent.class)
             .convertToDTO(OtherTestEventDTO.class)
-            .convertWith(((event, typeName) -> new OtherTestEventDTO(
+            .convertWith((event, typeName) -> new OtherTestEventDTO(
                 typeName,
                 event.getPayload(),
                 event.eventId().serialize(),
-                event.getAggregateId().getId())))
-            .typeName("other-type");
+                event.getAggregateId().getId()))
+            .typeName("other-type")
+            .withFactory(EventDTOModule::new);
 }
diff --git a/event-sourcing/event-store-cassandra/pom.xml b/json/pom.xml
similarity index 54%
copy from event-sourcing/event-store-cassandra/pom.xml
copy to json/pom.xml
index 12e4f21..7ede601 100644
--- a/event-sourcing/event-store-cassandra/pom.xml
+++ b/json/pom.xml
@@ -17,69 +17,21 @@
     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>
-
+<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/xsd/maven-4.0.0.xsd">
     <parent>
+        <artifactId>james-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <artifactId>event-sourcing</artifactId>
         <version>3.4.0-SNAPSHOT</version>
     </parent>
+    <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>event-sourcing-event-store-cassandra</artifactId>
+    <artifactId>james-json</artifactId>
 
-    <name>Apache James :: Event sourcing :: Event Store :: Cassandra</name>
-    <description>Cassandra implementation for James Event Store</description>
+    <name>Apache James :: Json serialization</name>
+    <description>Provides basic support for Json serialization of domain objects</description>
 
     <dependencies>
         <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-backends-cassandra</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-backends-cassandra</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>event-sourcing-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>event-sourcing-event-store-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>event-sourcing-event-store-api</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>event-sourcing-core</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.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>
@@ -96,13 +48,17 @@
             <artifactId>jackson-datatype-jsr310</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.github.steveash.guavate</groupId>
+            <artifactId>guavate</artifactId>
+        </dependency>
+        <dependency>
             <groupId>net.javacrumbs.json-unit</groupId>
             <artifactId>json-unit-assertj</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>nl.jqno.equalsverifier</groupId>
-            <artifactId>equalsverifier</artifactId>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -111,21 +67,19 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.junit.platform</groupId>
-            <artifactId>junit-platform-launcher</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.testcontainers</groupId>
-            <artifactId>testcontainers</artifactId>
-            <scope>test</scope>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
         </dependency>
     </dependencies>
 
-
-</project>
\ No newline at end of file
+</project>
diff --git a/json/src/main/java/org/apache/james/json/DTO.java b/json/src/main/java/org/apache/james/json/DTO.java
new file mode 100644
index 0000000..b0e8564
--- /dev/null
+++ b/json/src/main/java/org/apache/james/json/DTO.java
@@ -0,0 +1,5 @@
+package org.apache.james.json;
+
+public interface DTO<T> {
+    T toDomainObject();
+}
diff --git a/json/src/main/java/org/apache/james/json/DTOModule.java b/json/src/main/java/org/apache/james/json/DTOModule.java
new file mode 100644
index 0000000..8baeb8f
--- /dev/null
+++ b/json/src/main/java/org/apache/james/json/DTOModule.java
@@ -0,0 +1,114 @@
+/****************************************************************
+ * 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.json;
+
+public class DTOModule<T, U extends DTO<T>> {
+
+    public interface DTOConverter<T, U extends DTO<T>> {
+        U convert(T domainObject, String typeName);
+    }
+
+    public interface ModuleFactory<T, U extends DTO<T>, ModuleTypeT extends DTOModule<T, U>> {
+        ModuleTypeT create(DTOConverter<T, U> converter, Class<T> domainObjectType, Class<U> dtoType, String typeName);
+    }
+
+    public static <T> Builder<T> forDomainObject(Class<T> domainObjectType) {
+        return new Builder<>(domainObjectType);
+    }
+
+    public static class Builder<T> {
+
+        private final Class<T> type;
+
+        public Builder(Class<T> type) {
+            this.type = type;
+        }
+
+        public <U extends DTO<T>> RequireConversionFunctionBuilder<U> convertToDTO(Class<U> dtoType) {
+            return new RequireConversionFunctionBuilder<>(dtoType);
+        }
+
+        public class RequireConversionFunctionBuilder<U extends DTO<T>> {
+
+            private final Class<U> dtoType;
+
+            private RequireConversionFunctionBuilder(Class<U> dtoType) {
+                this.dtoType = dtoType;
+            }
+
+            public RequireTypeNameBuilder convertWith(DTOConverter<T, U> converter) {
+                return new RequireTypeNameBuilder(converter);
+            }
+
+            public class RequireTypeNameBuilder {
+                private final DTOConverter<T, U> converter;
+
+                private RequireTypeNameBuilder(DTOConverter<T, U> converter) {
+                    this.converter = converter;
+                }
+
+                public RequireModuleFactory typeName(String typeName) {
+                    return new RequireModuleFactory(typeName);
+                }
+
+                public class RequireModuleFactory {
+
+                    private final String typeName;
+
+                    private RequireModuleFactory(String typeName) {
+                        this.typeName = typeName;
+                    }
+
+                    public <ModuleTypeT extends DTOModule<T, U>> ModuleTypeT withFactory(ModuleFactory<T, U, ModuleTypeT> factory) {
+                        return factory.create(converter, type, dtoType, typeName);
+                    }
+                }
+            }
+        }
+    }
+
+    private final DTOConverter<T, U> converter;
+    private final Class<T> domainObjectType;
+    private final Class<U> dtoType;
+    private final String typeName;
+
+    protected DTOModule(DTOConverter<T, U> converter, Class<T> domainObjectType, Class<U> dtoType, String typeName) {
+        this.converter = converter;
+        this.domainObjectType = domainObjectType;
+        this.dtoType = dtoType;
+        this.typeName = typeName;
+    }
+
+    public String getDomainObjectType() {
+        return typeName;
+    }
+
+    public Class<U> getDTOClass() {
+        return dtoType;
+    }
+
+    public Class<T> getDomainObjectClass() {
+        return domainObjectType;
+    }
+
+    public U toDTO(T domainObject) {
+        return converter.convert(domainObject, typeName);
+    }
+}
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java b/json/src/main/java/org/apache/james/json/JsonGenericSerializer.java
similarity index 61%
copy from event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
copy to json/src/main/java/org/apache/james/json/JsonGenericSerializer.java
index 2ab9019..cd8283d 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/JsonEventSerializer.java
+++ b/json/src/main/java/org/apache/james/json/JsonGenericSerializer.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra;
+package org.apache.james.json;
 
 import java.io.IOException;
 import java.util.Map;
@@ -25,12 +25,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
-import javax.inject.Inject;
-
-import org.apache.james.eventsourcing.Event;
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
-
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -41,21 +35,23 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.github.steveash.guavate.Guavate;
 import com.google.common.collect.ImmutableSet;
 
-public class JsonEventSerializer {
-    public static class UnknownEventException extends RuntimeException {
-        public UnknownEventException(String message) {
+public class JsonGenericSerializer<T, U extends DTO<T>> {
+    public static class UnknownTypeException extends RuntimeException {
+        public UnknownTypeException(String message) {
             super(message);
         }
     }
 
-    @SuppressWarnings("rawtypes")
-    private final Map<Class<? extends Event>, EventDTOModule> eventClassToModule;
-    @SuppressWarnings("rawtypes")
-    private final Map<String, EventDTOModule> typeToModule;
+    private final Map<Class<? extends T>, DTOModule<T, U>> domainClassToModule;
+    private final Map<String, DTOModule<T, U>> typeToModule;
     private final ObjectMapper objectMapper;
 
-    @Inject
-    public JsonEventSerializer(@SuppressWarnings("rawtypes") Set<EventDTOModule> modules) {
+    @SafeVarargs
+    public static <T, U extends DTO<T>> JsonGenericSerializer of(DTOModule<T, U>... modules) {
+        return new JsonGenericSerializer<>(ImmutableSet.copyOf(modules));
+    }
+
+    public JsonGenericSerializer(Set<DTOModule<T, U>> modules) {
         objectMapper = new ObjectMapper();
         objectMapper.registerModule(new Jdk8Module());
         objectMapper.registerModule(new JavaTimeModule());
@@ -64,43 +60,37 @@ public class JsonEventSerializer {
 
         typeToModule = modules.stream()
             .collect(Guavate.toImmutableMap(
-                EventDTOModule::getType,
+                DTOModule::getDomainObjectType,
                 Function.identity()));
 
-        eventClassToModule = modules.stream()
+        domainClassToModule = modules.stream()
             .collect(Guavate.toImmutableMap(
-                EventDTOModule::getEventClass,
+                DTOModule::getDomainObjectClass,
                 Function.identity()));
     }
-    
-    public JsonEventSerializer(@SuppressWarnings("rawtypes") EventDTOModule... modules) {
-        this(ImmutableSet.copyOf(modules));
-    }
 
-    public String serialize(Event event) throws JsonProcessingException {
-        @SuppressWarnings("unchecked")
-        EventDTO dto = Optional.ofNullable(eventClassToModule.get(event.getClass()))
-            .orElseThrow(() -> new UnknownEventException("unknown event class " + event.getClass()))
-            .toDTO(event);
+    public String serialize(T domainObject) throws JsonProcessingException {
+        U dto = Optional.ofNullable(domainClassToModule.get(domainObject.getClass()))
+            .orElseThrow(() -> new UnknownTypeException("unknown type " + domainObject.getClass()))
+            .toDTO(domainObject);
         return objectMapper.writeValueAsString(dto);
     }
 
-    public Event deserialize(String value) throws IOException {
+    public T deserialize(String value) throws IOException {
         JsonNode jsonNode = objectMapper.readTree(value);
 
         String type = jsonNode.path("type").asText();
 
-        EventDTO dto = objectMapper.readValue(
+        U dto = objectMapper.readValue(
             objectMapper.treeAsTokens(jsonNode),
             retrieveDTOClass(type));
-        return dto.toEvent();
+        return dto.toDomainObject();
     }
 
-    @SuppressWarnings("unchecked")
-    public Class<? extends EventDTO> retrieveDTOClass(String type) {
+    private Class<? extends U> retrieveDTOClass(String type) {
         return Optional.ofNullable(typeToModule.get(type))
-            .map(EventDTOModule::getDTOClass)
-            .orElseThrow(() -> new UnknownEventException("unknown event type " + type));
+            .map(DTOModule::getDTOClass)
+            .orElseThrow(() -> new UnknownTypeException("unknown type " + type));
     }
 
 }
diff --git a/json/src/test/java/org/apache/JsonGenericSerializerTest.java b/json/src/test/java/org/apache/JsonGenericSerializerTest.java
new file mode 100644
index 0000000..c69a400
--- /dev/null
+++ b/json/src/test/java/org/apache/JsonGenericSerializerTest.java
@@ -0,0 +1,114 @@
+/****************************************************************
+ * 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;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import org.apache.dto.BaseType;
+import org.apache.dto.FirstDomainObject;
+import org.apache.dto.SecondDomainObject;
+import org.apache.dto.TestModules;
+import org.apache.james.json.DTO;
+import org.apache.james.json.JsonGenericSerializer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JsonGenericSerializerTest {
+    private static final FirstDomainObject FIRST = new FirstDomainObject(Optional.of(1L), ZonedDateTime.parse("2016-04-03T02:01+07:00[Asia/Vientiane]"), "first payload");
+    private static final SecondDomainObject SECOND = new SecondDomainObject(UUID.fromString("4a2c853f-7ffc-4ce3-9410-a47e85b3b741"), "second payload");
+
+    private static final String FIRST_JSON = "{\"type\":\"first\",\"id\":1,\"time\":\"2016-04-03T02:01+07:00[Asia/Vientiane]\",\"payload\":\"first payload\"}";
+    private static final String SECOND_JSON = "{\"type\":\"second\",\"id\":\"4a2c853f-7ffc-4ce3-9410-a47e85b3b741\",\"payload\":\"second payload\"}";
+
+    @Test
+    void shouldDeserializeKnownType() throws Exception {
+        assertThat(JsonGenericSerializer.of(TestModules.FIRST_TYPE)
+            .deserialize(FIRST_JSON))
+            .isEqualTo(FIRST);
+    }
+
+    @Test
+    void shouldThrowWhenDeserializeUnknownType() {
+        assertThatThrownBy(() -> JsonGenericSerializer.of()
+            .deserialize(FIRST_JSON))
+            .isInstanceOf(JsonGenericSerializer.UnknownTypeException.class);
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void serializeShouldHandleAllKnownTypes(BaseType domainObject, String serializedJson) throws Exception {
+        JsonGenericSerializer<BaseType, DTO<BaseType>> serializer = JsonGenericSerializer.of(
+                TestModules.FIRST_TYPE,
+                TestModules.SECOND_TYPE);
+
+        assertThatJson(serializer.serialize(domainObject))
+            .isEqualTo(serializedJson);
+    }
+
+    private static Stream<Arguments> serializeShouldHandleAllKnownTypes() {
+        return allKnownTypes();
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void deserializeShouldHandleAllKnownTypes(BaseType domainObject, String serializedJson) throws Exception {
+        JsonGenericSerializer serializer = JsonGenericSerializer.of(
+                TestModules.FIRST_TYPE,
+                TestModules.SECOND_TYPE);
+
+        assertThatJson(serializer.deserialize(serializedJson))
+            .isEqualTo(domainObject);
+    }
+
+    private static Stream<Arguments> deserializeShouldHandleAllKnownTypes() {
+        return allKnownTypes();
+    }
+
+    private static Stream<Arguments> allKnownTypes() {
+        return Stream.of(
+                Arguments.of(SECOND, SECOND_JSON),
+                Arguments.of(FIRST, FIRST_JSON)
+        );
+    }
+
+    @Test
+    void shouldSerializeKnownType() throws Exception {
+        assertThatJson(JsonGenericSerializer.of(TestModules.FIRST_TYPE)
+            .serialize(FIRST))
+            .isEqualTo(FIRST_JSON);
+    }
+
+    @Test
+    void shouldThrowWhenSerializeUnknownType() {
+        assertThatThrownBy(() -> JsonGenericSerializer.of()
+            .serialize(FIRST))
+            .isInstanceOf(JsonGenericSerializer.UnknownTypeException.class);
+    }
+
+}
\ No newline at end of file
diff --git a/json/src/test/java/org/apache/dto/BaseType.java b/json/src/test/java/org/apache/dto/BaseType.java
new file mode 100644
index 0000000..e2eb2e7
--- /dev/null
+++ b/json/src/test/java/org/apache/dto/BaseType.java
@@ -0,0 +1,4 @@
+package org.apache.dto;
+
+public interface BaseType {
+}
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java b/json/src/test/java/org/apache/dto/FirstDTO.java
similarity index 62%
copy from event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
copy to json/src/test/java/org/apache/dto/FirstDTO.java
index d4739f8..f8b1496 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
+++ b/json/src/test/java/org/apache/dto/FirstDTO.java
@@ -17,56 +17,54 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra.dto;
+package org.apache.dto;
 
-import org.apache.james.eventsourcing.Event;
-import org.apache.james.eventsourcing.EventId;
-import org.apache.james.eventsourcing.TestAggregateId;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.apache.james.json.DTO;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class OtherTestEventDTO implements EventDTO {
+public class FirstDTO implements DTO<FirstDomainObject> {
     private final String type;
-    private final long data;
-    private final int eventId;
-    private final int aggregate;
+    private final Optional<Long> id;
+    private final String time;
+    private final String payload;
 
     @JsonCreator
-    public OtherTestEventDTO(
+    public FirstDTO(
             @JsonProperty("type") String type,
-            @JsonProperty("data") long data,
-            @JsonProperty("eventId") int eventId,
-            @JsonProperty("aggregate") int aggregate) {
+            @JsonProperty("id") Optional<Long> id,
+            @JsonProperty("time") String time,
+            @JsonProperty("payload") String payload) {
         this.type = type;
-        this.data = data;
-        this.eventId = eventId;
-        this.aggregate = aggregate;
+        this.id = id;
+        this.time = time;
+        this.payload = payload;
     }
 
     public String getType() {
         return type;
     }
 
-    public long getData() {
-        return data;
+    public Optional<Long> getId() {
+        return id;
     }
 
-    public long getEventId() {
-        return eventId;
+    public String getTime() {
+        return time;
     }
 
-    public int getAggregate() {
-        return aggregate;
+    public String getPayload() {
+        return payload;
     }
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
-        return new OtherEvent(
-            EventId.fromSerialized(eventId),
-            TestAggregateId.testId(aggregate),
-            data);
+    public FirstDomainObject toDomainObject() {
+        return new FirstDomainObject(id, ZonedDateTime.parse(time), payload);
     }
 }
diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java b/json/src/test/java/org/apache/dto/FirstDomainObject.java
similarity index 52%
copy from server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java
copy to json/src/test/java/org/apache/dto/FirstDomainObject.java
index c068900..6c8a787 100644
--- a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java
+++ b/json/src/test/java/org/apache/dto/FirstDomainObject.java
@@ -17,27 +17,47 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.dlp.eventsourcing.cassandra;
+package org.apache.dto;
 
-import org.apache.james.dlp.eventsourcing.events.ConfigurationItemsAdded;
-import org.apache.james.dlp.eventsourcing.events.ConfigurationItemsRemoved;
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import java.util.Optional;
 
-public interface DLPConfigurationModules {
+public class FirstDomainObject implements BaseType {
+    private final Optional<Long> id;
+    private final ZonedDateTime time;
+    private final String payload;
 
-    EventDTOModule<ConfigurationItemsAdded, DLPConfigurationItemAddedDTO> DLP_CONFIGURATION_STORE =
-        EventDTOModule
-            .forEvent(ConfigurationItemsAdded.class)
-            .convertToDTO(DLPConfigurationItemAddedDTO.class)
-            .convertWith(DLPConfigurationItemAddedDTO::from)
-            .typeName("dlp-configuration-store");
+    public FirstDomainObject(Optional<Long> id, ZonedDateTime time, String payload) {
+        this.id = id;
+        this.time = time;
+        this.payload = payload;
+    }
 
-    EventDTOModule<ConfigurationItemsRemoved, DLPConfigurationItemsRemovedDTO> DLP_CONFIGURATION_CLEAR =
-        EventDTOModule
-            .forEvent(ConfigurationItemsRemoved.class)
-            .convertToDTO(DLPConfigurationItemsRemovedDTO.class)
-            .convertWith(DLPConfigurationItemsRemovedDTO::from)
-            .typeName("dlp-configuration-clear");
+    public Optional<Long> getId() {
+        return id;
+    }
 
+    public ZonedDateTime getTime() {
+        return time;
+    }
 
+    public String getPayload() {
+        return payload;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        FirstDomainObject that = (FirstDomainObject) o;
+        return Objects.equals(id, that.id) &&
+                Objects.equals(time, that.time) &&
+                Objects.equals(payload, that.payload);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, time, payload);
+    }
 }
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java b/json/src/test/java/org/apache/dto/SecondDTO.java
similarity index 62%
copy from event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
copy to json/src/test/java/org/apache/dto/SecondDTO.java
index d4739f8..b900100 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/OtherTestEventDTO.java
+++ b/json/src/test/java/org/apache/dto/SecondDTO.java
@@ -17,56 +17,46 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra.dto;
+package org.apache.dto;
 
-import org.apache.james.eventsourcing.Event;
-import org.apache.james.eventsourcing.EventId;
-import org.apache.james.eventsourcing.TestAggregateId;
+import java.util.UUID;
+
+import org.apache.james.json.DTO;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class OtherTestEventDTO implements EventDTO {
+public class SecondDTO implements DTO<SecondDomainObject> {
     private final String type;
-    private final long data;
-    private final int eventId;
-    private final int aggregate;
+    private final String id;
+    private final String payload;
 
     @JsonCreator
-    public OtherTestEventDTO(
+    public SecondDTO(
             @JsonProperty("type") String type,
-            @JsonProperty("data") long data,
-            @JsonProperty("eventId") int eventId,
-            @JsonProperty("aggregate") int aggregate) {
+            @JsonProperty("id") String id,
+            @JsonProperty("payload") String payload) {
         this.type = type;
-        this.data = data;
-        this.eventId = eventId;
-        this.aggregate = aggregate;
+        this.id = id;
+        this.payload = payload;
     }
 
     public String getType() {
         return type;
     }
 
-    public long getData() {
-        return data;
-    }
-
-    public long getEventId() {
-        return eventId;
+    public String getId() {
+        return id;
     }
 
-    public int getAggregate() {
-        return aggregate;
+    public String getPayload() {
+        return payload;
     }
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
-        return new OtherEvent(
-            EventId.fromSerialized(eventId),
-            TestAggregateId.testId(aggregate),
-            data);
+    public SecondDomainObject toDomainObject() {
+        return new SecondDomainObject(UUID.fromString(id), payload);
     }
 }
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java b/json/src/test/java/org/apache/dto/SecondDomainObject.java
similarity index 59%
copy from server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java
copy to json/src/test/java/org/apache/dto/SecondDomainObject.java
index 2248eec..30ac702 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java
+++ b/json/src/test/java/org/apache/dto/SecondDomainObject.java
@@ -17,20 +17,39 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.cassandra.filtering;
+package org.apache.dto;
 
-import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
-import org.apache.james.jmap.api.filtering.impl.RuleSetDefined;
+import java.util.Objects;
+import java.util.UUID;
 
-public interface FilteringRuleSetDefineDTOModules {
+public class SecondDomainObject implements BaseType {
+    private final UUID id;
+    private final String payload;
 
-    String TYPE = "filtering-rule-set-defined";
+    public SecondDomainObject(UUID id, String payload) {
+        this.id = id;
+        this.payload = payload;
+    }
 
-    EventDTOModule<RuleSetDefined, FilteringRuleSetDefinedDTO> FILTERING_RULE_SET_DEFINED =
-        EventDTOModule
-            .forEvent(RuleSetDefined.class)
-            .convertToDTO(FilteringRuleSetDefinedDTO.class)
-            .convertWith(FilteringRuleSetDefinedDTO::from)
-            .typeName(TYPE);
+    public UUID getId() {
+        return id;
+    }
 
+    public String getPayload() {
+        return payload;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SecondDomainObject that = (SecondDomainObject) o;
+        return Objects.equals(id, that.id) &&
+                Objects.equals(payload, that.payload);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, payload);
+    }
 }
diff --git a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java b/json/src/test/java/org/apache/dto/TestModule.java
similarity index 76%
copy from event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java
copy to json/src/test/java/org/apache/dto/TestModule.java
index 461212a..afa4f2c 100644
--- a/event-sourcing/event-store-cassandra/src/main/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/EventDTO.java
+++ b/json/src/test/java/org/apache/dto/TestModule.java
@@ -17,10 +17,14 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra.dto;
+package org.apache.dto;
 
-import org.apache.james.eventsourcing.Event;
+import org.apache.james.json.DTO;
+import org.apache.james.json.DTOModule;
 
-public interface EventDTO {
-    Event toEvent();
+public class TestModule<T extends BaseType, U extends DTO<T>> extends DTOModule<T, U> {
+
+    protected TestModule(DTOConverter<T, U> converter, Class<T> domainObjectType, Class<U> dtoType, String typeName) {
+        super(converter, domainObjectType, dtoType, typeName);
+    }
 }
diff --git a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java b/json/src/test/java/org/apache/dto/TestModules.java
similarity index 56%
copy from event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java
copy to json/src/test/java/org/apache/dto/TestModules.java
index 1040380..534f6ed 100644
--- a/event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/dto/TestEventDTOModules.java
+++ b/json/src/test/java/org/apache/dto/TestModules.java
@@ -17,31 +17,31 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.eventsourcing.eventstore.cassandra.dto;
+package org.apache.dto;
 
-import org.apache.james.eventsourcing.TestEvent;
+import org.apache.james.json.DTOModule;
 
-public interface TestEventDTOModules {
+public interface TestModules {
 
-    EventDTOModule<TestEvent, TestEventDTO> TEST_TYPE =
-        EventDTOModule
-            .forEvent(TestEvent.class)
-            .convertToDTO(TestEventDTO.class)
-            .convertWith((event, typeName) -> new TestEventDTO(
+    TestModule FIRST_TYPE = DTOModule
+            .forDomainObject(FirstDomainObject.class)
+            .convertToDTO(FirstDTO.class)
+            .convertWith((domainObject, typeName) -> new FirstDTO(
                 typeName,
-                event.getData(),
-                event.eventId().serialize(),
-                event.getAggregateId().getId()))
-            .typeName("TestType");
+                domainObject.getId(),
+                domainObject.getTime().toString(),
+                domainObject.getPayload()))
+            .typeName("first")
+            .withFactory(TestModule::new);
 
-    EventDTOModule<OtherEvent, OtherTestEventDTO> OTHER_TEST_TYPE =
-        EventDTOModule
-            .forEvent(OtherEvent.class)
-            .convertToDTO(OtherTestEventDTO.class)
-            .convertWith(((event, typeName) -> new OtherTestEventDTO(
+    TestModule SECOND_TYPE = DTOModule
+            .forDomainObject(SecondDomainObject.class)
+            .convertToDTO(SecondDTO.class)
+            .convertWith((domainObject, typeName) -> new SecondDTO(
                 typeName,
-                event.getPayload(),
-                event.eventId().serialize(),
-                event.getAggregateId().getId())))
-            .typeName("other-type");
+                domainObject.getId().toString(),
+                domainObject.getPayload()))
+            .typeName("second")
+            .withFactory(TestModule::new);
+
 }
diff --git a/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaEventDTOModules.java b/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaEventDTOModules.java
index 1697d5e..8012a00 100644
--- a/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaEventDTOModules.java
+++ b/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaEventDTOModules.java
@@ -29,6 +29,7 @@ public interface QuotaEventDTOModules {
             .forEvent(QuotaThresholdChangedEvent.class)
             .convertToDTO(QuotaThresholdChangedEventDTO.class)
             .convertWith(QuotaThresholdChangedEventDTO::from)
-            .typeName("quota-threshold-change");
+            .typeName("quota-threshold-change")
+            .withFactory(EventDTOModule::new);
 
 }
diff --git a/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaThresholdChangedEventDTO.java b/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaThresholdChangedEventDTO.java
index 6d1ab84..499f28f 100644
--- a/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaThresholdChangedEventDTO.java
+++ b/mailbox/plugin/quota-mailing-cassandra/src/main/java/org/apache/james/mailbox/quota/cassandra/dto/QuotaThresholdChangedEventDTO.java
@@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-class QuotaThresholdChangedEventDTO implements EventDTO {
+class QuotaThresholdChangedEventDTO implements EventDTO<QuotaThresholdChangedEvent> {
 
     @JsonIgnore
     public static QuotaThresholdChangedEventDTO from(QuotaThresholdChangedEvent event, String type) {
diff --git a/pom.xml b/pom.xml
index 2021201..8fa9701 100644
--- a/pom.xml
+++ b/pom.xml
@@ -530,6 +530,7 @@
         <module>event-sourcing</module>
         <module>examples</module>
         <module>javax-mail-extension</module>
+        <module>json</module>
         <module>mailbox</module>
         <module>mailet</module>
         <module>mdn</module>
@@ -1240,6 +1241,11 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-json</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-blob</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemAddedDTO.java b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemAddedDTO.java
index fd1776b..1545679 100644
--- a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemAddedDTO.java
+++ b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemAddedDTO.java
@@ -34,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Preconditions;
 
-class DLPConfigurationItemAddedDTO implements EventDTO {
+class DLPConfigurationItemAddedDTO implements EventDTO<ConfigurationItemsAdded> {
 
     public static DLPConfigurationItemAddedDTO from(ConfigurationItemsAdded event, String type) {
         return new DLPConfigurationItemAddedDTO(
diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemsRemovedDTO.java b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemsRemovedDTO.java
index 6cdd865..5d3a80a 100644
--- a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemsRemovedDTO.java
+++ b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationItemsRemovedDTO.java
@@ -34,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Preconditions;
 
-class DLPConfigurationItemsRemovedDTO implements EventDTO {
+class DLPConfigurationItemsRemovedDTO implements EventDTO<ConfigurationItemsRemoved> {
 
     public static DLPConfigurationItemsRemovedDTO from(ConfigurationItemsRemoved event, String type) {
         return new DLPConfigurationItemsRemovedDTO(
diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java
index c068900..692aa0d 100644
--- a/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java
+++ b/server/data/data-cassandra/src/main/java/org/apache/james/dlp/eventsourcing/cassandra/DLPConfigurationModules.java
@@ -30,14 +30,16 @@ public interface DLPConfigurationModules {
             .forEvent(ConfigurationItemsAdded.class)
             .convertToDTO(DLPConfigurationItemAddedDTO.class)
             .convertWith(DLPConfigurationItemAddedDTO::from)
-            .typeName("dlp-configuration-store");
+            .typeName("dlp-configuration-store")
+            .withFactory(EventDTOModule::new);
 
     EventDTOModule<ConfigurationItemsRemoved, DLPConfigurationItemsRemovedDTO> DLP_CONFIGURATION_CLEAR =
         EventDTOModule
             .forEvent(ConfigurationItemsRemoved.class)
             .convertToDTO(DLPConfigurationItemsRemovedDTO.class)
             .convertWith(DLPConfigurationItemsRemovedDTO::from)
-            .typeName("dlp-configuration-clear");
+            .typeName("dlp-configuration-clear")
+            .withFactory(EventDTOModule::new);
 
 
 }
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java
index 2248eec..f8002f6 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefineDTOModules.java
@@ -31,6 +31,7 @@ public interface FilteringRuleSetDefineDTOModules {
             .forEvent(RuleSetDefined.class)
             .convertToDTO(FilteringRuleSetDefinedDTO.class)
             .convertWith(FilteringRuleSetDefinedDTO::from)
-            .typeName(TYPE);
+            .typeName(TYPE)
+            .withFactory(EventDTOModule::new);
 
 }
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefinedDTO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefinedDTO.java
index 0aa5bf6..b655711 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefinedDTO.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/filtering/FilteringRuleSetDefinedDTO.java
@@ -21,7 +21,6 @@ package org.apache.james.jmap.cassandra.filtering;
 
 import java.util.Objects;
 
-import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.EventId;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 import org.apache.james.jmap.api.filtering.impl.FilteringAggregateId;
@@ -32,7 +31,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableList;
 
-public class FilteringRuleSetDefinedDTO implements EventDTO {
+public class FilteringRuleSetDefinedDTO implements EventDTO<RuleSetDefined> {
 
     public static FilteringRuleSetDefinedDTO from(RuleSetDefined event, String type) {
         return new FilteringRuleSetDefinedDTO(
@@ -79,7 +78,7 @@ public class FilteringRuleSetDefinedDTO implements EventDTO {
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
+    public RuleSetDefined toEvent() {
         return new RuleSetDefined(
             FilteringAggregateId.parse(aggregateId),
             EventId.fromSerialized(eventId),
diff --git a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/CassandraMailQueueViewConfigurationModule.java b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/CassandraMailQueueViewConfigurationModule.java
index 74febbc..e3b4fc6 100644
--- a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/CassandraMailQueueViewConfigurationModule.java
+++ b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/CassandraMailQueueViewConfigurationModule.java
@@ -30,5 +30,6 @@ public interface CassandraMailQueueViewConfigurationModule {
             .forEvent(ConfigurationChanged.class)
             .convertToDTO(ConfigurationChangedDTO.class)
             .convertWith(ConfigurationChangedDTO::from)
-            .typeName(TYPE_NAME);
+            .typeName(TYPE_NAME)
+            .withFactory(EventDTOModule::new);
 }
diff --git a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/ConfigurationChangedDTO.java b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/ConfigurationChangedDTO.java
index afb959a..80a8ab7 100644
--- a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/ConfigurationChangedDTO.java
+++ b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/view/cassandra/configuration/ConfigurationChangedDTO.java
@@ -22,7 +22,6 @@ package org.apache.james.queue.rabbitmq.view.cassandra.configuration;
 import java.time.Duration;
 import java.util.Objects;
 
-import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.EventId;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 
@@ -31,7 +30,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Preconditions;
 
-class ConfigurationChangedDTO implements EventDTO {
+class ConfigurationChangedDTO implements EventDTO<ConfigurationChanged> {
 
     static ConfigurationChangedDTO from(ConfigurationChanged configurationChanged, String type) {
         Preconditions.checkNotNull(configurationChanged);
@@ -76,7 +75,7 @@ class ConfigurationChangedDTO implements EventDTO {
 
     @JsonIgnore
     @Override
-    public Event toEvent() {
+    public ConfigurationChanged toEvent() {
         return new ConfigurationChanged(
             () -> aggregateKey,
             EventId.fromSerialized(eventId),


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


[james-project] 01/02: JAMES-2813 Introduce Task serialization

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 21f5793a9c10f9af67adf22f12f0c2c0bc4c7f24
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Mon Jul 15 17:35:32 2019 +0200

    JAMES-2813 Introduce Task serialization
---
 pom.xml                                            |  5 ++
 server/pom.xml                                     |  1 +
 server/task-json/pom.xml                           | 80 ++++++++++++++++++++
 .../server/task/json/InvalidTaskTypeException.java | 22 ++++++
 .../james/server/task/json/TaskDeserializer.java   | 78 +++++++++++++++++++
 .../james/server/task/json/TaskSerializer.java     | 40 ++++++++++
 .../server/task/json/UnsupportedTypeException.java | 32 ++++++++
 .../server/task/json/TaskDeserializerTest.java     | 88 ++++++++++++++++++++++
 .../james/server/task/json/TaskSerializerTest.java | 37 +++++++++
 .../apache/james/server/task/json/TestTask.java    | 64 ++++++++++++++++
 .../src/main/java/org/apache/james/task/Task.java  |  6 ++
 11 files changed, 453 insertions(+)

diff --git a/pom.xml b/pom.xml
index 9eee488..2021201 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1704,6 +1704,11 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-server-task-json</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-util</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/pom.xml b/server/pom.xml
index 0d82ea6..db5d711 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -104,6 +104,7 @@
         <module>queue/queue-rabbitmq</module>
 
         <module>task</module>
+        <module>task-json</module>
         <module>testing</module>
     </modules>
 
diff --git a/server/task-json/pom.xml b/server/task-json/pom.xml
new file mode 100644
index 0000000..443edcb
--- /dev/null
+++ b/server/task-json/pom.xml
@@ -0,0 +1,80 @@
+<?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/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>james-server</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.4.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>james-server-task-json</artifactId>
+    <name>Apache James :: Server :: Task :: Json</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-task</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</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>
+    </dependencies>
+</project>
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java b/server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java
new file mode 100644
index 0000000..8382937
--- /dev/null
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/InvalidTaskTypeException.java
@@ -0,0 +1,22 @@
+/****************************************************************
+ * 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.server.task.json;
+
+public class InvalidTaskTypeException extends RuntimeException {
+}
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java b/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java
new file mode 100644
index 0000000..335a2d1
--- /dev/null
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/TaskDeserializer.java
@@ -0,0 +1,78 @@
+package org.apache.james.server.task.json;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.james.task.Task;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.MoreObjects;
+
+public class TaskDeserializer {
+
+    private final ObjectMapper objectMapper;
+
+    public interface Factory {
+        Task create(JsonNode parameters);
+    }
+
+    public static class Type {
+        public static Type of(String typeName) {
+            return new Type(typeName);
+        }
+
+        private final String typeName;
+
+        private Type(String typeName) {
+            this.typeName = typeName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof Type) {
+                Type that = (Type) o;
+
+                return Objects.equals(this.typeName, that.typeName);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(typeName);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                    .add("typeName", typeName)
+                    .toString();
+        }
+    }
+
+    private final Map<Type, Factory> registry;
+
+    public TaskDeserializer(Map<Type, Factory> registry) {
+        this.registry = registry;
+        objectMapper = new ObjectMapper();
+    }
+
+    public Task deserialize(String taskAsString) throws IOException {
+        JsonNode taskAsJson = objectMapper.readTree(taskAsString);
+        JsonNode parameters = taskAsJson.get("parameters");
+
+        return getFactory(taskAsJson).create(parameters);
+    }
+
+    private Factory getFactory(JsonNode taskAsJson) {
+        Type type = Optional.ofNullable(taskAsJson.get("type"))
+            .map(JsonNode::asText)
+            .map(Type::of)
+            .orElseThrow(() -> new InvalidTaskTypeException());
+        return Optional.ofNullable(registry.get(type))
+            .orElseThrow(() -> new UnsupportedTypeException(type));
+    }
+}
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java b/server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java
new file mode 100644
index 0000000..ea39a08
--- /dev/null
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/TaskSerializer.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.server.task.json;
+
+import org.apache.james.task.Task;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class TaskSerializer {
+
+    private ObjectMapper objectMapper;
+
+    public TaskSerializer() {
+        this.objectMapper = new ObjectMapper();
+    }
+
+    public JsonNode serialize(Task task) {
+        return JsonNodeFactory.instance.objectNode()
+            .put("type", task.type())
+            .set("parameters", objectMapper.valueToTree(task.parameters()));
+    }
+}
diff --git a/server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java b/server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java
new file mode 100644
index 0000000..498ee8c
--- /dev/null
+++ b/server/task-json/src/main/java/org/apache/james/server/task/json/UnsupportedTypeException.java
@@ -0,0 +1,32 @@
+/****************************************************************
+ * 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.server.task.json;
+
+public class UnsupportedTypeException extends RuntimeException {
+    private final TaskDeserializer.Type type;
+
+    public UnsupportedTypeException(TaskDeserializer.Type type) {
+        this.type = type;
+    }
+
+    @Override
+    public String getMessage() {
+        return "The type " + type + " is not registered when trying to deserialize it";
+    }
+}
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.java
new file mode 100644
index 0000000..2698b69
--- /dev/null
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskDeserializerTest.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.server.task.json;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.IOException;
+
+import org.apache.james.task.Task;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+class TaskDeserializerTest {
+
+    private static final String TASK_AS_STRING = "{" +
+        "\"type\": \"testTask\"," +
+        "\"parameters\": {\"parameter\": \"1\"}" +
+        "}";
+
+    private static final String UNREGISTERED_TASK_AS_STRING = "{" +
+        "\"type\": \"unknown\"," +
+        "\"parameters\": {\"parameter\": \"1\"}" +
+        "}";
+
+    private static final String TWO_TYPES_TASK_AS_STRING = "{" +
+        "\"type\": \"testTask\"," +
+        "\"type\": \"unknown\"," +
+        "\"parameters\": {\"parameter\": \"1\"}" +
+        "}";
+
+    private static final String MISSING_TASK_AS_STRING = "{" +
+        "\"parameters\": {\"parameter\": \"1\"}" +
+        "}";
+
+    private TaskDeserializer testee;
+
+    @BeforeEach
+    void setUp() {
+        TestTask.Factory factory = new TestTask.Factory();
+        ImmutableMap<TaskDeserializer.Type, TaskDeserializer.Factory> registry = ImmutableMap.of(TaskDeserializer.Type.of("testTask"), factory);
+        testee = new TaskDeserializer(registry);
+    }
+
+    @Test
+    void shouldDeserializeTaskWithRegisteredType() throws IOException {
+        Task task = testee.deserialize(TASK_AS_STRING);
+        Assertions.assertThat(task).isInstanceOf(TestTask.class);
+        Assertions.assertThat(task.parameters()).isEqualTo(ImmutableMap.of("parameter", "1"));
+    }
+
+    @Test
+    void shouldThrowWhenNotRegisteredType() {
+        assertThatThrownBy(() -> testee.deserialize(UNREGISTERED_TASK_AS_STRING))
+            .isInstanceOf(UnsupportedTypeException.class);
+    }
+
+    @Test
+    void shouldThrowWhenMissingType() {
+        assertThatThrownBy(() -> testee.deserialize(MISSING_TASK_AS_STRING))
+            .isInstanceOf(InvalidTaskTypeException.class);
+    }
+
+    @Disabled("Not supported yet, fixed later")
+    @Test
+    void shouldThrowWhenDuplicateType() {
+        assertThatThrownBy(() -> testee.deserialize(TWO_TYPES_TASK_AS_STRING))
+            .isInstanceOf(InvalidTaskTypeException.class);
+    }
+
+}
\ No newline at end of file
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.java
new file mode 100644
index 0000000..12a3758
--- /dev/null
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TaskSerializerTest.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.server.task.json;
+
+import org.junit.jupiter.api.Test;
+
+import net.javacrumbs.jsonunit.assertj.JsonAssertions;
+
+class TaskSerializerTest {
+
+    private static final String TASK_AS_STRING = "{" +
+        "\"type\": \"testTask\"," +
+        "\"parameters\": {\"parameter\": \"1\"}" +
+        "}";
+
+    @Test
+    void shouldSerializeTaskWithItsType() {
+        TaskSerializer testee = new TaskSerializer();
+        long parameter = 1L;
+        TestTask task = new TestTask(parameter);
+        JsonAssertions.assertThatJson(testee.serialize(task).toString()).isEqualTo(TASK_AS_STRING);
+    }
+}
\ No newline at end of file
diff --git a/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java b/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java
new file mode 100644
index 0000000..4ea611f
--- /dev/null
+++ b/server/task-json/src/test/java/org/apache/james/server/task/json/TestTask.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.server.task.json;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskExecutionDetails;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableMap;
+
+public class TestTask implements Task {
+
+    private final long parameter;
+
+    public TestTask(long parameter) {
+        this.parameter = parameter;
+    }
+
+    @Override
+    public Result run() throws InterruptedException {
+        return null;
+    }
+
+    @Override
+    public String type() {
+        return "testTask";
+    }
+
+    @Override
+    public Map<String, String> parameters() {
+        return ImmutableMap.of("parameter", String.valueOf(parameter));
+    }
+
+    @Override
+    public Optional<TaskExecutionDetails.AdditionalInformation> details() {
+        return Optional.empty();
+    }
+
+    public static class Factory implements TaskDeserializer.Factory {
+
+        @Override
+        public Task create(JsonNode parameters) {
+            long parameter = parameters.get("parameter").asLong();
+            return new TestTask(parameter);
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/task/src/main/java/org/apache/james/task/Task.java b/server/task/src/main/java/org/apache/james/task/Task.java
index 2edb39d..3a3368d 100644
--- a/server/task/src/main/java/org/apache/james/task/Task.java
+++ b/server/task/src/main/java/org/apache/james/task/Task.java
@@ -20,9 +20,11 @@
 package org.apache.james.task;
 
 import java.util.Arrays;
+import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
+import org.apache.commons.lang3.NotImplementedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -97,6 +99,10 @@ public interface Task {
         return UNKNOWN;
     }
 
+    default Map<String, String> parameters() {
+        throw new NotImplementedException("Tasks should implement 'parameters' to be serializable");
+    }
+
     default Optional<TaskExecutionDetails.AdditionalInformation> details() {
         return Optional.empty();
     }


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