Merge commit '5c85418cc43318eb2a9c3fa1a4c94c124a17676c'

* commit '5c85418cc43318eb2a9c3fa1a4c94c124a17676c':
  - completed moving "toAccounts" from List to Map - created CustomerEvent superclass - removed redundant CustomersNotFoundException - added integration tests for customers
  refactoring
  - updated swagger description - changed customer registration endpoint - updated tests
  - updated swagger description - added /customers/{id}/toaccounts endpoint
  updated swagger description
  removed customerId from Account aggregate
  added customers-query-side part
  added customer-command-side
  fixed fields names added swagger  definition for new endpoints
  added customer backend module added auth module added customerId to Account aggregate
This commit is contained in:
Andrew Revinsky (DART)
2016-02-12 21:59:58 +03:00
80 changed files with 2416 additions and 31 deletions

View File

@@ -16,7 +16,7 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
private BigDecimal balance;
public List<Event> process(OpenAccountCommand cmd) {
return EventUtil.events(new AccountOpenedEvent(cmd.getInitialBalance()));
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance()));
}
public List<Event> process(DebitAccountCommand cmd) {

View File

@@ -14,8 +14,8 @@ public class AccountService {
this.accountRepository = accountRepository;
}
public rx.Observable<EntityWithIdAndVersion<Account>> openAccount(BigDecimal initialBalance) {
return accountRepository.save(new OpenAccountCommand(initialBalance));
public rx.Observable<EntityWithIdAndVersion<Account>> openAccount(String customerId, String title, BigDecimal initialBalance) {
return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance));
}
}

View File

@@ -5,13 +5,25 @@ import java.math.BigDecimal;
public class OpenAccountCommand implements AccountCommand {
private String customerId;
private String title;
private BigDecimal initialBalance;
public OpenAccountCommand(BigDecimal initialBalance) {
public OpenAccountCommand(String customerId, String title, BigDecimal initialBalance) {
this.customerId = customerId;
this.title = title;
this.initialBalance = initialBalance;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
}

View File

@@ -14,14 +14,15 @@ public class AccountTest {
@Test
public void testSomething() {
Account account = new Account();
String title = "My Account";
String customerId = "00000000-00000000";
BigDecimal initialBalance = new BigDecimal(512);
List<Event> events = CommandProcessingAggregates.processToList(account, (AccountCommand)new OpenAccountCommand(initialBalance));
List<Event> events = CommandProcessingAggregates.processToList(account, (AccountCommand)new OpenAccountCommand(customerId, title, initialBalance));
Assert.assertEquals(1, events.size());
Assert.assertEquals(AccountOpenedEvent.class, events.get(0).getClass());
account.applyEvent(events.get(0));
Assert.assertEquals(initialBalance, account.getBalance());
}
}

View File

@@ -37,11 +37,13 @@ public class AccountsCommandSideServiceIntegrationTest {
BigDecimal initialFromAccountBalance = new BigDecimal(500);
BigDecimal initialToAccountBalance = new BigDecimal(100);
BigDecimal amountToTransfer = new BigDecimal(150);
String customerId = "00000000-00000000";
String title = "My Account";
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialFromAccountBalance), CreateAccountResponse.class).getBody();
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, initialFromAccountBalance), CreateAccountResponse.class).getBody();
final String fromAccountId = fromAccount.getAccountId();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialToAccountBalance), CreateAccountResponse.class).getBody();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, initialToAccountBalance), CreateAccountResponse.class).getBody();
String toAccountId = toAccount.getAccountId();
Assert.assertNotNull(fromAccountId);

View File

@@ -22,7 +22,7 @@ public class AccountController {
@RequestMapping(method = RequestMethod.POST)
public Observable<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
return accountService.openAccount(request.getInitialBalance())
return accountService.openAccount(request.getCustomerId(), request.getTitle(), request.getInitialBalance())
.map(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityIdentifier().getId()));
}
}

View File

@@ -7,6 +7,11 @@ import java.math.BigDecimal;
public class CreateAccountRequest {
@NotNull
private String customerId;
private String title;
@NotNull
@DecimalMin("0")
private BigDecimal initialBalance;
@@ -14,11 +19,20 @@ public class CreateAccountRequest {
public CreateAccountRequest() {
}
public CreateAccountRequest(BigDecimal initialBalance) {
public CreateAccountRequest(String customerId, String title, BigDecimal initialBalance) {
this.customerId = customerId;
this.title = title;
this.initialBalance = initialBalance;
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}
@@ -26,4 +40,12 @@ public class CreateAccountRequest {
public void setInitialBalance(BigDecimal initialBalance) {
this.initialBalance = initialBalance;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

View File

@@ -37,7 +37,7 @@ public class AccountControllerIntegrationTest {
public void shouldCreateAccount() throws Exception {
mockMvc.perform(post("/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"initialBalance\" : 500}")
.content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

View File

@@ -8,6 +8,8 @@ import java.util.List;
public class AccountInfo {
private String id;
private String customerId;
private String title;
private long balance;
private List<AccountChangeInfo> changes;
private List<AccountTransactionInfo> transactions;
@@ -16,9 +18,11 @@ public class AccountInfo {
private AccountInfo() {
}
public AccountInfo(String id, long balance, List<AccountChangeInfo> changes, List<AccountTransactionInfo> transactions, String version) {
public AccountInfo(String id, String customerId, String title, long balance, List<AccountChangeInfo> changes, List<AccountTransactionInfo> transactions, String version) {
this.id = id;
this.customerId = customerId;
this.title = title;
this.balance = balance;
this.changes = changes;
this.transactions = transactions;
@@ -29,6 +33,14 @@ public class AccountInfo {
return id;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
public long getBalance() {
return balance;
}

View File

@@ -26,10 +26,12 @@ public class AccountInfoUpdateService {
public void create(String accountId, BigDecimal initialBalance, String version) {
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String version) {
try {
accountInfoRepository.save(new AccountInfo(
accountId,
customerId,
title,
toIntegerRepr(initialBalance),
Collections.<AccountChangeInfo>emptyList(),
Collections.<AccountTransactionInfo>emptyList(),

View File

@@ -19,7 +19,7 @@ import java.math.BigDecimal;
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
@EventSubscriber(id="querySideEventHandlers")
@EventSubscriber(id="accountQuerySideEventHandlers")
public class AccountQueryWorkflow implements CompoundEventHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@@ -37,7 +37,9 @@ public class AccountQueryWorkflow implements CompoundEventHandler {
String eventId = de.eventId().asString();
logger.info("**************** account version=" + id + ", " + eventId);
BigDecimal initialBalance = event.getInitialBalance();
accountInfoUpdateService.create(id, initialBalance, eventId);
String customerId = event.getCustomerId();
String title = event.getTitle();
accountInfoUpdateService.create(id, customerId, title, initialBalance, eventId);
return Observable.just(null);
}

View File

@@ -5,6 +5,8 @@ dependencies {
testCompile project(":accounts-command-side-backend")
testCompile project(":transactions-command-side-backend")
testCompile project(":accounts-query-side-backend")
testCompile project(":customers-command-side-backend")
testCompile project(":customers-query-side-backend")
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"

View File

@@ -43,9 +43,9 @@ public class MoneyTransferIntegrationTest {
@Test
public void shouldTransferMoney() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300)));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
@@ -98,9 +98,9 @@ public class MoneyTransferIntegrationTest {
@Test
public void shouldFailDueToInsufficientFunds() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300)));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),

View File

@@ -45,9 +45,9 @@ public class AccountQuerySideIntegrationTest {
@Test
public void shouldUpdateQuerySide() throws Exception {
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300)));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),

View File

@@ -0,0 +1,91 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
import net.chrisrichardson.eventstore.EventStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import rx.Observable;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
/**
* Created by Main on 10.02.2016.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomerQuerySideTestConfiguration.class)
@IntegrationTest
public class CustomerQuerySideIntegrationTest {
@Autowired
private CustomerService customerService;
@Autowired
private CustomerQueryService customerQueryService;
@Autowired
private EventStore eventStore;
@Test
public void shouldCreateCustomerAndAddToAccount() throws Exception {
CustomerInfo customerInfo = generateCustomerInfo();
EntityWithIdAndVersion<Customer> customer = await(customerService.createCustomer(customerInfo));
ToAccountInfo toAccountInfo = generateToAccountInfo();
EntityWithIdAndVersion<Customer> customerWithNewAccount = await(customerService.addToAccount(customer.getEntityIdentifier().getId(), toAccountInfo));
eventually(
new Producer<QuerySideCustomer>() {
@Override
public Observable<QuerySideCustomer> produce() {
return customerQueryService.findByCustomerId(customer.getEntityIdentifier());
}
},
new Verifier<QuerySideCustomer>() {
@Override
public void verify(QuerySideCustomer querySideCustomer) {
Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName());
Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
Assert.assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail());
Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
Assert.assertNotNull(querySideCustomer.getToAccounts());
Assert.assertFalse(querySideCustomer.getToAccounts().isEmpty());
Assert.assertEquals(querySideCustomer.getToAccounts().get("11111111-11111111"), toAccountInfo);
}
});
}
private CustomerInfo generateCustomerInfo() {
return new CustomerInfo(
new Name("John", "Doe"),
"current@email.com",
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
"street 2",
"City",
"State",
"1111111")
);
}
private ToAccountInfo generateToAccountInfo() {
return new ToAccountInfo("11111111-11111111", "New Account", "John Doe");
}
}

View File

@@ -0,0 +1,11 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerConfiguration;
import net.chrisrichardson.eventstore.jdbc.config.JdbcEventStoreConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({CustomerConfiguration.class, JdbcEventStoreConfiguration.class, QuerySideCustomerConfiguration.class})
public class CustomerQuerySideTestConfiguration {
}

View File

@@ -0,0 +1,13 @@
apply plugin: 'java'
dependencies {
compile project(":common-auth")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.security:spring-security-config:4.0.2.RELEASE"
compile "org.springframework.security:spring-security-web:4.0.2.RELEASE"
testCompile "junit:junit:4.11"
}

View File

@@ -0,0 +1,45 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.AuthRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.AuthResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.token.Token;
import org.springframework.security.core.token.TokenService;
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.RestController;
import javax.validation.Valid;
import java.io.IOException;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* Created by popikyardo on 21.09.15.
*/
@RestController
@Validated
public class AuthController {
@Autowired
private TokenService tokenService;
private static ObjectMapper objectMapper = new ObjectMapper();
@RequestMapping(value = "/login", method = POST)
public ResponseEntity<AuthResponse> doAuth(@RequestBody @Valid AuthRequest request) throws IOException {
User user = new User();
user.setEmail(request.getEmail());
Token token = tokenService.allocateToken(objectMapper.writeValueAsString(user));
return ResponseEntity.status(HttpStatus.OK)
.body(new AuthResponse(token.getKey()));
}
}

View File

@@ -0,0 +1,29 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
/**
* Created by popikyardo on 19.10.15.
*/
public class AuthRequest {
@NotBlank
@Email
private String email;
public AuthRequest() {
}
public AuthRequest(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,23 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
/**
* Created by popikyardo on 21.09.15.
*/
public class AuthResponse {
private String token;
public AuthResponse() {
}
public AuthResponse(String token) {
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -0,0 +1,11 @@
apply plugin: 'java'
dependencies {
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.security:spring-security-config:4.0.2.RELEASE"
compile "org.springframework.security:spring-security-web:4.0.2.RELEASE"
testCompile "junit:junit:4.11"
}

View File

@@ -0,0 +1,74 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.token.KeyBasedPersistenceTokenService;
import org.springframework.security.core.token.TokenService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.security.SecureRandom;
/**
* Created by popikyardo on 21.09.15.
*/
@Configuration
@ComponentScan
@EnableWebSecurity
@EnableConfigurationProperties({AuthProperties.class})
public class AuthConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private AuthProperties securityProperties;
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().loginPage("/index.html").and()
.authorizeRequests()
.antMatchers("/health").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/styles/**").permitAll()
.antMatchers("/views/**").permitAll()
.antMatchers(HttpMethod.POST, "/register/step_1").permitAll()
.antMatchers(HttpMethod.POST, "/register/step_2").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public TokenService tokenService() {
KeyBasedPersistenceTokenService res = new KeyBasedPersistenceTokenService();
res.setSecureRandom(new SecureRandom());
res.setServerSecret(securityProperties.getServerSecret());
res.setServerInteger(securityProperties.getServerInteger());
return res;
}
}

View File

@@ -0,0 +1,28 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Created by popikyardo on 21.09.15.
*/
@ConfigurationProperties(locations = "classpath:auth.properties", ignoreUnknownFields = false, prefix = "auth")
public class AuthProperties {
private String serverSecret;
private Integer serverInteger;
public String getServerSecret() {
return serverSecret;
}
public void setServerSecret(String serverSecret) {
this.serverSecret = serverSecret;
}
public Integer getServerInteger() {
return serverInteger;
}
public void setServerInteger(Integer serverInteger) {
this.serverInteger = serverInteger;
}
}

View File

@@ -0,0 +1,43 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.UserAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.token.Token;
import org.springframework.security.core.token.TokenService;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
@Service
public class TokenAuthenticationService {
@Autowired
private TokenService tokenService;
private static final String AUTH_HEADER_NAME = "x-access-token";
private static final long DAY = 1000 * 60 * 60 * 24;
private ObjectMapper mapper = new ObjectMapper();
public Authentication getAuthentication(HttpServletRequest request) throws IOException {
final String tokenString = request.getHeader(AUTH_HEADER_NAME);
if (tokenString != null) {
Token token = tokenService.verifyToken(tokenString);
final User user = mapper.readValue(token.getExtendedInformation(), User.class);
if (user != null && (System.currentTimeMillis() - token.getKeyCreationTime()) < DAY) {
return new UserAuthentication(user);
}
}
return null;
}
}

View File

@@ -0,0 +1,32 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.TokenAuthenticationService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
this.tokenAuthenticationService = taService;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
SecurityContextHolder.getContext().setAuthentication(
tokenAuthenticationService.getAuthentication((HttpServletRequest) req));
chain.doFilter(req, res);
}
}

View File

@@ -0,0 +1,71 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by popikyardo on 23.09.15.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails {
private String email;
public void setUsername(String username) {
this.email = username;
}
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("USER");
Set<GrantedAuthority> res = new HashSet<GrantedAuthority>();
res.add(authority);
return res;
}
@Override
public String getPassword() {
return "";
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,54 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* Created by popikyardo on 23.09.15.
*/
public class UserAuthentication implements Authentication {
private final User user;
private boolean authenticated = true;
public UserAuthentication(User user) {
this.user = user;
}
@Override
public String getName() {
return user.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return user.getPassword();
}
@Override
public User getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user.getUsername();
}
@Override
public boolean isAuthenticated() {
return authenticated;
}
@Override
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
}

View File

@@ -1,6 +1,7 @@
apply plugin: 'java'
dependencies {
compile project(":common-customers")
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
testCompile "junit:junit:4.11"

View File

@@ -7,15 +7,27 @@ import java.math.BigDecimal;
public class AccountOpenedEvent implements Event {
private String customerId;
private String title;
private BigDecimal initialBalance;
private AccountOpenedEvent() {
}
public AccountOpenedEvent(BigDecimal initialBalance) {
public AccountOpenedEvent(String customerId, String title, BigDecimal initialBalance) {
this.customerId = customerId;
this.title = title;
this.initialBalance = initialBalance;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
/**
* Created by Main on 08.02.2016.
*/
public class CustomerAddedToAccount extends CustomerEvent {
private ToAccountInfo toAccountInfo;
public CustomerAddedToAccount() {
}
public CustomerAddedToAccount(ToAccountInfo toAccountInfo) {
this.toAccountInfo = toAccountInfo;
}
public ToAccountInfo getToAccountInfo() {
return toAccountInfo;
}
public void setToAccountInfo(ToAccountInfo toAccountInfo) {
this.toAccountInfo = toAccountInfo;
}
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
/**
* Created by popikyardo on 02.02.16.
*/
public class CustomerCreatedEvent extends CustomerEvent {
private CustomerInfo customerInfo;
public CustomerCreatedEvent() {
}
public CustomerCreatedEvent(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
public void setCustomerInfo(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
}

View File

@@ -0,0 +1,11 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import net.chrisrichardson.eventstore.Event;
import net.chrisrichardson.eventstore.EventEntity;
/**
* Created by Main on 11.02.2016.
*/
@EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer")
public abstract class CustomerEvent implements Event {
}

View File

@@ -13,7 +13,7 @@ public class AccountOpenEventSerializationTest {
@Test
public void shouldSerde() {
AccountOpenedEvent event = new AccountOpenedEvent(new BigDecimal(55));
AccountOpenedEvent event = new AccountOpenedEvent("00000000-00000000", "My Account", new BigDecimal(55));
String json = JSonMapper.toJson(event, EventStoreCommonObjectMapping.getObjectMapper());
System.out.println("json=" + json);

View File

@@ -0,0 +1,8 @@
apply plugin: 'java'
dependencies {
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
testCompile group: 'junit', name: 'junit', version: '4.11'
}

View File

@@ -0,0 +1,82 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.validation.constraints.NotNull;
/**
* Created by popikyardo on 02.02.16.
*/
public class Address {
@NotNull
private String street1;
private String street2;
@NotNull
private String city;
@NotNull
private String state;
@NotNull
private String zipCode;
public Address() {
}
public Address(String street1, String street2, String city, String state, String zipCode) {
this.street1 = street1;
this.street2 = street2;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getStreet1() {
return street1;
}
public void setStreet1(String street1) {
this.street1 = street1;
}
public String getStreet2() {
return street2;
}
public void setStreet2(String street2) {
this.street2 = street2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,61 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.validation.constraints.NotNull;
/**
* Created by popikyardo on 03.02.16.
*/
public class CustomerInfo {
private Name name;
@NotNull
protected String email;
@NotNull
protected String ssn;
@NotNull
protected String phoneNumber;
protected Address address;
public CustomerInfo() {
}
public CustomerInfo(Name name, String email, String ssn, String phoneNumber, Address address) {
this.name = name;
this.email = email;
this.ssn = ssn;
this.phoneNumber = phoneNumber;
this.address = address;
}
public Name getName() {
return name;
}
public String getEmail() {
return email;
}
public String getSsn() {
return ssn;
}
public String getPhoneNumber() {
return phoneNumber;
}
public Address getAddress() {
return address;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,34 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
/**
* Created by popikyardo on 03.02.16.
*/
public class CustomerResponse {
private String id;
private CustomerInfo customerInfo;
public CustomerResponse() {
}
public CustomerResponse(String id, CustomerInfo customerInfo) {
this.id = id;
this.customerInfo = customerInfo;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
public void setCustomerInfo(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
}

View File

@@ -0,0 +1,50 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.validation.constraints.NotNull;
/**
* Created by Main on 10.02.2016.
*/
public class Name {
@NotNull
private String firstName;
@NotNull
private String lastName;
public Name() {
}
public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,57 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* Created by Main on 08.02.2016.
*/
public class ToAccountInfo {
private String id;
private String title;
private String owner;
public ToAccountInfo() {
}
public ToAccountInfo(String id, String title, String owner) {
this.id = id;
this.title = title;
this.owner = owner;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,12 @@
apply plugin: 'java'
dependencies {
compile project(":common-customers")
compile project(":common-backend")
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
}

View File

@@ -0,0 +1,19 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
/**
* Created by Main on 08.02.2016.
*/
public class AddToAccountCommand implements CustomerCommand {
private ToAccountInfo toAccountInfo;
public AddToAccountCommand(ToAccountInfo toAccountInfo) {
this.toAccountInfo = toAccountInfo;
}
public ToAccountInfo getToAccountInfo() {
return toAccountInfo;
}
}

View File

@@ -0,0 +1,18 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
/**
* Created by popikyardo on 02.02.16.
*/
public class CreateCustomerCommand implements CustomerCommand {
private CustomerInfo customerInfo;
public CreateCustomerCommand(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
}

View File

@@ -0,0 +1,37 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.Event;
import net.chrisrichardson.eventstore.EventUtil;
import net.chrisrichardson.eventstore.ReflectiveMutableCommandProcessingAggregate;
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;
import java.util.List;
/**
* Created by popikyardo on 02.02.16.
*/
public class Customer extends ReflectiveMutableCommandProcessingAggregate<Customer, CustomerCommand> {
private CustomerInfo customerInfo;
public List<Event> process(CreateCustomerCommand cmd) {
return EventUtil.events(new CustomerCreatedEvent(cmd.getCustomerInfo()));
}
public List<Event> process(AddToAccountCommand cmd) {
return EventUtil.events(new CustomerAddedToAccount(cmd.getToAccountInfo()));
}
public void apply(CustomerCreatedEvent event) {
customerInfo = event.getCustomerInfo();
}
public void apply(CustomerAddedToAccount event) {
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
}

View File

@@ -0,0 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.Command;
interface CustomerCommand extends Command {
}

View File

@@ -0,0 +1,25 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.EventStore;
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
import net.chrisrichardson.eventstore.repository.AggregateRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableJavaEventHandlers
public class CustomerConfiguration {
@Bean
public CustomerService customerService(AggregateRepository<Customer, CustomerCommand> customerRepository) {
return new CustomerService(customerRepository);
}
@Bean
public AggregateRepository<Customer, CustomerCommand> customerRepository(EventStore eventStore) {
return new AggregateRepository<Customer, CustomerCommand>(Customer.class, eventStore);
}
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstore.repository.AggregateRepository;
public class CustomerService {
private final AggregateRepository<Customer, CustomerCommand> accountRepository;
public CustomerService(AggregateRepository<Customer, CustomerCommand> accountRepository) {
this.accountRepository = accountRepository;
}
public rx.Observable<EntityWithIdAndVersion<Customer>> createCustomer(CustomerInfo customerInfo) {
return accountRepository.save(new CreateCustomerCommand(customerInfo));
}
public rx.Observable<EntityWithIdAndVersion<Customer>> addToAccount(String customerId, ToAccountInfo toAccountInfo) {
return accountRepository.update(new EntityIdentifier(customerId), new AddToAccountCommand(toAccountInfo));
}
}

View File

@@ -0,0 +1,12 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AbstractEntityEventTest;
public class CustomerEventTest extends AbstractEntityEventTest {
@Override
protected Class<Customer> entityClass() {
return Customer.class;
}
}

View File

@@ -0,0 +1,44 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
import net.chrisrichardson.eventstore.CommandProcessingAggregates;
import net.chrisrichardson.eventstore.Event;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerCreatedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CustomerTest {
@Test
public void testSomething() {
Customer customer = new Customer();
CustomerInfo customerInfo = generateCustomerInfo();
List<Event> events = CommandProcessingAggregates.processToList(customer, new CreateCustomerCommand(customerInfo));
Assert.assertEquals(1, events.size());
Assert.assertEquals(CustomerCreatedEvent.class, events.get(0).getClass());
customer.applyEvent(events.get(0));
Assert.assertEquals(customerInfo, customer.getCustomerInfo());
}
private CustomerInfo generateCustomerInfo() {
return new CustomerInfo(
new Name("John", "Doe"),
"current@email.com",
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
"street 2",
"City",
"State",
"1111111")
);
}
}

View File

@@ -0,0 +1,22 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: 'spring-boot'
dependencies {
compile project(":customers-command-side-web")
compile project(":common-swagger")
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
}
run {
environment 'EVENTUATE_API_KEY_ID', '20CAXGPA3DE56WXO2SFBUDGZ9'
environment 'EVENTUATE_API_KEY_SECRET', 'gU6n78drWCIgkgzVStvI3BhV3MfzDyjWKCN7p0PBimI'
environment 'SPRING_DATA_MONGODB_URI', 'mongodb://198.50.218.51/mydb'
}

View File

@@ -0,0 +1,29 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomersCommandSideWebConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({CustomersCommandSideWebConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class CustomersCommandSideServiceConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverters(additional);
}
}

View File

@@ -0,0 +1,11 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.main;
import net.chrisrichardson.eventstore.javaexamples.banking.web.CustomersCommandSideServiceConfiguration;
import org.springframework.boot.SpringApplication;
public class CustomersCommandSideServiceMain {
public static void main(String[] args) {
SpringApplication.run(CustomersCommandSideServiceConfiguration.class, args);
}
}

View File

@@ -0,0 +1,60 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomersCommandSideServiceTestConfiguration.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0", "management.port=0"})
public class CustomersCommandSideServiceIntegrationTest {
@Value("${local.server.port}")
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path;
}
@Autowired
RestTemplate restTemplate;
@Test
public void shouldCreateCustomer() {
CustomerInfo customerInfo = generateCustomerInfo();
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
Assert.assertNotNull(customerId);
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
}
private CustomerInfo generateCustomerInfo() {
return new CustomerInfo(
new Name("John", "Doe"),
"current@email.com",
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
"street 2",
"City",
"State",
"1111111")
);
}
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.List;
@Configuration
@Import(CustomersCommandSideServiceConfiguration.class)
public class CustomersCommandSideServiceTestConfiguration {
@Bean
public RestTemplate restTemplate(HttpMessageConverters converters) {
RestTemplate restTemplate = new RestTemplate();
HttpMessageConverter<?> httpMessageConverter = converters.getConverters().get(0);
List<? extends HttpMessageConverter<?>> httpMessageConverters = Arrays.asList(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters((List<HttpMessageConverter<?>>) httpMessageConverters);
return restTemplate;
}
}

View File

@@ -0,0 +1,12 @@
apply plugin: 'java'
dependencies {
compile project(":common-customers")
compile project(":customers-command-side-backend")
compile project(":common-web")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
}

View File

@@ -0,0 +1,39 @@
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.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import rx.Observable;
/**
* Created by popikyardo on 03.02.16.
*/
@RestController
@RequestMapping("/customers")
public class CustomerController {
private CustomerService customerService;
@Autowired
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
@RequestMapping(method = RequestMethod.POST)
public Observable<CustomerResponse> createCustomer(@Validated @RequestBody CustomerInfo request) {
return customerService.createCustomer(request)
.map(entityAndEventInfo -> new CustomerResponse(entityAndEventInfo.getEntityIdentifier().getId(), request));
}
@RequestMapping(value = "/{id}/toaccounts", method = RequestMethod.POST)
public Observable<ResponseEntity<?>> addToAccount(@PathVariable String id, @Validated @RequestBody ToAccountInfo request) {
return customerService.addToAccount(id, request)
.map(entityAndEventInfo -> ResponseEntity.ok().build());
}
}

View File

@@ -0,0 +1,35 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by popikyardo on 03.02.16.
*/
@Configuration
@Import({CustomerConfiguration.class})
@ComponentScan
public class CustomersCommandSideWebConfiguration extends WebMvcConfigurerAdapter {
class FakeThing {}
@Bean
public FakeThing init(RequestMappingHandlerAdapter adapter) {
// https://jira.spring.io/browse/SPR-13083
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
handlers.add(0, new ObservableReturnValueHandler());
adapter.setReturnValueHandlers(handlers);
return new FakeThing();
}
}

View File

@@ -0,0 +1,18 @@
apply plugin: 'java'
dependencies {
compile project(":common-backend")
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
compile 'com.fasterxml.jackson.core:jackson-core:2.4.3'
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.3'
compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:2.4.3'
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
}

View File

@@ -0,0 +1,47 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
/**
* Created by Main on 04.02.2016.
*/
public class CustomerInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass());
private QuerySideCustomerRepository accountInfoRepository;
public CustomerInfoUpdateService(QuerySideCustomerRepository accountInfoRepository) {
this.accountInfoRepository = accountInfoRepository;
}
public void create(String id, CustomerInfo customerInfo) {
try {
accountInfoRepository.save(new QuerySideCustomer(id,
customerInfo.getName(),
customerInfo.getEmail(),
customerInfo.getSsn(),
customerInfo.getPhoneNumber(),
customerInfo.getAddress(),
Collections.<String, ToAccountInfo>emptyMap()
)
);
logger.info("Saved in mongo");
} catch (Throwable t) {
logger.error("Error during saving: ", t);
throw new RuntimeException(t);
}
}
public void addToAccount(String id, ToAccountInfo accountInfo) {
QuerySideCustomer customer = accountInfoRepository.findOne(id);
customer.getToAccounts().put(accountInfo.getId(), accountInfo);
accountInfoRepository.save(customer);
}
}

View File

@@ -0,0 +1,32 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.EntityIdentifier;
import org.springframework.dao.EmptyResultDataAccessException;
import rx.Observable;
import java.util.List;
public class CustomerQueryService {
private QuerySideCustomerRepository querySideCustomerRepository;
public CustomerQueryService(QuerySideCustomerRepository querySideCustomerRepository) {
this.querySideCustomerRepository = querySideCustomerRepository;
}
public Observable<QuerySideCustomer> findByCustomerId(EntityIdentifier customerId) {
QuerySideCustomer customer = querySideCustomerRepository.findOne(customerId.getId());
if (customer == null)
return Observable.error(new EmptyResultDataAccessException(1));
else
return Observable.just(customer);
}
public Observable<List<QuerySideCustomer>> findByEmail(String email){
List<QuerySideCustomer> customers = querySideCustomerRepository.findByEmailLike(email);
if (customers.isEmpty())
return Observable.error(new EmptyResultDataAccessException(1));
else
return Observable.just(customers);
}
}

View File

@@ -0,0 +1,49 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
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;
import net.chrisrichardson.eventstore.subscriptions.CompoundEventHandler;
import net.chrisrichardson.eventstore.subscriptions.DispatchedEvent;
import net.chrisrichardson.eventstore.subscriptions.EventHandlerMethod;
import net.chrisrichardson.eventstore.subscriptions.EventSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
/**
* Created by Main on 04.02.2016.
*/
@EventSubscriber(id = "customerQuerySideEventHandlers")
public class CustomerQueryWorkflow implements CompoundEventHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
private CustomerInfoUpdateService customerInfoUpdateService;
public CustomerQueryWorkflow(CustomerInfoUpdateService customerInfoUpdateService) {
this.customerInfoUpdateService = customerInfoUpdateService;
}
@EventHandlerMethod
public Observable<Object> create(DispatchedEvent<CustomerCreatedEvent> de) {
CustomerCreatedEvent event = de.event();
String id = de.getEntityIdentifier().getId();
customerInfoUpdateService.create(id, event.getCustomerInfo());
return Observable.just(null);
}
@EventHandlerMethod
public Observable<Object> addToAccount(DispatchedEvent<CustomerAddedToAccount> de) {
CustomerAddedToAccount event = de.event();
String id = de.getEntityIdentifier().getId();
ToAccountInfo toAccountInfo = event.getToAccountInfo();
customerInfoUpdateService.addToAccount(id, toAccountInfo);
return Observable.just(null);
}
}

View File

@@ -0,0 +1,30 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import java.util.Map;
/**
* Created by Main on 05.02.2016.
*/
public class QuerySideCustomer extends CustomerInfo {
private String id;
private Map<String, ToAccountInfo> toAccounts;
public QuerySideCustomer(String id, Name name, String email, String ssn, String phoneNumber, Address address, Map<String, ToAccountInfo> toAccounts) {
super(name, email, ssn, phoneNumber, address);
this.id = id;
this.toAccounts = toAccounts;
}
public String getId() {
return id;
}
public Map<String, ToAccountInfo> getToAccounts() {
return toAccounts;
}
}

View File

@@ -0,0 +1,37 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Created by Main on 04.02.2016.
*/
@Configuration
@EnableMongoRepositories
@EnableJavaEventHandlers
public class QuerySideCustomerConfiguration {
@Bean
public CustomerQueryWorkflow customerQueryWorkflow(CustomerInfoUpdateService accountInfoUpdateService) {
return new CustomerQueryWorkflow(accountInfoUpdateService);
}
@Bean
public CustomerInfoUpdateService customerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
return new CustomerInfoUpdateService(querySideCustomerRepository);
}
@Bean
public CustomerQueryService customerQueryService(QuerySideCustomerRepository accountInfoRepository) {
return new CustomerQueryService(accountInfoRepository);
}
@Bean
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
return new QuerySideDependencyChecker(mongoTemplate);
}
}

View File

@@ -0,0 +1,10 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
interface QuerySideCustomerRepository extends MongoRepository<QuerySideCustomer, String> {
List<QuerySideCustomer> findByEmailLike(String email);
}

View File

@@ -0,0 +1,41 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import rx.Observable;
import rx.Subscriber;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
public class QuerySideDependencyChecker {
private Logger logger = LoggerFactory.getLogger(getClass());
private MongoTemplate mongoTemplate;
public QuerySideDependencyChecker(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@PostConstruct
public void checkDependencies() {
try {
logger.info("Checking mongodb connectivity {}", System.getenv("SPRING_DATA_MONGODB_URI"));
Observable.<Object>create(new Observable.OnSubscribe<Object>() {
@Override
public void call(Subscriber<? super Object> subscriber) {
try {
subscriber.onNext(mongoTemplate.getDb().getCollectionNames());
subscriber.onCompleted();
} catch (Throwable t) {
subscriber.onError(t);
}
}
}).timeout(5, TimeUnit.SECONDS).toBlocking().first();
} catch (Throwable e) {
throw new RuntimeException("Error connecting to Mongo - have you set SPRING_DATA_MONGODB_URI or --spring.data.mongodb_uri?", e);
}
}
}

View File

@@ -0,0 +1,27 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: 'spring-boot'
dependencies {
compile project(":customers-query-side-web")
compile project(":common-swagger")
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
testCompile project(":testutil")
testCompile project(":customers-command-side-service")
testCompile "org.springframework.boot:spring-boot-starter-test"
}
test {
ignoreFailures true
environment 'EVENTUATE_API_KEY_ID', '20CAXGPA3DE56WXO2SFBUDGZ9'
environment 'EVENTUATE_API_KEY_SECRET', 'gU6n78drWCIgkgzVStvI3BhV3MfzDyjWKCN7p0PBimI'
environment 'SPRING_DATA_MONGODB_URI', 'mongodb://198.50.218.51/mydb'
}

View File

@@ -0,0 +1,28 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers.QuerySideCustomerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({QuerySideCustomerConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class CustomersQuerySideServiceConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverters(additional);
}
}

View File

@@ -0,0 +1,11 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.main;
import net.chrisrichardson.eventstore.javaexamples.banking.web.CustomersQuerySideServiceConfiguration;
import org.springframework.boot.SpringApplication;
public class CustomersQuerySideServiceMain {
public static void main(String[] args) {
SpringApplication.run(CustomersQuerySideServiceConfiguration.class, args);
}
}

View File

@@ -0,0 +1,82 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomersQuerySideServiceTestConfiguration.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0", "management.port=0"})
public class CustomersQuerySideServiceIntegrationTest {
@Value("${local.server.port}")
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path;
}
@Autowired
RestTemplate restTemplate;
@Test
public void shouldGetCustomerById() {
CustomerInfo customerInfo = generateCustomerInfo();
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"),customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
assertCustomerResponse(customerId, customerInfo);
}
private void assertCustomerResponse(final String customerId, final CustomerInfo customerInfo) {
eventually(
new Producer<CustomerResponse>() {
@Override
public Observable<CustomerResponse> produce() {
return Observable.just(restTemplate.getForEntity(baseUrl("/customers/" + customerId), CustomerResponse.class).getBody());
}
},
new Verifier<CustomerResponse>() {
@Override
public void verify(CustomerResponse customerResponse) {
Assert.assertEquals(customerId, customerResponse.getId());
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
}
});
}
private CustomerInfo generateCustomerInfo() {
return new CustomerInfo(
new Name("John", "Doe"),
"current@email.com",
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
"street 2",
"City",
"State",
"1111111")
);
}
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.List;
@Configuration
@Import({CustomersQuerySideServiceConfiguration.class, CustomersCommandSideServiceConfiguration.class})
public class CustomersQuerySideServiceTestConfiguration {
@Bean
public RestTemplate restTemplate(HttpMessageConverters converters) {
RestTemplate restTemplate = new RestTemplate();
HttpMessageConverter<?> httpMessageConverter = converters.getConverters().get(0);
List<? extends HttpMessageConverter<?>> httpMessageConverters = Arrays.asList(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters((List<HttpMessageConverter<?>>) httpMessageConverters);
return restTemplate;
}
}

View File

@@ -0,0 +1,9 @@
apply plugin: 'java'
dependencies {
compile project(":customers-query-side-backend")
compile project(":common-web")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

View File

@@ -0,0 +1,33 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers.QuerySideCustomerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
@Configuration
@Import({QuerySideCustomerConfiguration.class})
@ComponentScan
public class CustomersQuerySideWebConfiguration extends WebMvcConfigurerAdapter {
class FakeThing {
}
@Bean
public FakeThing init(RequestMappingHandlerAdapter adapter) {
// https://jira.spring.io/browse/SPR-13083
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
handlers.add(0, new ObservableReturnValueHandler());
adapter.setReturnValueHandlers(handlers);
return new FakeThing();
}
}

View File

@@ -0,0 +1,59 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.customers;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers.CustomerQueryService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import rx.Observable;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created by Main on 05.02.2016.
*/
@RestController
public class CustomerQueryController {
private CustomerQueryService customerQueryService;
@Autowired
public CustomerQueryController(CustomerQueryService customerQueryService) {
this.customerQueryService = customerQueryService;
}
@RequestMapping(value = "/customers/{customerId}", method = RequestMethod.GET)
public Observable<CustomerResponse> getCustomer(@PathVariable String customerId) {
return customerQueryService.findByCustomerId(new EntityIdentifier(customerId))
.map(this::getCustomerResponse);
}
@RequestMapping(value = "/customers", method = RequestMethod.GET)
public Observable<CustomersQueryResponse> getCustomersByEmail(@RequestParam String email) {
return customerQueryService.findByEmail(email)
.map(this::getCustomersQueryResponse);
}
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "customers not found")
@ExceptionHandler(EmptyResultDataAccessException.class)
public void customersNotFound() {
}
private CustomerResponse getCustomerResponse(QuerySideCustomer querySideCustomer) {
return new CustomerResponse(querySideCustomer.getId(), querySideCustomer);
}
private CustomersQueryResponse getCustomersQueryResponse(List<QuerySideCustomer> customersList) {
return new CustomersQueryResponse(customersList
.stream()
.map(this::getCustomerResponse)
.collect(Collectors.toList())
);
}
}

View File

@@ -0,0 +1,28 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import java.util.List;
/**
* Created by Main on 05.02.2016.
*/
public class CustomersQueryResponse {
private List<CustomerResponse> customers;
public CustomersQueryResponse() {
}
public CustomersQueryResponse(List<CustomerResponse> customers) {
this.customers = customers;
}
public List<CustomerResponse> getCustomers() {
return customers;
}
public void setCustomers(List<CustomerResponse> customers) {
this.customers = customers;
}
}

View File

@@ -63,10 +63,10 @@ public class EndToEndTest {
BigDecimal finalFromAccountBalance = initialFromAccountBalance.subtract(amountToTransfer);
BigDecimal finalToAccountBalance = initialToAccountBalance.add(amountToTransfer);
final CreateAccountResponse fromAccount = restTemplate.postForEntity(accountsCommandSideBaseUrl("/accounts"), new CreateAccountRequest(initialFromAccountBalance), CreateAccountResponse.class).getBody();
final CreateAccountResponse fromAccount = restTemplate.postForEntity(accountsCommandSideBaseUrl("/accounts"), new CreateAccountRequest("00000000-00000000", "My #1 Account", initialFromAccountBalance), CreateAccountResponse.class).getBody();
final String fromAccountId = fromAccount.getAccountId();
CreateAccountResponse toAccount = restTemplate.postForEntity(accountsCommandSideBaseUrl("/accounts"), new CreateAccountRequest(initialToAccountBalance), CreateAccountResponse.class).getBody();
CreateAccountResponse toAccount = restTemplate.postForEntity(accountsCommandSideBaseUrl("/accounts"), new CreateAccountRequest("00000000-00000000", "My #2 Account", initialToAccountBalance), CreateAccountResponse.class).getBody();
String toAccountId = toAccount.getAccountId();
Assert.assertNotNull(fromAccountId);

View File

@@ -6,6 +6,8 @@ dependencies {
compile project(":accounts-query-side-web")
compile project(":accounts-command-side-web")
compile project(":transactions-command-side-web")
compile project(":customers-command-side-web")
compile project(":customers-query-side-web")
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"

View File

@@ -1,7 +1,9 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CommandSideWebAccountsConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomersCommandSideWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.transactions.CommandSideWebTransactionsConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.CustomersQuerySideWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.QuerySideWebConfiguration;
import net.chrisrichardson.eventstore.jdbc.config.JdbcEventStoreConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -14,7 +16,7 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({CommandSideWebAccountsConfiguration.class, CommandSideWebTransactionsConfiguration.class, JdbcEventStoreConfiguration.class, QuerySideWebConfiguration.class})
@Import({CommandSideWebAccountsConfiguration.class, CommandSideWebTransactionsConfiguration.class, JdbcEventStoreConfiguration.class, QuerySideWebConfiguration.class, CustomersQuerySideWebConfiguration.class, CustomersCommandSideWebConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class BankingWebConfiguration {

View File

@@ -1,5 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.*;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.transactions.CreateMoneyTransferRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.transactions.CreateMoneyTransferResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts.GetAccountResponse;
@@ -50,10 +51,10 @@ public class BankingWebIntegrationTest {
BigDecimal finalFromAccountBalance = initialFromAccountBalance.subtract(amountToTransfer);
BigDecimal finalToAccountBalance = initialToAccountBalance.add(amountToTransfer);
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialFromAccountBalance), CreateAccountResponse.class).getBody();
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest("00000000-00000000", "My Account", initialFromAccountBalance), CreateAccountResponse.class).getBody();
final String fromAccountId = fromAccount.getAccountId();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialToAccountBalance), CreateAccountResponse.class).getBody();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest("00000000-00000000", "My Account", initialToAccountBalance), CreateAccountResponse.class).getBody();
String toAccountId = toAccount.getAccountId();
Assert.assertNotNull(fromAccountId);
@@ -71,6 +72,19 @@ public class BankingWebIntegrationTest {
}
@Test
public void shouldCreateCustomers() {
CustomerInfo customerInfo = generateCustomerInfo();
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
Assert.assertNotNull(customerId);
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
assertCustomerResponse(customerId, customerInfo);
}
private BigDecimal toCents(BigDecimal dollarAmount) {
return dollarAmount.multiply(new BigDecimal(100));
}
@@ -93,4 +107,38 @@ public class BankingWebIntegrationTest {
});
}
private void assertCustomerResponse(final String customerId, final CustomerInfo customerInfo) {
eventually(
new Producer<CustomerResponse>() {
@Override
public Observable<CustomerResponse> produce() {
return Observable.just(restTemplate.getForEntity(baseUrl("/customers/" + customerId), CustomerResponse.class).getBody());
}
},
new Verifier<CustomerResponse>() {
@Override
public void verify(CustomerResponse customerResponse) {
Assert.assertEquals(customerId, customerResponse.getId());
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
}
});
}
private CustomerInfo generateCustomerInfo() {
return new CustomerInfo(
new Name("John", "Doe"),
"current@email.com",
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
"street 2",
"City",
"State",
"1111111")
);
}
private ToAccountInfo generateToAccountInfo() {
return new ToAccountInfo("11111111-11111111", "New Account", "John Doe");
}
}

View File

@@ -0,0 +1,377 @@
{
"swagger": "2.0",
"info": {
"description": "Api Documentation",
"version": "1.0",
"title": "Api Documentation",
"termsOfService": "urn:tos",
"contact": {
"name": "Contact Email"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
},
"host": "localhost:8080",
"basePath": "/",
"tags": [
{
"name": "customer-service-command-side-controller",
"description": "Customer Service Commandside Controller"
},
{
"name": "customer-service-query-side-controller",
"description": "Customer Service Queryside Controller"
},
{
"name": "account-query-side-controller",
"description": "Account Service Queryside Controller"
},
{
"name": "auth-controller",
"description": "Authentication Controller"
}
],
"paths": {
"/login": {
"post": {
"tags": [
"auth-controller"
],
"summary": "doAuth",
"operationId": "doAuthUsingPOST",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "request",
"description": "request",
"required": true,
"schema": {
"$ref": "#/definitions/AuthRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/AuthResponse"
}
}
}
}
},
"/accounts": {
"get": {
"tags": [
"account-query-side-controller"
],
"summary": "getAllAccountsByCustomer",
"operationId": "getAllAccountsByCustomerUsingGET",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"name": "customerId",
"in": "query",
"description": "customer id",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/CustomersQueryResponse"
}
}
}
}
},
"/customers": {
"get": {
"tags": [
"customer-service-query-side-controller"
],
"summary": "getAllCustomersByEmail",
"operationId": "getAllCustomersByEmailUsingGET",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"name": "email",
"in": "query",
"description": "customer's email",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/CustomersQueryResponse"
}
}
}
},
"post": {
"tags": [
"customer-service-command-side-controller"
],
"summary": "saveCustomer",
"operationId": "saveCustomerUsingPOST",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"in": "body",
"name": "customer",
"description": "customer",
"required": true,
"schema": {
"$ref": "#/definitions/CreateCustomerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/CustomerResponse"
}
},
"400": {
"description": "Validation error"
}
}
}
},
"/customers/{id}": {
"get": {
"tags": [
"customer-service-query-side-controller"
],
"summary": "getBoard",
"operationId": "getBoardUsingGET",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"name": "id",
"in": "path",
"description": "id",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/CustomerResponse"
}
}
}
}
},
"/customers/{id}/toaccounts": {
"post": {
"tags": [
"customer-service-command-side-controller"
],
"summary": "addToAccount",
"operationId": "addToAccountUsingPOST",
"consumes": [
"application/json"
],
"produces": [
"*/*"
],
"parameters": [
{
"name": "id",
"in": "path",
"description": "id",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "request",
"description": "request",
"required": true,
"schema": {
"$ref": "#/definitions/ToAccountsRequest"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Validation error"
}
}
}
}
},
"definitions": {
"AuthRequest": {
"required": [ "email" ],
"properties": {
"email": {
"type": "string"
}
}
},
"AuthResponse": {
"properties": {
"token": {
"type": "string"
}
}
},
"CustomerInfo": {
"required": [ "email" ],
"properties": {
"email": {
"type": "string"
},
"ssn": {
"type": "string"
},
"phoneNumber": {
"type": "string"
},
"address": {
"$ref": "#/definitions/Address"
}
}
},
"CustomersQueryResponse": {
"properties": {
"customers": {
"type": "array",
"items": {
"$ref": "#/definitions/CustomerResponse"
}
}
}
},
"CustomerResponse": {
"required": [ "id", "customerInfo" ],
"properties": {
"id": {
"type": "string"
},
"customerInfo": {
"$ref": "#/definitions/CustomerInfo"
}
}
},
"AccountsQueryResponse": {
"properties": {
"customers": {
"type": "array",
"items": {
"$ref": "#/definitions/GetAccountResponse"
}
}
}
},
"GetAccountResponse": {
"properties": {
"accountId": {
"type": "string"
},
"balance": {
"type": "number"
}
}
},
"CreateCustomerRequest": {
"required": [ "email", "ssn", "phoneNumber" ],
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"ssn": {
"type": "string"
},
"phoneNumber": {
"type": "string"
},
"address": {
"$ref": "#/definitions/Address"
}
}
},
"ToAccountsRequest":{
"required": [ "id", "owner" ],
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"owner": {
"type": "string"
}
}
},
"Address": {
"required": [ "street1", "city", "state", "zipCode" ],
"properties": {
"street1": {
"type": "string"
},
"street2": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"zipCode": {
"type": "string"
}
}
}
}
}

View File

@@ -24,5 +24,13 @@ include 'transactions-command-side-service'
include 'e2e-test'
rootProject.name = 'java-spring-event-sourcing-example'
include 'common-auth'
include 'customers-command-side-backend'
include 'customers-command-side-web'
include 'customers-query-side-backend'
include 'customers-query-side-web'
include 'common-customers'
include 'customers-command-side-service'
include 'customers-query-side-service'
include 'common-auth-controller'

View File

@@ -47,7 +47,7 @@ public class TestUtil {
public static <T> void eventually(final Producer<T> producer, final Verifier<T> verifier) {
final int n = 50;
Object possibleException = Observable.timer(0, 100, TimeUnit.MILLISECONDS).flatMap(new Func1<Long, Observable<Outcome<T>>>() {
Object possibleException = Observable.timer(0, 200, TimeUnit.MILLISECONDS).flatMap(new Func1<Long, Observable<Outcome<T>>>() {
@Override
public Observable<Outcome<T>> call(Long aLong) {