226 lines
8.9 KiB
Java
226 lines
8.9 KiB
Java
package demo.account;
|
|
|
|
import demo.event.AccountEvent;
|
|
import demo.event.AccountEventType;
|
|
import demo.event.EventService;
|
|
import demo.event.ConsistencyModel;
|
|
import org.springframework.cache.CacheManager;
|
|
import org.springframework.cache.annotation.CacheConfig;
|
|
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.util.Assert;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
|
|
import static demo.account.AccountStatus.*;
|
|
|
|
/**
|
|
* The {@link AccountService} provides transactional support for managing {@link Account}
|
|
* entities. This service also provides event sourcing support for {@link AccountEvent}.
|
|
* Events can be appended to an {@link Account}, which contains a append-only log of
|
|
* actions that can be used to support remediation for distributed transactions that encountered
|
|
* a partial failure.
|
|
*
|
|
* @author kbastani
|
|
*/
|
|
@Service
|
|
@CacheConfig(cacheNames = {"accounts"})
|
|
public class AccountService {
|
|
|
|
private final AccountRepository accountRepository;
|
|
private final EventService eventService;
|
|
private final CacheManager cacheManager;
|
|
|
|
public AccountService(AccountRepository accountRepository, EventService eventService, CacheManager cacheManager) {
|
|
this.accountRepository = accountRepository;
|
|
this.eventService = eventService;
|
|
this.cacheManager = cacheManager;
|
|
}
|
|
|
|
@CacheEvict(cacheNames = "accounts", key = "#account.getAccountId().toString()")
|
|
public Account registerAccount(Account account) {
|
|
|
|
account = createAccount(account);
|
|
|
|
cacheManager.getCache("accounts")
|
|
.evict(account.getAccountId());
|
|
|
|
// Trigger the account creation event
|
|
AccountEvent event = appendEvent(account.getAccountId(),
|
|
new AccountEvent(AccountEventType.ACCOUNT_CREATED));
|
|
|
|
// Attach account identifier
|
|
event.getAccount().setAccountId(account.getAccountId());
|
|
|
|
// Return the result
|
|
return event.getAccount();
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Account} entity.
|
|
*
|
|
* @param account is the {@link Account} to create
|
|
* @return the newly created {@link Account}
|
|
*/
|
|
@CacheEvict(cacheNames = "accounts", key = "#account.getAccountId().toString()")
|
|
public Account createAccount(Account account) {
|
|
|
|
// Assert for uniqueness constraint
|
|
Assert.isNull(accountRepository.findAccountByEmail(account.getEmail()),
|
|
"An account with the supplied email already exists");
|
|
|
|
// Save the account to the repository
|
|
account = accountRepository.saveAndFlush(account);
|
|
|
|
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(cacheNames = "accounts", key = "#id.toString()")
|
|
public Account getAccount(Long id) {
|
|
return accountRepository.findOne(id);
|
|
}
|
|
|
|
/**
|
|
* Update an {@link Account} entity with the supplied identifier.
|
|
*
|
|
* @param id is the unique identifier of the {@link Account} entity
|
|
* @param account is the {@link Account} containing updated fields
|
|
* @return the updated {@link Account} entity
|
|
*/
|
|
@CachePut(cacheNames = "accounts", 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");
|
|
|
|
if (account.getAccountId() != null) {
|
|
Assert.isTrue(Objects.equals(id, account.getAccountId()),
|
|
"The account id in the request body must match the resource URL");
|
|
} else {
|
|
account.setAccountId(id);
|
|
}
|
|
|
|
Assert.state(accountRepository.exists(id),
|
|
"The account with the supplied id does not exist");
|
|
|
|
Account currentAccount = accountRepository.findOne(id);
|
|
currentAccount.setEmail(account.getEmail());
|
|
currentAccount.setFirstName(account.getFirstName());
|
|
currentAccount.setLastName(account.getLastName());
|
|
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(cacheNames = "accounts", 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.
|
|
*
|
|
* @param accountId is the unique identifier for the {@link Account}
|
|
* @param event is the {@link AccountEvent} to append to the {@link Account} entity
|
|
* @return the newly appended {@link AccountEvent}
|
|
*/
|
|
public AccountEvent appendEvent(Long accountId, AccountEvent event) {
|
|
return appendEvent(accountId, event, ConsistencyModel.ACID);
|
|
}
|
|
|
|
/**
|
|
* Append a new {@link AccountEvent} to the {@link Account} reference for the supplied identifier.
|
|
*
|
|
* @param accountId is the unique identifier for the {@link Account}
|
|
* @param event is the {@link AccountEvent} to append to the {@link Account} entity
|
|
* @return the newly appended {@link AccountEvent}
|
|
*/
|
|
public AccountEvent appendEvent(Long accountId, AccountEvent event, ConsistencyModel consistencyModel) {
|
|
Account account = getAccount(accountId);
|
|
Assert.notNull(account, "The account with the supplied id does not exist");
|
|
event.setAccount(account);
|
|
event = eventService.createEvent(accountId, event);
|
|
account.getEvents().add(event);
|
|
accountRepository.saveAndFlush(account);
|
|
eventService.raiseEvent(event, consistencyModel);
|
|
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(cacheNames = "accounts", 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);
|
|
this.updateAccount(id, updateAccount);
|
|
this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED))
|
|
.getAccount();
|
|
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);
|
|
this.updateAccount(id, account);
|
|
this.appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED))
|
|
.getAccount();
|
|
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;
|
|
}
|
|
}
|