refactor: Event sourcing 2445 (#2518)

* #2445 Refactored event sourcing

* #2445 Minor cleanup

* #2445 Formatted with Google java style guide

* #2445 Fixed Javadoc

* #2445 Fixing sonar gate bug
This commit is contained in:
Saiful Haque
2023-07-04 20:52:13 +05:30
committed by GitHub
parent 3636e9c8af
commit 4808865c0b
9 changed files with 101 additions and 62 deletions
+2 -2
View File
@@ -40,8 +40,8 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
@@ -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.
*
* <p>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.
* <p>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.
*
* <p>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:............");
@@ -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
*
* <p>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;
@@ -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;
@@ -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;
@@ -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.
*
* <p>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();
}
}
@@ -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();
}
@@ -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;
*
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
*/
public class JsonFileJournal {
public class JsonFileJournal extends EventJournal {
private final File file;
private final List<String> 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);
@@ -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);