Javadoc comments and hypermedia tweaks
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package demo;
|
||||
|
||||
import demo.account.Account;
|
||||
import demo.account.AccountController;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
|
||||
@Configuration
|
||||
public class AccountResourceConfig {
|
||||
|
||||
/**
|
||||
* Enriches the {@link Account} resource with hypermedia links.
|
||||
*
|
||||
* @return a hypermedia processor for the {@link Account} resource
|
||||
*/
|
||||
@Bean
|
||||
public ResourceProcessor<Resource<Account>> accountProcessor() {
|
||||
return new ResourceProcessor<Resource<Account>>() {
|
||||
@Override
|
||||
public Resource<Account> process(Resource<Account> resource) {
|
||||
resource.add(
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(resource.getContent().getAccountId())
|
||||
.slash("commands")
|
||||
.withRel("commands"));
|
||||
return resource;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package demo.event;
|
||||
package demo;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableBinding(ProducerChannels.class)
|
||||
public class StreamConfig {
|
||||
|
||||
@EnableBinding(Source.class)
|
||||
public class AccountStreamConfig {
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package demo.account;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import demo.domain.BaseEntity;
|
||||
import demo.event.AccountEvent;
|
||||
|
||||
@@ -12,6 +13,8 @@ import java.util.Set;
|
||||
* a user's account. The status of an account is event sourced using
|
||||
* events logged to the {@link AccountEvent} collection attached to
|
||||
* this resource.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
@Entity
|
||||
public class Account extends BaseEntity {
|
||||
@@ -39,11 +42,12 @@ public class Account extends BaseEntity {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@JsonIgnore
|
||||
public Long getAccountId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
public void setAccountId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package demo.account;
|
||||
|
||||
/**
|
||||
* The {@link AccountCommand} represents an action that can be performed to an
|
||||
* {@link Account} aggregate. Commands initiate an action that can mutate the state of
|
||||
* an account entity as it transitions between {@link AccountEventStatus} values.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum AccountCommand {
|
||||
CONFIRM_ACCOUNT,
|
||||
ACTIVATE_ACCOUNT,
|
||||
|
||||
@@ -2,5 +2,11 @@ package demo.account;
|
||||
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
|
||||
/**
|
||||
* A hypermedia resource that describes the collection of commands that
|
||||
* can be applied to a {@link Account} aggregate.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public class AccountCommandsResource extends ResourceSupport {
|
||||
}
|
||||
|
||||
@@ -38,6 +38,13 @@ public class AccountController {
|
||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/events")
|
||||
public ResponseEntity getAccountEvents(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.getAccountEventResources(id))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Could not get account events"));
|
||||
}
|
||||
|
||||
@PostMapping(path = "/accounts/{id}/events")
|
||||
public ResponseEntity createAccount(@PathVariable Long id, @RequestBody AccountEvent event) {
|
||||
return Optional.ofNullable(accountService.appendEventResource(id, event))
|
||||
|
||||
@@ -4,6 +4,8 @@ package demo.account;
|
||||
* The {@link AccountEventStatus} describes the state of an {@link Account}.
|
||||
* The aggregate state of a {@link Account} is sourced from attached domain
|
||||
* events in the form of {@link demo.event.AccountEvent}.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum AccountEventStatus {
|
||||
ACCOUNT_CREATED,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package demo.account;
|
||||
|
||||
/**
|
||||
* The {@link AccountEventType} represents a collection of possible events that describe
|
||||
* state transitions of {@link AccountEventStatus} on the {@link Account} aggregate.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum AccountEventType {
|
||||
ACCOUNT_CREATED,
|
||||
ACCOUNT_CONFIRMED,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package demo.account;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import demo.event.AccountEvent;
|
||||
import demo.event.EventController;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
|
||||
public class AccountEvents extends Resources<AccountEvent> {
|
||||
|
||||
private Long accountId;
|
||||
|
||||
/**
|
||||
* Creates an empty {@link Resources} instance.
|
||||
*/
|
||||
public AccountEvents(Long accountId, Iterable<AccountEvent> content) {
|
||||
this(content);
|
||||
this.accountId = accountId;
|
||||
|
||||
// Add hypermedia links to resources parent
|
||||
add(linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.slash("events")
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.withRel("account"));
|
||||
|
||||
// Add hypermedia links to each item of the collection
|
||||
content.forEach(event -> event.add(
|
||||
linkTo(EventController.class)
|
||||
.slash("events")
|
||||
.slash(event.getEventId())
|
||||
.withSelfRel()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Resources} instance with the given content and {@link Link}s (optional).
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param links the links to be added to the {@link Resources}.
|
||||
*/
|
||||
public AccountEvents(Iterable<AccountEvent> content, Link... links) {
|
||||
super(content, links);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,5 @@ import org.springframework.data.repository.query.Param;
|
||||
public interface AccountRepository extends JpaRepository<Account, Long> {
|
||||
|
||||
Account findAccountByUserId(@Param("userId") Long userId);
|
||||
|
||||
Account findAccountByAccountNumber(@Param("accountNumber") String accountNumber);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
* 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
|
||||
@Transactional
|
||||
@@ -101,17 +103,15 @@ public class AccountService {
|
||||
|
||||
if (event != null) {
|
||||
eventResource = new Resource<>(event,
|
||||
entityLinks.linkFor(AccountEvent.class, event.getId())
|
||||
.slash(event.getId())
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.slash("events")
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.withRel("account"),
|
||||
entityLinks.linkFor(AccountEvent.class, event.getId())
|
||||
.slash(event.getId())
|
||||
.slash("logs")
|
||||
.withRel("logs")
|
||||
.withRel("account")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,55 +128,61 @@ public class AccountService {
|
||||
public Resource<Account> applyCommand(Long id, AccountCommand accountCommand) {
|
||||
Resource<Account> account = getAccountResource(id);
|
||||
|
||||
if (account != null) {
|
||||
Assert.notNull(account, "The account for the supplied id could not be found");
|
||||
|
||||
AccountEventStatus status = account.getContent().getStatus();
|
||||
AccountEventStatus status = account.getContent().getStatus();
|
||||
|
||||
switch (accountCommand) {
|
||||
case CONFIRM_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_CREATED, "The account has already been confirmed");
|
||||
switch (accountCommand) {
|
||||
case CONFIRM_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_CREATED, "The account has already been confirmed");
|
||||
|
||||
// Confirm the account
|
||||
Account updateAccount = account.getContent();
|
||||
updateAccount.setStatus(ACCOUNT_CONFIRMED);
|
||||
account = updateAccountResource(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");
|
||||
// Confirm the account
|
||||
Account updateAccount = account.getContent();
|
||||
updateAccount.setStatus(ACCOUNT_CONFIRMED);
|
||||
account = updateAccountResource(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.getContent().setStatus(ACCOUNT_ACTIVE);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED));
|
||||
break;
|
||||
case SUSPEND_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
||||
// Activate the account
|
||||
account.getContent().setStatus(ACCOUNT_ACTIVE);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
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.getContent().setStatus(ACCOUNT_SUSPENDED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED));
|
||||
break;
|
||||
case ARCHIVE_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
||||
// Suspend the account
|
||||
account.getContent().setStatus(ACCOUNT_SUSPENDED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
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.getContent().setStatus(ACCOUNT_ARCHIVED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
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");
|
||||
}
|
||||
// Archive the account
|
||||
account.getContent().setStatus(ACCOUNT_ARCHIVED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
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 the {@link AccountCommand} hypermedia resource that lists the available commands that can be applied
|
||||
* to an {@link Account} entity.
|
||||
*
|
||||
* @param id is the {@link Account} identifier to provide command links for
|
||||
* @return an {@link AccountCommandsResource} with a collection of embedded command links
|
||||
*/
|
||||
public AccountCommandsResource getCommandsResource(Long id) {
|
||||
// Get the account resource for the identifier
|
||||
Resource<Account> accountResource = getAccountResource(id);
|
||||
@@ -205,6 +211,22 @@ public class AccountService {
|
||||
return commandResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link AccountEvents} for the supplied {@link Account} identifier.
|
||||
*
|
||||
* @param id is the unique identifier of the {@link Account}
|
||||
* @return a list of {@link AccountEvent} wrapped in a hypermedia {@link AccountEvents} resource
|
||||
*/
|
||||
public AccountEvents getAccountEventResources(Long id) {
|
||||
return eventService.getEvents(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link LinkBuilder} for generating the {@link AccountCommandsResource}.
|
||||
*
|
||||
* @param id is the unique identifier for a {@link Account}
|
||||
* @return a {@link LinkBuilder} for the {@link AccountCommandsResource}
|
||||
*/
|
||||
private LinkBuilder getCommandLinkBuilder(Long id) {
|
||||
return linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
@@ -212,6 +234,12 @@ public class AccountService {
|
||||
.slash("commands");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hypermedia enriched {@link Account} entity.
|
||||
*
|
||||
* @param account is the {@link Account} to enrich with hypermedia links
|
||||
* @return is a hypermedia enriched resource for the supplied {@link Account} entity
|
||||
*/
|
||||
private Resource<Account> getAccountResource(Account account) {
|
||||
Resource<Account> accountResource;
|
||||
|
||||
@@ -219,23 +247,36 @@ public class AccountService {
|
||||
accountResource = new Resource<>(account,
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getId())
|
||||
.slash(account.getAccountId())
|
||||
.withSelfRel(),
|
||||
entityLinks.linkFor(Account.class, account.getId())
|
||||
.slash(account.getId())
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getAccountId())
|
||||
.slash("events")
|
||||
.withRel("events"),
|
||||
getCommandLinkBuilder(account.getId())
|
||||
getCommandLinkBuilder(account.getAccountId())
|
||||
.withRel("commands")
|
||||
);
|
||||
|
||||
return accountResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private 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}
|
||||
*/
|
||||
private Account createAccount(Account account) {
|
||||
// Assert for uniqueness constraint
|
||||
Assert.isNull(accountRepository.findAccountByUserId(account.getUserId()),
|
||||
@@ -247,32 +288,40 @@ public class AccountService {
|
||||
account = accountRepository.save(account);
|
||||
|
||||
// Trigger the account creation event
|
||||
appendEventResource(account.getId(),
|
||||
appendEventResource(account.getAccountId(),
|
||||
new AccountEvent(AccountEventType.ACCOUNT_CREATED));
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private Account updateAccount(Long id, Account account) {
|
||||
Assert.notNull(id);
|
||||
Assert.notNull(account);
|
||||
Assert.isTrue(Objects.equals(id, account.getId()));
|
||||
Assert.isTrue(Objects.equals(id, account.getAccountId()));
|
||||
return accountRepository.save(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
private AccountEvent appendEvent(Long accountId, AccountEvent event) {
|
||||
Account account = accountRepository.findOne(accountId);
|
||||
if (account != null) {
|
||||
event.setAccount(account);
|
||||
event = eventService.createEvent(event).getContent();
|
||||
if (event != null) {
|
||||
account.getEvents().add(event);
|
||||
accountRepository.save(account);
|
||||
}
|
||||
} else {
|
||||
event = null;
|
||||
}
|
||||
|
||||
Account account = getAccount(accountId);
|
||||
Assert.notNull(account, "The account with the supplied id does not exist");
|
||||
event.setAccount(account);
|
||||
event = eventService.createEvent(event).getContent();
|
||||
account.getEvents().add(event);
|
||||
accountRepository.save(account);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ package demo.domain;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class BaseEntity {
|
||||
public class BaseEntity extends ResourceSupport {
|
||||
|
||||
@CreatedDate
|
||||
private Long createdAt;
|
||||
|
||||
@@ -19,6 +19,8 @@ import java.util.Set;
|
||||
* This event resource also provides a transaction log that can be used to append
|
||||
* actions to the event. The collection of {@link Log} items can be used to remediate
|
||||
* partial failures.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
@Entity
|
||||
@RestResource(path = "events", rel = "events")
|
||||
@@ -46,11 +48,12 @@ public class AccountEvent extends BaseEntity {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@JsonIgnore
|
||||
public Long getEventId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
public void setEventId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package demo.event;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
@RepositoryRestResource(path = "events", collectionResourceRel = "events", itemResourceRel = "event")
|
||||
public interface EventRepository extends JpaRepository<AccountEvent, Long> {
|
||||
Page<AccountEvent> findAccountEventsByAccountId(@Param("accountId") Long accountId, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@ package demo.event;
|
||||
import demo.account.Account;
|
||||
import demo.account.AccountController;
|
||||
import demo.account.AccountEventType;
|
||||
import demo.account.AccountEvents;
|
||||
import demo.log.Log;
|
||||
import demo.log.LogRepository;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -22,6 +26,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
* entities of the Account Service. Account domain events are generated with a {@link AccountEventType},
|
||||
* and action logs are appended to the {@link AccountEvent}. The logs resource provides an append-only transaction
|
||||
* log that can be used to source the state of the {@link Account}
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
@@ -30,57 +36,58 @@ public class EventService {
|
||||
private final EventRepository eventRepository;
|
||||
private final LogRepository logRepository;
|
||||
private final RepositoryEntityLinks entityLinks;
|
||||
private final ProducerChannels producer;
|
||||
private final Source accountStreamSource;
|
||||
|
||||
public EventService(EventRepository eventRepository, LogRepository logRepository,
|
||||
RepositoryEntityLinks entityLinks, ProducerChannels producerChannels) {
|
||||
RepositoryEntityLinks entityLinks, Source accountStreamSource) {
|
||||
this.eventRepository = eventRepository;
|
||||
this.logRepository = logRepository;
|
||||
this.entityLinks = entityLinks;
|
||||
this.producer = producerChannels;
|
||||
this.accountStreamSource = accountStreamSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AccountEvent} and publish it to the account stream.
|
||||
*
|
||||
* @param event is the {@link AccountEvent} to publish to the account stream
|
||||
* @return a hypermedia {@link AccountEvent} resource
|
||||
*/
|
||||
public Resource<AccountEvent> createEvent(AccountEvent event) {
|
||||
Resource<AccountEvent> eventResource = null;
|
||||
|
||||
// Save new event
|
||||
event = addEvent(event);
|
||||
Assert.notNull(event, "The event could not be appended to the account");
|
||||
|
||||
if (event != null) {
|
||||
// Create account event resource
|
||||
eventResource = new Resource<>(event, Arrays.asList(
|
||||
entityLinks.linkFor(AccountEvent.class, event.getId())
|
||||
.slash(event.getId())
|
||||
.withRel("self"),
|
||||
entityLinks.linkFor(AccountEvent.class)
|
||||
.slash(event.getId())
|
||||
.slash("logs")
|
||||
.withRel("logs"),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(event.getAccount().getId())
|
||||
.withRel("account"))
|
||||
);
|
||||
// Create account event resource
|
||||
eventResource = getAccountEventResource(event);
|
||||
|
||||
// Produce account event
|
||||
producer.output()
|
||||
.send(MessageBuilder
|
||||
.withPayload(eventResource)
|
||||
.build());
|
||||
}
|
||||
// Append the account event to the stream
|
||||
accountStreamSource.output()
|
||||
.send(MessageBuilder
|
||||
.withPayload(eventResource)
|
||||
.build());
|
||||
|
||||
return eventResource;
|
||||
}
|
||||
|
||||
private AccountEvent addEvent(AccountEvent event) {
|
||||
event = eventRepository.save(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link AccountEvent} with the supplied identifier.
|
||||
*
|
||||
* @param id is the unique identifier for the {@link AccountEvent}
|
||||
* @return an {@link AccountEvent}
|
||||
*/
|
||||
public AccountEvent getEvent(Long id) {
|
||||
return eventRepository.findOne(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an {@link AccountEvent} with the supplied identifier.
|
||||
*
|
||||
* @param id is the unique identifier for the {@link AccountEvent}
|
||||
* @param event is the {@link AccountEvent} to update
|
||||
* @return the updated {@link AccountEvent}
|
||||
*/
|
||||
public AccountEvent updateEvent(Long id, AccountEvent event) {
|
||||
Assert.notNull(id);
|
||||
Assert.isTrue(event.getId() == null || Objects.equals(id, event.getId()));
|
||||
@@ -88,6 +95,13 @@ public class EventService {
|
||||
return eventRepository.save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a {@link Log} to an {@link AccountEvent} entity.
|
||||
*
|
||||
* @param eventId is the unique identifier for an {@link AccountEvent}
|
||||
* @param log is the {@link Log} descirbing an action performed on an {@link AccountEvent}
|
||||
* @return a hypermedia resource for the appended {@link Log}
|
||||
*/
|
||||
public Resource<Log> appendEventLog(Long eventId, Log log) {
|
||||
Assert.notNull(eventId);
|
||||
Assert.notNull(log);
|
||||
@@ -95,20 +109,75 @@ public class EventService {
|
||||
Resource<Log> logResource = null;
|
||||
AccountEvent event = getEvent(eventId);
|
||||
|
||||
if (event != null) {
|
||||
log = logRepository.save(log);
|
||||
event.getLogs().add(log);
|
||||
Assert.notNull(event, "The event with the supplied id could not be found");
|
||||
|
||||
logResource = new Resource<>(log, Arrays.asList(
|
||||
entityLinks.linkFor(Log.class)
|
||||
.slash(log.getId())
|
||||
.withSelfRel(),
|
||||
entityLinks.linkFor(AccountEvent.class)
|
||||
.slash(event.getId())
|
||||
.withRel("event")
|
||||
));
|
||||
}
|
||||
log = logRepository.save(log);
|
||||
event.getLogs().add(log);
|
||||
logResource = getLogResource(log, event);
|
||||
|
||||
return logResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hypermedia resource for a {@link Log} entity.
|
||||
*
|
||||
* @param log is the {@link Log} descirbing an action performed on an {@link AccountEvent}
|
||||
* @param event is the {@link AccountEvent} to associate with the {@link Log}
|
||||
* @return a hypermedia resource for the appended {@link Log}
|
||||
*/
|
||||
private Resource<Log> getLogResource(Log log, AccountEvent event) {
|
||||
return new Resource<>(log, Arrays.asList(
|
||||
entityLinks.linkFor(Log.class)
|
||||
.slash(log.getLogId())
|
||||
.withSelfRel(),
|
||||
entityLinks.linkFor(AccountEvent.class)
|
||||
.slash(event.getId())
|
||||
.withRel("event")
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hypermedia resource for a {@link AccountEvent} entity.
|
||||
*
|
||||
* @param event is the {@link AccountEvent} to enrich with hypermedia
|
||||
* @return a hypermedia resource for the supplied {@link AccountEvent} entity
|
||||
*/
|
||||
private Resource<AccountEvent> getAccountEventResource(AccountEvent event) {
|
||||
return new Resource<>(event, Arrays.asList(
|
||||
linkTo(AccountController.class)
|
||||
.slash("events")
|
||||
.slash(event.getEventId())
|
||||
.withSelfRel(),
|
||||
entityLinks.linkFor(AccountEvent.class)
|
||||
.slash(event.getId())
|
||||
.slash("logs")
|
||||
.withRel("logs"),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(event.getAccount().getAccountId())
|
||||
.withRel("account"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link AccountEvent} to an {@link Account} entity.
|
||||
*
|
||||
* @param event is the {@link AccountEvent} to append to an {@link Account} entity
|
||||
* @return the newly appended {@link AccountEvent} entity
|
||||
*/
|
||||
private AccountEvent addEvent(AccountEvent event) {
|
||||
event = eventRepository.save(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link AccountEvents} for the supplied {@link Account} identifier.
|
||||
*
|
||||
* @param id is the unique identifier of the {@link Account}
|
||||
* @return a list of {@link AccountEvent} wrapped in a hypermedia {@link AccountEvents} resource
|
||||
*/
|
||||
public AccountEvents getEvents(Long id) {
|
||||
Page<AccountEvent> events = eventRepository.findAccountEventsByAccountId(id, new PageRequest(0, Integer.MAX_VALUE));
|
||||
return new AccountEvents(id, events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package demo.event;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
public interface ProducerChannels {
|
||||
|
||||
@Output
|
||||
MessageChannel output();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package demo.log;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import demo.account.AccountEventType;
|
||||
import demo.domain.BaseEntity;
|
||||
|
||||
@@ -25,11 +26,12 @@ public class Log extends BaseEntity {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@JsonIgnore
|
||||
public Long getLogId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
public void setLogId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,6 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
@@ -49,10 +45,6 @@
|
||||
<version>1.1.1.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
package demo.domain;
|
||||
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class BaseEntity extends ResourceSupport {
|
||||
|
||||
@CreatedDate
|
||||
private Long createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
private Long lastModified;
|
||||
|
||||
public BaseEntity() {
|
||||
@@ -42,6 +31,6 @@ public class BaseEntity extends ResourceSupport {
|
||||
return "BaseEntity{" +
|
||||
"createdAt=" + createdAt +
|
||||
", lastModified=" + lastModified +
|
||||
'}';
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package demo.event;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Sink {
|
||||
String INPUT = "input";
|
||||
|
||||
@Input(INPUT)
|
||||
SubscribableChannel input();
|
||||
}
|
||||
Reference in New Issue
Block a user