diff --git a/account-parent/account-web/pom.xml b/account-parent/account-web/pom.xml
index a985dec..e794708 100644
--- a/account-parent/account-web/pom.xml
+++ b/account-parent/account-web/pom.xml
@@ -31,6 +31,10 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ org.springframework.boot
+ spring-boot-starter-redis
+
org.springframework.boot
spring-boot-starter-actuator
diff --git a/account-parent/account-web/src/main/java/demo/account/AccountController.java b/account-parent/account-web/src/main/java/demo/account/AccountController.java
index 360ac1c..e0a2ec3 100644
--- a/account-parent/account-web/src/main/java/demo/account/AccountController.java
+++ b/account-parent/account-web/src/main/java/demo/account/AccountController.java
@@ -31,14 +31,14 @@ public class AccountController {
public ResponseEntity createAccount(@RequestBody Account account) {
return Optional.ofNullable(createAccountResource(account))
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
- .orElseThrow(() -> new IllegalArgumentException("Account creation failed"));
+ .orElseThrow(() -> new RuntimeException("Account creation failed"));
}
@PutMapping(path = "/accounts/{id}")
public ResponseEntity updateAccount(@RequestBody Account account, @PathVariable Long id) {
return Optional.ofNullable(updateAccountResource(id, account))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("Account update failed"));
+ .orElseThrow(() -> new RuntimeException("Account update failed"));
}
@GetMapping(path = "/accounts/{id}")
@@ -48,25 +48,32 @@ public class AccountController {
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
+ @DeleteMapping(path = "/accounts/{id}")
+ public ResponseEntity deleteAccount(@PathVariable Long id) {
+ return Optional.ofNullable(accountService.deleteAccount(id))
+ .map(e -> new ResponseEntity<>(HttpStatus.NO_CONTENT))
+ .orElseThrow(() -> new RuntimeException("Account deletion failed"));
+ }
+
@GetMapping(path = "/accounts/{id}/events")
public ResponseEntity getAccountEvents(@PathVariable Long id) {
return Optional.ofNullable(getAccountEventResources(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("Could not get account events"));
+ .orElseThrow(() -> new RuntimeException("Could not get account events"));
}
@PostMapping(path = "/accounts/{id}/events")
public ResponseEntity createAccount(@PathVariable Long id, @RequestBody AccountEvent event) {
return Optional.ofNullable(appendEventResource(id, event))
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
- .orElseThrow(() -> new IllegalArgumentException("Append account event failed"));
+ .orElseThrow(() -> new RuntimeException("Append account event failed"));
}
@GetMapping(path = "/accounts/{id}/commands")
public ResponseEntity getAccountCommands(@PathVariable Long id) {
return Optional.ofNullable(getCommandsResource(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("The account could not be found"));
+ .orElseThrow(() -> new RuntimeException("The account could not be found"));
}
@GetMapping(path = "/accounts/{id}/commands/confirm")
@@ -74,7 +81,7 @@ public class AccountController {
return Optional.ofNullable(getAccountResource(
accountService.applyCommand(id, AccountCommand.CONFIRM_ACCOUNT)))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
+ .orElseThrow(() -> new RuntimeException("The command could not be applied"));
}
@GetMapping(path = "/accounts/{id}/commands/activate")
@@ -82,7 +89,7 @@ public class AccountController {
return Optional.ofNullable(getAccountResource(
accountService.applyCommand(id, AccountCommand.ACTIVATE_ACCOUNT)))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
+ .orElseThrow(() -> new RuntimeException("The command could not be applied"));
}
@GetMapping(path = "/accounts/{id}/commands/suspend")
@@ -90,7 +97,7 @@ public class AccountController {
return Optional.ofNullable(getAccountResource(
accountService.applyCommand(id, AccountCommand.SUSPEND_ACCOUNT)))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
+ .orElseThrow(() -> new RuntimeException("The command could not be applied"));
}
@GetMapping(path = "/accounts/{id}/commands/archive")
@@ -98,7 +105,7 @@ public class AccountController {
return Optional.ofNullable(getAccountResource(
accountService.applyCommand(id, AccountCommand.ARCHIVE_ACCOUNT)))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
- .orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
+ .orElseThrow(() -> new RuntimeException("The command could not be applied"));
}
/**
diff --git a/account-parent/account-web/src/main/java/demo/account/AccountService.java b/account-parent/account-web/src/main/java/demo/account/AccountService.java
index e6d83f0..a604fec 100644
--- a/account-parent/account-web/src/main/java/demo/account/AccountService.java
+++ b/account-parent/account-web/src/main/java/demo/account/AccountService.java
@@ -3,6 +3,9 @@ package demo.account;
import demo.event.AccountEvent;
import demo.event.AccountEventType;
import demo.event.EventService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@@ -11,6 +14,8 @@ import java.util.Arrays;
import java.util.Objects;
import static demo.account.AccountStatus.*;
+import static demo.account.AccountStatus.ACCOUNT_ACTIVE;
+import static demo.account.AccountStatus.ACCOUNT_ARCHIVED;
/**
* The {@link AccountService} provides transactional support for managing {@link Account}
@@ -33,81 +38,13 @@ public class AccountService {
this.eventService = eventService;
}
- /**
- * Apply an {@link AccountCommand} to the {@link Account} with a specified identifier.
- *
- * @param id is the unique identifier of the {@link Account}
- * @param accountCommand is the command to apply to the {@link Account}
- * @return a hypermedia resource containing the updated {@link Account}
- */
- public Account applyCommand(Long id, AccountCommand accountCommand) {
- Account account = getAccount(id);
-
- Assert.notNull(account, "The account for the supplied id could not be found");
-
- AccountStatus status = account.getStatus();
-
- switch (accountCommand) {
- case CONFIRM_ACCOUNT:
- Assert.isTrue(status == ACCOUNT_PENDING, "The account has already been confirmed");
-
- // Confirm the account
- Account updateAccount = account;
- updateAccount.setStatus(ACCOUNT_CONFIRMED);
- account = updateAccount(id, updateAccount);
- appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED));
- break;
- case ACTIVATE_ACCOUNT:
- Assert.isTrue(status != ACCOUNT_ACTIVE, "The account is already active");
- Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
- .contains(status), "The account cannot be activated");
-
- // Activate the account
- account.setStatus(ACCOUNT_ACTIVE);
- account = updateAccount(id, account);
- appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED));
- break;
- case SUSPEND_ACCOUNT:
- Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
-
- // Suspend the account
- account.setStatus(ACCOUNT_SUSPENDED);
- account = updateAccount(id, account);
- appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED));
- break;
- case ARCHIVE_ACCOUNT:
- Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
-
- // Archive the account
- account.setStatus(ACCOUNT_ARCHIVED);
- account = updateAccount(id, account);
- appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED));
- break;
- default:
- Assert.notNull(accountCommand,
- "The provided command cannot be applied to this account in its current state");
- }
-
- return account;
- }
-
-
- /**
- * Get an {@link Account} entity for the supplied identifier.
- *
- * @param id is the unique identifier of a {@link Account} entity
- * @return an {@link Account} entity
- */
- public Account getAccount(Long id) {
- return accountRepository.findOne(id);
- }
-
/**
* Create a new {@link Account} entity.
*
* @param account is the {@link Account} to create
* @return the newly created {@link Account}
*/
+ @CacheEvict(value = "account", key = "#account.getAccountId().toString()")
public Account createAccount(Account account) {
// Assert for uniqueness constraint
Assert.isNull(accountRepository.findAccountByUserId(account.getUserId()),
@@ -125,6 +62,17 @@ public class AccountService {
return account;
}
+ /**
+ * Get an {@link Account} entity for the supplied identifier.
+ *
+ * @param id is the unique identifier of a {@link Account} entity
+ * @return an {@link Account} entity
+ */
+ @Cacheable(value = "account", key = "#id.toString()")
+ public Account getAccount(Long id) {
+ return accountRepository.findOne(id);
+ }
+
/**
* Update an {@link Account} entity with the supplied identifier.
*
@@ -132,6 +80,7 @@ public class AccountService {
* @param account is the {@link Account} containing updated fields
* @return the updated {@link Account} entity
*/
+ @CachePut(value = "account", key = "#id.toString()")
public Account updateAccount(Long id, Account account) {
Assert.notNull(id, "Account id must be present in the resource URL");
Assert.notNull(account, "Account request body cannot be null");
@@ -143,15 +92,31 @@ public class AccountService {
account.setAccountId(id);
}
- Account currentAccount = getAccount(id);
- currentAccount.setStatus(account.getStatus());
- currentAccount.setDefaultAccount(account.getDefaultAccount());
- currentAccount.setAccountNumber(account.getAccountNumber());
+ Assert.state(accountRepository.exists(id),
+ "The account with the supplied id does not exist");
+
+ Account currentAccount = accountRepository.findOne(id);
currentAccount.setUserId(account.getUserId());
+ currentAccount.setAccountNumber(account.getAccountNumber());
+ currentAccount.setDefaultAccount(account.getDefaultAccount());
+ currentAccount.setStatus(account.getStatus());
return accountRepository.save(currentAccount);
}
+ /**
+ * Delete the {@link Account} with the supplied identifier.
+ *
+ * @param id is the unique identifier for the {@link Account}
+ */
+ @CacheEvict(value = "account", key = "#id.toString()")
+ public Boolean deleteAccount(Long id) {
+ Assert.state(accountRepository.exists(id),
+ "The account with the supplied id does not exist");
+ this.accountRepository.delete(id);
+ return true;
+ }
+
/**
* Append a new {@link AccountEvent} to the {@link Account} reference for the supplied identifier.
*
@@ -168,4 +133,63 @@ public class AccountService {
accountRepository.save(account);
return event;
}
+
+ /**
+ * Apply an {@link AccountCommand} to the {@link Account} with a specified identifier.
+ *
+ * @param id is the unique identifier of the {@link Account}
+ * @param accountCommand is the command to apply to the {@link Account}
+ * @return a hypermedia resource containing the updated {@link Account}
+ */
+ @CachePut(value = "account", key = "#id.toString()")
+ public Account applyCommand(Long id, AccountCommand accountCommand) {
+ Account account = getAccount(id);
+
+ Assert.notNull(account, "The account for the supplied id could not be found");
+
+ AccountStatus status = account.getStatus();
+
+ switch (accountCommand) {
+ case CONFIRM_ACCOUNT:
+ Assert.isTrue(status == ACCOUNT_PENDING, "The account has already been confirmed");
+
+ // Confirm the account
+ Account updateAccount = account;
+ updateAccount.setStatus(ACCOUNT_CONFIRMED);
+ account = this.updateAccount(id, updateAccount);
+ this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED));
+ break;
+ case ACTIVATE_ACCOUNT:
+ Assert.isTrue(status != ACCOUNT_ACTIVE, "The account is already active");
+ Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
+ .contains(status), "The account cannot be activated");
+
+ // Activate the account
+ account.setStatus(ACCOUNT_ACTIVE);
+ account = this.updateAccount(id, account);
+ this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED));
+ break;
+ case SUSPEND_ACCOUNT:
+ Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
+
+ // Suspend the account
+ account.setStatus(ACCOUNT_SUSPENDED);
+ account = this.updateAccount(id, account);
+ this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED));
+ break;
+ case ARCHIVE_ACCOUNT:
+ Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
+
+ // Archive the account
+ account.setStatus(ACCOUNT_ARCHIVED);
+ account = this.updateAccount(id, account);
+ this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED));
+ break;
+ default:
+ Assert.notNull(accountCommand,
+ "The provided command cannot be applied to this account in its current state");
+ }
+
+ return account;
+ }
}
diff --git a/account-parent/account-web/src/main/java/demo/config/CacheConfiguration.java b/account-parent/account-web/src/main/java/demo/config/CacheConfiguration.java
new file mode 100644
index 0000000..1ec7986
--- /dev/null
+++ b/account-parent/account-web/src/main/java/demo/config/CacheConfiguration.java
@@ -0,0 +1,42 @@
+package demo.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+
+@Configuration
+@EnableCaching
+public class CacheConfiguration {
+
+ @Bean
+ public JedisConnectionFactory redisConnectionFactory(
+ @Value("${spring.redis.port}") Integer redisPort,
+ @Value("${spring.redis.host}") String redisHost) {
+ JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
+
+ redisConnectionFactory.setHostName(redisHost);
+ redisConnectionFactory.setPort(redisPort);
+
+ return redisConnectionFactory;
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory cf) {
+ RedisTemplate redisTemplate = new RedisTemplate();
+ redisTemplate.setConnectionFactory(cf);
+ return redisTemplate;
+ }
+
+ @Bean
+ public CacheManager cacheManager(RedisTemplate redisTemplate) {
+ RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
+ cacheManager.setDefaultExpiration(3000);
+ return cacheManager;
+ }
+}
\ No newline at end of file
diff --git a/account-parent/account-web/src/main/java/demo/config/JpaConfig.java b/account-parent/account-web/src/main/java/demo/config/JpaConfiguration.java
similarity index 89%
rename from account-parent/account-web/src/main/java/demo/config/JpaConfig.java
rename to account-parent/account-web/src/main/java/demo/config/JpaConfiguration.java
index c6c09e0..7c300cb 100644
--- a/account-parent/account-web/src/main/java/demo/config/JpaConfig.java
+++ b/account-parent/account-web/src/main/java/demo/config/JpaConfiguration.java
@@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
*/
@Configuration
@EnableJpaAuditing
-public class JpaConfig {
+public class JpaConfiguration {
}
diff --git a/account-parent/account-web/src/main/java/demo/config/AccountResourceConfig.java b/account-parent/account-web/src/main/java/demo/config/ResourceConfiguration.java
similarity index 96%
rename from account-parent/account-web/src/main/java/demo/config/AccountResourceConfig.java
rename to account-parent/account-web/src/main/java/demo/config/ResourceConfiguration.java
index ef9587b..41b3f25 100644
--- a/account-parent/account-web/src/main/java/demo/config/AccountResourceConfig.java
+++ b/account-parent/account-web/src/main/java/demo/config/ResourceConfiguration.java
@@ -10,7 +10,7 @@ import org.springframework.hateoas.ResourceProcessor;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@Configuration
-public class AccountResourceConfig {
+public class ResourceConfiguration {
/**
* Enriches the {@link Account} resource with hypermedia links.
diff --git a/account-parent/account-web/src/main/java/demo/config/AccountStreamConfig.java b/account-parent/account-web/src/main/java/demo/config/StreamConfiguration.java
similarity index 87%
rename from account-parent/account-web/src/main/java/demo/config/AccountStreamConfig.java
rename to account-parent/account-web/src/main/java/demo/config/StreamConfiguration.java
index 45ceb93..cec5ec1 100644
--- a/account-parent/account-web/src/main/java/demo/config/AccountStreamConfig.java
+++ b/account-parent/account-web/src/main/java/demo/config/StreamConfiguration.java
@@ -6,5 +6,5 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBinding(Source.class)
-public class AccountStreamConfig {
+public class StreamConfiguration {
}
diff --git a/account-parent/account-web/src/main/java/demo/domain/BaseEntity.java b/account-parent/account-web/src/main/java/demo/domain/BaseEntity.java
index db285dc..fb472ef 100644
--- a/account-parent/account-web/src/main/java/demo/domain/BaseEntity.java
+++ b/account-parent/account-web/src/main/java/demo/domain/BaseEntity.java
@@ -7,10 +7,11 @@ import org.springframework.hateoas.ResourceSupport;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
+import java.io.Serializable;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
-public class BaseEntity extends ResourceSupport {
+public class BaseEntity extends ResourceSupport implements Serializable {
@CreatedDate
private Long createdAt;
diff --git a/account-parent/account-web/src/main/resources/application.yml b/account-parent/account-web/src/main/resources/application.yml
index 722c9b5..e940116 100644
--- a/account-parent/account-web/src/main/resources/application.yml
+++ b/account-parent/account-web/src/main/resources/application.yml
@@ -10,5 +10,8 @@ spring:
output:
destination: account
contentType: 'application/json'
+ redis:
+ host: localhost
+ port: 6379
server:
port: 0
\ No newline at end of file
diff --git a/account-parent/account-web/src/test/java/demo/account/AccountServiceTests.java b/account-parent/account-web/src/test/java/demo/account/AccountServiceTests.java
index 42a3d96..dbdd816 100644
--- a/account-parent/account-web/src/test/java/demo/account/AccountServiceTests.java
+++ b/account-parent/account-web/src/test/java/demo/account/AccountServiceTests.java
@@ -70,6 +70,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED)))
.willReturn(accountEvent);
@@ -91,6 +92,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
.willReturn(accountEvent);
@@ -112,6 +114,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED)))
.willReturn(accountEvent);
@@ -133,6 +136,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
.willReturn(accountEvent);
@@ -154,6 +158,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED)))
.willReturn(accountEvent);
@@ -175,6 +180,7 @@ public class AccountServiceTests {
accountEvent.setEventId(1L);
given(this.accountRepository.findOne(1L)).willReturn(account);
+ given(this.accountRepository.exists(1L)).willReturn(true);
given(this.accountRepository.save(account)).willReturn(account);
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
.willReturn(accountEvent);