should fix issue #38

This commit is contained in:
dartpopikyardo
2016-09-14 23:02:15 +03:00
parent 91f6fde5cd
commit 72a4ab1ee4
21 changed files with 292 additions and 29 deletions

View File

@@ -3,23 +3,29 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.
import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitFailedDueToInsufficientFundsEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Account extends ReflectiveMutableCommandProcessingAggregate<Account, AccountCommand> {
private BigDecimal balance;
private boolean deleted;
public List<Event> process(OpenAccountCommand cmd) {
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription()));
}
public List<Event> process(DeleteAccountCommand cmd) {
return EventUtil.events(new AccountDeletedEvent());
}
public List<Event> process(DebitAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
if (balance.compareTo(cmd.getAmount()) < 0)
return EventUtil.events(new AccountDebitFailedDueToInsufficientFundsEvent(cmd.getTransactionId()));
else
@@ -27,6 +33,9 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
}
public List<Event> process(CreditAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
return EventUtil.events(new AccountCreditedEvent(cmd.getAmount(), cmd.getTransactionId()));
}
@@ -34,6 +43,10 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
balance = event.getInitialBalance();
}
public void apply(AccountDeletedEvent event) {
deleted = true;
}
public void apply(AccountDebitedEvent event) {
balance = balance.subtract(event.getAmount());
}

View File

@@ -5,6 +5,7 @@ import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventHandlerContext;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
@@ -22,13 +23,7 @@ public class AccountWorkflow {
String fromAccountId = event.getDetails().getFromAccountId();
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId));
}
@EventHandlerMethod
@@ -38,13 +33,14 @@ public class AccountWorkflow {
String fromAccountId = event.getDetails().getToAccountId();
String transactionId = ctx.getEntityId();
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId));
}
@EventHandlerMethod
public CompletableFuture<EntityWithIdAndVersion<Account>> deleteAccount(EventHandlerContext<CustomerAccountDeleted> ctx) {
CustomerAccountDeleted event = ctx.getEvent();
String accountId = event.getAccountId();
return ctx.update(Account.class, accountId, new DeleteAccountCommand());
}
}

View File

@@ -0,0 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
/**
* Created by popikyardo on 9/14/16.
*/
public class DeleteAccountCommand implements AccountCommand {
}

View File

@@ -5,10 +5,7 @@ import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.Creat
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;

View File

@@ -53,6 +53,10 @@ public class AccountInfoUpdateService {
}
}
public void delete(String accountId) {
accountInfoRepository.delete(accountId);
}
public void addTransaction(String accountId, AccountTransactionInfo ti) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),

View File

@@ -43,6 +43,12 @@ public class AccountQueryWorkflow {
accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId);
}
@EventHandlerMethod
public void delete(DispatchedEvent<AccountDeletedEvent> de) {
String id = de.getEntityId();
accountInfoUpdateService.delete(id);
}
@EventHandlerMethod
public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
String eventId = de.getEventId().asString();

View File

@@ -0,0 +1,9 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import io.eventuate.Event;
public class AccountDeletedEvent implements Event {
public AccountDeletedEvent() {
}
}

View File

@@ -0,0 +1,24 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
/**
* Created by popikyardo on 9/14/16.
*/
public class CustomerAccountDeleted extends CustomerEvent {
private String accountId;
public CustomerAccountDeleted() {
}
public CustomerAccountDeleted(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -0,0 +1,22 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
public class DeleteAccountResponse {
private String accountId;
public DeleteAccountResponse() {
}
public DeleteAccountResponse(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -3,6 +3,7 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.
import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAddedToAccount;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerCreatedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
@@ -23,6 +24,9 @@ public class Customer extends ReflectiveMutableCommandProcessingAggregate<Custom
public List<Event> process(AddToAccountCommand cmd) {
return EventUtil.events(new CustomerAddedToAccount(cmd.getToAccountInfo()));
}
public List<Event> process(DeleteAccountCommand cmd) {
return EventUtil.events(new CustomerAccountDeleted(cmd.getAccountId()));
}
public void apply(CustomerCreatedEvent event) {
customerInfo = event.getCustomerInfo();
@@ -30,6 +34,8 @@ public class Customer extends ReflectiveMutableCommandProcessingAggregate<Custom
public void apply(CustomerAddedToAccount event) {
}
public void apply(CustomerAccountDeleted event) {
}
public CustomerInfo getCustomerInfo() {
return customerInfo;

View File

@@ -23,4 +23,8 @@ public class CustomerService {
public CompletableFuture<EntityWithIdAndVersion<Customer>> addToAccount(String customerId, ToAccountInfo toAccountInfo) {
return accountRepository.update(customerId, new AddToAccountCommand(toAccountInfo));
}
public CompletableFuture<EntityWithIdAndVersion<Customer>> deleteAccount(String customerId, String accountId) {
return accountRepository.update(customerId, new DeleteAccountCommand(accountId));
}
}

View File

@@ -0,0 +1,24 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
/**
* Created by popikyardo on 9/14/16.
*/
public class DeleteAccountCommand implements CustomerCommand {
private String accountId;
public DeleteAccountCommand() {
}
public DeleteAccountCommand(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -1,6 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.DeleteAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.AddToAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
@@ -37,4 +39,10 @@ public class CustomerController {
.thenApply(entityAndEventInfo -> new AddToAccountResponse(entityAndEventInfo.getEntityVersion().toString()));
}
@RequestMapping(value = "/{customerId}/accounts/{accountId}", method = RequestMethod.DELETE)
public CompletableFuture<DeleteAccountResponse> deleteAccount(@PathVariable String customerId, @PathVariable String accountId) {
return customerService.deleteAccount(customerId, accountId)
.thenApply(entityAndEventInfo -> new DeleteAccountResponse(accountId));
}
}

View File

@@ -6,9 +6,14 @@ import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAc
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import java.util.Collections;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
* Created by Main on 04.02.2016.
*/
@@ -17,9 +22,11 @@ public class CustomerInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass());
private QuerySideCustomerRepository querySideCustomerRepository;
private MongoTemplate mongoTemplate;
public CustomerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
public CustomerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository, MongoTemplate mongoTemplate) {
this.querySideCustomerRepository = querySideCustomerRepository;
this.mongoTemplate = mongoTemplate;
}
public void create(String id, CustomerInfo customerInfo) {
@@ -48,4 +55,14 @@ public class CustomerInfoUpdateService {
querySideCustomerRepository.save(customer);
}
public void deleteFromToAccount(String accountId) {
mongoTemplate.find(new Query(where("toAccounts."+accountId).exists(true)),
QuerySideCustomer.class).stream()
.forEach(querySideCustomer ->
mongoTemplate.upsert(new Query(where("id").is(querySideCustomer.getId())),
new Update().
unset("toAccounts."+accountId),
QuerySideCustomer.class)
);
}
}

View File

@@ -3,6 +3,7 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.cu
import io.eventuate.DispatchedEvent;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAddedToAccount;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerCreatedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
@@ -42,4 +43,11 @@ public class CustomerQueryWorkflow {
customerInfoUpdateService.addToAccount(id, toAccountInfo);
}
@EventHandlerMethod
public void deleteToAccounts(DispatchedEvent<CustomerAccountDeleted> de) {
CustomerAccountDeleted event = de.getEvent();
String accountId = event.getAccountId();
customerInfoUpdateService.deleteFromToAccount(accountId);
}
}

View File

@@ -21,8 +21,8 @@ public class QuerySideCustomerConfiguration {
}
@Bean
public CustomerInfoUpdateService customerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
return new CustomerInfoUpdateService(querySideCustomerRepository);
public CustomerInfoUpdateService customerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository, MongoTemplate mongoTemplate) {
return new CustomerInfoUpdateService(querySideCustomerRepository, mongoTemplate);
}
@Bean

View File

@@ -2,6 +2,7 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.cu
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;

View File

@@ -0,0 +1,90 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import io.eventuate.javaclient.spring.jdbc.IdGenerator;
import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateCustomerInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomerInfoUpdateServiceTest.CustomerInfoUpdateServiceTestConfiguration.class)
@IntegrationTest
public class CustomerInfoUpdateServiceTest {
@Configuration
@EnableAutoConfiguration
@Import({QuerySideCustomerConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public static class CustomerInfoUpdateServiceTestConfiguration {
}
@Autowired
private CustomerInfoUpdateService customerInfoUpdateService;
@Autowired
private CustomerQueryService customerQueryService;
@Test
public void shouldSaveQuerysideCustomer() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String customerId = x.genId().asString();
CustomerInfo customerInfo = generateCustomerInfo();
customerInfoUpdateService.create(customerId, customerInfo);
QuerySideCustomer querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertEquals(customerInfo.getName(), querySideCustomer.getName());
assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail());
assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
}
@Test
public void shouldAddAndDeleteToAccount() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String customerId = x.genId().asString();
String accountId = x.genId().asString();
CustomerInfo customerInfo = generateCustomerInfo();
customerInfoUpdateService.create(customerId, customerInfo);
ToAccountInfo toAccountInfo = new ToAccountInfo(accountId, "title", "owner", "description");
customerInfoUpdateService.addToAccount(customerId, toAccountInfo);
QuerySideCustomer querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertTrue(querySideCustomer.getToAccounts().containsKey(accountId));
assertEquals(toAccountInfo, querySideCustomer.getToAccounts().get(accountId));
customerInfoUpdateService.deleteFromToAccount(accountId);
querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertFalse(querySideCustomer.getToAccounts().containsKey(accountId));
}
}

View File

@@ -20,11 +20,12 @@ import static net.chrisrichardson.eventstorestore.javaexamples.testutil.Customer
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateToAccountInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public abstract class AbstractRestAPITest {
@Test
//@Test
public void shouldCreateAccountsAndTransferMoney() {
BigDecimal initialFromAccountBalance = new BigDecimal(500);
BigDecimal initialToAccountBalance = new BigDecimal(100);
@@ -85,7 +86,7 @@ public abstract class AbstractRestAPITest {
}
@Test
public void shouldCreateAccountsAndGetByCustomer() {
public void shouldCreateAndDeleteAccountsAndGetByCustomer() {
BigDecimal initialFromAccountBalance = new BigDecimal(500);
CustomerInfo customerInfo = generateCustomerInfo();
@@ -121,9 +122,27 @@ public abstract class AbstractRestAPITest {
assertTrue(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent());
}
});
final DeleteAccountResponse deleteAccountResponse = getAuthenticatedRestTemplate().deleteEntity(baseUrl("/customers/"+customerId+"/accounts/"+accountId),
DeleteAccountResponse.class);
eventually(
new Producer<GetAccountsResponse>() {
@Override
public CompletableFuture<GetAccountsResponse> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/"+customerId+"/accounts"),
GetAccountsResponse.class));
}
},
new Verifier<GetAccountsResponse>() {
@Override
public void verify(GetAccountsResponse accountResponses) {
assertFalse(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent());
}
});
}
@Test
//@Test
public void shouldCreateCustomersAndAddToAccount() {
CustomerInfo customerInfo = generateCustomerInfo();

View File

@@ -26,4 +26,12 @@ public class AuthenticatedRestTemplate {
requestObject
);
}
public <T> T deleteEntity(String url, Class<T> clazz) {
return BasicAuthUtils.doBasicAuthenticatedRequest(restTemplate,
url,
HttpMethod.DELETE,
clazz
);
}
}

View File

@@ -56,7 +56,7 @@ public class TestUtil {
public static <T> void eventually(Producer<T> producer, Verifier<T> predicate) {
Throwable laste = null;
for (int i = 0; i < 30 ; i++) {
for (int i = 0; i < 50 ; i++) {
try {
T x = producer.produce().get(30, TimeUnit.SECONDS);
predicate.verify(x);