Add caching
This commit is contained in:
@@ -31,6 +31,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaConfig {
|
||||
public class JpaConfiguration {
|
||||
}
|
||||
@@ -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.
|
||||
@@ -6,5 +6,5 @@ import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableBinding(Source.class)
|
||||
public class AccountStreamConfig {
|
||||
public class StreamConfiguration {
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -10,5 +10,8 @@ spring:
|
||||
output:
|
||||
destination: account
|
||||
contentType: 'application/json'
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
server:
|
||||
port: 0
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user