diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index ae609d67e..569a5fcee 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -40,8 +40,8 @@ test - com.google.code.gson - gson + com.fasterxml.jackson.core + jackson-databind diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java index d356a8b99..4b90c6ae0 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java @@ -28,6 +28,7 @@ import com.iluwatar.event.sourcing.event.AccountCreateEvent; import com.iluwatar.event.sourcing.event.MoneyDepositEvent; import com.iluwatar.event.sourcing.event.MoneyTransferEvent; import com.iluwatar.event.sourcing.processor.DomainEventProcessor; +import com.iluwatar.event.sourcing.processor.JsonFileJournal; import com.iluwatar.event.sourcing.state.AccountAggregate; import java.math.BigDecimal; import java.util.Date; @@ -42,16 +43,18 @@ import lombok.extern.slf4j.Slf4j; * transactional data, and maintain full audit trails and history that can enable compensating * actions. * - *

This App class is an example usage of an Event Sourcing pattern. As an example, two bank accounts - * are created, then some money deposit and transfer actions are taken, so a new state of accounts is - * created. At that point, state is cleared in order to represent a system shut-down. After the shut-down, - * system state is recovered by re-creating the past events from event journals. Then state is - * printed so a user can view the last state is same with the state before a system shut-down. + *

This App class is an example usage of an Event Sourcing pattern. As an example, two bank + * accounts are created, then some money deposit and transfer actions are taken, so a new state of + * accounts is created. At that point, state is cleared in order to represent a system shut-down. + * After the shut-down, system state is recovered by re-creating the past events from event + * journals. Then state is printed so a user can view the last state is same with the state before a + * system shut-down. * *

Created by Serdar Hamzaogullari on 06.08.2017. */ @Slf4j public class App { + /** * The constant ACCOUNT OF DAENERYS. */ @@ -68,8 +71,7 @@ public class App { */ public static void main(String[] args) { - var eventProcessor = new DomainEventProcessor(); - + var eventProcessor = new DomainEventProcessor(new JsonFileJournal()); LOGGER.info("Running the system first time............"); eventProcessor.reset(); @@ -103,7 +105,7 @@ public class App { LOGGER.info("Recover the system by the events in journal file............"); - eventProcessor = new DomainEventProcessor(); + eventProcessor = new DomainEventProcessor(new JsonFileJournal()); eventProcessor.recover(); LOGGER.info("...............Recovered State:............"); diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java index 9e6b40e62..f3f4e78e6 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java @@ -24,14 +24,15 @@ */ package com.iluwatar.event.sourcing.event; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.iluwatar.event.sourcing.domain.Account; import com.iluwatar.event.sourcing.state.AccountAggregate; import lombok.Getter; /** - * This is the class that implements account created event. - * Holds the necessary info for an account created event. - * Implements the process function that finds the event-related domain objects and + * This is the class that implements account created event. Holds the necessary info for an account + * created event. Implements the process function that finds the event-related domain objects and * calls the related domain object's handle event functions * *

Created by Serdar Hamzaogullari on 06.08.2017. @@ -50,7 +51,10 @@ public class AccountCreateEvent extends DomainEvent { * @param accountNo the account no * @param owner the owner */ - public AccountCreateEvent(long sequenceId, long createdTime, int accountNo, String owner) { + @JsonCreator + public AccountCreateEvent(@JsonProperty("sequenceId") long sequenceId, + @JsonProperty("createdTime") long createdTime, + @JsonProperty("accountNo") int accountNo, @JsonProperty("owner") String owner) { super(sequenceId, createdTime, "AccountCreateEvent"); this.accountNo = accountNo; this.owner = owner; diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java index 96afd4640..07e88bc0e 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java @@ -24,6 +24,8 @@ */ package com.iluwatar.event.sourcing.event; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.iluwatar.event.sourcing.state.AccountAggregate; import java.math.BigDecimal; import java.util.Optional; @@ -50,7 +52,10 @@ public class MoneyDepositEvent extends DomainEvent { * @param accountNo the account no * @param money the money */ - public MoneyDepositEvent(long sequenceId, long createdTime, int accountNo, BigDecimal money) { + @JsonCreator + public MoneyDepositEvent(@JsonProperty("sequenceId") long sequenceId, + @JsonProperty("createdTime") long createdTime, + @JsonProperty("accountNo") int accountNo, @JsonProperty("money") BigDecimal money) { super(sequenceId, createdTime, "MoneyDepositEvent"); this.money = money; this.accountNo = accountNo; diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java index 18d98cded..3b4fa1ed1 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java @@ -24,6 +24,8 @@ */ package com.iluwatar.event.sourcing.event; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.iluwatar.event.sourcing.state.AccountAggregate; import java.math.BigDecimal; import java.util.Optional; @@ -52,8 +54,11 @@ public class MoneyTransferEvent extends DomainEvent { * @param accountNoFrom the account no from * @param accountNoTo the account no to */ - public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom, - int accountNoTo) { + @JsonCreator + public MoneyTransferEvent(@JsonProperty("sequenceId") long sequenceId, + @JsonProperty("createdTime") long createdTime, + @JsonProperty("money") BigDecimal money, @JsonProperty("accountNoFrom") int accountNoFrom, + @JsonProperty("accountNoTo") int accountNoTo) { super(sequenceId, createdTime, "MoneyTransferEvent"); this.money = money; this.accountNoFrom = accountNoFrom; diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java index e1f11eebe..cdfe44e33 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java @@ -28,13 +28,17 @@ import com.iluwatar.event.sourcing.event.DomainEvent; /** * This is the implementation of event processor. All events are processed by this class. This - * processor uses processorJournal to persist and recover events. + * processor uses eventJournal to persist and recover events. * *

Created by Serdar Hamzaogullari on 06.08.2017. */ public class DomainEventProcessor { - private final JsonFileJournal processorJournal = new JsonFileJournal(); + private final EventJournal eventJournal; + + public DomainEventProcessor(EventJournal eventJournal) { + this.eventJournal = eventJournal; + } /** * Process. @@ -43,14 +47,14 @@ public class DomainEventProcessor { */ public void process(DomainEvent domainEvent) { domainEvent.process(); - processorJournal.write(domainEvent); + eventJournal.write(domainEvent); } /** * Reset. */ public void reset() { - processorJournal.reset(); + eventJournal.reset(); } /** @@ -58,7 +62,7 @@ public class DomainEventProcessor { */ public void recover() { DomainEvent domainEvent; - while ((domainEvent = processorJournal.readNext()) != null) { + while ((domainEvent = eventJournal.readNext()) != null) { domainEvent.process(); } } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java new file mode 100644 index 000000000..49bc89582 --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java @@ -0,0 +1,37 @@ +package com.iluwatar.event.sourcing.processor; + +import com.iluwatar.event.sourcing.event.DomainEvent; +import java.io.File; +import lombok.extern.slf4j.Slf4j; + +/** + * Base class for Journaling implementations. + */ +@Slf4j +public abstract class EventJournal { + + File file; + + /** + * Write. + * + * @param domainEvent the domain event. + */ + abstract void write(DomainEvent domainEvent); + + /** + * Reset. + */ + void reset() { + if (file.delete()) { + LOGGER.info("File cleared successfully............"); + } + } + + /** + * Read domain event. + * + * @return the domain event. + */ + abstract DomainEvent readNext(); +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java index 048e270e2..cfde566dc 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java @@ -24,9 +24,8 @@ */ package com.iluwatar.event.sourcing.processor; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.iluwatar.event.sourcing.event.AccountCreateEvent; import com.iluwatar.event.sourcing.event.DomainEvent; import com.iluwatar.event.sourcing.event.MoneyDepositEvent; @@ -49,9 +48,8 @@ import java.util.List; * *

Created by Serdar Hamzaogullari on 06.08.2017. */ -public class JsonFileJournal { +public class JsonFileJournal extends EventJournal { - private final File file; private final List events = new ArrayList<>(); private int index = 0; @@ -81,22 +79,12 @@ public class JsonFileJournal { * * @param domainEvent the domain event */ + @Override public void write(DomainEvent domainEvent) { - var gson = new Gson(); - JsonElement jsonElement; - if (domainEvent instanceof AccountCreateEvent) { - jsonElement = gson.toJsonTree(domainEvent, AccountCreateEvent.class); - } else if (domainEvent instanceof MoneyDepositEvent) { - jsonElement = gson.toJsonTree(domainEvent, MoneyDepositEvent.class); - } else if (domainEvent instanceof MoneyTransferEvent) { - jsonElement = gson.toJsonTree(domainEvent, MoneyTransferEvent.class); - } else { - throw new RuntimeException("Journal Event not recognized"); - } - + var mapper = new ObjectMapper(); try (var output = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) { - var eventString = jsonElement.toString(); + var eventString = mapper.writeValueAsString(domainEvent); output.write(eventString + "\r\n"); } catch (IOException e) { throw new RuntimeException(e); @@ -104,14 +92,6 @@ public class JsonFileJournal { } - /** - * Reset. - */ - public void reset() { - file.delete(); - } - - /** * Read the next domain event. * @@ -124,19 +104,19 @@ public class JsonFileJournal { var event = events.get(index); index++; - var parser = new JsonParser(); - var jsonElement = parser.parse(event); - var eventClassName = jsonElement.getAsJsonObject().get("eventClassName").getAsString(); - var gson = new Gson(); + var mapper = new ObjectMapper(); DomainEvent domainEvent; - if (eventClassName.equals("AccountCreateEvent")) { - domainEvent = gson.fromJson(jsonElement, AccountCreateEvent.class); - } else if (eventClassName.equals("MoneyDepositEvent")) { - domainEvent = gson.fromJson(jsonElement, MoneyDepositEvent.class); - } else if (eventClassName.equals("MoneyTransferEvent")) { - domainEvent = gson.fromJson(jsonElement, MoneyTransferEvent.class); - } else { - throw new RuntimeException("Journal Event not recegnized"); + try { + var jsonElement = mapper.readTree(event); + var eventClassName = jsonElement.get("eventClassName").asText(); + domainEvent = switch (eventClassName) { + case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class); + case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class); + case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class); + default -> throw new RuntimeException("Journal Event not recognized"); + }; + } catch (JsonProcessingException jsonProcessingException) { + throw new RuntimeException("Failed to convert JSON"); } domainEvent.setRealTime(false); diff --git a/event-sourcing/src/test/java/IntegrationTest.java b/event-sourcing/src/test/java/IntegrationTest.java index 9e15a2163..c737bd0da 100644 --- a/event-sourcing/src/test/java/IntegrationTest.java +++ b/event-sourcing/src/test/java/IntegrationTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_DAENERYS; import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_JON; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -30,6 +31,7 @@ import com.iluwatar.event.sourcing.event.AccountCreateEvent; import com.iluwatar.event.sourcing.event.MoneyDepositEvent; import com.iluwatar.event.sourcing.event.MoneyTransferEvent; import com.iluwatar.event.sourcing.processor.DomainEventProcessor; +import com.iluwatar.event.sourcing.processor.JsonFileJournal; import com.iluwatar.event.sourcing.state.AccountAggregate; import java.math.BigDecimal; import java.util.Date; @@ -53,7 +55,7 @@ class IntegrationTest { */ @BeforeEach void initialize() { - eventProcessor = new DomainEventProcessor(); + eventProcessor = new DomainEventProcessor(new JsonFileJournal()); } /** @@ -84,7 +86,7 @@ class IntegrationTest { AccountAggregate.resetState(); - eventProcessor = new DomainEventProcessor(); + eventProcessor = new DomainEventProcessor(new JsonFileJournal()); eventProcessor.recover(); var accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS);