diff --git a/order/order-web/pom.xml b/order/order-web/pom.xml
index f169d55..b2d6d44 100644
--- a/order/order-web/pom.xml
+++ b/order/order-web/pom.xml
@@ -51,6 +51,11 @@
org.springframework.bootspring-boot-starter-integration
+
+ org.kbastani
+ spring-boot-starter-data-events
+ 1.0-SNAPSHOT
+ com.h2database
diff --git a/order/order-web/src/main/java/demo/OrderServiceApplication.java b/order/order-web/src/main/java/demo/OrderServiceApplication.java
index bae1da9..e1754ab 100644
--- a/order/order-web/src/main/java/demo/OrderServiceApplication.java
+++ b/order/order-web/src/main/java/demo/OrderServiceApplication.java
@@ -2,11 +2,13 @@ package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.hateoas.config.EnableHypermediaSupport;
@SpringBootApplication
+@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class OrderServiceApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderServiceApplication.class, args);
- }
+ public static void main(String[] args) {
+ SpringApplication.run(OrderServiceApplication.class, args);
+ }
}
diff --git a/order/order-web/src/main/java/demo/domain/BaseEntity.java b/order/order-web/src/main/java/demo/domain/AbstractEntity.java
similarity index 57%
rename from order/order-web/src/main/java/demo/domain/BaseEntity.java
rename to order/order-web/src/main/java/demo/domain/AbstractEntity.java
index fb472ef..ce9652c 100644
--- a/order/order-web/src/main/java/demo/domain/BaseEntity.java
+++ b/order/order-web/src/main/java/demo/domain/AbstractEntity.java
@@ -1,17 +1,20 @@
package demo.domain;
+import demo.event.Event;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-import org.springframework.hateoas.ResourceSupport;
-import javax.persistence.EntityListeners;
-import javax.persistence.MappedSuperclass;
+import javax.persistence.*;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
-public class BaseEntity extends ResourceSupport implements Serializable {
+public abstract class AbstractEntity extends Aggregate implements Serializable {
+
+ private T identity;
@CreatedDate
private Long createdAt;
@@ -19,7 +22,10 @@ public class BaseEntity extends ResourceSupport implements Serializable {
@LastModifiedDate
private Long lastModified;
- public BaseEntity() {
+ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ private List events = new ArrayList<>();
+
+ public AbstractEntity() {
}
public Long getCreatedAt() {
@@ -38,6 +44,24 @@ public class BaseEntity extends ResourceSupport implements Serializable {
this.lastModified = lastModified;
}
+ @Override
+ public List getEvents() {
+ return events;
+ }
+
+ public void setEvents(List events) {
+ this.events = events;
+ }
+
+ @Override
+ public T getIdentity() {
+ return identity;
+ }
+
+ public void setIdentity(T id) {
+ this.identity = id;
+ }
+
@Override
public String toString() {
return "BaseEntity{" +
diff --git a/order/order-web/src/main/java/demo/event/ConsistencyModel.java b/order/order-web/src/main/java/demo/event/ConsistencyModel.java
deleted file mode 100644
index 8bef081..0000000
--- a/order/order-web/src/main/java/demo/event/ConsistencyModel.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package demo.event;
-
-public enum ConsistencyModel {
- BASE,
- ACID
-}
diff --git a/order/order-web/src/main/java/demo/event/EventController.java b/order/order-web/src/main/java/demo/event/EventController.java
deleted file mode 100644
index ffcd8da..0000000
--- a/order/order-web/src/main/java/demo/event/EventController.java
+++ /dev/null
@@ -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));
- }
-}
diff --git a/order/order-web/src/main/java/demo/event/EventRepository.java b/order/order-web/src/main/java/demo/event/EventRepository.java
deleted file mode 100644
index a17083d..0000000
--- a/order/order-web/src/main/java/demo/event/EventRepository.java
+++ /dev/null
@@ -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 {
- Page findOrderEventsByOrderId(@Param("orderId") Long orderId, Pageable pageable);
-}
diff --git a/order/order-web/src/main/java/demo/event/EventService.java b/order/order-web/src/main/java/demo/event/EventService.java
deleted file mode 100644
index 09f0e92..0000000
--- a/order/order-web/src/main/java/demo/event/EventService.java
+++ /dev/null
@@ -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.
- *
- * 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.
- *
- * 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> 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 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 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 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(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;
- }
-}
diff --git a/order/order-web/src/main/java/demo/event/OrderEvent.java b/order/order-web/src/main/java/demo/event/OrderEvent.java
index c494449..d2ae60e 100644
--- a/order/order-web/src/main/java/demo/event/OrderEvent.java
+++ b/order/order-web/src/main/java/demo/event/OrderEvent.java
@@ -1,34 +1,41 @@
package demo.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
-import demo.domain.BaseEntity;
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.*;
/**
- * The domain event {@link OrderEvent} tracks the type and state of events as
- * applied to the {@link Order} domain object. This event resource can be used
- * to event source the aggregate state of {@link Order}.
+ * The domain event {@link OrderEvent} tracks the type and state of events as applied to the {@link Order} domain
+ * object. This event resource can be used to event source the aggregate state of {@link Order}.
*
- * This event resource also provides a transaction log that can be used to append
- * actions to the event.
+ * This event resource also provides a transaction log that can be used to append actions to the event.
*
- * @author kbastani
+ * @author Kenny Bastani
*/
@Entity
-public class OrderEvent extends BaseEntity {
+@EntityListeners(AuditingEntityListener.class)
+public class OrderEvent extends Event {
@Id
- @GeneratedValue
- private Long id;
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long eventId;
@Enumerated(EnumType.STRING)
private OrderEventType type;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
- private Order order;
+ private Order entity;
+
+ @CreatedDate
+ private Long createdAt;
+
+ @LastModifiedDate
+ private Long lastModified;
public OrderEvent() {
}
@@ -37,37 +44,69 @@ public class OrderEvent extends BaseEntity {
this.type = type;
}
- @JsonIgnore
+ public OrderEvent(OrderEventType type, Order entity) {
+ this.type = type;
+ this.entity = entity;
+ }
+
+ @Override
public Long getEventId() {
- return id;
+ return eventId;
}
+ @Override
public void setEventId(Long id) {
- this.id = id;
+ eventId = id;
}
+ @Override
public OrderEventType getType() {
return type;
}
+ @Override
public void setType(OrderEventType type) {
this.type = type;
}
- public Order getOrder() {
- return order;
+ @Override
+ public Order getEntity() {
+ return entity;
}
- public void setOrder(Order order) {
- this.order = order;
+ @Override
+ 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
public String toString() {
return "OrderEvent{" +
- "id=" + id +
+ "eventId=" + eventId +
", type=" + type +
- ", order=" + order +
+ ", entity=" + entity +
+ ", createdAt=" + createdAt +
+ ", lastModified=" + lastModified +
"} " + super.toString();
}
}
diff --git a/order/order-web/src/main/java/demo/event/OrderEventRepository.java b/order/order-web/src/main/java/demo/event/OrderEventRepository.java
new file mode 100644
index 0000000..7b0ac30
--- /dev/null
+++ b/order/order-web/src/main/java/demo/event/OrderEventRepository.java
@@ -0,0 +1,4 @@
+package demo.event;
+
+public interface OrderEventRepository extends EventRepository {
+}
diff --git a/order/order-web/src/main/java/demo/event/OrderEventType.java b/order/order-web/src/main/java/demo/event/OrderEventType.java
index 38e79b5..23a028c 100644
--- a/order/order-web/src/main/java/demo/event/OrderEventType.java
+++ b/order/order-web/src/main/java/demo/event/OrderEventType.java
@@ -4,10 +4,10 @@ import demo.order.Order;
import demo.order.OrderStatus;
/**
- * The {@link OrderEventType} represents a collection of possible events that describe
- * state transitions of {@link OrderStatus} on the {@link Order} aggregate.
+ * The {@link OrderEventType} represents a collection of possible events that describe state transitions of
+ * {@link OrderStatus} on the {@link Order} aggregate.
*
- * @author kbastani
+ * @author Kenny Bastani
*/
public enum OrderEventType {
ORDER_CREATED,
diff --git a/order/order-web/src/main/java/demo/event/OrderEvents.java b/order/order-web/src/main/java/demo/event/OrderEvents.java
deleted file mode 100644
index 8eb0356..0000000
--- a/order/order-web/src/main/java/demo/event/OrderEvents.java
+++ /dev/null
@@ -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 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 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 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;
- }
-}
diff --git a/order/order-web/src/main/java/demo/order/LineItem.java b/order/order-web/src/main/java/demo/order/LineItem.java
index 2d3f2d0..58d22c9 100644
--- a/order/order-web/src/main/java/demo/order/LineItem.java
+++ b/order/order-web/src/main/java/demo/order/LineItem.java
@@ -1,15 +1,15 @@
package demo.order;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import demo.domain.Value;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
-import java.io.Serializable;
@Entity
-public class LineItem implements Serializable {
+public class LineItem implements Value {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
diff --git a/order/order-web/src/main/java/demo/order/Order.java b/order/order-web/src/main/java/demo/order/Order.java
index c5398c8..410a2d1 100644
--- a/order/order-web/src/main/java/demo/order/Order.java
+++ b/order/order-web/src/main/java/demo/order/Order.java
@@ -1,26 +1,26 @@
package demo.order;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
import demo.address.Address;
import demo.address.AddressType;
-import demo.domain.BaseEntity;
+import demo.domain.AbstractEntity;
+import demo.domain.Command;
import demo.event.OrderEvent;
+import demo.order.action.*;
+import demo.order.controller.OrderController;
+import org.springframework.hateoas.Link;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
+
@Entity(name = "orders")
-public class Order extends BaseEntity {
-
+public class Order extends AbstractEntity {
@Id
- @GeneratedValue(strategy = GenerationType.AUTO)
+ @GeneratedValue
private Long id;
- private Long accountId;
- private Long paymentId;
-
- @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- private Set events = new HashSet<>();
@Enumerated(value = EnumType.STRING)
private OrderStatus status;
@@ -31,6 +31,8 @@ public class Order extends BaseEntity {
@OneToOne(cascade = CascadeType.ALL)
private Address shippingAddress;
+ private Long accountId, paymentId;
+
public Order() {
this.status = OrderStatus.ORDER_CREATED;
}
@@ -43,33 +45,17 @@ public class Order extends BaseEntity {
this.shippingAddress.setAddressType(AddressType.SHIPPING);
}
- @JsonIgnore
- public Long getOrderId() {
- return id;
+ @JsonProperty("orderId")
+ @Override
+ public Long getIdentity() {
+ return this.id;
}
- public void setOrderId(Long id) {
+ @Override
+ public void setIdentity(Long 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() {
return status;
}
@@ -78,15 +64,6 @@ public class Order extends BaseEntity {
this.status = status;
}
- @JsonIgnore
- public Set getEvents() {
- return events;
- }
-
- public void setEvents(Set events) {
- this.events = events;
- }
-
public Set getLineItems() {
return lineItems;
}
@@ -103,19 +80,70 @@ public class Order extends BaseEntity {
this.shippingAddress = shippingAddress;
}
- public void addLineItem(LineItem lineItem) {
- lineItems.add(lineItem);
+ public Long getAccountId() {
+ 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
- public String toString() {
- return "Order{" +
- "id=" + id +
- ", accountId=" + accountId +
- ", paymentId=" + paymentId +
- ", status=" + status +
- ", lineItems=" + lineItems +
- ", shippingAddress=" + shippingAddress +
- "} " + super.toString();
+ public Link getId() {
+ return linkTo(OrderController.class)
+ .slash("orders")
+ .slash(getIdentity())
+ .withSelfRel();
}
}
diff --git a/order/order-web/src/main/java/demo/order/OrderCommand.java b/order/order-web/src/main/java/demo/order/OrderCommand.java
deleted file mode 100644
index 17ea5ce..0000000
--- a/order/order-web/src/main/java/demo/order/OrderCommand.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package demo.order;
-
-public enum OrderCommand {
- CONNECT_ACCOUNT,
- RESERVE_INVENTORY,
- CREATE_PAYMENT,
- CONNECT_PAYMENT,
- PROCESS_PAYMENT
-}
diff --git a/order/order-web/src/main/java/demo/order/OrderCommands.java b/order/order-web/src/main/java/demo/order/OrderCommands.java
deleted file mode 100644
index c491ac1..0000000
--- a/order/order-web/src/main/java/demo/order/OrderCommands.java
+++ /dev/null
@@ -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 {
-}
diff --git a/order/order-web/src/main/java/demo/order/OrderProvider.java b/order/order-web/src/main/java/demo/order/OrderProvider.java
new file mode 100644
index 0000000..1c5b1aa
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/OrderProvider.java
@@ -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 {
+
+ private final OrderService orderService;
+ private final EventService eventService;
+
+ public OrderProvider(OrderService orderService, EventService eventService) {
+ this.orderService = orderService;
+ this.eventService = eventService;
+ }
+
+ public OrderService getOrderService() {
+ return orderService;
+ }
+
+ public EventService getEventService() {
+ return eventService;
+ }
+
+ @Override
+ public OrderService getDefaultService() {
+ return orderService;
+ }
+
+ @Override
+ public EventService getDefaultEventService() {
+ return eventService;
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/OrderService.java b/order/order-web/src/main/java/demo/order/OrderService.java
index 551fc76..b931bfa 100644
--- a/order/order-web/src/main/java/demo/order/OrderService.java
+++ b/order/order-web/src/main/java/demo/order/OrderService.java
@@ -1,60 +1,27 @@
package demo.order;
-import demo.event.ConsistencyModel;
-import demo.event.EventService;
+import demo.domain.Service;
import demo.event.OrderEvent;
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.web.client.RestTemplate;
-import java.net.URI;
-import java.util.Objects;
-import java.util.Optional;
-
-@Service
-@CacheConfig(cacheNames = {"orders"})
-public class OrderService {
-
- private final Logger log = Logger.getLogger(OrderService.class);
+@org.springframework.stereotype.Service
+public class OrderService extends Service {
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.eventService = eventService;
- this.restTemplate = restTemplate;
}
- @CacheEvict(cacheNames = "orders", key = "#order.getOrderId().toString()")
public Order registerOrder(Order order) {
- order = createOrder(order);
-
- //cacheManager.getCache("orders").evict(order.getOrderId());
+ order = create(order);
// Trigger the order creation event
- OrderEvent event = appendEvent(order.getOrderId(),
- new OrderEvent(OrderEventType.ORDER_CREATED));
+ order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_CREATED, order));
- // Attach order identifier
- event.getOrder().setOrderId(order.getOrderId());
-
- // Return the result
- return event.getOrder();
+ return order;
}
/**
@@ -63,8 +30,7 @@ public class OrderService {
* @param order is the {@link Order} to create
* @return the newly created {@link Order}
*/
- @CacheEvict(cacheNames = "orders", key = "#order.getOrderId().toString()")
- public Order createOrder(Order order) {
+ public Order create(Order order) {
// Save the order to the repository
order = orderRepository.saveAndFlush(order);
@@ -78,34 +44,24 @@ public class OrderService {
* @param id is the unique identifier of a {@link Order} entity
* @return an {@link Order} entity
*/
- @Cacheable(cacheNames = "orders", key = "#id.toString()")
- public Order getOrder(Long id) {
+ public Order get(Long id) {
return orderRepository.findOne(id);
}
/**
* 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
* @return the updated {@link Order} entity
*/
- @CachePut(cacheNames = "orders", key = "#id.toString()")
- public Order updateOrder(Long id, Order order) {
- Assert.notNull(id, "Order id must be present in the resource URL");
+ public Order update(Order order) {
+ Assert.notNull(order.getIdentity(), "Order id must be present in the resource URL");
Assert.notNull(order, "Order request body cannot be null");
- if (order.getOrderId() != null) {
- 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),
+ Assert.state(orderRepository.exists(order.getIdentity()),
"The order with the supplied id does not exist");
- Order currentOrder = orderRepository.findOne(id);
+ Order currentOrder = get(order.getIdentity());
currentOrder.setAccountId(order.getAccountId());
currentOrder.setPaymentId(order.getPaymentId());
currentOrder.setLineItems(order.getLineItems());
@@ -120,174 +76,10 @@ public class OrderService {
*
* @param id is the unique identifier for the {@link Order}
*/
- @CacheEvict(cacheNames = "orders", key = "#id.toString()")
- public Boolean deleteOrder(Long id) {
+ public boolean delete(Long id) {
Assert.state(orderRepository.exists(id),
"The order with the supplied id does not exist");
this.orderRepository.delete(id);
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> requestEntity = RequestEntity.post(
- URI.create("http://localhost:8082/v1/payments"))
- .contentType(MediaTypes.HAL_JSON)
- .body(new Resource(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();
- }
}
diff --git a/order/order-web/src/main/java/demo/order/action/ConnectAccount.java b/order/order-web/src/main/java/demo/order/action/ConnectAccount.java
new file mode 100644
index 0000000..a504e6e
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/action/ConnectAccount.java
@@ -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 {
+
+ public BiConsumer 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));
+ };
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/action/ConnectPayment.java b/order/order-web/src/main/java/demo/order/action/ConnectPayment.java
new file mode 100644
index 0000000..ef830d5
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/action/ConnectPayment.java
@@ -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 {
+ public BiConsumer 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));
+ };
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/action/CreatePayment.java b/order/order-web/src/main/java/demo/order/action/CreatePayment.java
new file mode 100644
index 0000000..ddcd71f
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/action/CreatePayment.java
@@ -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 {
+
+ private final Logger log = Logger.getLogger(CreatePayment.class);
+
+ private RestTemplate restTemplate;
+
+ public CreatePayment(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ public Consumer 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> 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);
+ };
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/action/ProcessPayment.java b/order/order-web/src/main/java/demo/order/action/ProcessPayment.java
new file mode 100644
index 0000000..faa837e
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/action/ProcessPayment.java
@@ -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 {
+ public Consumer getConsumer() {
+ return (order) -> {};
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/action/ReserveInventory.java b/order/order-web/src/main/java/demo/order/action/ReserveInventory.java
new file mode 100644
index 0000000..5a7d434
--- /dev/null
+++ b/order/order-web/src/main/java/demo/order/action/ReserveInventory.java
@@ -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 {
+ public Consumer getConsumer() {
+ return (order) -> {};
+ }
+}
diff --git a/order/order-web/src/main/java/demo/order/OrderController.java b/order/order-web/src/main/java/demo/order/controller/OrderController.java
similarity index 50%
rename from order/order-web/src/main/java/demo/order/OrderController.java
rename to order/order-web/src/main/java/demo/order/controller/OrderController.java
index b7140ea..5ce4421 100644
--- a/order/order-web/src/main/java/demo/order/OrderController.java
+++ b/order/order-web/src/main/java/demo/order/controller/OrderController.java
@@ -1,15 +1,21 @@
-package demo.order;
+package demo.order.controller;
import demo.event.EventController;
import demo.event.EventService;
+import demo.event.Events;
import demo.event.OrderEvent;
-import demo.event.OrderEvents;
-import org.springframework.hateoas.*;
+import demo.order.Order;
+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.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
+import java.lang.reflect.Method;
import java.util.Optional;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@@ -19,9 +25,9 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
public class OrderController {
private final OrderService orderService;
- private final EventService eventService;
+ private final EventService eventService;
- public OrderController(OrderService orderService, EventService eventService) {
+ public OrderController(OrderService orderService, EventService eventService) {
this.orderService = orderService;
this.eventService = eventService;
}
@@ -40,7 +46,7 @@ public class OrderController {
.orElseThrow(() -> new RuntimeException("Order update failed"));
}
- @GetMapping(path = "/orders/{id}")
+ @RequestMapping(path = "/orders/{id}")
public ResponseEntity getOrder(@PathVariable Long id) {
return Optional.ofNullable(getOrderResource(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
@@ -49,12 +55,12 @@ public class OrderController {
@DeleteMapping(path = "/orders/{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))
.orElseThrow(() -> new RuntimeException("Order deletion failed"));
}
- @GetMapping(path = "/orders/{id}/events")
+ @RequestMapping(path = "/orders/{id}/events")
public ResponseEntity getOrderEvents(@PathVariable Long id) {
return Optional.of(getOrderEventResources(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
@@ -68,48 +74,50 @@ public class OrderController {
.orElseThrow(() -> new RuntimeException("Append order event failed"));
}
- @GetMapping(path = "/orders/{id}/commands")
- public ResponseEntity getOrderCommands(@PathVariable Long id) {
- return Optional.ofNullable(getCommandsResource(id))
+ @RequestMapping(path = "/orders/{id}/commands")
+ public ResponseEntity getCommands(@PathVariable Long id) {
+ return Optional.ofNullable(getCommandsResources(id))
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
.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) {
- return Optional.ofNullable(getOrderResource(orderService.connectAccount(id, accountId)))
- .map(e -> new ResponseEntity<>(e, HttpStatus.OK))
+ return Optional.ofNullable(orderService.get(id)
+ .connectAccount(accountId))
+ .map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
.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) {
- return Optional.ofNullable(getOrderResource(orderService.connectPayment(id, paymentId)))
- .map(e -> new ResponseEntity<>(e, HttpStatus.OK))
+ return Optional.ofNullable(orderService.get(id)
+ .connectPayment(paymentId))
+ .map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
.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) {
- return Optional.ofNullable(getOrderResource(
- orderService.createPayment(id)))
- .map(e -> new ResponseEntity<>(e, HttpStatus.OK))
+ return Optional.ofNullable(orderService.get(id)
+ .createPayment())
+ .map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
.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) {
- return Optional.ofNullable(getOrderResource(
- orderService.applyCommand(id, OrderCommand.PROCESS_PAYMENT)))
- .map(e -> new ResponseEntity<>(e, HttpStatus.OK))
+ return Optional.ofNullable(orderService.get(id)
+ .processPayment())
+ .map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
.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) {
- return Optional.ofNullable(getOrderResource(
- orderService.applyCommand(id, OrderCommand.RESERVE_INVENTORY)))
- .map(e -> new ResponseEntity<>(e, HttpStatus.OK))
+ return Optional.ofNullable(orderService.get(id)
+ .reserveInventory(id))
+ .map(e -> new ResponseEntity<>(getOrderResource(e), HttpStatus.OK))
.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}
*/
private Resource getOrderResource(Long id) {
- Resource orderResource = null;
-
// 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
- if (order != null)
- orderResource = getOrderResource(order);
-
-
- return orderResource;
+ return getOrderResource(order);
}
/**
@@ -156,12 +157,13 @@ public class OrderController {
* @return a hypermedia resource for the updated {@link Order}
*/
private Resource 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}
- * aggregate with the specified orderId.
+ * Appends an {@link OrderEvent} domain event to the event log of the {@link Order} aggregate with the
+ * specified orderId.
*
* @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}
@@ -170,7 +172,8 @@ public class OrderController {
private Resource appendEventResource(Long orderId, OrderEvent event) {
Resource eventResource = null;
- event = orderService.appendEvent(orderId, event);
+ orderService.get(orderId)
+ .sendAsyncEvent(event);
if (event != null) {
eventResource = new Resource<>(event,
@@ -188,75 +191,20 @@ public class OrderController {
return eventResource;
}
- /**
- * Get the {@link OrderCommand} hypermedia resource that lists the available commands that can be applied
- * 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 orderResource = getOrderResource(id);
+ private Events getOrderEventResources(Long id) {
+ return eventService.find(id);
+ }
- // Create a new order commands hypermedia resource
- OrderCommands commandResource = new OrderCommands();
+ private LinkBuilder linkBuilder(String name, Long id) {
+ Method method;
- // Add order command hypermedia links
- if (orderResource != null) {
- commandResource.add(
- new Link(new UriTemplate(
- 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")
- );
+ try {
+ method = OrderController.class.getMethod(name, Long.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
}
- return commandResource;
- }
-
- /**
- * 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");
+ return linkTo(OrderController.class, method, id);
}
/**
@@ -266,29 +214,26 @@ public class OrderController {
* @return is a hypermedia enriched resource for the supplied {@link Order} entity
*/
private Resource getOrderResource(Order order) {
- Resource orderResource;
+ Assert.notNull(order, "Order must not be null");
- // Prepare hypermedia response
- orderResource = new Resource<>(order,
- linkTo(OrderController.class)
- .slash("orders")
- .slash(order.getOrderId())
- .withSelfRel(),
- linkTo(OrderController.class)
- .slash("orders")
- .slash(order.getOrderId())
- .slash("events")
- .withRel("events"),
- getCommandLinkBuilder(order.getOrderId())
- .withRel("commands")
- );
+ // Add command link
+ order.add(linkBuilder("getCommands", order.getIdentity()).withRel("commands"));
+
+ // Add get events link
+ order.add(linkBuilder("getOrderEvents", order.getIdentity()).withRel("events"));
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)
- 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());
}
}
diff --git a/order/order-web/src/main/java/demo/payment/Payment.java b/order/order-web/src/main/java/demo/payment/Payment.java
index 13324e0..c600399 100644
--- a/order/order-web/src/main/java/demo/payment/Payment.java
+++ b/order/order-web/src/main/java/demo/payment/Payment.java
@@ -1,8 +1,6 @@
package demo.payment;
-import demo.domain.BaseEntity;
-
-public class Payment extends BaseEntity {
+public class Payment {
private Double amount;
private PaymentMethod paymentMethod;
diff --git a/order/order-worker/src/main/java/demo/event/EventService.java b/order/order-worker/src/main/java/demo/event/EventService.java
index dc7ee97..b424485 100644
--- a/order/order-worker/src/main/java/demo/event/EventService.java
+++ b/order/order-worker/src/main/java/demo/event/EventService.java
@@ -29,6 +29,8 @@ public class EventService {
Order result;
+ log.info(orderEvent);
+
log.info("Order event received: " + orderEvent.getLink("self").getHref());
// Generate a state machine for computing the state of the order resource
diff --git a/payment/payment-web/src/main/java/demo/domain/AbstractEntity.java b/payment/payment-web/src/main/java/demo/domain/AbstractEntity.java
index 431a1d0..e0d6b22 100644
--- a/payment/payment-web/src/main/java/demo/domain/AbstractEntity.java
+++ b/payment/payment-web/src/main/java/demo/domain/AbstractEntity.java
@@ -1,16 +1,19 @@
package demo.domain;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import demo.event.Event;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-import javax.persistence.EntityListeners;
-import javax.persistence.MappedSuperclass;
+import javax.persistence.*;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
-public class AbstractEntity extends Aggregate implements Serializable {
+public abstract class AbstractEntity extends Aggregate implements Serializable {
private T identity;
@@ -20,6 +23,9 @@ public class AbstractEntity extends Aggregate impleme
@LastModifiedDate
private Long lastModified;
+ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ private List events = new ArrayList<>();
+
public AbstractEntity() {
}
@@ -39,6 +45,16 @@ public class AbstractEntity extends Aggregate impleme
this.lastModified = lastModified;
}
+ @Override
+ @JsonIgnore
+ public List getEvents() {
+ return events;
+ }
+
+ public void setEvents(List events) {
+ this.events = events;
+ }
+
@Override
public T getIdentity() {
return identity;
diff --git a/payment/payment-web/src/main/java/demo/payment/Payment.java b/payment/payment-web/src/main/java/demo/payment/Payment.java
index 1bfa404..3b4f069 100644
--- a/payment/payment-web/src/main/java/demo/payment/Payment.java
+++ b/payment/payment-web/src/main/java/demo/payment/Payment.java
@@ -11,8 +11,6 @@ import demo.payment.controller.PaymentController;
import org.springframework.hateoas.Link;
import javax.persistence.*;
-import java.util.HashSet;
-import java.util.Set;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@@ -23,15 +21,12 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
* @author Kenny Bastani
*/
@Entity
-public class Payment extends AbstractEntity {
+public class Payment extends AbstractEntity {
@Id
@GeneratedValue
private Long id;
- @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- private Set events = new HashSet<>();
-
@Enumerated(value = EnumType.STRING)
private PaymentStatus status;
@@ -61,15 +56,6 @@ public class Payment extends AbstractEntity {
this.id = id;
}
- @JsonIgnore
- public Set getEvents() {
- return events;
- }
-
- public void setEvents(Set events) {
- this.events = events;
- }
-
public PaymentStatus getStatus() {
return status;
}
@@ -131,6 +117,4 @@ public class Payment extends AbstractEntity {
.slash(getIdentity())
.withSelfRel();
}
-
-
}
diff --git a/payment/payment-web/src/main/java/demo/payment/PaymentProvider.java b/payment/payment-web/src/main/java/demo/payment/PaymentProvider.java
index 8e83593..9f76a06 100644
--- a/payment/payment-web/src/main/java/demo/payment/PaymentProvider.java
+++ b/payment/payment-web/src/main/java/demo/payment/PaymentProvider.java
@@ -1,19 +1,27 @@
package demo.payment;
import demo.domain.Provider;
-import demo.domain.Service;
+import demo.event.EventService;
+import demo.event.PaymentEvent;
@org.springframework.stereotype.Service
public class PaymentProvider extends Provider {
private final PaymentService paymentService;
+ private final EventService eventService;
- public PaymentProvider(PaymentService paymentService) {
+ public PaymentProvider(PaymentService paymentService, EventService eventService) {
this.paymentService = paymentService;
+ this.eventService = eventService;
}
@Override
- protected Service extends Payment> getDefaultService() {
+ public PaymentService getDefaultService() {
return paymentService;
}
+
+ @Override
+ public EventService getDefaultEventService() {
+ return eventService;
+ }
}
diff --git a/payment/payment-web/src/main/java/demo/payment/PaymentService.java b/payment/payment-web/src/main/java/demo/payment/PaymentService.java
index f4fe652..a18b15f 100644
--- a/payment/payment-web/src/main/java/demo/payment/PaymentService.java
+++ b/payment/payment-web/src/main/java/demo/payment/PaymentService.java
@@ -4,11 +4,8 @@ import demo.domain.Service;
import demo.event.EventService;
import demo.event.PaymentEvent;
import demo.event.PaymentEventType;
-import demo.util.ConsistencyModel;
import org.springframework.util.Assert;
-import java.util.Objects;
-
/**
* 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
@@ -18,7 +15,7 @@ import java.util.Objects;
* @author Kenny Bastani
*/
@org.springframework.stereotype.Service
-public class PaymentService extends Service {
+public class PaymentService extends Service {
private final PaymentRepository paymentRepository;
private final EventService eventService;
@@ -29,125 +26,50 @@ public class PaymentService extends Service {
}
public Payment registerPayment(Payment payment) {
-
- payment = createPayment(payment);
+ payment = create(payment);
// Trigger the payment creation event
- PaymentEvent event = appendEvent(payment.getIdentity(),
- new PaymentEvent(PaymentEventType.PAYMENT_CREATED));
+ PaymentEvent event = payment.sendEvent(new PaymentEvent(PaymentEventType.PAYMENT_CREATED, payment));
// Attach payment identifier
- event.getEntity().setIdentity(payment.getIdentity());
+ event.getEntity()
+ .setIdentity(payment.getIdentity());
+
+ event.getEntity().getLinks().clear();
// Return the result
return event.getEntity();
}
- /**
- * 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) {
+ public Payment get(Long id) {
return paymentRepository.findOne(id);
}
- /**
- * Update an {@link Payment} entity with the supplied identifier.
- *
- * @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 updatePayment(Long id, Payment payment) {
- Assert.notNull(id, "Payment id must be present in the resource URL");
+ public Payment create(Payment payment) {
+ // Save the payment to the repository
+ return paymentRepository.saveAndFlush(payment);
+ }
+
+ public Payment update(Payment payment) {
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.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),
+ Assert.state(paymentRepository.exists(payment.getIdentity()),
"The payment with the supplied id does not exist");
- Payment currentPayment = paymentRepository.findOne(id);
+ Payment currentPayment = get(payment.getIdentity());
currentPayment.setStatus(payment.getStatus());
+ currentPayment.setPaymentMethod(payment.getPaymentMethod());
+ currentPayment.setOrderId(payment.getOrderId());
+ currentPayment.setAmount(payment.getAmount());
return paymentRepository.save(currentPayment);
}
- /**
- * Delete the {@link Payment} with the supplied identifier.
- *
- * @param id is the unique identifier for the {@link Payment}
- */
- public Boolean deletePayment(Long id) {
+ public boolean delete(Long id) {
Assert.state(paymentRepository.exists(id),
"The payment with the supplied id does not exist");
this.paymentRepository.delete(id);
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;
- }
}
diff --git a/payment/payment-web/src/main/java/demo/payment/controller/PaymentController.java b/payment/payment-web/src/main/java/demo/payment/controller/PaymentController.java
index 0cf72ea..ddff6c8 100644
--- a/payment/payment-web/src/main/java/demo/payment/controller/PaymentController.java
+++ b/payment/payment-web/src/main/java/demo/payment/controller/PaymentController.java
@@ -61,7 +61,7 @@ public class PaymentController {
@RequestMapping(path = "/payments/{id}", method = RequestMethod.DELETE)
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))
.orElseThrow(() -> new RuntimeException("Payment deletion failed"));
}
@@ -74,7 +74,7 @@ public class PaymentController {
}
@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))
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
.orElseThrow(() -> new RuntimeException("Append payment event failed"));
@@ -89,7 +89,7 @@ public class PaymentController {
@RequestMapping(path = "/payments/{id}/commands/connectOrder")
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))
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
@@ -97,7 +97,7 @@ public class PaymentController {
@RequestMapping(path = "/payments/{id}/commands/processPayment")
public ResponseEntity processPayment(@PathVariable Long id) {
- return Optional.of(paymentService.getPayment(id)
+ return Optional.of(paymentService.get(id)
.processPayment())
.map(e -> new ResponseEntity<>(getPaymentResource(e), HttpStatus.OK))
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
@@ -111,7 +111,7 @@ public class PaymentController {
*/
private Resource getPaymentResource(Long id) {
// Get the payment for the provided id
- Payment payment = paymentService.getPayment(id);
+ Payment payment = paymentService.get(id);
return getPaymentResource(payment);
}
@@ -128,7 +128,9 @@ public class PaymentController {
// Create the new 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}
*/
private Resource 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 appendEventResource(Long paymentId, PaymentEvent event) {
Resource eventResource = null;
- event = paymentService.appendEvent(paymentId, event);
+ event = paymentService.get(paymentId).sendEvent(event);
if (event != null) {
eventResource = new Resource<>(event,
@@ -196,11 +199,15 @@ public class PaymentController {
private Resource getPaymentResource(Payment payment) {
Assert.notNull(payment, "Payment must not be null");
- // Add command link
- payment.add(linkBuilder("getCommands", payment.getIdentity()).withRel("commands"));
+ if(!payment.hasLink("commands")) {
+ // Add command link
+ payment.add(linkBuilder("getCommands", payment.getIdentity()).withRel("commands"));
+ }
- // Add get events link
- payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
+ if(!payment.hasLink("events")) {
+ // Add get events link
+ payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
+ }
return new Resource<>(payment);
}
diff --git a/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java b/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java
index e128b12..a08e2fe 100644
--- a/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java
+++ b/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java
@@ -40,7 +40,7 @@ public class PaymentControllerTest {
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
.singletonList(new PaymentEvent(PaymentEventType
.PAYMENT_CREATED))));
diff --git a/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java b/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java
index 72f50ef..78ba5ef 100644
--- a/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java
+++ b/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java
@@ -33,7 +33,7 @@ public class PaymentServiceTests {
given(this.paymentRepository.findOne(1L)).willReturn(expected);
- Payment actual = paymentService.getPayment(1L);
+ Payment actual = paymentService.get(1L);
assertThat(actual).isNotNull();
assertThat(actual.getPaymentMethod()).isEqualTo(PaymentMethod.CREDIT_CARD);
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java
index f65e125..427f555 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java
@@ -2,9 +2,12 @@ package demo.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import demo.event.Event;
+import demo.event.EventService;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.hateoas.*;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestParam;
@@ -24,7 +27,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
*
* @author Kenny Bastani
*/
-public abstract class Aggregate extends ResourceSupport implements Value {
+public abstract class Aggregate extends ResourceSupport implements
+ Value {
@JsonProperty("id")
abstract ID getIdentity();
@@ -39,6 +43,7 @@ public abstract class Aggregate extends ResourceSupport
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
*/
@SuppressWarnings("unchecked")
+ @JsonIgnore
protected , A extends Aggregate> T getAction(
Class actionType) throws IllegalArgumentException {
Provider provider = getProvider();
@@ -53,7 +58,8 @@ public abstract class Aggregate extends ResourceSupport
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
*/
@SuppressWarnings("unchecked")
- protected , A extends Aggregate> T getProvider() throws IllegalArgumentException {
+ @JsonIgnore
+ public , A extends Aggregate> T getProvider() throws IllegalArgumentException {
return getProvider((Class) ResolvableType
.forClassWithGenerics(Provider.class, ResolvableType.forInstance(this))
.getRawClass());
@@ -65,12 +71,50 @@ public abstract class Aggregate extends ResourceSupport
* @return an instance of the requested {@link Provider}
* @throws IllegalArgumentException if the application context is unavailable or the provider does not exist
*/
- protected , A extends Aggregate> T getProvider(
- Class providerType) throws IllegalArgumentException {
+ @JsonIgnore
+ public , A extends Aggregate> T getProvider(Class providerType) throws
+ IllegalArgumentException {
Assert.notNull(applicationContext, "The application context is unavailable");
T provider = applicationContext.getBean(providerType);
Assert.notNull(provider, "The requested provider is not registered in the application context");
- return provider;
+ return (T) provider;
+ }
+
+ @JsonIgnore
+ public abstract List 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 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
@@ -79,13 +123,13 @@ public abstract class Aggregate extends ResourceSupport
.stream()
.collect(Collectors.toList());
- links.add(getId());
+ if(!super.hasLink("self"))
+ links.add(getId());
return links;
}
@JsonIgnore
- @SuppressWarnings("unchecked")
public CommandResources getCommands() {
CommandResources commandResources = new CommandResources();
@@ -120,6 +164,18 @@ public abstract class Aggregate extends ResourceSupport
return commandResources;
}
+ @SuppressWarnings("unchecked")
+ private Service getEntityService() {
+ return (Service) getProvider().getDefaultService();
+ }
+
+ @SuppressWarnings("unchecked")
+ private EventService getEventService() {
+ return (EventService) getProvider().getDefaultEventService();
+ }
+
public static class CommandResources extends ResourceSupport {
}
+
+
}
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java
index f1b70fc..82d227f 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java
@@ -1,5 +1,6 @@
package demo.domain;
+import demo.event.EventService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -24,5 +25,7 @@ public abstract class Provider implements ApplicationContex
Provider.applicationContext = applicationContext;
}
- protected abstract Service extends T> getDefaultService();
+ public abstract Service, ?> getDefaultService();
+
+ public abstract EventService, ?> getDefaultEventService();
}
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Service.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Service.java
index c36762e..7e0eb11 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Service.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Service.java
@@ -4,6 +4,8 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
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
* 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
*/
@org.springframework.stereotype.Service
-public abstract class Service implements ApplicationContextAware {
+public abstract class Service implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
@@ -19,6 +21,11 @@ public abstract class Service implements 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")
public > A getAction(Class extends A> clazz) {
return applicationContext.getBean(clazz);
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Event.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Event.java
index c5e2abe..8ea9716 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Event.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Event.java
@@ -1,5 +1,6 @@
package demo.event;
+import demo.domain.Aggregate;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
@@ -21,7 +22,7 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
* @see org.springframework.stereotype.Repository
* @see ResourceSupport
*/
-public abstract class Event extends ResourceSupport {
+public abstract class Event extends ResourceSupport {
public Event() {
}
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventService.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventService.java
index 1a73a45..b18b845 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventService.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventService.java
@@ -1,7 +1,7 @@
package demo.event;
+import demo.domain.Aggregate;
import org.springframework.hateoas.Link;
-import org.springframework.hateoas.ResourceSupport;
import java.io.Serializable;
@@ -19,18 +19,14 @@ public interface EventService {
* Raises a synchronous domain event. An {@link Event} will be applied to an entity through a chain of HTTP
* requests/responses.
*
- * @param event
- * @param links
* @return the applied {@link Event}
*/
- S send(S event, Link... links);
+ 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
* messages.
*
- * @param event
- * @param links
* @return a flag indicating if the {@link Event} message was sent successfully
*/
Boolean sendAsync(S event, Link... links);
@@ -39,7 +35,6 @@ public interface EventService {
* Saves a given event entity. Use the returned instance for further operations as the save operation might have
* changed the entity instance completely.
*
- * @param event
* @return the saved event entity
*/
S save(S event);
@@ -48,8 +43,6 @@ public interface EventService {
* 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.
*
- * @param id
- * @param event
* @return the saved event entity
*/
S save(ID id, S event);
@@ -57,7 +50,6 @@ public interface EventService {
/**
* 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
*/
T findOne(EID id);
@@ -65,7 +57,6 @@ public interface EventService {
/**
* Retrieves an entity's {@link Event}s by its id.
*
- * @param entityId
* @return a {@link Events} containing a collection of {@link Event}s
*/
E find(ID entityId);
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java
index 3eeec65..933477d 100755
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java
@@ -1,12 +1,13 @@
package demo.event;
+import demo.domain.Aggregate;
import org.apache.log4j.Logger;
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.hateoas.ResourceSupport;
+import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.web.client.RestTemplate;
@@ -38,14 +39,18 @@ class EventServiceImpl implements Even
this.restTemplate = restTemplate;
}
- public S send(S event, Link... links) {
+ public S send(S event, Link... links) {
// Assemble request to the event stream processor
RequestEntity> requestEntity = RequestEntity.post(URI.create(EVENT_PROCESSOR_URL))
- .contentType(MediaTypes.HAL_JSON).body(new Resource(event), Resource.class);
+ .contentType(MediaTypes.HAL_JSON)
+ .body(new Resource(event), Resource.class);
try {
// 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
event.setEntity(entity);
} catch (Exception ex) {
@@ -56,7 +61,10 @@ class EventServiceImpl implements Even
}
public 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 save(S event) {
@@ -75,6 +83,7 @@ class EventServiceImpl implements Even
public E find(ID entityId) {
return (E) new Events(entityId, eventRepository.findEventsByEntityId(entityId,
- new PageRequest(0, Integer.MAX_VALUE)).getContent());
+ new PageRequest(0, Integer.MAX_VALUE))
+ .getContent());
}
}
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Events.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Events.java
index 9513e4e..6f7c3d1 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Events.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/Events.java
@@ -1,8 +1,8 @@
package demo.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import demo.domain.Aggregate;
import org.springframework.hateoas.Link;
-import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import java.io.Serializable;
@@ -13,7 +13,7 @@ import java.util.List;
*
* @author Kenny Bastani
*/
-public class Events extends Resources> {
+public class Events extends Resources> {
private ID entityId;
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/EmptyEvent.java b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/EmptyEvent.java
new file mode 100644
index 0000000..61a962b
--- /dev/null
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/EmptyEvent.java
@@ -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) {
+
+ }
+}
diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java
index 34f9aca..98e9dae 100644
--- a/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java
+++ b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java
@@ -1,5 +1,6 @@
package demo.domain;
+import demo.event.EventService;
import lombok.*;
import org.junit.After;
import org.junit.Before;
@@ -14,6 +15,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
+import java.util.List;
import java.util.function.Consumer;
import static junit.framework.TestCase.assertEquals;
@@ -97,7 +99,7 @@ public class ProviderTests {
@NoArgsConstructor
@Getter
@Setter
- public static class EmptyAggregate extends Aggregate {
+ public static class EmptyAggregate extends Aggregate {
@NonNull
private Long id;
@NonNull
@@ -121,6 +123,11 @@ public class ProviderTests {
Long getIdentity() {
return this.id;
}
+
+ @Override
+ public List getEvents() {
+ return null;
+ }
}
@Getter
@@ -128,15 +135,41 @@ public class ProviderTests {
public static class EmptyProvider extends Provider {
private final EmptyService emptyService;
- public Service extends EmptyAggregate> getDefaultService() {
+ public Service extends EmptyAggregate, Long> getDefaultService() {
return emptyService;
}
+
+ @Override
+ public EventService, ?> getDefaultEventService() {
+ return null;
+ }
}
- public static class EmptyService extends Service {
+ public static class EmptyService extends Service {
public EmptyAggregate getEmptyAggregate(Long id) {
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 {