Files
event-stream-processing-mic…/account-parent/account-web/src/main/java/demo/account/AccountService.java
2016-12-20 11:47:44 -08:00

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;
}
}