mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-13 16:14:58 +00:00
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:
1 parent
3636e9c8af
commit
4808865c0b
9 files changed
+101
-62
No files matched your search
@@ -40,8 +40,8 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<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.MoneyDepositEvent;
|
||||||
import com.iluwatar.event.sourcing.event.MoneyTransferEvent;
|
import com.iluwatar.event.sourcing.event.MoneyTransferEvent;
|
||||||
import com.iluwatar.event.sourcing.processor.DomainEventProcessor;
|
import com.iluwatar.event.sourcing.processor.DomainEventProcessor;
|
||||||
|
import com.iluwatar.event.sourcing.processor.JsonFileJournal;
|
||||||
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
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
|
* transactional data, and maintain full audit trails and history that can enable compensating
|
||||||
* actions.
|
* actions.
|
||||||
*
|
*
|
||||||
* <p>This App class is an example usage of an Event Sourcing pattern. As an example, two bank accounts
|
* <p>This App class is an example usage of an Event Sourcing pattern. As an example, two bank
|
||||||
* are created, then some money deposit and transfer actions are taken, so a new state of accounts is
|
* accounts are created, then some money deposit and transfer actions are taken, so a new state of
|
||||||
* created. At that point, state is cleared in order to represent a system shut-down. After the shut-down,
|
* accounts is created. At that point, state is cleared in order to represent a system shut-down.
|
||||||
* system state is recovered by re-creating the past events from event journals. Then state is
|
* After the shut-down, system state is recovered by re-creating the past events from event
|
||||||
* printed so a user can view the last state is same with the state before a system shut-down.
|
* 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.
|
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The constant ACCOUNT OF DAENERYS.
|
* The constant ACCOUNT OF DAENERYS.
|
||||||
*/
|
*/
|
||||||
@@ -68,8 +71,7 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var eventProcessor = new DomainEventProcessor();
|
var eventProcessor = new DomainEventProcessor(new JsonFileJournal());
|
||||||
|
|
||||||
|
|
||||||
LOGGER.info("Running the system first time............");
|
LOGGER.info("Running the system first time............");
|
||||||
eventProcessor.reset();
|
eventProcessor.reset();
|
||||||
@@ -103,7 +105,7 @@ public class App {
|
|||||||
|
|
||||||
LOGGER.info("Recover the system by the events in journal file............");
|
LOGGER.info("Recover the system by the events in journal file............");
|
||||||
|
|
||||||
eventProcessor = new DomainEventProcessor();
|
eventProcessor = new DomainEventProcessor(new JsonFileJournal());
|
||||||
eventProcessor.recover();
|
eventProcessor.recover();
|
||||||
|
|
||||||
LOGGER.info("...............Recovered State:............");
|
LOGGER.info("...............Recovered State:............");
|
||||||
|
|||||||
+8
-4
@@ -24,14 +24,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.event.sourcing.event;
|
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.domain.Account;
|
||||||
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the class that implements account created event.
|
* This is the class that implements account created event. Holds the necessary info for an account
|
||||||
* Holds the necessary info for an account created event.
|
* created event. Implements the process function that finds the event-related domain objects and
|
||||||
* Implements the process function that finds the event-related domain objects and
|
|
||||||
* calls the related domain object's handle event functions
|
* calls the related domain object's handle event functions
|
||||||
*
|
*
|
||||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||||
@@ -50,7 +51,10 @@ public class AccountCreateEvent extends DomainEvent {
|
|||||||
* @param accountNo the account no
|
* @param accountNo the account no
|
||||||
* @param owner the owner
|
* @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");
|
super(sequenceId, createdTime, "AccountCreateEvent");
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
|||||||
+6
-1
@@ -24,6 +24,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.event.sourcing.event;
|
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 com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -50,7 +52,10 @@ public class MoneyDepositEvent extends DomainEvent {
|
|||||||
* @param accountNo the account no
|
* @param accountNo the account no
|
||||||
* @param money the money
|
* @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");
|
super(sequenceId, createdTime, "MoneyDepositEvent");
|
||||||
this.money = money;
|
this.money = money;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
|
|||||||
+7
-2
@@ -24,6 +24,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.event.sourcing.event;
|
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 com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -52,8 +54,11 @@ public class MoneyTransferEvent extends DomainEvent {
|
|||||||
* @param accountNoFrom the account no from
|
* @param accountNoFrom the account no from
|
||||||
* @param accountNoTo the account no to
|
* @param accountNoTo the account no to
|
||||||
*/
|
*/
|
||||||
public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom,
|
@JsonCreator
|
||||||
int accountNoTo) {
|
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");
|
super(sequenceId, createdTime, "MoneyTransferEvent");
|
||||||
this.money = money;
|
this.money = money;
|
||||||
this.accountNoFrom = accountNoFrom;
|
this.accountNoFrom = accountNoFrom;
|
||||||
|
|||||||
+9
-5
@@ -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
|
* 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.
|
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||||
*/
|
*/
|
||||||
public class DomainEventProcessor {
|
public class DomainEventProcessor {
|
||||||
|
|
||||||
private final JsonFileJournal processorJournal = new JsonFileJournal();
|
private final EventJournal eventJournal;
|
||||||
|
|
||||||
|
public DomainEventProcessor(EventJournal eventJournal) {
|
||||||
|
this.eventJournal = eventJournal;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process.
|
* Process.
|
||||||
@@ -43,14 +47,14 @@ public class DomainEventProcessor {
|
|||||||
*/
|
*/
|
||||||
public void process(DomainEvent domainEvent) {
|
public void process(DomainEvent domainEvent) {
|
||||||
domainEvent.process();
|
domainEvent.process();
|
||||||
processorJournal.write(domainEvent);
|
eventJournal.write(domainEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset.
|
* Reset.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
processorJournal.reset();
|
eventJournal.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,7 +62,7 @@ public class DomainEventProcessor {
|
|||||||
*/
|
*/
|
||||||
public void recover() {
|
public void recover() {
|
||||||
DomainEvent domainEvent;
|
DomainEvent domainEvent;
|
||||||
while ((domainEvent = processorJournal.readNext()) != null) {
|
while ((domainEvent = eventJournal.readNext()) != null) {
|
||||||
domainEvent.process();
|
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();
|
||||||
|
}
|
||||||
+18
-38
@@ -24,9 +24,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.event.sourcing.processor;
|
package com.iluwatar.event.sourcing.processor;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.iluwatar.event.sourcing.event.AccountCreateEvent;
|
import com.iluwatar.event.sourcing.event.AccountCreateEvent;
|
||||||
import com.iluwatar.event.sourcing.event.DomainEvent;
|
import com.iluwatar.event.sourcing.event.DomainEvent;
|
||||||
import com.iluwatar.event.sourcing.event.MoneyDepositEvent;
|
import com.iluwatar.event.sourcing.event.MoneyDepositEvent;
|
||||||
@@ -49,9 +48,8 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
* <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 final List<String> events = new ArrayList<>();
|
||||||
private int index = 0;
|
private int index = 0;
|
||||||
|
|
||||||
@@ -81,22 +79,12 @@ public class JsonFileJournal {
|
|||||||
*
|
*
|
||||||
* @param domainEvent the domain event
|
* @param domainEvent the domain event
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void write(DomainEvent domainEvent) {
|
public void write(DomainEvent domainEvent) {
|
||||||
var gson = new Gson();
|
var mapper = new ObjectMapper();
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var output = new BufferedWriter(
|
try (var output = new BufferedWriter(
|
||||||
new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) {
|
new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) {
|
||||||
var eventString = jsonElement.toString();
|
var eventString = mapper.writeValueAsString(domainEvent);
|
||||||
output.write(eventString + "\r\n");
|
output.write(eventString + "\r\n");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -104,14 +92,6 @@ public class JsonFileJournal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset.
|
|
||||||
*/
|
|
||||||
public void reset() {
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the next domain event.
|
* Read the next domain event.
|
||||||
*
|
*
|
||||||
@@ -124,19 +104,19 @@ public class JsonFileJournal {
|
|||||||
var event = events.get(index);
|
var event = events.get(index);
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
var parser = new JsonParser();
|
var mapper = new ObjectMapper();
|
||||||
var jsonElement = parser.parse(event);
|
|
||||||
var eventClassName = jsonElement.getAsJsonObject().get("eventClassName").getAsString();
|
|
||||||
var gson = new Gson();
|
|
||||||
DomainEvent domainEvent;
|
DomainEvent domainEvent;
|
||||||
if (eventClassName.equals("AccountCreateEvent")) {
|
try {
|
||||||
domainEvent = gson.fromJson(jsonElement, AccountCreateEvent.class);
|
var jsonElement = mapper.readTree(event);
|
||||||
} else if (eventClassName.equals("MoneyDepositEvent")) {
|
var eventClassName = jsonElement.get("eventClassName").asText();
|
||||||
domainEvent = gson.fromJson(jsonElement, MoneyDepositEvent.class);
|
domainEvent = switch (eventClassName) {
|
||||||
} else if (eventClassName.equals("MoneyTransferEvent")) {
|
case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class);
|
||||||
domainEvent = gson.fromJson(jsonElement, MoneyTransferEvent.class);
|
case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class);
|
||||||
} else {
|
case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class);
|
||||||
throw new RuntimeException("Journal Event not recegnized");
|
default -> throw new RuntimeException("Journal Event not recognized");
|
||||||
|
};
|
||||||
|
} catch (JsonProcessingException jsonProcessingException) {
|
||||||
|
throw new RuntimeException("Failed to convert JSON");
|
||||||
}
|
}
|
||||||
|
|
||||||
domainEvent.setRealTime(false);
|
domainEvent.setRealTime(false);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_DAENERYS;
|
import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_DAENERYS;
|
||||||
import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_JON;
|
import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_JON;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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.MoneyDepositEvent;
|
||||||
import com.iluwatar.event.sourcing.event.MoneyTransferEvent;
|
import com.iluwatar.event.sourcing.event.MoneyTransferEvent;
|
||||||
import com.iluwatar.event.sourcing.processor.DomainEventProcessor;
|
import com.iluwatar.event.sourcing.processor.DomainEventProcessor;
|
||||||
|
import com.iluwatar.event.sourcing.processor.JsonFileJournal;
|
||||||
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -53,7 +55,7 @@ class IntegrationTest {
|
|||||||
*/
|
*/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initialize() {
|
void initialize() {
|
||||||
eventProcessor = new DomainEventProcessor();
|
eventProcessor = new DomainEventProcessor(new JsonFileJournal());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +86,7 @@ class IntegrationTest {
|
|||||||
|
|
||||||
AccountAggregate.resetState();
|
AccountAggregate.resetState();
|
||||||
|
|
||||||
eventProcessor = new DomainEventProcessor();
|
eventProcessor = new DomainEventProcessor(new JsonFileJournal());
|
||||||
eventProcessor.recover();
|
eventProcessor.recover();
|
||||||
|
|
||||||
var accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS);
|
var accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS);
|
||||||
|
|||||||
Reference in new issue
Block a user