Eventing through aggregate
This commit is contained in:
@@ -51,6 +51,11 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-integration</artifactId>
|
<artifactId>spring-boot-starter-integration</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kbastani</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-events</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package demo;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
|
||||||
public class OrderServiceApplication {
|
public class OrderServiceApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(OrderServiceApplication.class, args);
|
SpringApplication.run(OrderServiceApplication.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package demo.domain;
|
package demo.domain;
|
||||||
|
|
||||||
|
import demo.event.Event;
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
|
||||||
|
|
||||||
import javax.persistence.EntityListeners;
|
import javax.persistence.*;
|
||||||
import javax.persistence.MappedSuperclass;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
@EntityListeners(AuditingEntityListener.class)
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
public class BaseEntity extends ResourceSupport implements Serializable {
|
public abstract class AbstractEntity<E extends Event, T extends Serializable> extends Aggregate<E, T> implements Serializable {
|
||||||
|
|
||||||
|
private T identity;
|
||||||
|
|
||||||
@CreatedDate
|
@CreatedDate
|
||||||
private Long createdAt;
|
private Long createdAt;
|
||||||
@@ -19,7 +22,10 @@ public class BaseEntity extends ResourceSupport implements Serializable {
|
|||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
|
|
||||||
public BaseEntity() {
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
private List<E> events = new ArrayList<>();
|
||||||
|
|
||||||
|
public AbstractEntity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getCreatedAt() {
|
public Long getCreatedAt() {
|
||||||
@@ -38,6 +44,24 @@ public class BaseEntity extends ResourceSupport implements Serializable {
|
|||||||
this.lastModified = lastModified;
|
this.lastModified = lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<E> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEvents(List<E> events) {
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getIdentity() {
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentity(T id) {
|
||||||
|
this.identity = id;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "BaseEntity{" +
|
return "BaseEntity{" +
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package demo.event;
|
|
||||||
|
|
||||||
public enum ConsistencyModel {
|
|
||||||
BASE,
|
|
||||||
ACID
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package demo.event;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/v1")
|
|
||||||
public class EventController {
|
|
||||||
|
|
||||||
private final EventService eventService;
|
|
||||||
|
|
||||||
public EventController(EventService eventService) {
|
|
||||||
this.eventService = eventService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(path = "/events/{id}")
|
|
||||||
public ResponseEntity createEvent(@RequestBody OrderEvent event, @PathVariable Long id) {
|
|
||||||
return Optional.ofNullable(eventService.createEvent(id, event, ConsistencyModel.ACID))
|
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Event creation failed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping(path = "/events/{id}")
|
|
||||||
public ResponseEntity updateEvent(@RequestBody OrderEvent event, @PathVariable Long id) {
|
|
||||||
return Optional.ofNullable(eventService.updateEvent(id, event))
|
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Event update failed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(path = "/events/{id}")
|
|
||||||
public ResponseEntity getEvent(@PathVariable Long id) {
|
|
||||||
return Optional.ofNullable(eventService.getEvent(id))
|
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
|
||||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
public interface EventRepository extends JpaRepository<OrderEvent, Long> {
|
|
||||||
Page<OrderEvent> findOrderEventsByOrderId(@Param("orderId") Long orderId, Pageable pageable);
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
package demo.event;
|
|
||||||
|
|
||||||
import demo.order.Order;
|
|
||||||
import demo.order.OrderController;
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
import org.springframework.cache.annotation.CacheConfig;
|
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
|
||||||
import org.springframework.cloud.stream.messaging.Source;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.hateoas.Link;
|
|
||||||
import org.springframework.hateoas.MediaTypes;
|
|
||||||
import org.springframework.hateoas.Resource;
|
|
||||||
import org.springframework.http.RequestEntity;
|
|
||||||
import org.springframework.integration.support.MessageBuilder;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link EventService} provides transactional service methods for {@link OrderEvent}
|
|
||||||
* entities of the Order Service. Order domain events are generated with a {@link OrderEventType},
|
|
||||||
* and action logs are appended to the {@link OrderEvent}.
|
|
||||||
*
|
|
||||||
* @author kbastani
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@CacheConfig(cacheNames = {"order-events"})
|
|
||||||
public class EventService {
|
|
||||||
|
|
||||||
private final Logger log = Logger.getLogger(EventService.class);
|
|
||||||
|
|
||||||
private final EventRepository eventRepository;
|
|
||||||
private final Source orderStreamSource;
|
|
||||||
private final RestTemplate restTemplate;
|
|
||||||
|
|
||||||
public EventService(EventRepository eventRepository, Source orderStreamSource, RestTemplate restTemplate) {
|
|
||||||
this.eventRepository = eventRepository;
|
|
||||||
this.orderStreamSource = orderStreamSource;
|
|
||||||
this.restTemplate = restTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new {@link OrderEvent} and append it to the event log of the referenced {@link Order}.
|
|
||||||
* After the {@link OrderEvent} has been persisted, send the event to the order stream. Events can
|
|
||||||
* be raised as a blocking or non-blocking operation depending on the {@link ConsistencyModel}.
|
|
||||||
*
|
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
|
||||||
* @param event is the {@link OrderEvent} to create
|
|
||||||
* @param consistencyModel is the desired consistency model for the response
|
|
||||||
* @return an {@link OrderEvent} that has been appended to the {@link Order}'s event log
|
|
||||||
*/
|
|
||||||
public OrderEvent createEvent(Long orderId, OrderEvent event, ConsistencyModel consistencyModel) {
|
|
||||||
event = createEvent(orderId, event);
|
|
||||||
return raiseEvent(event, consistencyModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raise an {@link OrderEvent} that attempts to transition the state of an {@link Order}.
|
|
||||||
*
|
|
||||||
* @param event is an {@link OrderEvent} that will be raised
|
|
||||||
* @param consistencyModel is the consistency model for this request
|
|
||||||
* @return an {@link OrderEvent} that has been appended to the {@link Order}'s event log
|
|
||||||
*/
|
|
||||||
public OrderEvent raiseEvent(OrderEvent event, ConsistencyModel consistencyModel, Link... links) {
|
|
||||||
// Add embedded links
|
|
||||||
event.add(links);
|
|
||||||
|
|
||||||
switch (consistencyModel) {
|
|
||||||
case BASE:
|
|
||||||
asyncRaiseEvent(event);
|
|
||||||
break;
|
|
||||||
case ACID:
|
|
||||||
event = raiseEvent(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raise an asynchronous {@link OrderEvent} by sending an AMQP message to the order stream. Any
|
|
||||||
* state changes will be applied to the {@link Order} outside of the current HTTP request context.
|
|
||||||
* <p>
|
|
||||||
* Use this operation when a workflow can be processed asynchronously outside of the current HTTP
|
|
||||||
* request context.
|
|
||||||
*
|
|
||||||
* @param event is an {@link OrderEvent} that will be raised
|
|
||||||
*/
|
|
||||||
private void asyncRaiseEvent(OrderEvent event) {
|
|
||||||
// Append the order event to the stream
|
|
||||||
orderStreamSource.output()
|
|
||||||
.send(MessageBuilder
|
|
||||||
.withPayload(getOrderEventResource(event))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raise a synchronous {@link OrderEvent} by sending a HTTP request to the order stream. The response
|
|
||||||
* is a blocking operation, which ensures that the result of a multi-step workflow will not return until
|
|
||||||
* the transaction reaches a consistent state.
|
|
||||||
* <p>
|
|
||||||
* Use this operation when the result of a workflow must be returned within the current HTTP request context.
|
|
||||||
*
|
|
||||||
* @param event is an {@link OrderEvent} that will be raised
|
|
||||||
* @return an {@link OrderEvent} which contains the consistent state of an {@link Order}
|
|
||||||
*/
|
|
||||||
private OrderEvent raiseEvent(OrderEvent event) {
|
|
||||||
try {
|
|
||||||
// Create a new request entity
|
|
||||||
RequestEntity<Resource<OrderEvent>> requestEntity = RequestEntity.post(
|
|
||||||
URI.create("http://localhost:8081/v1/events"))
|
|
||||||
.contentType(MediaTypes.HAL_JSON)
|
|
||||||
.body(getOrderEventResource(event), Resource.class);
|
|
||||||
|
|
||||||
// Update the order entity's status
|
|
||||||
Order result = restTemplate.exchange(requestEntity, Order.class)
|
|
||||||
.getBody();
|
|
||||||
|
|
||||||
log.info(result);
|
|
||||||
event.setOrder(result);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new {@link OrderEvent} and publish it to the order stream.
|
|
||||||
*
|
|
||||||
* @param event is the {@link OrderEvent} to publish to the order stream
|
|
||||||
* @return a hypermedia {@link OrderEvent} resource
|
|
||||||
*/
|
|
||||||
@CacheEvict(cacheNames = "order-events", key = "#id.toString()")
|
|
||||||
public OrderEvent createEvent(Long id, OrderEvent event) {
|
|
||||||
// Save new event
|
|
||||||
event = addEvent(event);
|
|
||||||
Assert.notNull(event, "The event could not be appended to the order");
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an {@link OrderEvent} with the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier for the {@link OrderEvent}
|
|
||||||
* @return an {@link OrderEvent}
|
|
||||||
*/
|
|
||||||
public Resource<OrderEvent> getEvent(Long id) {
|
|
||||||
return getOrderEventResource(eventRepository.findOne(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an {@link OrderEvent} with the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier for the {@link OrderEvent}
|
|
||||||
* @param event is the {@link OrderEvent} to update
|
|
||||||
* @return the updated {@link OrderEvent}
|
|
||||||
*/
|
|
||||||
@CacheEvict(cacheNames = "order-events", key = "#event.order().getOrderId().toString()")
|
|
||||||
public OrderEvent updateEvent(Long id, OrderEvent event) {
|
|
||||||
Assert.notNull(id);
|
|
||||||
Assert.isTrue(event.getId() == null || Objects.equals(id, event.getId()));
|
|
||||||
|
|
||||||
return eventRepository.save(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get {@link OrderEvents} for the supplied {@link Order} identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier of the {@link Order}
|
|
||||||
* @return a list of {@link OrderEvent} wrapped in a hypermedia {@link OrderEvents} resource
|
|
||||||
*/
|
|
||||||
@Cacheable(cacheNames = "order-events", key = "#id.toString()")
|
|
||||||
public List<OrderEvent> getOrderEvents(Long id) {
|
|
||||||
return eventRepository.findOrderEventsByOrderId(id,
|
|
||||||
new PageRequest(0, Integer.MAX_VALUE)).getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a hypermedia resource for a {@link OrderEvent} entity.
|
|
||||||
*
|
|
||||||
* @param event is the {@link OrderEvent} to enrich with hypermedia
|
|
||||||
* @return a hypermedia resource for the supplied {@link OrderEvent} entity
|
|
||||||
*/
|
|
||||||
private Resource<OrderEvent> getOrderEventResource(OrderEvent event) {
|
|
||||||
event.add(Arrays.asList(
|
|
||||||
linkTo(OrderController.class)
|
|
||||||
.slash("events")
|
|
||||||
.slash(event.getEventId())
|
|
||||||
.withSelfRel(),
|
|
||||||
linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
|
||||||
.slash(event.getOrder().getOrderId())
|
|
||||||
.withRel("order")));
|
|
||||||
return new Resource<OrderEvent>(event, event.getLinks());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a {@link OrderEvent} to an {@link Order} entity.
|
|
||||||
*
|
|
||||||
* @param event is the {@link OrderEvent} to append to an {@link Order} entity
|
|
||||||
* @return the newly appended {@link OrderEvent} entity
|
|
||||||
*/
|
|
||||||
@CacheEvict(cacheNames = "order-events", key = "#event.order().getOrderId().toString()")
|
|
||||||
private OrderEvent addEvent(OrderEvent event) {
|
|
||||||
event = eventRepository.saveAndFlush(event);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,41 @@
|
|||||||
package demo.event;
|
package demo.event;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import demo.domain.BaseEntity;
|
|
||||||
import demo.order.Order;
|
import demo.order.Order;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The domain event {@link OrderEvent} tracks the type and state of events as
|
* The domain event {@link OrderEvent} tracks the type and state of events as applied to the {@link Order} domain
|
||||||
* applied to the {@link Order} domain object. This event resource can be used
|
* object. This event resource can be used to event source the aggregate state of {@link Order}.
|
||||||
* to event source the aggregate state of {@link Order}.
|
|
||||||
* <p>
|
* <p>
|
||||||
* This event resource also provides a transaction log that can be used to append
|
* This event resource also provides a transaction log that can be used to append actions to the event.
|
||||||
* actions to the event.
|
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
public class OrderEvent extends BaseEntity {
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class OrderEvent extends Event<Order, OrderEventType, Long> {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
private Long id;
|
private Long eventId;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private OrderEventType type;
|
private OrderEventType type;
|
||||||
|
|
||||||
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Order order;
|
private Order entity;
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
private Long createdAt;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
private Long lastModified;
|
||||||
|
|
||||||
public OrderEvent() {
|
public OrderEvent() {
|
||||||
}
|
}
|
||||||
@@ -37,37 +44,69 @@ public class OrderEvent extends BaseEntity {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
public OrderEvent(OrderEventType type, Order entity) {
|
||||||
|
this.type = type;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Long getEventId() {
|
public Long getEventId() {
|
||||||
return id;
|
return eventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setEventId(Long id) {
|
public void setEventId(Long id) {
|
||||||
this.id = id;
|
eventId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public OrderEventType getType() {
|
public OrderEventType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setType(OrderEventType type) {
|
public void setType(OrderEventType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Order getOrder() {
|
@Override
|
||||||
return order;
|
public Order getEntity() {
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrder(Order order) {
|
@Override
|
||||||
this.order = order;
|
public void setEntity(Order entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreatedAt(Long createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLastModified() {
|
||||||
|
return lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastModified(Long lastModified) {
|
||||||
|
this.lastModified = lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "OrderEvent{" +
|
return "OrderEvent{" +
|
||||||
"id=" + id +
|
"eventId=" + eventId +
|
||||||
", type=" + type +
|
", type=" + type +
|
||||||
", order=" + order +
|
", entity=" + entity +
|
||||||
|
", createdAt=" + createdAt +
|
||||||
|
", lastModified=" + lastModified +
|
||||||
"} " + super.toString();
|
"} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package demo.event;
|
||||||
|
|
||||||
|
public interface OrderEventRepository extends EventRepository<OrderEvent, Long> {
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import demo.order.Order;
|
|||||||
import demo.order.OrderStatus;
|
import demo.order.OrderStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OrderEventType} represents a collection of possible events that describe
|
* The {@link OrderEventType} represents a collection of possible events that describe state transitions of
|
||||||
* state transitions of {@link OrderStatus} on the {@link Order} aggregate.
|
* {@link OrderStatus} on the {@link Order} aggregate.
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
public enum OrderEventType {
|
public enum OrderEventType {
|
||||||
ORDER_CREATED,
|
ORDER_CREATED,
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package demo.event;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import demo.order.Order;
|
|
||||||
import demo.order.OrderController;
|
|
||||||
import org.springframework.hateoas.Link;
|
|
||||||
import org.springframework.hateoas.LinkBuilder;
|
|
||||||
import org.springframework.hateoas.Resources;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link OrderEvents} is a hypermedia collection of {@link OrderEvent} resources.
|
|
||||||
*
|
|
||||||
* @author kbastani
|
|
||||||
*/
|
|
||||||
public class OrderEvents extends Resources<OrderEvent> implements Serializable {
|
|
||||||
|
|
||||||
private Long orderId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new {@link OrderEvents} hypermedia resources collection for an {@link Order}.
|
|
||||||
*
|
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
|
||||||
* @param content is the collection of {@link OrderEvents} attached to the {@link Order}
|
|
||||||
*/
|
|
||||||
public OrderEvents(Long orderId, List<OrderEvent> content) {
|
|
||||||
this(content);
|
|
||||||
this.orderId = orderId;
|
|
||||||
|
|
||||||
// Add hypermedia links to resources parent
|
|
||||||
add(linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
|
||||||
.slash(orderId)
|
|
||||||
.slash("events")
|
|
||||||
.withSelfRel(),
|
|
||||||
linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
|
||||||
.slash(orderId)
|
|
||||||
.withRel("order"));
|
|
||||||
|
|
||||||
LinkBuilder linkBuilder = linkTo(EventController.class);
|
|
||||||
|
|
||||||
// Add hypermedia links to each item of the collection
|
|
||||||
content.stream().parallel().forEach(event -> event.add(
|
|
||||||
linkBuilder.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}.
|
|
||||||
*/
|
|
||||||
private OrderEvents(Iterable<OrderEvent> content, Link... links) {
|
|
||||||
super(content, links);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the {@link Order} identifier that the {@link OrderEvents} apply to.
|
|
||||||
*
|
|
||||||
* @return the order identifier
|
|
||||||
*/
|
|
||||||
@JsonIgnore
|
|
||||||
public Long getOrderId() {
|
|
||||||
return orderId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
package demo.order;
|
package demo.order;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import demo.domain.Value;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public class LineItem implements Serializable {
|
public class LineItem implements Value<Long> {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
package demo.order;
|
package demo.order;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import demo.address.Address;
|
import demo.address.Address;
|
||||||
import demo.address.AddressType;
|
import demo.address.AddressType;
|
||||||
import demo.domain.BaseEntity;
|
import demo.domain.AbstractEntity;
|
||||||
|
import demo.domain.Command;
|
||||||
import demo.event.OrderEvent;
|
import demo.event.OrderEvent;
|
||||||
|
import demo.order.action.*;
|
||||||
|
import demo.order.controller.OrderController;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||||
|
|
||||||
@Entity(name = "orders")
|
@Entity(name = "orders")
|
||||||
public class Order extends BaseEntity {
|
public class Order extends AbstractEntity<OrderEvent, Long> {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue
|
||||||
private Long id;
|
private Long id;
|
||||||
private Long accountId;
|
|
||||||
private Long paymentId;
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
|
||||||
private Set<OrderEvent> events = new HashSet<>();
|
|
||||||
|
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
private OrderStatus status;
|
private OrderStatus status;
|
||||||
@@ -31,6 +31,8 @@ public class Order extends BaseEntity {
|
|||||||
@OneToOne(cascade = CascadeType.ALL)
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
private Address shippingAddress;
|
private Address shippingAddress;
|
||||||
|
|
||||||
|
private Long accountId, paymentId;
|
||||||
|
|
||||||
public Order() {
|
public Order() {
|
||||||
this.status = OrderStatus.ORDER_CREATED;
|
this.status = OrderStatus.ORDER_CREATED;
|
||||||
}
|
}
|
||||||
@@ -43,33 +45,17 @@ public class Order extends BaseEntity {
|
|||||||
this.shippingAddress.setAddressType(AddressType.SHIPPING);
|
this.shippingAddress.setAddressType(AddressType.SHIPPING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonProperty("orderId")
|
||||||
public Long getOrderId() {
|
@Override
|
||||||
return id;
|
public Long getIdentity() {
|
||||||
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrderId(Long id) {
|
@Override
|
||||||
|
public void setIdentity(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Long getAccountId() {
|
|
||||||
return accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccountId(Long accountId) {
|
|
||||||
this.accountId = accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Long getPaymentId() {
|
|
||||||
return paymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaymentId(Long paymentId) {
|
|
||||||
this.paymentId = paymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OrderStatus getStatus() {
|
public OrderStatus getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -78,15 +64,6 @@ public class Order extends BaseEntity {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Set<OrderEvent> getEvents() {
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEvents(Set<OrderEvent> events) {
|
|
||||||
this.events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<LineItem> getLineItems() {
|
public Set<LineItem> getLineItems() {
|
||||||
return lineItems;
|
return lineItems;
|
||||||
}
|
}
|
||||||
@@ -103,19 +80,70 @@ public class Order extends BaseEntity {
|
|||||||
this.shippingAddress = shippingAddress;
|
this.shippingAddress = shippingAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLineItem(LineItem lineItem) {
|
public Long getAccountId() {
|
||||||
lineItems.add(lineItem);
|
return accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAccountId(Long accountId) {
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPaymentId() {
|
||||||
|
return paymentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentId(Long paymentId) {
|
||||||
|
this.paymentId = paymentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(method = "connectAccount", controller = OrderController.class)
|
||||||
|
public Order connectAccount(Long accountId) {
|
||||||
|
getAction(ConnectAccount.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this, accountId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(method = "connectPayment", controller = OrderController.class)
|
||||||
|
public Order connectPayment(Long paymentId) {
|
||||||
|
getAction(ConnectPayment.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this, paymentId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(method = "createPayment", controller = OrderController.class)
|
||||||
|
public Order createPayment() {
|
||||||
|
getAction(CreatePayment.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(method = "processPayment", controller = OrderController.class)
|
||||||
|
public Order processPayment() {
|
||||||
|
getAction(ProcessPayment.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(method = "reserveInventory", controller = OrderController.class)
|
||||||
|
public Order reserveInventory(Long paymentId) {
|
||||||
|
getAction(ReserveInventory.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public Link getId() {
|
||||||
return "Order{" +
|
return linkTo(OrderController.class)
|
||||||
"id=" + id +
|
.slash("orders")
|
||||||
", accountId=" + accountId +
|
.slash(getIdentity())
|
||||||
", paymentId=" + paymentId +
|
.withSelfRel();
|
||||||
", status=" + status +
|
|
||||||
", lineItems=" + lineItems +
|
|
||||||
", shippingAddress=" + shippingAddress +
|
|
||||||
"} " + super.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package demo.order;
|
|
||||||
|
|
||||||
public enum OrderCommand {
|
|
||||||
CONNECT_ACCOUNT,
|
|
||||||
RESERVE_INVENTORY,
|
|
||||||
CREATE_PAYMENT,
|
|
||||||
CONNECT_PAYMENT,
|
|
||||||
PROCESS_PAYMENT
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package demo.order;
|
|
||||||
|
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A hypermedia resource that describes the collection of commands that
|
|
||||||
* can be applied to a {@link Order} aggregate.
|
|
||||||
*
|
|
||||||
* @author kbastani
|
|
||||||
*/
|
|
||||||
public class OrderCommands extends ResourceSupport {
|
|
||||||
}
|
|
||||||
35
order/order-web/src/main/java/demo/order/OrderProvider.java
Normal file
35
order/order-web/src/main/java/demo/order/OrderProvider.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package demo.order;
|
||||||
|
|
||||||
|
import demo.domain.Provider;
|
||||||
|
import demo.event.EventService;
|
||||||
|
import demo.event.OrderEvent;
|
||||||
|
|
||||||
|
@org.springframework.stereotype.Service
|
||||||
|
public class OrderProvider extends Provider<Order> {
|
||||||
|
|
||||||
|
private final OrderService orderService;
|
||||||
|
private final EventService<OrderEvent, Long> eventService;
|
||||||
|
|
||||||
|
public OrderProvider(OrderService orderService, EventService<OrderEvent, Long> eventService) {
|
||||||
|
this.orderService = orderService;
|
||||||
|
this.eventService = eventService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderService getOrderService() {
|
||||||
|
return orderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventService<OrderEvent, Long> getEventService() {
|
||||||
|
return eventService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrderService getDefaultService() {
|
||||||
|
return orderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventService<OrderEvent, Long> getDefaultEventService() {
|
||||||
|
return eventService;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +1,27 @@
|
|||||||
package demo.order;
|
package demo.order;
|
||||||
|
|
||||||
import demo.event.ConsistencyModel;
|
import demo.domain.Service;
|
||||||
import demo.event.EventService;
|
|
||||||
import demo.event.OrderEvent;
|
import demo.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.event.OrderEventType;
|
||||||
import demo.payment.Payment;
|
|
||||||
import demo.payment.PaymentMethod;
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
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.hateoas.Link;
|
|
||||||
import org.springframework.hateoas.MediaTypes;
|
|
||||||
import org.springframework.hateoas.Resource;
|
|
||||||
import org.springframework.http.RequestEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.net.URI;
|
@org.springframework.stereotype.Service
|
||||||
import java.util.Objects;
|
public class OrderService extends Service<Order, Long> {
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CacheConfig(cacheNames = {"orders"})
|
|
||||||
public class OrderService {
|
|
||||||
|
|
||||||
private final Logger log = Logger.getLogger(OrderService.class);
|
|
||||||
|
|
||||||
private final OrderRepository orderRepository;
|
private final OrderRepository orderRepository;
|
||||||
private final EventService eventService;
|
|
||||||
private final RestTemplate restTemplate;
|
|
||||||
|
|
||||||
public OrderService(OrderRepository orderRepository, EventService eventService, RestTemplate restTemplate) {
|
public OrderService(OrderRepository orderRepository) {
|
||||||
this.orderRepository = orderRepository;
|
this.orderRepository = orderRepository;
|
||||||
this.eventService = eventService;
|
|
||||||
this.restTemplate = restTemplate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(cacheNames = "orders", key = "#order.getOrderId().toString()")
|
|
||||||
public Order registerOrder(Order order) {
|
public Order registerOrder(Order order) {
|
||||||
|
|
||||||
order = createOrder(order);
|
order = create(order);
|
||||||
|
|
||||||
//cacheManager.getCache("orders").evict(order.getOrderId());
|
|
||||||
|
|
||||||
// Trigger the order creation event
|
// Trigger the order creation event
|
||||||
OrderEvent event = appendEvent(order.getOrderId(),
|
order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_CREATED, order));
|
||||||
new OrderEvent(OrderEventType.ORDER_CREATED));
|
|
||||||
|
|
||||||
// Attach order identifier
|
return order;
|
||||||
event.getOrder().setOrderId(order.getOrderId());
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return event.getOrder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +30,7 @@ public class OrderService {
|
|||||||
* @param order is the {@link Order} to create
|
* @param order is the {@link Order} to create
|
||||||
* @return the newly created {@link Order}
|
* @return the newly created {@link Order}
|
||||||
*/
|
*/
|
||||||
@CacheEvict(cacheNames = "orders", key = "#order.getOrderId().toString()")
|
public Order create(Order order) {
|
||||||
public Order createOrder(Order order) {
|
|
||||||
|
|
||||||
// Save the order to the repository
|
// Save the order to the repository
|
||||||
order = orderRepository.saveAndFlush(order);
|
order = orderRepository.saveAndFlush(order);
|
||||||
@@ -78,34 +44,24 @@ public class OrderService {
|
|||||||
* @param id is the unique identifier of a {@link Order} entity
|
* @param id is the unique identifier of a {@link Order} entity
|
||||||
* @return an {@link Order} entity
|
* @return an {@link Order} entity
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheNames = "orders", key = "#id.toString()")
|
public Order get(Long id) {
|
||||||
public Order getOrder(Long id) {
|
|
||||||
return orderRepository.findOne(id);
|
return orderRepository.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an {@link Order} entity with the supplied identifier.
|
* Update an {@link Order} entity with the supplied identifier.
|
||||||
*
|
*
|
||||||
* @param id is the unique identifier of the {@link Order} entity
|
|
||||||
* @param order is the {@link Order} containing updated fields
|
* @param order is the {@link Order} containing updated fields
|
||||||
* @return the updated {@link Order} entity
|
* @return the updated {@link Order} entity
|
||||||
*/
|
*/
|
||||||
@CachePut(cacheNames = "orders", key = "#id.toString()")
|
public Order update(Order order) {
|
||||||
public Order updateOrder(Long id, Order order) {
|
Assert.notNull(order.getIdentity(), "Order id must be present in the resource URL");
|
||||||
Assert.notNull(id, "Order id must be present in the resource URL");
|
|
||||||
Assert.notNull(order, "Order request body cannot be null");
|
Assert.notNull(order, "Order request body cannot be null");
|
||||||
|
|
||||||
if (order.getOrderId() != null) {
|
Assert.state(orderRepository.exists(order.getIdentity()),
|
||||||
Assert.isTrue(Objects.equals(id, order.getOrderId()),
|
|
||||||
"The order id in the request body must match the resource URL");
|
|
||||||
} else {
|
|
||||||
order.setOrderId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.state(orderRepository.exists(id),
|
|
||||||
"The order with the supplied id does not exist");
|
"The order with the supplied id does not exist");
|
||||||
|
|
||||||
Order currentOrder = orderRepository.findOne(id);
|
Order currentOrder = get(order.getIdentity());
|
||||||
currentOrder.setAccountId(order.getAccountId());
|
currentOrder.setAccountId(order.getAccountId());
|
||||||
currentOrder.setPaymentId(order.getPaymentId());
|
currentOrder.setPaymentId(order.getPaymentId());
|
||||||
currentOrder.setLineItems(order.getLineItems());
|
currentOrder.setLineItems(order.getLineItems());
|
||||||
@@ -120,174 +76,10 @@ public class OrderService {
|
|||||||
*
|
*
|
||||||
* @param id is the unique identifier for the {@link Order}
|
* @param id is the unique identifier for the {@link Order}
|
||||||
*/
|
*/
|
||||||
@CacheEvict(cacheNames = "orders", key = "#id.toString()")
|
public boolean delete(Long id) {
|
||||||
public Boolean deleteOrder(Long id) {
|
|
||||||
Assert.state(orderRepository.exists(id),
|
Assert.state(orderRepository.exists(id),
|
||||||
"The order with the supplied id does not exist");
|
"The order with the supplied id does not exist");
|
||||||
this.orderRepository.delete(id);
|
this.orderRepository.delete(id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new {@link OrderEvent} to the {@link Order} reference for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
|
||||||
* @param event is the {@link OrderEvent} to append to the {@link Order} entity
|
|
||||||
* @param links is the optional {@link Link} to embed in the {@link org.springframework.hateoas.Resource}
|
|
||||||
* @return the newly appended {@link OrderEvent}
|
|
||||||
*/
|
|
||||||
public OrderEvent appendEvent(Long orderId, OrderEvent event, Link... links) {
|
|
||||||
return appendEvent(orderId, event, ConsistencyModel.ACID, links);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new {@link OrderEvent} to the {@link Order} reference for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
|
||||||
* @param event is the {@link OrderEvent} to append to the {@link Order} entity
|
|
||||||
* @return the newly appended {@link OrderEvent}
|
|
||||||
*/
|
|
||||||
public OrderEvent appendEvent(Long orderId, OrderEvent event) {
|
|
||||||
return appendEvent(orderId, event, ConsistencyModel.ACID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new {@link OrderEvent} to the {@link Order} reference for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
|
||||||
* @param event is the {@link OrderEvent} to append to the {@link Order} entity
|
|
||||||
* @return the newly appended {@link OrderEvent}
|
|
||||||
*/
|
|
||||||
public OrderEvent appendEvent(Long orderId, OrderEvent event, ConsistencyModel consistencyModel, Link... links) {
|
|
||||||
Order order = getOrder(orderId);
|
|
||||||
Assert.notNull(order, "The order with the supplied id does not exist");
|
|
||||||
event.setOrder(order);
|
|
||||||
event = eventService.createEvent(orderId, event);
|
|
||||||
order.getEvents().add(event);
|
|
||||||
order = orderRepository.saveAndFlush(order);
|
|
||||||
event.setOrder(order);
|
|
||||||
eventService.raiseEvent(event, consistencyModel, links);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply an {@link OrderCommand} to the {@link Order} with a specified identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier of the {@link Order}
|
|
||||||
* @param orderCommand is the command to apply to the {@link Order}
|
|
||||||
* @return a hypermedia resource containing the updated {@link Order}
|
|
||||||
*/
|
|
||||||
@CachePut(cacheNames = "orders", key = "#id.toString()")
|
|
||||||
public Order applyCommand(Long id, OrderCommand orderCommand) {
|
|
||||||
Order order = getOrder(id);
|
|
||||||
|
|
||||||
Assert.notNull(order, "The order for the supplied id could not be found");
|
|
||||||
|
|
||||||
OrderStatus status = order.getStatus();
|
|
||||||
|
|
||||||
// TODO: Implement apply command
|
|
||||||
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order connectAccount(Long id, Long accountId) {
|
|
||||||
// Get the order
|
|
||||||
Order order = getOrder(id);
|
|
||||||
|
|
||||||
// Connect the account
|
|
||||||
order.setAccountId(accountId);
|
|
||||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
|
||||||
order = updateOrder(id, order);
|
|
||||||
|
|
||||||
//cacheManager.getCache("orders").evict(id);
|
|
||||||
|
|
||||||
// Trigger the account connected event
|
|
||||||
OrderEvent event = appendEvent(order.getOrderId(),
|
|
||||||
new OrderEvent(OrderEventType.ACCOUNT_CONNECTED));
|
|
||||||
|
|
||||||
// Set non-serializable fields
|
|
||||||
event.getOrder().setAccountId(order.getAccountId());
|
|
||||||
event.getOrder().setPaymentId(order.getPaymentId());
|
|
||||||
event.getOrder().setOrderId(order.getOrderId());
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return event.getOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order connectPayment(Long id, Long paymentId) {
|
|
||||||
// Get the order
|
|
||||||
Order order = getOrder(id);
|
|
||||||
|
|
||||||
// Connect the account
|
|
||||||
order.setPaymentId(paymentId);
|
|
||||||
order.setStatus(OrderStatus.PAYMENT_CONNECTED);
|
|
||||||
order = updateOrder(id, order);
|
|
||||||
|
|
||||||
// cacheManager.getCache("orders").evict(id);
|
|
||||||
|
|
||||||
// Trigger the account connected event
|
|
||||||
OrderEvent event = appendEvent(order.getOrderId(),
|
|
||||||
new OrderEvent(OrderEventType.PAYMENT_CONNECTED));
|
|
||||||
|
|
||||||
// Set non-serializable fields
|
|
||||||
event.getOrder().setAccountId(order.getAccountId());
|
|
||||||
event.getOrder().setPaymentId(order.getPaymentId());
|
|
||||||
event.getOrder().setOrderId(order.getOrderId());
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return event.getOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order createPayment(Long id) {
|
|
||||||
// Get the order
|
|
||||||
Order order = getOrder(id);
|
|
||||||
|
|
||||||
Payment payment = new Payment();
|
|
||||||
|
|
||||||
// Calculate payment amount
|
|
||||||
payment.setAmount(order.getLineItems()
|
|
||||||
.stream()
|
|
||||||
.mapToDouble(a -> (a.getPrice() + a.getTax()) * a.getQuantity())
|
|
||||||
.sum());
|
|
||||||
|
|
||||||
// Set payment method
|
|
||||||
payment.setPaymentMethod(PaymentMethod.CREDIT_CARD);
|
|
||||||
|
|
||||||
// Create a new request entity
|
|
||||||
RequestEntity<Resource<Payment>> requestEntity = RequestEntity.post(
|
|
||||||
URI.create("http://localhost:8082/v1/payments"))
|
|
||||||
.contentType(MediaTypes.HAL_JSON)
|
|
||||||
.body(new Resource<Payment>(payment), Resource.class);
|
|
||||||
|
|
||||||
// Update the order entity's status
|
|
||||||
payment = restTemplate.exchange(requestEntity, Payment.class)
|
|
||||||
.getBody();
|
|
||||||
|
|
||||||
log.info(payment);
|
|
||||||
|
|
||||||
// Update the status
|
|
||||||
order.setStatus(OrderStatus.PAYMENT_CREATED);
|
|
||||||
order = updateOrder(id, order);
|
|
||||||
|
|
||||||
// cacheManager.getCache("orders").evict(id);
|
|
||||||
|
|
||||||
// Trigger the account connected event
|
|
||||||
OrderEvent event = appendEvent(order.getOrderId(),
|
|
||||||
new OrderEvent(OrderEventType.PAYMENT_CREATED),
|
|
||||||
new Link(payment.getId().getHref(), "payment"));
|
|
||||||
|
|
||||||
// Set non-serializable fields
|
|
||||||
event.getOrder()
|
|
||||||
.setAccountId(Optional.ofNullable(event.getOrder().getAccountId())
|
|
||||||
.orElse(order.getAccountId()));
|
|
||||||
|
|
||||||
event.getOrder()
|
|
||||||
.setPaymentId(Optional.ofNullable(event.getOrder().getPaymentId())
|
|
||||||
.orElse(order.getPaymentId()));
|
|
||||||
|
|
||||||
event.getOrder().setOrderId(order.getOrderId());
|
|
||||||
|
|
||||||
// Return the result
|
|
||||||
return event.getOrder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.event.OrderEvent;
|
||||||
|
import demo.event.OrderEventType;
|
||||||
|
import demo.order.Order;
|
||||||
|
import demo.order.OrderProvider;
|
||||||
|
import demo.order.OrderService;
|
||||||
|
import demo.order.OrderStatus;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ConnectAccount extends Action<Order> {
|
||||||
|
|
||||||
|
public BiConsumer<Order, Long> getConsumer() {
|
||||||
|
return (order, accountId) -> {
|
||||||
|
OrderService orderService = order.getProvider(OrderProvider.class)
|
||||||
|
.getDefaultService();
|
||||||
|
|
||||||
|
// Connect the account
|
||||||
|
order.setAccountId(accountId);
|
||||||
|
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||||
|
order = orderService.update(order);
|
||||||
|
|
||||||
|
// Trigger the account connected event
|
||||||
|
order.sendAsyncEvent(new OrderEvent(OrderEventType.ACCOUNT_CONNECTED));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.event.OrderEvent;
|
||||||
|
import demo.event.OrderEventType;
|
||||||
|
import demo.order.Order;
|
||||||
|
import demo.order.OrderProvider;
|
||||||
|
import demo.order.OrderService;
|
||||||
|
import demo.order.OrderStatus;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ConnectPayment extends Action<Order> {
|
||||||
|
public BiConsumer<Order, Long> getConsumer() {
|
||||||
|
return (order, paymentId) -> {
|
||||||
|
|
||||||
|
OrderService orderService = order.getProvider(OrderProvider.class)
|
||||||
|
.getDefaultService();
|
||||||
|
|
||||||
|
// Connect the account
|
||||||
|
order.setPaymentId(paymentId);
|
||||||
|
order.setStatus(OrderStatus.PAYMENT_CONNECTED);
|
||||||
|
order = orderService.update(order);
|
||||||
|
|
||||||
|
// Trigger the account connected event
|
||||||
|
order.sendAsyncEvent(new OrderEvent(OrderEventType.PAYMENT_CONNECTED));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.event.OrderEvent;
|
||||||
|
import demo.event.OrderEventType;
|
||||||
|
import demo.order.Order;
|
||||||
|
import demo.order.OrderProvider;
|
||||||
|
import demo.order.OrderService;
|
||||||
|
import demo.order.OrderStatus;
|
||||||
|
import demo.payment.Payment;
|
||||||
|
import demo.payment.PaymentMethod;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.springframework.hateoas.MediaTypes;
|
||||||
|
import org.springframework.hateoas.Resource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CreatePayment extends Action<Order> {
|
||||||
|
|
||||||
|
private final Logger log = Logger.getLogger(CreatePayment.class);
|
||||||
|
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public CreatePayment(RestTemplate restTemplate) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Consumer<Order> getConsumer() {
|
||||||
|
return order -> {
|
||||||
|
|
||||||
|
OrderService orderService = (OrderService) order.getProvider(OrderProvider.class)
|
||||||
|
.getDefaultService();
|
||||||
|
|
||||||
|
Payment payment = new Payment();
|
||||||
|
|
||||||
|
// Calculate payment amount
|
||||||
|
payment.setAmount(order.getLineItems()
|
||||||
|
.stream()
|
||||||
|
.mapToDouble(a -> (a.getPrice() + a.getTax()) * a.getQuantity())
|
||||||
|
.sum());
|
||||||
|
|
||||||
|
// Set payment method
|
||||||
|
payment.setPaymentMethod(PaymentMethod.CREDIT_CARD);
|
||||||
|
|
||||||
|
// Create a new request entity
|
||||||
|
RequestEntity<Resource<Payment>> requestEntity = RequestEntity.post(
|
||||||
|
URI.create("http://localhost:8082/v1/payments"))
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.accept(MediaTypes.HAL_JSON)
|
||||||
|
.body(new Resource<>(payment), Resource.class);
|
||||||
|
|
||||||
|
// Update the order entity's status
|
||||||
|
Resource paymentResource = restTemplate
|
||||||
|
.exchange(requestEntity, Resource.class)
|
||||||
|
.getBody();
|
||||||
|
|
||||||
|
log.info(paymentResource);
|
||||||
|
|
||||||
|
// Update the status
|
||||||
|
order.setStatus(OrderStatus.PAYMENT_CREATED);
|
||||||
|
order = orderService.update(order);
|
||||||
|
|
||||||
|
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_CREATED, order);
|
||||||
|
event.add(paymentResource.getLink("self")
|
||||||
|
.withRel("payment"));
|
||||||
|
|
||||||
|
// Trigger the payment created
|
||||||
|
order.sendAsyncEvent(event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.order.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ProcessPayment extends Action<Order> {
|
||||||
|
public Consumer<Order> getConsumer() {
|
||||||
|
return (order) -> {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.order.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ReserveInventory extends Action<Order> {
|
||||||
|
public Consumer<Order> getConsumer() {
|
||||||
|
return (order) -> {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
package demo.order;
|
package demo.order.controller;
|
||||||
|
|
||||||
import demo.event.EventController;
|
import demo.event.EventController;
|
||||||
import demo.event.EventService;
|
import demo.event.EventService;
|
||||||
|
import demo.event.Events;
|
||||||
import demo.event.OrderEvent;
|
import demo.event.OrderEvent;
|
||||||
import demo.event.OrderEvents;
|
import demo.order.Order;
|
||||||
import org.springframework.hateoas.*;
|
import demo.order.OrderService;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
import org.springframework.hateoas.LinkBuilder;
|
||||||
|
import org.springframework.hateoas.Resource;
|
||||||
|
import org.springframework.hateoas.ResourceSupport;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||||
@@ -19,9 +25,9 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|||||||
public class OrderController {
|
public class OrderController {
|
||||||
|
|
||||||
private final OrderService orderService;
|
private final OrderService orderService;
|
||||||
private final EventService eventService;
|
private final EventService<OrderEvent, Long> eventService;
|
||||||
|
|
||||||
public OrderController(OrderService orderService, EventService eventService) {
|
public OrderController(OrderService orderService, EventService<OrderEvent, Long> eventService) {
|
||||||
this.orderService = orderService;
|
this.orderService = orderService;
|
||||||
this.eventService = eventService;
|
this.eventService = eventService;
|
||||||
}
|
}
|
||||||
@@ -40,7 +46,7 @@ public class OrderController {
|
|||||||
.orElseThrow(() -> new RuntimeException("Order update failed"));
|
.orElseThrow(() -> new RuntimeException("Order update failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}")
|
@RequestMapping(path = "/orders/{id}")
|
||||||
public ResponseEntity getOrder(@PathVariable Long id) {
|
public ResponseEntity getOrder(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(getOrderResource(id))
|
return Optional.ofNullable(getOrderResource(id))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||||
@@ -49,12 +55,12 @@ public class OrderController {
|
|||||||
|
|
||||||
@DeleteMapping(path = "/orders/{id}")
|
@DeleteMapping(path = "/orders/{id}")
|
||||||
public ResponseEntity deleteOrder(@PathVariable Long id) {
|
public ResponseEntity deleteOrder(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(orderService.deleteOrder(id))
|
return Optional.ofNullable(orderService.delete(id))
|
||||||
.map(e -> new ResponseEntity<>(HttpStatus.NO_CONTENT))
|
.map(e -> new ResponseEntity<>(HttpStatus.NO_CONTENT))
|
||||||
.orElseThrow(() -> new RuntimeException("Order deletion failed"));
|
.orElseThrow(() -> new RuntimeException("Order deletion failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/events")
|
@RequestMapping(path = "/orders/{id}/events")
|
||||||
public ResponseEntity getOrderEvents(@PathVariable Long id) {
|
public ResponseEntity getOrderEvents(@PathVariable Long id) {
|
||||||
return Optional.of(getOrderEventResources(id))
|
return Optional.of(getOrderEventResources(id))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||||
@@ -68,48 +74,50 @@ public class OrderController {
|
|||||||
.orElseThrow(() -> new RuntimeException("Append order event failed"));
|
.orElseThrow(() -> new RuntimeException("Append order event failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands")
|
@RequestMapping(path = "/orders/{id}/commands")
|
||||||
public ResponseEntity getOrderCommands(@PathVariable Long id) {
|
public ResponseEntity getCommands(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(getCommandsResource(id))
|
return Optional.ofNullable(getCommandsResources(id))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The order could not be found"));
|
.orElseThrow(() -> new RuntimeException("The order could not be found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands/connectAccount")
|
@RequestMapping(path = "/orders/{id}/commands/connectAccount")
|
||||||
public ResponseEntity connectAccount(@PathVariable Long id, @RequestParam(value = "accountId") Long accountId) {
|
public ResponseEntity connectAccount(@PathVariable Long id, @RequestParam(value = "accountId") Long accountId) {
|
||||||
return Optional.ofNullable(getOrderResource(orderService.connectAccount(id, accountId)))
|
return Optional.ofNullable(orderService.get(id)
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.connectAccount(accountId))
|
||||||
|
.map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands/connectPayment")
|
@RequestMapping(path = "/orders/{id}/commands/connectPayment")
|
||||||
public ResponseEntity connectPayment(@PathVariable Long id, @RequestParam(value = "paymentId") Long paymentId) {
|
public ResponseEntity connectPayment(@PathVariable Long id, @RequestParam(value = "paymentId") Long paymentId) {
|
||||||
return Optional.ofNullable(getOrderResource(orderService.connectPayment(id, paymentId)))
|
return Optional.ofNullable(orderService.get(id)
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.connectPayment(paymentId))
|
||||||
|
.map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands/createPayment")
|
@RequestMapping(path = "/orders/{id}/commands/createPayment")
|
||||||
public ResponseEntity createPayment(@PathVariable Long id) {
|
public ResponseEntity createPayment(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(getOrderResource(
|
return Optional.ofNullable(orderService.get(id)
|
||||||
orderService.createPayment(id)))
|
.createPayment())
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands/processPayment")
|
@RequestMapping(path = "/orders/{id}/commands/processPayment")
|
||||||
public ResponseEntity processPayment(@PathVariable Long id) {
|
public ResponseEntity processPayment(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(getOrderResource(
|
return Optional.ofNullable(orderService.get(id)
|
||||||
orderService.applyCommand(id, OrderCommand.PROCESS_PAYMENT)))
|
.processPayment())
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/orders/{id}/commands/reserveInventory")
|
@RequestMapping(path = "/orders/{id}/commands/reserveInventory")
|
||||||
public ResponseEntity reserveInventory(@PathVariable Long id) {
|
public ResponseEntity reserveInventory(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(getOrderResource(
|
return Optional.ofNullable(orderService.get(id)
|
||||||
orderService.applyCommand(id, OrderCommand.RESERVE_INVENTORY)))
|
.reserveInventory(id))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,17 +128,10 @@ public class OrderController {
|
|||||||
* @return a hypermedia resource for the fetched {@link Order}
|
* @return a hypermedia resource for the fetched {@link Order}
|
||||||
*/
|
*/
|
||||||
private Resource<Order> getOrderResource(Long id) {
|
private Resource<Order> getOrderResource(Long id) {
|
||||||
Resource<Order> orderResource = null;
|
|
||||||
|
|
||||||
// Get the order for the provided id
|
// Get the order for the provided id
|
||||||
Order order = orderService.getOrder(id);
|
Order order = orderService.get(id);
|
||||||
|
|
||||||
// If the order exists, wrap the hypermedia response
|
return getOrderResource(order);
|
||||||
if (order != null)
|
|
||||||
orderResource = getOrderResource(order);
|
|
||||||
|
|
||||||
|
|
||||||
return orderResource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,12 +157,13 @@ public class OrderController {
|
|||||||
* @return a hypermedia resource for the updated {@link Order}
|
* @return a hypermedia resource for the updated {@link Order}
|
||||||
*/
|
*/
|
||||||
private Resource<Order> updateOrderResource(Long id, Order order) {
|
private Resource<Order> updateOrderResource(Long id, Order order) {
|
||||||
return getOrderResource(orderService.updateOrder(id, order));
|
order.setIdentity(id);
|
||||||
|
return getOrderResource(orderService.update(order));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends an {@link OrderEvent} domain event to the event log of the {@link Order}
|
* Appends an {@link OrderEvent} domain event to the event log of the {@link Order} aggregate with the
|
||||||
* aggregate with the specified orderId.
|
* specified orderId.
|
||||||
*
|
*
|
||||||
* @param orderId is the unique identifier for the {@link Order}
|
* @param orderId is the unique identifier for the {@link Order}
|
||||||
* @param event is the {@link OrderEvent} that attempts to alter the state of the {@link Order}
|
* @param event is the {@link OrderEvent} that attempts to alter the state of the {@link Order}
|
||||||
@@ -170,7 +172,8 @@ public class OrderController {
|
|||||||
private Resource<OrderEvent> appendEventResource(Long orderId, OrderEvent event) {
|
private Resource<OrderEvent> appendEventResource(Long orderId, OrderEvent event) {
|
||||||
Resource<OrderEvent> eventResource = null;
|
Resource<OrderEvent> eventResource = null;
|
||||||
|
|
||||||
event = orderService.appendEvent(orderId, event);
|
orderService.get(orderId)
|
||||||
|
.sendAsyncEvent(event);
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
eventResource = new Resource<>(event,
|
eventResource = new Resource<>(event,
|
||||||
@@ -188,75 +191,20 @@ public class OrderController {
|
|||||||
return eventResource;
|
return eventResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Events getOrderEventResources(Long id) {
|
||||||
* Get the {@link OrderCommand} hypermedia resource that lists the available commands that can be applied
|
return eventService.find(id);
|
||||||
* to an {@link Order} entity.
|
}
|
||||||
*
|
|
||||||
* @param id is the {@link Order} identifier to provide command links for
|
|
||||||
* @return an {@link OrderCommands} with a collection of embedded command links
|
|
||||||
*/
|
|
||||||
private OrderCommands getCommandsResource(Long id) {
|
|
||||||
// Get the order resource for the identifier
|
|
||||||
Resource<Order> orderResource = getOrderResource(id);
|
|
||||||
|
|
||||||
// Create a new order commands hypermedia resource
|
private LinkBuilder linkBuilder(String name, Long id) {
|
||||||
OrderCommands commandResource = new OrderCommands();
|
Method method;
|
||||||
|
|
||||||
// Add order command hypermedia links
|
try {
|
||||||
if (orderResource != null) {
|
method = OrderController.class.getMethod(name, Long.class);
|
||||||
commandResource.add(
|
} catch (NoSuchMethodException e) {
|
||||||
new Link(new UriTemplate(
|
throw new RuntimeException(e);
|
||||||
getCommandLinkBuilder(id)
|
|
||||||
.slash("connectAccount")
|
|
||||||
.toUri()
|
|
||||||
.toString(),
|
|
||||||
new TemplateVariables(
|
|
||||||
new TemplateVariable("accountId",
|
|
||||||
TemplateVariable.VariableType.REQUEST_PARAM))), "connectAccount"),
|
|
||||||
getCommandLinkBuilder(id)
|
|
||||||
.slash("reserveInventory")
|
|
||||||
.withRel("reserveInventory"),
|
|
||||||
getCommandLinkBuilder(id)
|
|
||||||
.slash("createPayment")
|
|
||||||
.withRel("createPayment"),
|
|
||||||
new Link(new UriTemplate(
|
|
||||||
getCommandLinkBuilder(id)
|
|
||||||
.slash("connectPayment")
|
|
||||||
.toUri()
|
|
||||||
.toString(),
|
|
||||||
new TemplateVariables(
|
|
||||||
new TemplateVariable("paymentId",
|
|
||||||
TemplateVariable.VariableType.REQUEST_PARAM))), "connectPayment"),
|
|
||||||
getCommandLinkBuilder(id)
|
|
||||||
.slash("processPayment")
|
|
||||||
.withRel("processPayment")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandResource;
|
return linkTo(OrderController.class, method, id);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get {@link OrderEvents} for the supplied {@link Order} identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier of the {@link Order}
|
|
||||||
* @return a list of {@link OrderEvent} wrapped in a hypermedia {@link OrderEvents} resource
|
|
||||||
*/
|
|
||||||
private OrderEvents getOrderEventResources(Long id) {
|
|
||||||
return new OrderEvents(id, eventService.getOrderEvents(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a {@link LinkBuilder} for generating the {@link OrderCommands}.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier for a {@link Order}
|
|
||||||
* @return a {@link LinkBuilder} for the {@link OrderCommands}
|
|
||||||
*/
|
|
||||||
private LinkBuilder getCommandLinkBuilder(Long id) {
|
|
||||||
return linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
|
||||||
.slash(id)
|
|
||||||
.slash("commands");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,29 +214,26 @@ public class OrderController {
|
|||||||
* @return is a hypermedia enriched resource for the supplied {@link Order} entity
|
* @return is a hypermedia enriched resource for the supplied {@link Order} entity
|
||||||
*/
|
*/
|
||||||
private Resource<Order> getOrderResource(Order order) {
|
private Resource<Order> getOrderResource(Order order) {
|
||||||
Resource<Order> orderResource;
|
Assert.notNull(order, "Order must not be null");
|
||||||
|
|
||||||
// Prepare hypermedia response
|
// Add command link
|
||||||
orderResource = new Resource<>(order,
|
order.add(linkBuilder("getCommands", order.getIdentity()).withRel("commands"));
|
||||||
linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
// Add get events link
|
||||||
.slash(order.getOrderId())
|
order.add(linkBuilder("getOrderEvents", order.getIdentity()).withRel("events"));
|
||||||
.withSelfRel(),
|
|
||||||
linkTo(OrderController.class)
|
|
||||||
.slash("orders")
|
|
||||||
.slash(order.getOrderId())
|
|
||||||
.slash("events")
|
|
||||||
.withRel("events"),
|
|
||||||
getCommandLinkBuilder(order.getOrderId())
|
|
||||||
.withRel("commands")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (order.getAccountId() != null)
|
if (order.getAccountId() != null)
|
||||||
orderResource.add(new Link("http://account-service/v1/accounts/" + order.getAccountId(), "account"));
|
order.add(new Link("http://account-service/v1/accounts/" + order.getAccountId(), "account"));
|
||||||
|
|
||||||
if (order.getPaymentId() != null)
|
if (order.getPaymentId() != null)
|
||||||
orderResource.add(new Link("http://localhost:8082/v1/payments/" + order.getPaymentId(), "payment"));
|
order.add(new Link("http://localhost:8082/v1/payments/" + order.getPaymentId(), "payment"));
|
||||||
|
|
||||||
return orderResource;
|
return new Resource<>(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceSupport getCommandsResources(Long id) {
|
||||||
|
Order order = new Order();
|
||||||
|
order.setIdentity(id);
|
||||||
|
return new Resource<>(order.getCommands());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package demo.payment;
|
package demo.payment;
|
||||||
|
|
||||||
import demo.domain.BaseEntity;
|
public class Payment {
|
||||||
|
|
||||||
public class Payment extends BaseEntity {
|
|
||||||
|
|
||||||
private Double amount;
|
private Double amount;
|
||||||
private PaymentMethod paymentMethod;
|
private PaymentMethod paymentMethod;
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public class EventService {
|
|||||||
|
|
||||||
Order result;
|
Order result;
|
||||||
|
|
||||||
|
log.info(orderEvent);
|
||||||
|
|
||||||
log.info("Order event received: " + orderEvent.getLink("self").getHref());
|
log.info("Order event received: " + orderEvent.getLink("self").getHref());
|
||||||
|
|
||||||
// Generate a state machine for computing the state of the order resource
|
// Generate a state machine for computing the state of the order resource
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
package demo.domain;
|
package demo.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import demo.event.Event;
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
import javax.persistence.EntityListeners;
|
import javax.persistence.*;
|
||||||
import javax.persistence.MappedSuperclass;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
@EntityListeners(AuditingEntityListener.class)
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
public class AbstractEntity<T extends Serializable> extends Aggregate<T> implements Serializable {
|
public abstract class AbstractEntity<E extends Event, T extends Serializable> extends Aggregate<E, T> implements Serializable {
|
||||||
|
|
||||||
private T identity;
|
private T identity;
|
||||||
|
|
||||||
@@ -20,6 +23,9 @@ public class AbstractEntity<T extends Serializable> extends Aggregate<T> impleme
|
|||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
private List<E> events = new ArrayList<>();
|
||||||
|
|
||||||
public AbstractEntity() {
|
public AbstractEntity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +45,16 @@ public class AbstractEntity<T extends Serializable> extends Aggregate<T> impleme
|
|||||||
this.lastModified = lastModified;
|
this.lastModified = lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
|
public List<E> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEvents(List<E> events) {
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T getIdentity() {
|
public T getIdentity() {
|
||||||
return identity;
|
return identity;
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import demo.payment.controller.PaymentController;
|
|||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||||
|
|
||||||
@@ -23,15 +21,12 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|||||||
* @author Kenny Bastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
public class Payment extends AbstractEntity<Long> {
|
public class Payment extends AbstractEntity<PaymentEvent, Long> {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
|
||||||
private Set<PaymentEvent> events = new HashSet<>();
|
|
||||||
|
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
private PaymentStatus status;
|
private PaymentStatus status;
|
||||||
|
|
||||||
@@ -61,15 +56,6 @@ public class Payment extends AbstractEntity<Long> {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Set<PaymentEvent> getEvents() {
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEvents(Set<PaymentEvent> events) {
|
|
||||||
this.events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentStatus getStatus() {
|
public PaymentStatus getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -131,6 +117,4 @@ public class Payment extends AbstractEntity<Long> {
|
|||||||
.slash(getIdentity())
|
.slash(getIdentity())
|
||||||
.withSelfRel();
|
.withSelfRel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
package demo.payment;
|
package demo.payment;
|
||||||
|
|
||||||
import demo.domain.Provider;
|
import demo.domain.Provider;
|
||||||
import demo.domain.Service;
|
import demo.event.EventService;
|
||||||
|
import demo.event.PaymentEvent;
|
||||||
|
|
||||||
@org.springframework.stereotype.Service
|
@org.springframework.stereotype.Service
|
||||||
public class PaymentProvider extends Provider<Payment> {
|
public class PaymentProvider extends Provider<Payment> {
|
||||||
|
|
||||||
private final PaymentService paymentService;
|
private final PaymentService paymentService;
|
||||||
|
private final EventService<PaymentEvent, Long> eventService;
|
||||||
|
|
||||||
public PaymentProvider(PaymentService paymentService) {
|
public PaymentProvider(PaymentService paymentService, EventService<PaymentEvent, Long> eventService) {
|
||||||
this.paymentService = paymentService;
|
this.paymentService = paymentService;
|
||||||
|
this.eventService = eventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Service<? extends Payment> getDefaultService() {
|
public PaymentService getDefaultService() {
|
||||||
return paymentService;
|
return paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventService<PaymentEvent, Long> getDefaultEventService() {
|
||||||
|
return eventService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import demo.domain.Service;
|
|||||||
import demo.event.EventService;
|
import demo.event.EventService;
|
||||||
import demo.event.PaymentEvent;
|
import demo.event.PaymentEvent;
|
||||||
import demo.event.PaymentEventType;
|
import demo.event.PaymentEventType;
|
||||||
import demo.util.ConsistencyModel;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link PaymentService} provides transactional support for managing {@link Payment} entities. This service also
|
* The {@link PaymentService} provides transactional support for managing {@link Payment} entities. This service also
|
||||||
* provides event sourcing support for {@link PaymentEvent}. Events can be appended to an {@link Payment}, which
|
* provides event sourcing support for {@link PaymentEvent}. Events can be appended to an {@link Payment}, which
|
||||||
@@ -18,7 +15,7 @@ import java.util.Objects;
|
|||||||
* @author Kenny Bastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
@org.springframework.stereotype.Service
|
@org.springframework.stereotype.Service
|
||||||
public class PaymentService extends Service<Payment> {
|
public class PaymentService extends Service<Payment, Long> {
|
||||||
|
|
||||||
private final PaymentRepository paymentRepository;
|
private final PaymentRepository paymentRepository;
|
||||||
private final EventService<PaymentEvent, Long> eventService;
|
private final EventService<PaymentEvent, Long> eventService;
|
||||||
@@ -29,125 +26,50 @@ public class PaymentService extends Service<Payment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Payment registerPayment(Payment payment) {
|
public Payment registerPayment(Payment payment) {
|
||||||
|
payment = create(payment);
|
||||||
payment = createPayment(payment);
|
|
||||||
|
|
||||||
// Trigger the payment creation event
|
// Trigger the payment creation event
|
||||||
PaymentEvent event = appendEvent(payment.getIdentity(),
|
PaymentEvent event = payment.sendEvent(new PaymentEvent(PaymentEventType.PAYMENT_CREATED, payment));
|
||||||
new PaymentEvent(PaymentEventType.PAYMENT_CREATED));
|
|
||||||
|
|
||||||
// Attach payment identifier
|
// Attach payment identifier
|
||||||
event.getEntity().setIdentity(payment.getIdentity());
|
event.getEntity()
|
||||||
|
.setIdentity(payment.getIdentity());
|
||||||
|
|
||||||
|
event.getEntity().getLinks().clear();
|
||||||
|
|
||||||
// Return the result
|
// Return the result
|
||||||
return event.getEntity();
|
return event.getEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Payment get(Long id) {
|
||||||
* Create a new {@link Payment} entity.
|
|
||||||
*
|
|
||||||
* @param payment is the {@link Payment} to create
|
|
||||||
* @return the newly created {@link Payment}
|
|
||||||
*/
|
|
||||||
public Payment createPayment(Payment payment) {
|
|
||||||
|
|
||||||
// Save the payment to the repository
|
|
||||||
payment = paymentRepository.saveAndFlush(payment);
|
|
||||||
|
|
||||||
return payment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an {@link Payment} entity for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier of a {@link Payment} entity
|
|
||||||
* @return an {@link Payment} entity
|
|
||||||
*/
|
|
||||||
public Payment getPayment(Long id) {
|
|
||||||
return paymentRepository.findOne(id);
|
return paymentRepository.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Payment create(Payment payment) {
|
||||||
* Update an {@link Payment} entity with the supplied identifier.
|
// Save the payment to the repository
|
||||||
*
|
return paymentRepository.saveAndFlush(payment);
|
||||||
* @param id is the unique identifier of the {@link Payment} entity
|
}
|
||||||
* @param payment is the {@link Payment} containing updated fields
|
|
||||||
* @return the updated {@link Payment} entity
|
public Payment update(Payment payment) {
|
||||||
*/
|
|
||||||
public Payment updatePayment(Long id, Payment payment) {
|
|
||||||
Assert.notNull(id, "Payment id must be present in the resource URL");
|
|
||||||
Assert.notNull(payment, "Payment request body cannot be null");
|
Assert.notNull(payment, "Payment request body cannot be null");
|
||||||
|
Assert.notNull(payment.getIdentity(), "Payment id must be present in the resource URL");
|
||||||
|
|
||||||
if (payment.getIdentity() != null) {
|
Assert.state(paymentRepository.exists(payment.getIdentity()),
|
||||||
Assert.isTrue(Objects.equals(id, payment.getIdentity()),
|
|
||||||
"The payment id in the request body must match the resource URL");
|
|
||||||
} else {
|
|
||||||
payment.setIdentity(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.state(paymentRepository.exists(id),
|
|
||||||
"The payment with the supplied id does not exist");
|
"The payment with the supplied id does not exist");
|
||||||
|
|
||||||
Payment currentPayment = paymentRepository.findOne(id);
|
Payment currentPayment = get(payment.getIdentity());
|
||||||
currentPayment.setStatus(payment.getStatus());
|
currentPayment.setStatus(payment.getStatus());
|
||||||
|
currentPayment.setPaymentMethod(payment.getPaymentMethod());
|
||||||
|
currentPayment.setOrderId(payment.getOrderId());
|
||||||
|
currentPayment.setAmount(payment.getAmount());
|
||||||
|
|
||||||
return paymentRepository.save(currentPayment);
|
return paymentRepository.save(currentPayment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean delete(Long id) {
|
||||||
* Delete the {@link Payment} with the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier for the {@link Payment}
|
|
||||||
*/
|
|
||||||
public Boolean deletePayment(Long id) {
|
|
||||||
Assert.state(paymentRepository.exists(id),
|
Assert.state(paymentRepository.exists(id),
|
||||||
"The payment with the supplied id does not exist");
|
"The payment with the supplied id does not exist");
|
||||||
this.paymentRepository.delete(id);
|
this.paymentRepository.delete(id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new {@link PaymentEvent} to the {@link Payment} reference for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param paymentId is the unique identifier for the {@link Payment}
|
|
||||||
* @param event is the {@link PaymentEvent} to append to the {@link Payment} entity
|
|
||||||
* @return the newly appended {@link PaymentEvent}
|
|
||||||
*/
|
|
||||||
public PaymentEvent appendEvent(Long paymentId, PaymentEvent event) {
|
|
||||||
return appendEvent(paymentId, event, ConsistencyModel.BASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a new {@link PaymentEvent} to the {@link Payment} reference for the supplied identifier.
|
|
||||||
*
|
|
||||||
* @param paymentId is the unique identifier for the {@link Payment}
|
|
||||||
* @param event is the {@link PaymentEvent} to append to the {@link Payment} entity
|
|
||||||
* @return the newly appended {@link PaymentEvent}
|
|
||||||
*/
|
|
||||||
public PaymentEvent appendEvent(Long paymentId, PaymentEvent event, ConsistencyModel consistencyModel) {
|
|
||||||
|
|
||||||
// Get the entity
|
|
||||||
Payment payment = getPayment(paymentId);
|
|
||||||
Assert.notNull(payment, "The payment with the supplied id does not exist");
|
|
||||||
|
|
||||||
// Add the entity to the event
|
|
||||||
event.setEntity(payment);
|
|
||||||
event = eventService.save(paymentId, event);
|
|
||||||
|
|
||||||
// Add the event to the entity
|
|
||||||
payment.getEvents().add(event);
|
|
||||||
paymentRepository.saveAndFlush(payment);
|
|
||||||
|
|
||||||
// Applies the event for the chosen consistency model
|
|
||||||
switch (consistencyModel) {
|
|
||||||
case BASE:
|
|
||||||
eventService.sendAsync(event);
|
|
||||||
break;
|
|
||||||
case ACID:
|
|
||||||
event = eventService.send(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class PaymentController {
|
|||||||
|
|
||||||
@RequestMapping(path = "/payments/{id}", method = RequestMethod.DELETE)
|
@RequestMapping(path = "/payments/{id}", method = RequestMethod.DELETE)
|
||||||
public ResponseEntity deletePayment(@PathVariable Long id) {
|
public ResponseEntity deletePayment(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(paymentService.deletePayment(id))
|
return Optional.ofNullable(paymentService.delete(id))
|
||||||
.map(e -> new ResponseEntity<>(HttpStatus.NO_CONTENT))
|
.map(e -> new ResponseEntity<>(HttpStatus.NO_CONTENT))
|
||||||
.orElseThrow(() -> new RuntimeException("Payment deletion failed"));
|
.orElseThrow(() -> new RuntimeException("Payment deletion failed"));
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ public class PaymentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(path = "/payments/{id}/events", method = RequestMethod.POST)
|
@RequestMapping(path = "/payments/{id}/events", method = RequestMethod.POST)
|
||||||
public ResponseEntity createPayment(@PathVariable Long id, @RequestBody PaymentEvent event) {
|
public ResponseEntity createPaymentEvents(@PathVariable Long id, @RequestBody PaymentEvent event) {
|
||||||
return Optional.ofNullable(appendEventResource(id, event))
|
return Optional.ofNullable(appendEventResource(id, event))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
|
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
|
||||||
.orElseThrow(() -> new RuntimeException("Append payment event failed"));
|
.orElseThrow(() -> new RuntimeException("Append payment event failed"));
|
||||||
@@ -89,7 +89,7 @@ public class PaymentController {
|
|||||||
|
|
||||||
@RequestMapping(path = "/payments/{id}/commands/connectOrder")
|
@RequestMapping(path = "/payments/{id}/commands/connectOrder")
|
||||||
public ResponseEntity connectOrder(@PathVariable Long id, @RequestParam(value = "orderId") Long orderId) {
|
public ResponseEntity connectOrder(@PathVariable Long id, @RequestParam(value = "orderId") Long orderId) {
|
||||||
return Optional.of(paymentService.getPayment(id)
|
return Optional.of(paymentService.get(id)
|
||||||
.connectOrder(orderId))
|
.connectOrder(orderId))
|
||||||
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
@@ -97,7 +97,7 @@ public class PaymentController {
|
|||||||
|
|
||||||
@RequestMapping(path = "/payments/{id}/commands/processPayment")
|
@RequestMapping(path = "/payments/{id}/commands/processPayment")
|
||||||
public ResponseEntity processPayment(@PathVariable Long id) {
|
public ResponseEntity processPayment(@PathVariable Long id) {
|
||||||
return Optional.of(paymentService.getPayment(id)
|
return Optional.of(paymentService.get(id)
|
||||||
.processPayment())
|
.processPayment())
|
||||||
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
|
||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
@@ -111,7 +111,7 @@ public class PaymentController {
|
|||||||
*/
|
*/
|
||||||
private Resource<Payment> getPaymentResource(Long id) {
|
private Resource<Payment> getPaymentResource(Long id) {
|
||||||
// Get the payment for the provided id
|
// Get the payment for the provided id
|
||||||
Payment payment = paymentService.getPayment(id);
|
Payment payment = paymentService.get(id);
|
||||||
|
|
||||||
return getPaymentResource(payment);
|
return getPaymentResource(payment);
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,9 @@ public class PaymentController {
|
|||||||
// Create the new payment
|
// Create the new payment
|
||||||
payment = paymentService.registerPayment(payment);
|
payment = paymentService.registerPayment(payment);
|
||||||
|
|
||||||
return getPaymentResource(payment);
|
payment.getLinks().clear();
|
||||||
|
|
||||||
|
return new Resource<>(payment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +141,8 @@ public class PaymentController {
|
|||||||
* @return a hypermedia resource for the updated {@link Payment}
|
* @return a hypermedia resource for the updated {@link Payment}
|
||||||
*/
|
*/
|
||||||
private Resource<Payment> updatePaymentResource(Long id, Payment payment) {
|
private Resource<Payment> updatePaymentResource(Long id, Payment payment) {
|
||||||
return getPaymentResource(paymentService.updatePayment(id, payment));
|
payment.setIdentity(id);
|
||||||
|
return getPaymentResource(paymentService.update(payment));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,7 +156,7 @@ public class PaymentController {
|
|||||||
private Resource<PaymentEvent> appendEventResource(Long paymentId, PaymentEvent event) {
|
private Resource<PaymentEvent> appendEventResource(Long paymentId, PaymentEvent event) {
|
||||||
Resource<PaymentEvent> eventResource = null;
|
Resource<PaymentEvent> eventResource = null;
|
||||||
|
|
||||||
event = paymentService.appendEvent(paymentId, event);
|
event = paymentService.get(paymentId).sendEvent(event);
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
eventResource = new Resource<>(event,
|
eventResource = new Resource<>(event,
|
||||||
@@ -196,11 +199,15 @@ public class PaymentController {
|
|||||||
private Resource<Payment> getPaymentResource(Payment payment) {
|
private Resource<Payment> getPaymentResource(Payment payment) {
|
||||||
Assert.notNull(payment, "Payment must not be null");
|
Assert.notNull(payment, "Payment must not be null");
|
||||||
|
|
||||||
// Add command link
|
if(!payment.hasLink("commands")) {
|
||||||
payment.add(linkBuilder("getCommands", payment.getIdentity()).withRel("commands"));
|
// Add command link
|
||||||
|
payment.add(linkBuilder("getCommands", payment.getIdentity()).withRel("commands"));
|
||||||
|
}
|
||||||
|
|
||||||
// Add get events link
|
if(!payment.hasLink("events")) {
|
||||||
payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
|
// Add get events link
|
||||||
|
payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
|
||||||
|
}
|
||||||
|
|
||||||
return new Resource<>(payment);
|
return new Resource<>(payment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class PaymentControllerTest {
|
|||||||
|
|
||||||
Payment payment = new Payment(42.0, PaymentMethod.CREDIT_CARD);
|
Payment payment = new Payment(42.0, PaymentMethod.CREDIT_CARD);
|
||||||
|
|
||||||
given(this.paymentService.getPayment(1L)).willReturn(payment);
|
given(this.paymentService.get(1L)).willReturn(payment);
|
||||||
given(this.eventService.find(1L)).willReturn(new Events<>(1L, Collections
|
given(this.eventService.find(1L)).willReturn(new Events<>(1L, Collections
|
||||||
.singletonList(new PaymentEvent(PaymentEventType
|
.singletonList(new PaymentEvent(PaymentEventType
|
||||||
.PAYMENT_CREATED))));
|
.PAYMENT_CREATED))));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class PaymentServiceTests {
|
|||||||
|
|
||||||
given(this.paymentRepository.findOne(1L)).willReturn(expected);
|
given(this.paymentRepository.findOne(1L)).willReturn(expected);
|
||||||
|
|
||||||
Payment actual = paymentService.getPayment(1L);
|
Payment actual = paymentService.get(1L);
|
||||||
|
|
||||||
assertThat(actual).isNotNull();
|
assertThat(actual).isNotNull();
|
||||||
assertThat(actual.getPaymentMethod()).isEqualTo(PaymentMethod.CREDIT_CARD);
|
assertThat(actual.getPaymentMethod()).isEqualTo(PaymentMethod.CREDIT_CARD);
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package demo.domain;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import demo.event.Event;
|
||||||
|
import demo.event.EventService;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.hateoas.*;
|
import org.springframework.hateoas.*;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -24,7 +27,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|||||||
*
|
*
|
||||||
* @author Kenny Bastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
public abstract class Aggregate<ID extends Serializable> extends ResourceSupport implements Value<Link> {
|
public abstract class Aggregate<E extends Event, ID extends Serializable> extends ResourceSupport implements
|
||||||
|
Value<Link> {
|
||||||
|
|
||||||
@JsonProperty("id")
|
@JsonProperty("id")
|
||||||
abstract ID getIdentity();
|
abstract ID getIdentity();
|
||||||
@@ -39,6 +43,7 @@ public abstract class Aggregate<ID extends Serializable> extends ResourceSupport
|
|||||||
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@JsonIgnore
|
||||||
protected <T extends Action<A>, A extends Aggregate> T getAction(
|
protected <T extends Action<A>, A extends Aggregate> T getAction(
|
||||||
Class<T> actionType) throws IllegalArgumentException {
|
Class<T> actionType) throws IllegalArgumentException {
|
||||||
Provider provider = getProvider();
|
Provider provider = getProvider();
|
||||||
@@ -53,7 +58,8 @@ public abstract class Aggregate<ID extends Serializable> extends ResourceSupport
|
|||||||
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected <T extends Provider<A>, A extends Aggregate> T getProvider() throws IllegalArgumentException {
|
@JsonIgnore
|
||||||
|
public <T extends Provider<A>, A extends Aggregate<E, ID>> T getProvider() throws IllegalArgumentException {
|
||||||
return getProvider((Class<T>) ResolvableType
|
return getProvider((Class<T>) ResolvableType
|
||||||
.forClassWithGenerics(Provider.class, ResolvableType.forInstance(this))
|
.forClassWithGenerics(Provider.class, ResolvableType.forInstance(this))
|
||||||
.getRawClass());
|
.getRawClass());
|
||||||
@@ -65,12 +71,50 @@ public abstract class Aggregate<ID extends Serializable> extends ResourceSupport
|
|||||||
* @return an instance of the requested {@link Provider}
|
* @return an instance of the requested {@link Provider}
|
||||||
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
|
||||||
*/
|
*/
|
||||||
protected <T extends Provider<A>, A extends Aggregate> T getProvider(
|
@JsonIgnore
|
||||||
Class<T> providerType) throws IllegalArgumentException {
|
public <T extends Provider<A>, A extends Aggregate<E, ID>> T getProvider(Class<T> providerType) throws
|
||||||
|
IllegalArgumentException {
|
||||||
Assert.notNull(applicationContext, "The application context is unavailable");
|
Assert.notNull(applicationContext, "The application context is unavailable");
|
||||||
T provider = applicationContext.getBean(providerType);
|
T provider = applicationContext.getBean(providerType);
|
||||||
Assert.notNull(provider, "The requested provider is not registered in the application context");
|
Assert.notNull(provider, "The requested provider is not registered in the application context");
|
||||||
return provider;
|
return (T) provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public abstract List<E> getEvents();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a new {@link Event} to the {@link Aggregate} reference for the supplied identifier.
|
||||||
|
*
|
||||||
|
* @param event is the {@link Event} to append to the {@link Aggregate} entity
|
||||||
|
* @return the newly appended {@link Event}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public E sendEvent(E event, Link... links) {
|
||||||
|
EventService<E, ID> eventService = getEventService();
|
||||||
|
event = eventService.send(appendEvent(event), links);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a new {@link Event} to the {@link Aggregate} reference for the supplied identifier.
|
||||||
|
*
|
||||||
|
* @param event is the {@link Event} to append to the {@link Aggregate} entity
|
||||||
|
* @return the newly appended {@link Event}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean sendAsyncEvent(E event, Link... links) {
|
||||||
|
return getEventService().sendAsync(appendEvent(event), links);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public E appendEvent(E event) {
|
||||||
|
event.setEntity(this);
|
||||||
|
getEventService().save(event);
|
||||||
|
getEvents().add(event);
|
||||||
|
getEntityService().update(this);
|
||||||
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,13 +123,13 @@ public abstract class Aggregate<ID extends Serializable> extends ResourceSupport
|
|||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
links.add(getId());
|
if(!super.hasLink("self"))
|
||||||
|
links.add(getId());
|
||||||
|
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public CommandResources getCommands() {
|
public CommandResources getCommands() {
|
||||||
CommandResources commandResources = new CommandResources();
|
CommandResources commandResources = new CommandResources();
|
||||||
|
|
||||||
@@ -120,6 +164,18 @@ public abstract class Aggregate<ID extends Serializable> extends ResourceSupport
|
|||||||
return commandResources;
|
return commandResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <A extends Aggregate> Service<A, ID> getEntityService() {
|
||||||
|
return (Service<A, ID>) getProvider().getDefaultService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private EventService<E, ID> getEventService() {
|
||||||
|
return (EventService<E, ID>) getProvider().getDefaultEventService();
|
||||||
|
}
|
||||||
|
|
||||||
public static class CommandResources extends ResourceSupport {
|
public static class CommandResources extends ResourceSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.domain;
|
package demo.domain;
|
||||||
|
|
||||||
|
import demo.event.EventService;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
@@ -24,5 +25,7 @@ public abstract class Provider<T extends Aggregate> implements ApplicationContex
|
|||||||
Provider.applicationContext = applicationContext;
|
Provider.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Service<? extends T> getDefaultService();
|
public abstract Service<?, ?> getDefaultService();
|
||||||
|
|
||||||
|
public abstract EventService<?, ?> getDefaultEventService();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import org.springframework.beans.BeansException;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Service} is a functional unit that provides a need. Services are immutable and often stateless. Services
|
* A {@link Service} is a functional unit that provides a need. Services are immutable and often stateless. Services
|
||||||
* always consume or produce {@link Commodity} objects. Services are addressable and discoverable by other services.
|
* always consume or produce {@link Commodity} objects. Services are addressable and discoverable by other services.
|
||||||
@@ -11,7 +13,7 @@ import org.springframework.context.ApplicationContextAware;
|
|||||||
* @author Kenny Bastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
@org.springframework.stereotype.Service
|
@org.springframework.stereotype.Service
|
||||||
public abstract class Service<T extends Aggregate> implements ApplicationContextAware {
|
public abstract class Service<T extends Aggregate, ID extends Serializable> implements ApplicationContextAware {
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -19,6 +21,11 @@ public abstract class Service<T extends Aggregate> implements ApplicationContext
|
|||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract T get(ID id);
|
||||||
|
public abstract T create(T entity);
|
||||||
|
public abstract T update(T entity);
|
||||||
|
public abstract boolean delete(ID id);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <A extends Action<T>> A getAction(Class<? extends A> clazz) {
|
public <A extends Action<T>> A getAction(Class<? extends A> clazz) {
|
||||||
return applicationContext.getBean(clazz);
|
return applicationContext.getBean(clazz);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.event;
|
package demo.event;
|
||||||
|
|
||||||
|
import demo.domain.Aggregate;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
import org.springframework.hateoas.ResourceSupport;
|
||||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||||
@@ -21,7 +22,7 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
|||||||
* @see org.springframework.stereotype.Repository
|
* @see org.springframework.stereotype.Repository
|
||||||
* @see ResourceSupport
|
* @see ResourceSupport
|
||||||
*/
|
*/
|
||||||
public abstract class Event<T extends ResourceSupport, E, ID extends Serializable> extends ResourceSupport {
|
public abstract class Event<T extends Aggregate, E, ID extends Serializable> extends ResourceSupport {
|
||||||
|
|
||||||
public Event() {
|
public Event() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package demo.event;
|
package demo.event;
|
||||||
|
|
||||||
|
import demo.domain.Aggregate;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@@ -19,18 +19,14 @@ public interface EventService<T extends Event, ID extends Serializable> {
|
|||||||
* Raises a synchronous domain event. An {@link Event} will be applied to an entity through a chain of HTTP
|
* Raises a synchronous domain event. An {@link Event} will be applied to an entity through a chain of HTTP
|
||||||
* requests/responses.
|
* requests/responses.
|
||||||
*
|
*
|
||||||
* @param event
|
|
||||||
* @param links
|
|
||||||
* @return the applied {@link Event}
|
* @return the applied {@link Event}
|
||||||
*/
|
*/
|
||||||
<E extends ResourceSupport, S extends T> S send(S event, Link... links);
|
<E extends Aggregate, S extends T> S send(S event, Link... links);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raises an asynchronous domain event. An {@link Event} will be applied to an entity through a chain of AMQP
|
* Raises an asynchronous domain event. An {@link Event} will be applied to an entity through a chain of AMQP
|
||||||
* messages.
|
* messages.
|
||||||
*
|
*
|
||||||
* @param event
|
|
||||||
* @param links
|
|
||||||
* @return a flag indicating if the {@link Event} message was sent successfully
|
* @return a flag indicating if the {@link Event} message was sent successfully
|
||||||
*/
|
*/
|
||||||
<S extends T> Boolean sendAsync(S event, Link... links);
|
<S extends T> Boolean sendAsync(S event, Link... links);
|
||||||
@@ -39,7 +35,6 @@ public interface EventService<T extends Event, ID extends Serializable> {
|
|||||||
* Saves a given event entity. Use the returned instance for further operations as the save operation might have
|
* Saves a given event entity. Use the returned instance for further operations as the save operation might have
|
||||||
* changed the entity instance completely.
|
* changed the entity instance completely.
|
||||||
*
|
*
|
||||||
* @param event
|
|
||||||
* @return the saved event entity
|
* @return the saved event entity
|
||||||
*/
|
*/
|
||||||
<S extends T> S save(S event);
|
<S extends T> S save(S event);
|
||||||
@@ -48,8 +43,6 @@ public interface EventService<T extends Event, ID extends Serializable> {
|
|||||||
* Saves a given event entity. Use the returned instance for further operations as the save operation might have
|
* Saves a given event entity. Use the returned instance for further operations as the save operation might have
|
||||||
* changed the entity instance completely. The {@link ID} parameter is the unique {@link Event} identifier.
|
* changed the entity instance completely. The {@link ID} parameter is the unique {@link Event} identifier.
|
||||||
*
|
*
|
||||||
* @param id
|
|
||||||
* @param event
|
|
||||||
* @return the saved event entity
|
* @return the saved event entity
|
||||||
*/
|
*/
|
||||||
<S extends T> S save(ID id, S event);
|
<S extends T> S save(ID id, S event);
|
||||||
@@ -57,7 +50,6 @@ public interface EventService<T extends Event, ID extends Serializable> {
|
|||||||
/**
|
/**
|
||||||
* Retrieves an {@link Event} entity by its id.
|
* Retrieves an {@link Event} entity by its id.
|
||||||
*
|
*
|
||||||
* @param id
|
|
||||||
* @return the {@link Event} entity with the given id or {@literal null} if none found
|
* @return the {@link Event} entity with the given id or {@literal null} if none found
|
||||||
*/
|
*/
|
||||||
<EID extends ID> T findOne(EID id);
|
<EID extends ID> T findOne(EID id);
|
||||||
@@ -65,7 +57,6 @@ public interface EventService<T extends Event, ID extends Serializable> {
|
|||||||
/**
|
/**
|
||||||
* Retrieves an entity's {@link Event}s by its id.
|
* Retrieves an entity's {@link Event}s by its id.
|
||||||
*
|
*
|
||||||
* @param entityId
|
|
||||||
* @return a {@link Events} containing a collection of {@link Event}s
|
* @return a {@link Events} containing a collection of {@link Event}s
|
||||||
*/
|
*/
|
||||||
<E extends Events> E find(ID entityId);
|
<E extends Events> E find(ID entityId);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package demo.event;
|
package demo.event;
|
||||||
|
|
||||||
|
import demo.domain.Aggregate;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.cloud.stream.messaging.Source;
|
import org.springframework.cloud.stream.messaging.Source;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
import org.springframework.hateoas.MediaTypes;
|
import org.springframework.hateoas.MediaTypes;
|
||||||
import org.springframework.hateoas.Resource;
|
import org.springframework.hateoas.Resource;
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.integration.support.MessageBuilder;
|
import org.springframework.integration.support.MessageBuilder;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
@@ -38,14 +39,18 @@ class EventServiceImpl<T extends Event, ID extends Serializable> implements Even
|
|||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <E extends ResourceSupport, S extends T> S send(S event, Link... links) {
|
public <E extends Aggregate, S extends T> S send(S event, Link... links) {
|
||||||
// Assemble request to the event stream processor
|
// Assemble request to the event stream processor
|
||||||
RequestEntity<Resource<T>> requestEntity = RequestEntity.post(URI.create(EVENT_PROCESSOR_URL))
|
RequestEntity<Resource<T>> requestEntity = RequestEntity.post(URI.create(EVENT_PROCESSOR_URL))
|
||||||
.contentType(MediaTypes.HAL_JSON).body(new Resource<T>(event), Resource.class);
|
.contentType(MediaTypes.HAL_JSON)
|
||||||
|
.body(new Resource<T>(event), Resource.class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Send the event to the event stream processor
|
// Send the event to the event stream processor
|
||||||
E entity = (E) restTemplate.exchange(requestEntity, event.getEntity().getClass()).getBody();
|
E entity = (E) restTemplate.exchange(requestEntity, event.getEntity()
|
||||||
|
.getClass())
|
||||||
|
.getBody();
|
||||||
|
|
||||||
// Set the applied entity reference to the event
|
// Set the applied entity reference to the event
|
||||||
event.setEntity(entity);
|
event.setEntity(entity);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -56,7 +61,10 @@ class EventServiceImpl<T extends Event, ID extends Serializable> implements Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <S extends T> Boolean sendAsync(S event, Link... links) {
|
public <S extends T> Boolean sendAsync(S event, Link... links) {
|
||||||
return eventStream.output().send(MessageBuilder.withPayload(event).build());
|
return eventStream.output()
|
||||||
|
.send(MessageBuilder.withPayload(event)
|
||||||
|
.setHeader("contentType", MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public <S extends T> S save(S event) {
|
public <S extends T> S save(S event) {
|
||||||
@@ -75,6 +83,7 @@ class EventServiceImpl<T extends Event, ID extends Serializable> implements Even
|
|||||||
|
|
||||||
public <E extends Events> E find(ID entityId) {
|
public <E extends Events> E find(ID entityId) {
|
||||||
return (E) new Events(entityId, eventRepository.findEventsByEntityId(entityId,
|
return (E) new Events(entityId, eventRepository.findEventsByEntityId(entityId,
|
||||||
new PageRequest(0, Integer.MAX_VALUE)).getContent());
|
new PageRequest(0, Integer.MAX_VALUE))
|
||||||
|
.getContent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package demo.event;
|
package demo.event;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import demo.domain.Aggregate;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
|
||||||
import org.springframework.hateoas.Resources;
|
import org.springframework.hateoas.Resources;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Kenny Bastani
|
* @author Kenny Bastani
|
||||||
*/
|
*/
|
||||||
public class Events<T extends ResourceSupport, E, ID extends Serializable> extends Resources<Event<T, E, ID>> {
|
public class Events<T extends Aggregate, E, ID extends Serializable> extends Resources<Event<T, E, ID>> {
|
||||||
|
|
||||||
private ID entityId;
|
private ID entityId;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package demo.domain;
|
||||||
|
|
||||||
|
import demo.event.Event;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class EmptyEvent extends Event {
|
||||||
|
@Override
|
||||||
|
public Serializable getEventId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEventId(Serializable eventId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setType(Object type) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Aggregate getEntity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEntity(Aggregate entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getCreatedAt() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreatedAt(Long createdAt) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLastModified() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastModified(Long lastModified) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.domain;
|
package demo.domain;
|
||||||
|
|
||||||
|
import demo.event.EventService;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -14,6 +15,7 @@ import org.springframework.test.context.junit4.SpringRunner;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
@@ -97,7 +99,7 @@ public class ProviderTests {
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public static class EmptyAggregate extends Aggregate<Long> {
|
public static class EmptyAggregate extends Aggregate<EmptyEvent, Long> {
|
||||||
@NonNull
|
@NonNull
|
||||||
private Long id;
|
private Long id;
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -121,6 +123,11 @@ public class ProviderTests {
|
|||||||
Long getIdentity() {
|
Long getIdentity() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EmptyEvent> getEvents() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -128,15 +135,41 @@ public class ProviderTests {
|
|||||||
public static class EmptyProvider extends Provider<EmptyAggregate> {
|
public static class EmptyProvider extends Provider<EmptyAggregate> {
|
||||||
private final EmptyService emptyService;
|
private final EmptyService emptyService;
|
||||||
|
|
||||||
public Service<? extends EmptyAggregate> getDefaultService() {
|
public Service<? extends EmptyAggregate, Long> getDefaultService() {
|
||||||
return emptyService;
|
return emptyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventService<?, ?> getDefaultEventService() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmptyService extends Service<EmptyAggregate> {
|
public static class EmptyService extends Service<EmptyAggregate, Long> {
|
||||||
public EmptyAggregate getEmptyAggregate(Long id) {
|
public EmptyAggregate getEmptyAggregate(Long id) {
|
||||||
return new EmptyAggregate(id, AggregateStatus.CREATED);
|
return new EmptyAggregate(id, AggregateStatus.CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmptyAggregate get(Long aLong) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmptyAggregate create(EmptyAggregate entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmptyAggregate update(EmptyAggregate entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(Long aLong) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmptyAction extends Action<EmptyAggregate> {
|
public static class EmptyAction extends Action<EmptyAggregate> {
|
||||||
|
|||||||
Reference in New Issue
Block a user