Example done with app class

This commit is contained in:
Serdar Hamzaoğulları 2017-08-06 22:51:43 +03:00
parent 9f612ecfda
commit 1474a50e5e
22 changed files with 708 additions and 0 deletions

View File

@ -39,5 +39,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,41 @@
package com.iluwatar.event.sourcing.api;
import java.io.Serializable;
/**
* Created by serdarh on 06.08.2017.
*/
public abstract class DomainEvent implements Serializable {
private final long sequenceId;
private final long createdTime;
private boolean replica = false;
private final String eventClassName;
public DomainEvent(long sequenceId, long createdTime, String eventClassName) {
this.sequenceId = sequenceId;
this.createdTime = createdTime;
this.eventClassName = eventClassName;
}
public long getSequenceId() {
return sequenceId;
}
public long getCreatedTime() {
return createdTime;
}
public boolean isReplica() {
return replica;
}
public void setReplica(boolean replica) {
this.replica = replica;
}
public abstract void process();
public String getEventClassName() {
return eventClassName;
}
}

View File

@ -0,0 +1,11 @@
package com.iluwatar.event.sourcing.api;
/**
* Created by serdarh on 06.08.2017.
*/
public interface EventProcessor {
void process(DomainEvent domainEvent);
void setPrecessorJournal(ProcessorJournal precessorJournal);
void addExternalEventListener(ExternalEventListener externalEventListener);
void recover();
}

View File

@ -0,0 +1,8 @@
package com.iluwatar.event.sourcing.api;
/**
* Created by serdarh on 06.08.2017.
*/
public interface ExternalEventListener {
void notify(DomainEvent domainEvent);
}

View File

@ -0,0 +1,10 @@
package com.iluwatar.event.sourcing.api;
/**
* Created by serdarh on 06.08.2017.
*/
public interface ProcessorJournal {
void write(DomainEvent domainEvent);
void reset();
DomainEvent readNext();
}

View File

@ -0,0 +1,58 @@
package com.iluwatar.event.sourcing.app;
import com.iluwatar.event.sourcing.journal.JsonFileJournal;
import com.iluwatar.event.sourcing.processor.DomainEventProcessor;
import com.iluwatar.event.sourcing.service.AccountService;
import com.iluwatar.event.sourcing.service.MoneyTransactionService;
import com.iluwatar.event.sourcing.state.AccountAggregate;
import java.math.BigDecimal;
/**
* Created by serdarh on 06.08.2017.
*/
public class App {
public static void main(String[] args) {
System.out.println("Running the system first time............");
DomainEventProcessor domainEventProcessor = new DomainEventProcessor();
JsonFileJournal jsonFileJournal = new JsonFileJournal();
jsonFileJournal.reset();
domainEventProcessor.setPrecessorJournal(jsonFileJournal);
System.out.println("Creating th accounts............");
AccountService accountService = new AccountService(domainEventProcessor);
MoneyTransactionService moneyTransactionService = new MoneyTransactionService(domainEventProcessor);
accountService.createAccount(1,"Daenerys Targaryen");
accountService.createAccount(2,"Jon Snow");
System.out.println("Do some money operations............");
moneyTransactionService.depositMoney(1,new BigDecimal("100000"));
moneyTransactionService.depositMoney(2,new BigDecimal("10"));
moneyTransactionService.transferMoney(1,2,new BigDecimal("10000"));
moneyTransactionService.withdrawalMoney(2, new BigDecimal("1000"));
System.out.println("...............State:............");
System.out.println(AccountAggregate.getAccount(1));
System.out.println(AccountAggregate.getAccount(2));
System.out.println("At that point system goes down state in memory cleared............");
AccountAggregate.resetState();
System.out.println("Recover the syste by the events in journal file............");
domainEventProcessor = new DomainEventProcessor();
jsonFileJournal = new JsonFileJournal();
domainEventProcessor.setPrecessorJournal(jsonFileJournal);
domainEventProcessor.recover();
System.out.println("...............State Recovered:............");
System.out.println(AccountAggregate.getAccount(1));
System.out.println(AccountAggregate.getAccount(2));
}
}

View File

@ -0,0 +1,63 @@
package com.iluwatar.event.sourcing.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* Created by serdarh on 06.08.2017.
*/
public class Account {
private final int accountNo;
private final String owner;
private BigDecimal money;
private List<Transaction> transactions;
public Account(int accountNo, String owner) {
this.accountNo = accountNo;
this.owner = owner;
money = BigDecimal.ZERO;
transactions = new ArrayList<>();
}
public int getAccountNo() {
return accountNo;
}
public String getOwner() {
return owner;
}
public BigDecimal getMoney() {
return money;
}
public List<Transaction> getTransactions() {
return transactions;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
public void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public Account copy() {
Account account = new Account(accountNo, owner);
account.setMoney(money);
account.setTransactions(transactions);
return account;
}
@Override
public String toString() {
return "Account{" +
"accountNo=" + accountNo +
", owner='" + owner + '\'' +
", money=" + money +
", transactions=" + transactions +
'}';
}
}

View File

@ -0,0 +1,46 @@
package com.iluwatar.event.sourcing.domain;
import java.math.BigDecimal;
/**
* Created by serdarh on 06.08.2017.
*/
public class Transaction {
private final int accountNo;
private final BigDecimal moneyIn;
private final BigDecimal moneyOut;
private final BigDecimal lastBalance;
public Transaction(int accountNo, BigDecimal moneyIn, BigDecimal moneyOut, BigDecimal lastBalance) {
this.accountNo = accountNo;
this.moneyIn = moneyIn;
this.moneyOut = moneyOut;
this.lastBalance = lastBalance;
}
public int getAccountNo() {
return accountNo;
}
public BigDecimal getMoneyIn() {
return moneyIn;
}
public BigDecimal getMoneyOut() {
return moneyOut;
}
public BigDecimal getLastBalance() {
return lastBalance;
}
@Override
public String toString() {
return "Transaction{" +
"accountNo=" + accountNo +
", moneyIn=" + moneyIn +
", moneyOut=" + moneyOut +
", lastBalance=" + lastBalance +
'}';
}
}

View File

@ -0,0 +1,43 @@
package com.iluwatar.event.sourcing.event;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.domain.Account;
import com.iluwatar.event.sourcing.gateway.Gateways;
import com.iluwatar.event.sourcing.state.AccountAggregate;
/**
* Created by serdarh on 06.08.2017.
*/
public class AccountCreateEvent extends DomainEvent {
private final int accountNo;
private final String owner;
public AccountCreateEvent(long sequenceId, long createdTime, int accountNo, String owner) {
super(sequenceId, createdTime, "AccountCreateEvent");
this.accountNo = accountNo;
this.owner = owner;
}
public int getAccountNo() {
return accountNo;
}
public String getOwner() {
return owner;
}
@Override
public void process() {
Account account = AccountAggregate.getAccount(accountNo);
if(account!=null){
throw new RuntimeException("Account already exists");
}
account = new Account(accountNo,owner);
AccountAggregate.putAccount(account);
// check if this event is replicated from journal before calling an external gateway function
if(!isReplica()) {
Gateways.getAccountCreateContractSender().sendContractInfo(account);
}
}
}

View File

@ -0,0 +1,38 @@
package com.iluwatar.event.sourcing.event;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.domain.Account;
import com.iluwatar.event.sourcing.domain.Transaction;
import com.iluwatar.event.sourcing.gateway.Gateways;
import com.iluwatar.event.sourcing.state.AccountAggregate;
import java.math.BigDecimal;
/**
* Created by serdarh on 06.08.2017.
*/
public class MoneyDepositEvent extends DomainEvent {
private BigDecimal money;
private int accountNo;
public MoneyDepositEvent(long sequenceId, long createdTime, int accountNo,BigDecimal money) {
super(sequenceId, createdTime, "MoneyDepositEvent");
this.money = money;
this.accountNo = accountNo;
}
@Override
public void process() {
Account account = AccountAggregate.getAccount(accountNo);
if(account==null){
throw new RuntimeException("Account not found");
}
account.setMoney(account.getMoney().add(money));
Transaction transaction = new Transaction(accountNo,money,BigDecimal.ZERO,account.getMoney());
account.getTransactions().add(transaction);
AccountAggregate.putAccount(account);
if(!isReplica()) {
Gateways.getTransactionLogger().log(transaction);
}
}
}

View File

@ -0,0 +1,56 @@
package com.iluwatar.event.sourcing.event;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.domain.Account;
import com.iluwatar.event.sourcing.domain.Transaction;
import com.iluwatar.event.sourcing.gateway.Gateways;
import com.iluwatar.event.sourcing.state.AccountAggregate;
import java.math.BigDecimal;
/**
* Created by serdarh on 06.08.2017.
*/
public class MoneyTransferEvent extends DomainEvent {
private BigDecimal money;
private int accountNoFrom;
private int accountNoTo;
public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom, int accountNoTo) {
super(sequenceId, createdTime, "MoneyTransferEvent");
this.money = money;
this.accountNoFrom = accountNoFrom;
this.accountNoTo = accountNoTo;
}
@Override
public void process() {
Account accountFrom = AccountAggregate.getAccount(accountNoFrom);
if(accountFrom==null){
throw new RuntimeException("Account not found "+accountNoFrom);
}
Account accountTo = AccountAggregate.getAccount(accountNoTo);
if(accountTo==null){
throw new RuntimeException("Account not found"+ accountTo);
}
if(accountFrom.getMoney().compareTo(money)==-1){
throw new RuntimeException("Insufficient Account Balance");
}
accountFrom.setMoney(accountFrom.getMoney().subtract(money));
accountTo.setMoney(accountTo.getMoney().add(money));
Transaction transactionFrom = new Transaction(accountNoFrom,BigDecimal.ZERO,money,accountFrom.getMoney());
accountFrom.getTransactions().add(transactionFrom);
Transaction transactionTo = new Transaction(accountNoTo,money,BigDecimal.ZERO,accountTo.getMoney());
accountTo.getTransactions().add(transactionTo);
AccountAggregate.putAccount(accountFrom);
AccountAggregate.putAccount(accountTo);
if(!isReplica()) {
Gateways.getTransactionLogger().log(transactionFrom);
Gateways.getTransactionLogger().log(transactionTo);
}
}
}

View File

@ -0,0 +1,38 @@
package com.iluwatar.event.sourcing.event;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.domain.Account;
import com.iluwatar.event.sourcing.domain.Transaction;
import com.iluwatar.event.sourcing.gateway.Gateways;
import com.iluwatar.event.sourcing.state.AccountAggregate;
import java.math.BigDecimal;
/**
* Created by serdarh on 06.08.2017.
*/
public class MoneyWithdrawalEvent extends DomainEvent {
private BigDecimal money;
private int accountNo;
public MoneyWithdrawalEvent(long sequenceId, long createdTime, int accountNo,BigDecimal money) {
super(sequenceId, createdTime, "MoneyWithdrawalEvent");
this.money = money;
this.accountNo = accountNo;
}
@Override
public void process() {
Account account = AccountAggregate.getAccount(accountNo);
if(account==null){
throw new RuntimeException("Account not found");
}
account.setMoney(account.getMoney().subtract(money));
Transaction transaction = new Transaction(accountNo,BigDecimal.ZERO,money,account.getMoney());
account.getTransactions().add(transaction);
AccountAggregate.putAccount(account);
if(!isReplica()) {
Gateways.getTransactionLogger().log(transaction);
}
}
}

View File

@ -0,0 +1,12 @@
package com.iluwatar.event.sourcing.gateway;
import com.iluwatar.event.sourcing.domain.Account;
/**
* Created by serdarh on 06.08.2017.
*/
public class AccountCreateContractSender {
public void sendContractInfo(Account account){
// an example imaginary funciton which sends account info to some external end point
}
}

View File

@ -0,0 +1,17 @@
package com.iluwatar.event.sourcing.gateway;
/**
* Created by serdarh on 06.08.2017.
*/
public class Gateways {
private static AccountCreateContractSender accountCreateContractSender = new AccountCreateContractSender();
private static TransactionLogger transactionLogger = new TransactionLogger();
public static AccountCreateContractSender getAccountCreateContractSender() {
return accountCreateContractSender;
}
public static TransactionLogger getTransactionLogger() {
return transactionLogger;
}
}

View File

@ -0,0 +1,12 @@
package com.iluwatar.event.sourcing.gateway;
import com.iluwatar.event.sourcing.domain.Transaction;
/**
* Created by serdarh on 06.08.2017.
*/
public class TransactionLogger {
public void log(Transaction transaction) {
// example imaginary function that logs the transaction to somewhere
}
}

View File

@ -0,0 +1,99 @@
package com.iluwatar.event.sourcing.journal;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.api.ProcessorJournal;
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.event.MoneyWithdrawalEvent;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by serdarh on 06.08.2017.
*/
public class JsonFileJournal implements ProcessorJournal{
private File aFile;
private List<String> events = new ArrayList<>();
private int index = 0;
public JsonFileJournal() {
aFile = new File("Journal.json");
if(aFile.exists()) {
try (BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(aFile), "UTF-8"))) {
String line;
while ((line = input.readLine()) != null) {
events.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}else{
reset();
}
}
@Override
public void write(DomainEvent domainEvent) {
Gson 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 MoneyWithdrawalEvent) {
jsonElement = gson.toJsonTree(domainEvent, MoneyWithdrawalEvent.class);
}else if(domainEvent instanceof MoneyTransferEvent) {
jsonElement = gson.toJsonTree(domainEvent, MoneyTransferEvent.class);
}else{
throw new RuntimeException("Journal Event not recegnized");
}
try (Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(aFile, true), "UTF-8"))) {
String eventString = jsonElement.toString();
output.write(eventString +"\r\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void reset() {
aFile.delete();
}
@Override
public DomainEvent readNext() {
if(index>=events.size()){
return null;
}
String event = events.get(index);
index++;
JsonParser parser = new JsonParser();
JsonElement jsonElement = parser.parse(event);
String eventClassName = jsonElement.getAsJsonObject().get("eventClassName").getAsString();
Gson gson = new Gson();
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 if(eventClassName.equals("MoneyWithdrawalEvent")) {
domainEvent = gson.fromJson(jsonElement, MoneyWithdrawalEvent.class);
}else{
throw new RuntimeException("Journal Event not recegnized");
}
domainEvent.setReplica(true);
return domainEvent;
}
}

View File

@ -0,0 +1,48 @@
package com.iluwatar.event.sourcing.processor;
import com.iluwatar.event.sourcing.api.DomainEvent;
import com.iluwatar.event.sourcing.api.EventProcessor;
import com.iluwatar.event.sourcing.api.ExternalEventListener;
import com.iluwatar.event.sourcing.api.ProcessorJournal;
import java.util.ArrayList;
import java.util.List;
/**
* Created by serdarh on 06.08.2017.
*/
public class DomainEventProcessor implements EventProcessor {
private ProcessorJournal precessorJournal;
private List<ExternalEventListener> externalEventListeners = new ArrayList<>();
@Override
public void process(DomainEvent domainEvent) {
externalEventListeners.forEach(externalEventListener -> externalEventListener.notify(domainEvent));
domainEvent.process();
precessorJournal.write(domainEvent);
}
@Override
public void setPrecessorJournal(ProcessorJournal precessorJournal) {
this.precessorJournal = precessorJournal;
}
@Override
public void addExternalEventListener(ExternalEventListener externalEventListener) {
externalEventListeners.add(externalEventListener);
}
@Override
public void recover() {
DomainEvent domainEvent;
while(true) {
domainEvent = precessorJournal.readNext();
if(domainEvent==null){
break;
}else{
domainEvent.process();
}
}
}
}

View File

@ -0,0 +1,22 @@
package com.iluwatar.event.sourcing.service;
import com.iluwatar.event.sourcing.api.EventProcessor;
import com.iluwatar.event.sourcing.event.AccountCreateEvent;
import java.util.Date;
/**
* Created by serdarh on 06.08.2017.
*/
public class AccountService {
private EventProcessor eventProcessor;
public AccountService(EventProcessor eventProcessor) {
this.eventProcessor = eventProcessor;
}
public void createAccount(int accountNo, String owner){
AccountCreateEvent accountCreateEvent = new AccountCreateEvent(SequenceIdGenerator.nextSequenceId(), new Date().getTime(),accountNo,owner);
eventProcessor.process(accountCreateEvent);
}
}

View File

@ -0,0 +1,35 @@
package com.iluwatar.event.sourcing.service;
import com.iluwatar.event.sourcing.api.EventProcessor;
import com.iluwatar.event.sourcing.event.MoneyDepositEvent;
import com.iluwatar.event.sourcing.event.MoneyTransferEvent;
import com.iluwatar.event.sourcing.event.MoneyWithdrawalEvent;
import java.math.BigDecimal;
import java.util.Date;
/**
* Created by serdarh on 06.08.2017.
*/
public class MoneyTransactionService {
private EventProcessor eventProcessor;
public MoneyTransactionService(EventProcessor eventProcessor) {
this.eventProcessor = eventProcessor;
}
public void depositMoney(int accountNo, BigDecimal money){
MoneyDepositEvent moneyDepositEvent = new MoneyDepositEvent(SequenceIdGenerator.nextSequenceId(), new Date().getTime(), accountNo, money);
eventProcessor.process(moneyDepositEvent);
}
public void withdrawalMoney(int accountNo, BigDecimal money){
MoneyWithdrawalEvent moneyWithdrawalEvent = new MoneyWithdrawalEvent(SequenceIdGenerator.nextSequenceId(), new Date().getTime(), accountNo, money);
eventProcessor.process(moneyWithdrawalEvent);
}
public void transferMoney(int accountNoFrom, int accountNoTo, BigDecimal money){
MoneyTransferEvent moneyTransferEvent = new MoneyTransferEvent(SequenceIdGenerator.nextSequenceId(), new Date().getTime(), money, accountNoFrom, accountNoTo);
eventProcessor.process(moneyTransferEvent);
}
}

View File

@ -0,0 +1,14 @@
package com.iluwatar.event.sourcing.service;
/**
* Created by serdarh on 06.08.2017.
*/
public class SequenceIdGenerator {
private static long sequenceId = 0L;
public static long nextSequenceId(){
sequenceId++;
return sequenceId;
}
}

View File

@ -0,0 +1,29 @@
package com.iluwatar.event.sourcing.state;
import com.iluwatar.event.sourcing.domain.Account;
import java.util.HashMap;
import java.util.Map;
/**
* Created by serdarh on 06.08.2017.
*/
public class AccountAggregate {
private static Map<Integer,Account> accounts = new HashMap<>();
public static void putAccount(Account account){
accounts.put(account.getAccountNo(), account);
}
public static Account getAccount(int accountNo){
Account account = accounts.get(accountNo);
if(account == null){
return null;
}
return account.copy();
}
public static void resetState(){
accounts = new HashMap<>();
}
}

View File

@ -143,6 +143,7 @@
<module>extension-objects</module>
<module>marker</module>
<module>cqrs</module>
<module>event-sourcing</module>
</modules>