Last git commit of 2016
This commit is contained in:
@@ -29,7 +29,7 @@ public class ActivateAccount extends Action<Account> {
|
|||||||
Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
|
Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
|
||||||
.contains(account.getStatus()), "The account cannot be activated");
|
.contains(account.getStatus()), "The account cannot be activated");
|
||||||
|
|
||||||
AccountService accountService = account.getProvider(AccountModule.class)
|
AccountService accountService = account.getModule(AccountModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Activate the account
|
// Activate the account
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class ArchiveAccount extends Action<Account> {
|
|||||||
return (account) -> {
|
return (account) -> {
|
||||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
||||||
|
|
||||||
AccountService accountService = account.getProvider(AccountModule.class)
|
AccountService accountService = account.getModule(AccountModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Archive the account
|
// Archive the account
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class ConfirmAccount extends Action<Account> {
|
|||||||
return (account) -> {
|
return (account) -> {
|
||||||
Assert.isTrue(account.getStatus() == ACCOUNT_PENDING, "The account has already been confirmed");
|
Assert.isTrue(account.getStatus() == ACCOUNT_PENDING, "The account has already been confirmed");
|
||||||
|
|
||||||
AccountService accountService = account.getProvider(AccountModule.class)
|
AccountService accountService = account.getModule(AccountModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Confirm the account
|
// Confirm the account
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class SuspendAccount extends Action<Account> {
|
|||||||
return (account) -> {
|
return (account) -> {
|
||||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
||||||
|
|
||||||
AccountService accountService = account.getProvider(AccountModule.class)
|
AccountService accountService = account.getModule(AccountModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Suspend the account
|
// Suspend the account
|
||||||
|
|||||||
@@ -140,9 +140,9 @@ public class Account extends AbstractEntity<AccountEvent, Long> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends Module<A>, A extends Aggregate<AccountEvent, Long>> T getProvider() throws
|
public <T extends Module<A>, A extends Aggregate<AccountEvent, Long>> T getModule() throws
|
||||||
IllegalArgumentException {
|
IllegalArgumentException {
|
||||||
AccountModule accountProvider = getProvider(AccountModule.class);
|
AccountModule accountProvider = getModule(AccountModule.class);
|
||||||
return (T) accountProvider;
|
return (T) accountProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.config;
|
package demo.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -24,6 +25,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
converter.setObjectMapper(objectMapper);
|
converter.setObjectMapper(objectMapper);
|
||||||
converters.add(converter);
|
converters.add(converter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class Order extends Aggregate<OrderEvent, Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Order post() {
|
public Order post() {
|
||||||
OrderModule orderProvider = getProvider();
|
OrderModule orderProvider = getModule();
|
||||||
return orderProvider.getDefaultService()
|
return orderProvider.getDefaultService()
|
||||||
.create(this);
|
.create(this);
|
||||||
}
|
}
|
||||||
@@ -115,9 +115,9 @@ public class Order extends Aggregate<OrderEvent, Long> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends Module<A>, A extends Aggregate<OrderEvent, Long>> T getProvider() throws
|
public <T extends Module<A>, A extends Aggregate<OrderEvent, Long>> T getModule() throws
|
||||||
IllegalArgumentException {
|
IllegalArgumentException {
|
||||||
OrderModule orderProvider = getProvider(OrderModule.class);
|
OrderModule orderProvider = getModule(OrderModule.class);
|
||||||
return (T) orderProvider;
|
return (T) orderProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ spring:
|
|||||||
---
|
---
|
||||||
spring:
|
spring:
|
||||||
profiles: development
|
profiles: development
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
cloud:
|
cloud:
|
||||||
stream:
|
stream:
|
||||||
bindings:
|
bindings:
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ spring:
|
|||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
consumer:
|
consumer:
|
||||||
durableSubscription: true
|
durableSubscription: true
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
server:
|
server:
|
||||||
port: 8081
|
port: 0
|
||||||
amazon:
|
amazon:
|
||||||
aws:
|
aws:
|
||||||
access-key-id: replace
|
access-key-id: replace
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.config;
|
package demo.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -24,6 +25,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
converter.setObjectMapper(objectMapper);
|
converter.setObjectMapper(objectMapper);
|
||||||
converters.add(converter);
|
converters.add(converter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ConnectAccount extends Action<Order> {
|
|||||||
|
|
||||||
public BiConsumer<Order, Long> getConsumer() {
|
public BiConsumer<Order, Long> getConsumer() {
|
||||||
return (order, accountId) -> {
|
return (order, accountId) -> {
|
||||||
OrderService orderService = order.getProvider(OrderModule.class)
|
OrderService orderService = order.getModule(OrderModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Connect the account
|
// Connect the account
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class ConnectPayment extends Action<Order> {
|
|||||||
public BiConsumer<Order, Long> getConsumer() {
|
public BiConsumer<Order, Long> getConsumer() {
|
||||||
return (order, paymentId) -> {
|
return (order, paymentId) -> {
|
||||||
|
|
||||||
OrderService orderService = order.getProvider(OrderModule.class)
|
OrderService orderService = order.getModule(OrderModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
// Connect the account
|
// Connect the account
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class CreatePayment extends Action<Order> {
|
|||||||
Assert.isTrue(order.getPaymentId() == null, "Payment has already been created");
|
Assert.isTrue(order.getPaymentId() == null, "Payment has already been created");
|
||||||
Assert.isTrue(order.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "Account must be connected first");
|
Assert.isTrue(order.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "Account must be connected first");
|
||||||
|
|
||||||
OrderService orderService = order.getProvider(OrderModule.class)
|
OrderService orderService = order.getModule(OrderModule.class)
|
||||||
.getDefaultService();
|
.getDefaultService();
|
||||||
|
|
||||||
Payment payment = new Payment();
|
Payment payment = new Payment();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package demo.order.action;
|
||||||
|
|
||||||
|
import demo.domain.Action;
|
||||||
|
import demo.order.domain.Order;
|
||||||
|
import demo.order.domain.OrderModule;
|
||||||
|
import demo.payment.domain.Payment;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a {@link Payment} for an {@link Order}.
|
||||||
|
*
|
||||||
|
* @author Kenny Bastani
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DeleteOrder extends Action<Order> {
|
||||||
|
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public DeleteOrder(RestTemplate restTemplate) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Consumer<Order> getConsumer() {
|
||||||
|
return (order) -> {
|
||||||
|
// Delete payment
|
||||||
|
if (order.getPaymentId() != null) {
|
||||||
|
String href = "http://payment-web/v1/payments/" + order.getPaymentId();
|
||||||
|
restTemplate.delete(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete order
|
||||||
|
order.getModule(OrderModule.class)
|
||||||
|
.getDefaultService()
|
||||||
|
.delete(order.getIdentity());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,14 +52,14 @@ public class OrderController {
|
|||||||
|
|
||||||
@RequestMapping(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(orderService.get(id)))
|
||||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping(path = "/orders/{id}")
|
@DeleteMapping(path = "/orders/{id}")
|
||||||
public ResponseEntity deleteOrder(@PathVariable Long id) {
|
public ResponseEntity deleteOrder(@PathVariable Long id) {
|
||||||
return Optional.ofNullable(orderService.delete(id))
|
return Optional.ofNullable(orderService.get(id).delete())
|
||||||
.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"));
|
||||||
}
|
}
|
||||||
@@ -132,19 +132,6 @@ public class OrderController {
|
|||||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a hypermedia resource for {@link Order} with the specified identifier.
|
|
||||||
*
|
|
||||||
* @param id is the unique identifier for looking up the {@link Order} entity
|
|
||||||
* @return a hypermedia resource for the fetched {@link Order}
|
|
||||||
*/
|
|
||||||
private Resource<Order> getOrderResource(Long id) {
|
|
||||||
// Get the order for the provided id
|
|
||||||
Order order = orderService.get(id);
|
|
||||||
|
|
||||||
return getOrderResource(order);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Order} entity and persists the result to the repository.
|
* Creates a new {@link Order} entity and persists the result to the repository.
|
||||||
*
|
*
|
||||||
@@ -225,7 +212,7 @@ 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) {
|
||||||
Assert.notNull(order, "Order must not be null");
|
if(order == null) return null;
|
||||||
|
|
||||||
// Add command link
|
// Add command link
|
||||||
order.add(linkBuilder("getCommands", order.getIdentity()).withRel("commands"));
|
order.add(linkBuilder("getCommands", order.getIdentity()).withRel("commands"));
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ public class Order extends AbstractEntity<OrderEvent, Long> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean delete() {
|
||||||
|
getAction(DeleteOrder.class)
|
||||||
|
.getConsumer()
|
||||||
|
.accept(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Double calculateTotal() {
|
public Double calculateTotal() {
|
||||||
return getLineItems()
|
return getLineItems()
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ spring:
|
|||||||
output:
|
output:
|
||||||
destination: order
|
destination: order
|
||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
server:
|
server:
|
||||||
port: 0
|
port: 0
|
||||||
events:
|
events:
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package demo.config;
|
package demo.config;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.function.*;
|
import demo.function.*;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import demo.payment.Payment;
|
import demo.order.event.OrderEvents;
|
||||||
import demo.stream.OrderStream;
|
import demo.payment.domain.Payment;
|
||||||
|
import demo.order.event.OrderEventProcessor;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -30,9 +31,9 @@ import java.util.Map;
|
|||||||
* expressions. Actions are executed during transitions between a source state and a target state.
|
* expressions. Actions are executed during transitions between a source state and a target state.
|
||||||
* <p>
|
* <p>
|
||||||
* A state machine provides a robust declarative language for describing the state of an {@link Order}
|
* A state machine provides a robust declarative language for describing the state of an {@link Order}
|
||||||
* resource given a sequence of ordered {@link demo.event.OrderEvents}. When an event is received
|
* resource given a sequence of ordered {@link OrderEvents}. When an event is received
|
||||||
* in {@link OrderStream}, an in-memory state machine is fully replicated given the
|
* in {@link OrderEventProcessor}, an in-memory state machine is fully replicated given the
|
||||||
* {@link demo.event.OrderEvents} attached to an {@link Order} resource.
|
* {@link OrderEvents} attached to an {@link Order} resource.
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author kbastani
|
||||||
*/
|
*/
|
||||||
@@ -50,7 +51,6 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
|||||||
@Override
|
@Override
|
||||||
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEventType> states) {
|
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEventType> states) {
|
||||||
try {
|
try {
|
||||||
// Describe the initial condition of the order state machine
|
|
||||||
states.withStates()
|
states.withStates()
|
||||||
.initial(OrderStatus.ORDER_CREATED)
|
.initial(OrderStatus.ORDER_CREATED)
|
||||||
.states(EnumSet.allOf(OrderStatus.class));
|
.states(EnumSet.allOf(OrderStatus.class));
|
||||||
@@ -59,6 +59,32 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions are mapped to actions that are triggered during the replication of a state machine. Functions
|
||||||
|
* should only be executed after the state machine has completed replication. This method checks the state
|
||||||
|
* context of the machine for an {@link OrderEvent}, which signals that the state machine is finished
|
||||||
|
* replication.
|
||||||
|
* <p>
|
||||||
|
* The {@link OrderFunction} argument is only applied if an {@link OrderEvent} is provided as a
|
||||||
|
* message header in the {@link StateContext}.
|
||||||
|
*
|
||||||
|
* @param context is the state machine context that may include an {@link OrderEvent}
|
||||||
|
* @param orderFunction is the order function to apply after the state machine has completed replication
|
||||||
|
* @return an {@link OrderEvent} only if this event has not yet been processed, otherwise returns null
|
||||||
|
*/
|
||||||
|
private OrderEvent applyEvent(StateContext<OrderStatus, OrderEventType> context, OrderFunction orderFunction) {
|
||||||
|
OrderEvent event = null;
|
||||||
|
log.info(String.format("Replicate event: %s", context.getMessage().getPayload()));
|
||||||
|
|
||||||
|
if (context.getMessageHeader("event") != null) {
|
||||||
|
event = context.getMessageHeaders().get("event", OrderEvent.class);
|
||||||
|
log.info(String.format("State replication complete: %s", event.getType()));
|
||||||
|
orderFunction.apply(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the {@link StateMachine} that describes how {@link OrderEventType} drives the state
|
* Configures the {@link StateMachine} that describes how {@link OrderEventType} drives the state
|
||||||
* of an {@link Order}. Events are applied as transitions from a source {@link OrderStatus} to
|
* of an {@link Order}. Events are applied as transitions from a source {@link OrderStatus} to
|
||||||
@@ -253,20 +279,26 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
|||||||
MediaTypes.HAL_JSON
|
MediaTypes.HAL_JSON
|
||||||
);
|
);
|
||||||
|
|
||||||
Payment payment = paymentResource.follow("self")
|
|
||||||
.toEntity(Payment.class)
|
|
||||||
.getBody();
|
|
||||||
|
|
||||||
Order order = orderResource.follow("self")
|
Order order = orderResource.follow("self")
|
||||||
.toEntity(Order.class)
|
.toObject(Order.class);
|
||||||
.getBody();
|
|
||||||
|
|
||||||
Map<String, Object> template = new HashMap<String, Object>();
|
Map<String, Object> template = new HashMap<>();
|
||||||
|
template.put("orderId", order.getIdentity());
|
||||||
|
|
||||||
|
// Connect payment to order
|
||||||
|
Payment payment = paymentResource.follow("self", "commands", "connectOrder")
|
||||||
|
.withTemplateParameters(template)
|
||||||
|
.toObject(Payment.class);
|
||||||
|
|
||||||
|
template = new HashMap<>();
|
||||||
template.put("paymentId", payment.getPaymentId());
|
template.put("paymentId", payment.getPaymentId());
|
||||||
return orderResource.follow("commands", "connectPayment")
|
|
||||||
|
// Connect order to payment
|
||||||
|
order = orderResource.follow("commands", "connectPayment")
|
||||||
.withTemplateParameters(template)
|
.withTemplateParameters(template)
|
||||||
.toObject(Order.class);
|
.toObject(Order.class);
|
||||||
|
|
||||||
|
return order;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,36 +353,6 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions are mapped to actions that are triggered during the replication of a state machine. Functions
|
|
||||||
* should only be executed after the state machine has completed replication. This method checks the state
|
|
||||||
* context of the machine for an {@link OrderEvent}, which signals that the state machine is finished
|
|
||||||
* replication.
|
|
||||||
* <p>
|
|
||||||
* The {@link OrderFunction} argument is only applied if an {@link OrderEvent} is provided as a
|
|
||||||
* message header in the {@link StateContext}.
|
|
||||||
*
|
|
||||||
* @param context is the state machine context that may include an {@link OrderEvent}
|
|
||||||
* @param orderFunction is the order function to apply after the state machine has completed replication
|
|
||||||
* @return an {@link OrderEvent} only if this event has not yet been processed, otherwise returns null
|
|
||||||
*/
|
|
||||||
private OrderEvent applyEvent(StateContext<OrderStatus, OrderEventType> context,
|
|
||||||
OrderFunction orderFunction) {
|
|
||||||
OrderEvent orderEvent = null;
|
|
||||||
|
|
||||||
// Log out the progress of the state machine replication
|
|
||||||
log.info("Replicate event: " + context.getMessage().getPayload());
|
|
||||||
|
|
||||||
// The machine is finished replicating when an OrderEvent is found in the message header
|
|
||||||
if (context.getMessageHeader("event") != null) {
|
|
||||||
orderEvent = (OrderEvent) context.getMessageHeader("event");
|
|
||||||
log.info("State machine replicated: " + orderEvent.getType());
|
|
||||||
|
|
||||||
// Apply the provided function to the OrderEvent
|
|
||||||
orderFunction.apply(orderEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package demo.domain;
|
|||||||
|
|
||||||
import org.springframework.hateoas.ResourceSupport;
|
import org.springframework.hateoas.ResourceSupport;
|
||||||
|
|
||||||
public class BaseEntity extends ResourceSupport {
|
public class AbstractEntity extends ResourceSupport {
|
||||||
|
|
||||||
private Long createdAt;
|
private Long createdAt;
|
||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
|
|
||||||
public BaseEntity() {
|
public AbstractEntity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getCreatedAt() {
|
public Long getCreatedAt() {
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package demo.event;
|
|
||||||
|
|
||||||
import demo.order.Order;
|
|
||||||
import demo.order.OrderStatus;
|
|
||||||
import demo.state.StateMachineService;
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
import org.springframework.hateoas.MediaTypes;
|
|
||||||
import org.springframework.hateoas.client.Traverson;
|
|
||||||
import org.springframework.messaging.MessageHeaders;
|
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
|
||||||
import org.springframework.statemachine.StateMachine;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class EventService {
|
|
||||||
|
|
||||||
final private Logger log = Logger.getLogger(EventService.class);
|
|
||||||
final private StateMachineService stateMachineService;
|
|
||||||
|
|
||||||
public EventService(StateMachineService stateMachineService) {
|
|
||||||
this.stateMachineService = stateMachineService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order apply(OrderEvent orderEvent) {
|
|
||||||
|
|
||||||
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
|
|
||||||
StateMachine<OrderStatus, OrderEventType> stateMachine =
|
|
||||||
stateMachineService.getStateMachine();
|
|
||||||
|
|
||||||
// Follow the hypermedia link to fetch the attached order
|
|
||||||
Traverson traverson = new Traverson(
|
|
||||||
URI.create(orderEvent.getLink("order").getHref()),
|
|
||||||
MediaTypes.HAL_JSON
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the event log for the attached order resource
|
|
||||||
OrderEvents events = traverson.follow("events")
|
|
||||||
.toEntity(OrderEvents.class)
|
|
||||||
.getBody();
|
|
||||||
|
|
||||||
// Prepare order event message headers
|
|
||||||
Map<String, Object> headerMap = new HashMap<>();
|
|
||||||
headerMap.put("event", orderEvent);
|
|
||||||
|
|
||||||
// Replicate the current state of the order resource
|
|
||||||
events.getContent()
|
|
||||||
.stream()
|
|
||||||
.sorted((a1, a2) -> a1.getCreatedAt().compareTo(a2.getCreatedAt()))
|
|
||||||
.forEach(e -> {
|
|
||||||
MessageHeaders headers = new MessageHeaders(null);
|
|
||||||
|
|
||||||
// Check to see if this is the current event
|
|
||||||
if (e.getLink("self").equals(orderEvent.getLink("self"))) {
|
|
||||||
headers = new MessageHeaders(headerMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the event to the state machine
|
|
||||||
stateMachine.sendEvent(MessageBuilder.createMessage(e.getType(), headers));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Get result
|
|
||||||
Map<Object, Object> context = stateMachine.getExtendedState()
|
|
||||||
.getVariables();
|
|
||||||
|
|
||||||
// Get the order result
|
|
||||||
result = (Order) context.getOrDefault("order", null);
|
|
||||||
|
|
||||||
// Destroy the state machine
|
|
||||||
stateMachine.stop();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OrderFunction} is an abstraction used to map actions that are triggered by
|
* The {@link OrderFunction} is an abstraction used to map actions that are triggered by
|
||||||
* state transitions on a {@link demo.order.Order} resource on to a function. Mapped functions
|
* state transitions on a {@link Order} resource on to a function. Mapped functions
|
||||||
* can take multiple forms and reside either remotely or locally on the classpath of this application.
|
* can take multiple forms and reside either remotely or locally on the classpath of this application.
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author kbastani
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package demo.function;
|
package demo.function;
|
||||||
|
|
||||||
import demo.event.OrderEvent;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.event.OrderEventType;
|
||||||
import demo.order.Order;
|
import demo.order.domain.Order;
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.OrderStatus;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package demo.order;
|
||||||
|
|
||||||
|
import demo.order.domain.Order;
|
||||||
|
import demo.order.domain.OrderStatus;
|
||||||
|
import demo.order.event.OrderEvent;
|
||||||
|
import demo.order.event.OrderEventType;
|
||||||
|
import demo.order.event.OrderEvents;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
import org.springframework.hateoas.MediaTypes;
|
||||||
|
import org.springframework.hateoas.client.Traverson;
|
||||||
|
import org.springframework.messaging.MessageHeaders;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StateFactory {
|
||||||
|
|
||||||
|
final private Logger log = Logger.getLogger(StateFactory.class);
|
||||||
|
final private StateService stateService;
|
||||||
|
|
||||||
|
public StateFactory(StateService stateService) {
|
||||||
|
this.stateService = stateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order apply(OrderEvent orderEvent) {
|
||||||
|
Assert.notNull(orderEvent, "Cannot apply a null event");
|
||||||
|
Assert.notNull(orderEvent.getId(), "The event payload's identity link was not found");
|
||||||
|
|
||||||
|
StateMachine<OrderStatus, OrderEventType> stateMachine = getStateMachine(orderEvent);
|
||||||
|
stateMachine.stop();
|
||||||
|
|
||||||
|
return stateMachine.getExtendedState().get("order", Order.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StateMachine<OrderStatus, OrderEventType> getStateMachine(OrderEvent orderEvent) {
|
||||||
|
Link eventId = orderEvent.getId();
|
||||||
|
log.info(String.format("Order event received: %s", eventId));
|
||||||
|
|
||||||
|
StateMachine<OrderStatus, OrderEventType> stateMachine;
|
||||||
|
Map<String, Object> contextMap;
|
||||||
|
OrderEvents eventLog;
|
||||||
|
|
||||||
|
eventLog = getEventLog(orderEvent);
|
||||||
|
contextMap = getEventHeaders(orderEvent);
|
||||||
|
stateMachine = stateService.newStateMachine();
|
||||||
|
|
||||||
|
// Replicate the aggregate state
|
||||||
|
eventLog.getContent().stream()
|
||||||
|
.sorted((a, b) -> a.getCreatedAt().compareTo(b.getCreatedAt()))
|
||||||
|
.forEach(e -> stateMachine.sendEvent(MessageBuilder.createMessage(e.getType(), e.getId()
|
||||||
|
.equals(eventId) ? new MessageHeaders(contextMap) : new MessageHeaders(null))));
|
||||||
|
|
||||||
|
return stateMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getEventHeaders(OrderEvent orderEvent) {
|
||||||
|
Map<String, Object> headerMap = new HashMap<>();
|
||||||
|
headerMap.put("event", orderEvent);
|
||||||
|
return headerMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventLog(OrderEvent event) {
|
||||||
|
// Follow the hypermedia link to fetch the attached order
|
||||||
|
Traverson traverson = new Traverson(
|
||||||
|
URI.create(event.getLink("order")
|
||||||
|
.getHref()),
|
||||||
|
MediaTypes.HAL_JSON
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the event log for the attached order resource
|
||||||
|
return traverson.follow("events")
|
||||||
|
.toEntity(OrderEvents.class)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package demo.state;
|
package demo.order;
|
||||||
|
|
||||||
import demo.order.OrderStatus;
|
import demo.order.domain.Order;
|
||||||
import demo.event.OrderEventType;
|
import demo.order.domain.OrderStatus;
|
||||||
|
import demo.order.event.OrderEventType;
|
||||||
|
import demo.order.event.OrderEvent;
|
||||||
|
import demo.order.event.OrderEvents;
|
||||||
import org.springframework.statemachine.StateMachine;
|
import org.springframework.statemachine.StateMachine;
|
||||||
import org.springframework.statemachine.config.StateMachineFactory;
|
import org.springframework.statemachine.config.StateMachineFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -9,27 +12,27 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link StateMachineService} provides factory access to get new state machines for
|
* The {@link StateService} provides factory access to get new state machines for
|
||||||
* replicating the state of an {@link demo.order.Order} from {@link demo.event.OrderEvents}.
|
* replicating the state of an {@link Order} from {@link OrderEvents}.
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author kbastani
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class StateMachineService {
|
public class StateService {
|
||||||
|
|
||||||
private final StateMachineFactory<OrderStatus, OrderEventType> factory;
|
private final StateMachineFactory<OrderStatus, OrderEventType> factory;
|
||||||
|
|
||||||
public StateMachineService(StateMachineFactory<OrderStatus, OrderEventType> factory) {
|
public StateService(StateMachineFactory<OrderStatus, OrderEventType> factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new state machine that is initially configured and ready for replicating
|
* Create a new state machine that is initially configured and ready for replicating
|
||||||
* the state of an {@link demo.order.Order} from a sequence of {@link demo.event.OrderEvent}.
|
* the state of an {@link Order} from a sequence of {@link OrderEvent}.
|
||||||
*
|
*
|
||||||
* @return a new instance of {@link StateMachine}
|
* @return a new instance of {@link StateMachine}
|
||||||
*/
|
*/
|
||||||
public StateMachine<OrderStatus, OrderEventType> getStateMachine() {
|
public StateMachine<OrderStatus, OrderEventType> newStateMachine() {
|
||||||
// Create a new state machine in its initial state
|
// Create a new state machine in its initial state
|
||||||
StateMachine<OrderStatus, OrderEventType> stateMachine =
|
StateMachine<OrderStatus, OrderEventType> stateMachine =
|
||||||
factory.getStateMachine(UUID.randomUUID().toString());
|
factory.getStateMachine(UUID.randomUUID().toString());
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package demo.event;
|
package demo.order.controller;
|
||||||
|
|
||||||
|
import demo.order.StateFactory;
|
||||||
|
import demo.order.event.OrderEvent;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -13,9 +15,9 @@ import java.util.Optional;
|
|||||||
@RequestMapping("/v1")
|
@RequestMapping("/v1")
|
||||||
public class EventController {
|
public class EventController {
|
||||||
|
|
||||||
private EventService eventService;
|
private StateFactory eventService;
|
||||||
|
|
||||||
public EventController(EventService eventService) {
|
public EventController(StateFactory eventService) {
|
||||||
this.eventService = eventService;
|
this.eventService = eventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.address;
|
package demo.order.domain;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.address;
|
package demo.order.domain;
|
||||||
|
|
||||||
public enum AddressType {
|
public enum AddressType {
|
||||||
SHIPPING,
|
SHIPPING,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.order;
|
package demo.order.domain;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package demo.order;
|
package demo.order.domain;
|
||||||
|
|
||||||
import demo.address.Address;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import demo.address.AddressType;
|
import demo.domain.AbstractEntity;
|
||||||
import demo.domain.BaseEntity;
|
import demo.order.event.OrderEvent;
|
||||||
import demo.event.OrderEvent;
|
import org.springframework.hateoas.Link;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class Order extends BaseEntity {
|
public class Order extends AbstractEntity {
|
||||||
|
|
||||||
private Long orderId;
|
private Long id;
|
||||||
|
|
||||||
private OrderStatus status;
|
private OrderStatus status;
|
||||||
|
|
||||||
@@ -23,19 +23,13 @@ public class Order extends BaseEntity {
|
|||||||
public Order() {
|
public Order() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Order(String accountNumber, Address shippingAddress) {
|
@JsonProperty("orderId")
|
||||||
this();
|
public Long getIdentity() {
|
||||||
this.shippingAddress = shippingAddress;
|
return this.id;
|
||||||
if (shippingAddress.getAddressType() == null)
|
|
||||||
this.shippingAddress.setAddressType(AddressType.SHIPPING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getOrderId() {
|
public void setIdentity(Long id) {
|
||||||
return orderId;
|
this.id = id;
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrderId(Long id) {
|
|
||||||
this.orderId = orderId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderStatus getStatus() {
|
public OrderStatus getStatus() {
|
||||||
@@ -74,14 +68,13 @@ public class Order extends BaseEntity {
|
|||||||
lineItems.add(lineItem);
|
lineItems.add(lineItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public Link getId() {
|
||||||
return "Order{" +
|
return getLink("self");
|
||||||
"orderId=" + orderId +
|
|
||||||
", status=" + status +
|
|
||||||
", events=" + events +
|
|
||||||
", lineItems=" + lineItems +
|
|
||||||
", shippingAddress=" + shippingAddress +
|
|
||||||
"} " + super.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.order;
|
package demo.order.domain;
|
||||||
|
|
||||||
public enum OrderStatus {
|
public enum OrderStatus {
|
||||||
ORDER_CREATED,
|
ORDER_CREATED,
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package demo.event;
|
package demo.order.event;
|
||||||
|
|
||||||
import demo.domain.BaseEntity;
|
import demo.domain.AbstractEntity;
|
||||||
|
|
||||||
public class OrderEvent extends BaseEntity {
|
public class OrderEvent extends AbstractEntity {
|
||||||
|
|
||||||
private OrderEventType type;
|
private OrderEventType type;
|
||||||
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
package demo.stream;
|
package demo.order.event;
|
||||||
|
|
||||||
import demo.event.EventService;
|
import demo.order.StateFactory;
|
||||||
import demo.event.OrderEvent;
|
import demo.order.domain.Order;
|
||||||
import demo.order.Order;
|
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||||
@@ -10,7 +9,7 @@ import org.springframework.cloud.stream.messaging.Sink;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OrderStream} monitors for a variety of {@link OrderEvent} domain
|
* The {@link OrderEventProcessor} monitors for a variety of {@link OrderEvent} domain
|
||||||
* events for an {@link Order}.
|
* events for an {@link Order}.
|
||||||
*
|
*
|
||||||
* @author kbastani
|
* @author kbastani
|
||||||
@@ -18,16 +17,16 @@ import org.springframework.context.annotation.Profile;
|
|||||||
@EnableAutoConfiguration
|
@EnableAutoConfiguration
|
||||||
@EnableBinding(Sink.class)
|
@EnableBinding(Sink.class)
|
||||||
@Profile({"cloud", "development"})
|
@Profile({"cloud", "development"})
|
||||||
public class OrderStream {
|
public class OrderEventProcessor {
|
||||||
|
|
||||||
private EventService eventService;
|
private StateFactory stateFactory;
|
||||||
|
|
||||||
public OrderStream(EventService eventService) {
|
public OrderEventProcessor(StateFactory stateFactory) {
|
||||||
this.eventService = eventService;
|
this.stateFactory = stateFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@StreamListener(Sink.INPUT)
|
@StreamListener(Sink.INPUT)
|
||||||
public void streamListener(OrderEvent orderEvent) {
|
public void streamListener(OrderEvent orderEvent) {
|
||||||
eventService.apply(orderEvent);
|
stateFactory.apply(orderEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.event;
|
package demo.order.event;
|
||||||
|
|
||||||
public enum OrderEventType {
|
public enum OrderEventType {
|
||||||
ORDER_CREATED,
|
ORDER_CREATED,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.event;
|
package demo.order.event;
|
||||||
|
|
||||||
import org.springframework.hateoas.Resources;
|
import org.springframework.hateoas.Resources;
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package demo.payment;
|
package demo.payment.domain;
|
||||||
|
|
||||||
import demo.domain.BaseEntity;
|
import demo.domain.AbstractEntity;
|
||||||
|
|
||||||
public class Payment extends BaseEntity {
|
public class Payment extends AbstractEntity {
|
||||||
|
|
||||||
private Long paymentId;
|
private Long paymentId;
|
||||||
private Double amount;
|
private Double amount;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package demo.payment;
|
package demo.payment.domain;
|
||||||
|
|
||||||
public enum PaymentMethod {
|
public enum PaymentMethod {
|
||||||
CREDIT_CARD
|
CREDIT_CARD
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package demo.payment;
|
package demo.payment.domain;
|
||||||
|
|
||||||
public enum PaymentStatus {
|
public enum PaymentStatus {
|
||||||
PAYMENT_CREATED,
|
PAYMENT_CREATED,
|
||||||
|
ORDER_CONNECTED,
|
||||||
PAYMENT_PENDING,
|
PAYMENT_PENDING,
|
||||||
PAYMENT_PROCESSED,
|
PAYMENT_PROCESSED,
|
||||||
PAYMENT_FAILED,
|
PAYMENT_FAILED,
|
||||||
@@ -13,6 +13,8 @@ spring:
|
|||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
consumer:
|
consumer:
|
||||||
durableSubscription: true
|
durableSubscription: true
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
server:
|
server:
|
||||||
port: 0
|
port: 0
|
||||||
amazon:
|
amazon:
|
||||||
@@ -25,3 +27,5 @@ spring:
|
|||||||
eureka:
|
eureka:
|
||||||
client:
|
client:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
server:
|
||||||
|
port: 0
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: account-worker
|
name: order-worker
|
||||||
---
|
---
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package demo.config;
|
package demo.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -24,6 +25,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
converter.setObjectMapper(objectMapper);
|
converter.setObjectMapper(objectMapper);
|
||||||
converters.add(converter);
|
converters.add(converter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package demo.payment.action;
|
|||||||
|
|
||||||
import demo.domain.Action;
|
import demo.domain.Action;
|
||||||
import demo.payment.domain.Payment;
|
import demo.payment.domain.Payment;
|
||||||
|
import demo.payment.domain.PaymentModule;
|
||||||
|
import demo.payment.domain.PaymentService;
|
||||||
|
import demo.payment.domain.PaymentStatus;
|
||||||
|
import demo.payment.event.PaymentEvent;
|
||||||
|
import demo.payment.event.PaymentEventType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -9,6 +14,17 @@ import java.util.function.BiConsumer;
|
|||||||
@Service
|
@Service
|
||||||
public class ConnectOrder extends Action<Payment> {
|
public class ConnectOrder extends Action<Payment> {
|
||||||
public BiConsumer<Payment, Long> getConsumer() {
|
public BiConsumer<Payment, Long> getConsumer() {
|
||||||
return Payment::setOrderId;
|
return (payment, orderId) -> {
|
||||||
|
PaymentService paymentService = payment.getModule(PaymentModule.class)
|
||||||
|
.getDefaultService();
|
||||||
|
|
||||||
|
// Connect the payment to the order
|
||||||
|
payment.setOrderId(orderId);
|
||||||
|
payment.setStatus(PaymentStatus.ORDER_CONNECTED);
|
||||||
|
payment = paymentService.update(payment);
|
||||||
|
|
||||||
|
// Trigger the payment connected
|
||||||
|
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.ORDER_CONNECTED, payment));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ import demo.event.Events;
|
|||||||
import demo.payment.event.PaymentEvent;
|
import demo.payment.event.PaymentEvent;
|
||||||
import demo.payment.domain.Payment;
|
import demo.payment.domain.Payment;
|
||||||
import demo.payment.domain.PaymentService;
|
import demo.payment.domain.PaymentService;
|
||||||
import org.springframework.hateoas.ExposesResourceFor;
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
import org.springframework.hateoas.LinkBuilder;
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
import org.springframework.hateoas.Resource;
|
import org.springframework.hateoas.*;
|
||||||
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.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||||
|
|
||||||
@@ -32,10 +33,13 @@ public class PaymentController {
|
|||||||
|
|
||||||
private final PaymentService paymentService;
|
private final PaymentService paymentService;
|
||||||
private final EventService<PaymentEvent, Long> eventService;
|
private final EventService<PaymentEvent, Long> eventService;
|
||||||
|
private final DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
public PaymentController(PaymentService paymentService, EventService<PaymentEvent, Long> eventService) {
|
public PaymentController(PaymentService paymentService, EventService<PaymentEvent, Long> eventService,
|
||||||
|
DiscoveryClient discoveryClient) {
|
||||||
this.paymentService = paymentService;
|
this.paymentService = paymentService;
|
||||||
this.eventService = eventService;
|
this.eventService = eventService;
|
||||||
|
this.discoveryClient = discoveryClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(path = "/payments", method = RequestMethod.POST)
|
@RequestMapping(path = "/payments", method = RequestMethod.POST)
|
||||||
@@ -209,6 +213,13 @@ public class PaymentController {
|
|||||||
payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
|
payment.add(linkBuilder("getPaymentEvents", payment.getIdentity()).withRel("events"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add remote payment link
|
||||||
|
if (payment.getOrderId() != null) {
|
||||||
|
Link result = getRemoteLink("order-web", "/v1/orders/{id}", payment.getOrderId(), "order ");
|
||||||
|
if (result != null)
|
||||||
|
payment.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
return new Resource<>(payment);
|
return new Resource<>(payment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,4 +228,19 @@ public class PaymentController {
|
|||||||
payment.setIdentity(id);
|
payment.setIdentity(id);
|
||||||
return new Resource<>(payment.getCommands());
|
return new Resource<>(payment.getCommands());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Link getRemoteLink(String service, String relative, Object identifier, String rel) {
|
||||||
|
Link result = null;
|
||||||
|
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(service);
|
||||||
|
if (serviceInstances.size() > 0) {
|
||||||
|
ServiceInstance serviceInstance = serviceInstances.get(new Random().nextInt(serviceInstances.size()));
|
||||||
|
result = new Link(new UriTemplate(serviceInstance.getUri()
|
||||||
|
.toString()
|
||||||
|
.concat(relative)).with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||||
|
.expand(identifier)
|
||||||
|
.toString())
|
||||||
|
.withRel(rel);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package demo.payment.domain;
|
package demo.payment.domain;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import demo.domain.AbstractEntity;
|
import demo.domain.AbstractEntity;
|
||||||
import demo.domain.Command;
|
import demo.domain.Command;
|
||||||
import demo.payment.event.PaymentEvent;
|
|
||||||
import demo.payment.action.ConnectOrder;
|
import demo.payment.action.ConnectOrder;
|
||||||
import demo.payment.action.ProcessPayment;
|
import demo.payment.action.ProcessPayment;
|
||||||
import demo.payment.controller.PaymentController;
|
import demo.payment.controller.PaymentController;
|
||||||
|
import demo.payment.event.PaymentEvent;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
@@ -41,6 +40,7 @@ public class Payment extends AbstractEntity<PaymentEvent, Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Payment(Double amount, PaymentMethod paymentMethod) {
|
public Payment(Double amount, PaymentMethod paymentMethod) {
|
||||||
|
this();
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.paymentMethod = paymentMethod;
|
this.paymentMethod = paymentMethod;
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,6 @@ public class Payment extends AbstractEntity<PaymentEvent, Long> {
|
|||||||
this.paymentMethod = paymentMethod;
|
this.paymentMethod = paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Long getOrderId() {
|
public Long getOrderId() {
|
||||||
return orderId;
|
return orderId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ import org.springframework.util.Assert;
|
|||||||
public class PaymentService extends Service<Payment, Long> {
|
public class PaymentService extends Service<Payment, Long> {
|
||||||
|
|
||||||
private final PaymentRepository paymentRepository;
|
private final PaymentRepository paymentRepository;
|
||||||
private final EventService<PaymentEvent, Long> eventService;
|
|
||||||
|
|
||||||
public PaymentService(PaymentRepository paymentRepository, EventService<PaymentEvent, Long> eventService) {
|
public PaymentService(PaymentRepository paymentRepository, EventService<PaymentEvent, Long> eventService) {
|
||||||
this.paymentRepository = paymentRepository;
|
this.paymentRepository = paymentRepository;
|
||||||
this.eventService = eventService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Payment registerPayment(Payment payment) {
|
public Payment registerPayment(Payment payment) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import demo.payment.event.PaymentEvent;
|
|||||||
*/
|
*/
|
||||||
public enum PaymentStatus {
|
public enum PaymentStatus {
|
||||||
PAYMENT_CREATED,
|
PAYMENT_CREATED,
|
||||||
|
ORDER_CONNECTED,
|
||||||
PAYMENT_PENDING,
|
PAYMENT_PENDING,
|
||||||
PAYMENT_PROCESSED,
|
PAYMENT_PROCESSED,
|
||||||
PAYMENT_FAILED,
|
PAYMENT_FAILED,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import demo.payment.domain.PaymentStatus;
|
|||||||
*/
|
*/
|
||||||
public enum PaymentEventType {
|
public enum PaymentEventType {
|
||||||
PAYMENT_CREATED,
|
PAYMENT_CREATED,
|
||||||
|
ORDER_CONNECTED,
|
||||||
PAYMENT_PENDING,
|
PAYMENT_PENDING,
|
||||||
PAYMENT_PROCESSED,
|
PAYMENT_PROCESSED,
|
||||||
PAYMENT_FAILED,
|
PAYMENT_FAILED,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ spring:
|
|||||||
output:
|
output:
|
||||||
destination: payment
|
destination: payment
|
||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
redis:
|
redis:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -25,6 +27,9 @@ public class EventServiceTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private EventService<PaymentEvent, Long> eventService;
|
private EventService<PaymentEvent, Long> eventService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getPaymentReturnsPayment() throws Exception {
|
public void getPaymentReturnsPayment() throws Exception {
|
||||||
Payment payment = new Payment(11.0, PaymentMethod.CREDIT_CARD);
|
Payment payment = new Payment(11.0, PaymentMethod.CREDIT_CARD);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.junit.runner.RunWith;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
@@ -39,6 +40,9 @@ public class PaymentControllerTest {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private EventService<PaymentEvent, Long> eventService;
|
private EventService<PaymentEvent, Long> eventService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getUserPaymentResourceShouldReturnPayment() throws Exception {
|
public void getUserPaymentResourceShouldReturnPayment() throws Exception {
|
||||||
String content = "{\"paymentMethod\": \"CREDIT_CARD\", \"amount\": 42.0 }";
|
String content = "{\"paymentMethod\": \"CREDIT_CARD\", \"amount\": 42.0 }";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ public class PaymentServiceTests {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private PaymentRepository paymentRepository;
|
private PaymentRepository paymentRepository;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
private PaymentService paymentService;
|
private PaymentService paymentService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<Paymen
|
|||||||
.and()
|
.and()
|
||||||
.withExternal()
|
.withExternal()
|
||||||
.source(PaymentStatus.PAYMENT_CREATED)
|
.source(PaymentStatus.PAYMENT_CREATED)
|
||||||
|
.target(PaymentStatus.ORDER_CONNECTED)
|
||||||
|
.event(PaymentEventType.ORDER_CONNECTED)
|
||||||
|
.action(orderConnected())
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(PaymentStatus.ORDER_CONNECTED)
|
||||||
.target(PaymentStatus.PAYMENT_PENDING)
|
.target(PaymentStatus.PAYMENT_PENDING)
|
||||||
.event(PaymentEventType.PAYMENT_PENDING)
|
.event(PaymentEventType.PAYMENT_PENDING)
|
||||||
.action(paymentPending())
|
.action(paymentPending())
|
||||||
@@ -149,6 +155,23 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<Paymen
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Action<PaymentStatus, PaymentEventType> orderConnected() {
|
||||||
|
return context -> applyEvent(context,
|
||||||
|
new OrderConnected(context, event -> {
|
||||||
|
log.info(event.getType() + ": " + event.getLink("payment").getHref());
|
||||||
|
// Get the payment resource for the event
|
||||||
|
Traverson traverson = new Traverson(
|
||||||
|
URI.create(event.getLink("payment").getHref()),
|
||||||
|
MediaTypes.HAL_JSON
|
||||||
|
);
|
||||||
|
|
||||||
|
return traverson.follow("self")
|
||||||
|
.toEntity(Payment.class)
|
||||||
|
.getBody();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Action<PaymentStatus, PaymentEventType> paymentPending() {
|
public Action<PaymentStatus, PaymentEventType> paymentPending() {
|
||||||
return context -> applyEvent(context,
|
return context -> applyEvent(context,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import demo.payment.PaymentStatus;
|
|||||||
*/
|
*/
|
||||||
public enum PaymentEventType {
|
public enum PaymentEventType {
|
||||||
PAYMENT_CREATED,
|
PAYMENT_CREATED,
|
||||||
|
ORDER_CONNECTED,
|
||||||
PAYMENT_PENDING,
|
PAYMENT_PENDING,
|
||||||
PAYMENT_PROCESSED,
|
PAYMENT_PROCESSED,
|
||||||
PAYMENT_FAILED,
|
PAYMENT_FAILED,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package demo.function;
|
||||||
|
|
||||||
|
import demo.event.PaymentEvent;
|
||||||
|
import demo.event.PaymentEventType;
|
||||||
|
import demo.payment.Payment;
|
||||||
|
import demo.payment.PaymentStatus;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.springframework.statemachine.StateContext;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class OrderConnected extends PaymentFunction {
|
||||||
|
|
||||||
|
final private Logger log = Logger.getLogger(OrderConnected.class);
|
||||||
|
|
||||||
|
public OrderConnected(StateContext<PaymentStatus, PaymentEventType> context, Function<PaymentEvent, Payment> lambda) {
|
||||||
|
super(context, lambda);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an {@link PaymentEvent} to the lambda function that was provided through the
|
||||||
|
* constructor of this {@link PaymentFunction}.
|
||||||
|
*
|
||||||
|
* @param event is the {@link PaymentEvent} to apply to the lambda function
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Payment apply(PaymentEvent event) {
|
||||||
|
log.info("Executing workflow for order connected...");
|
||||||
|
return super.apply(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ public class Payment extends BaseEntity {
|
|||||||
private Double amount;
|
private Double amount;
|
||||||
private PaymentMethod paymentMethod;
|
private PaymentMethod paymentMethod;
|
||||||
private PaymentStatus status;
|
private PaymentStatus status;
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
public Payment() {
|
public Payment() {
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,14 @@ public class Payment extends BaseEntity {
|
|||||||
this.paymentMethod = paymentMethod;
|
this.paymentMethod = paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(Long orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Payment{" +
|
return "Payment{" +
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package demo.payment;
|
|||||||
*/
|
*/
|
||||||
public enum PaymentStatus {
|
public enum PaymentStatus {
|
||||||
PAYMENT_CREATED,
|
PAYMENT_CREATED,
|
||||||
|
ORDER_CONNECTED,
|
||||||
PAYMENT_PENDING,
|
PAYMENT_PENDING,
|
||||||
PAYMENT_PROCESSED,
|
PAYMENT_PROCESSED,
|
||||||
PAYMENT_FAILED,
|
PAYMENT_FAILED,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ spring:
|
|||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
consumer:
|
consumer:
|
||||||
durableSubscription: true
|
durableSubscription: true
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
server:
|
server:
|
||||||
port: 0
|
port: 0
|
||||||
amazon:
|
amazon:
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public abstract class Aggregate<E extends Event, ID extends Serializable> extend
|
|||||||
@JsonIgnore
|
@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 {
|
||||||
Module provider = getProvider();
|
Module provider = getModule();
|
||||||
Service service = provider.getDefaultService();
|
Service service = provider.getDefaultService();
|
||||||
return (T) service.getAction(actionType);
|
return (T) service.getAction(actionType);
|
||||||
}
|
}
|
||||||
@@ -59,8 +59,8 @@ public abstract class Aggregate<E extends Event, ID extends Serializable> extend
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public <T extends Module<A>, A extends Aggregate<E, ID>> T getProvider() throws IllegalArgumentException {
|
public <T extends Module<A>, A extends Aggregate<E, ID>> T getModule() throws IllegalArgumentException {
|
||||||
return getProvider((Class<T>) ResolvableType
|
return getModule((Class<T>) ResolvableType
|
||||||
.forClassWithGenerics(Module.class, ResolvableType.forInstance(this))
|
.forClassWithGenerics(Module.class, ResolvableType.forInstance(this))
|
||||||
.getRawClass());
|
.getRawClass());
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ public abstract class Aggregate<E extends Event, ID extends Serializable> extend
|
|||||||
* @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
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public <T extends Module<A>, A extends Aggregate<E, ID>> T getProvider(Class<T> providerType) throws
|
public <T extends Module<A>, A extends Aggregate<E, ID>> T getModule(Class<T> providerType) throws
|
||||||
IllegalArgumentException {
|
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);
|
||||||
@@ -167,13 +167,13 @@ public abstract class Aggregate<E extends Event, ID extends Serializable> extend
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
protected Service<Aggregate<E, ID>, ID> getEntityService() {
|
protected Service<Aggregate<E, ID>, ID> getEntityService() {
|
||||||
return (Service<Aggregate<E, ID>, ID>) getProvider().getDefaultService();
|
return (Service<Aggregate<E, ID>, ID>) getModule().getDefaultService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
protected EventService<E, ID> getEventService() {
|
protected EventService<E, ID> getEventService() {
|
||||||
return (EventService<E, ID>) getProvider().getDefaultEventService();
|
return (EventService<E, ID>) getModule().getDefaultEventService();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CommandResources extends ResourceSupport {
|
public static class CommandResources extends ResourceSupport {
|
||||||
|
|||||||
@@ -42,18 +42,18 @@ public class ProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetProviderReturnsProvider() {
|
public void testGetProviderReturnsProvider() {
|
||||||
assertNotNull(new EmptyAggregate().getProvider(EmptyProvider.class));
|
assertNotNull(new EmptyAggregate().getModule(EmptyProvider.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetServiceReturnsService() {
|
public void testGetServiceReturnsService() {
|
||||||
EmptyProvider provider = new EmptyAggregate().getProvider(EmptyProvider.class);
|
EmptyProvider provider = new EmptyAggregate().getModule(EmptyProvider.class);
|
||||||
assertNotNull(provider.getEmptyService());
|
assertNotNull(provider.getEmptyService());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetActionReturnsAction() {
|
public void testGetActionReturnsAction() {
|
||||||
EmptyProvider provider = new EmptyAggregate().getProvider(EmptyProvider.class);
|
EmptyProvider provider = new EmptyAggregate().getModule(EmptyProvider.class);
|
||||||
EmptyService service = provider.getEmptyService();
|
EmptyService service = provider.getEmptyService();
|
||||||
assertNotNull(service.getAction(EmptyAction.class));
|
assertNotNull(service.getAction(EmptyAction.class));
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ public class ProviderTests {
|
|||||||
@Test
|
@Test
|
||||||
public void testProcessCommandChangesStatus() {
|
public void testProcessCommandChangesStatus() {
|
||||||
EmptyAggregate aggregate = new EmptyAggregate(0L, AggregateStatus.CREATED);
|
EmptyAggregate aggregate = new EmptyAggregate(0L, AggregateStatus.CREATED);
|
||||||
EmptyProvider provider = new EmptyAggregate().getProvider(EmptyProvider.class);
|
EmptyProvider provider = new EmptyAggregate().getModule(EmptyProvider.class);
|
||||||
EmptyService service = provider.getEmptyService();
|
EmptyService service = provider.getEmptyService();
|
||||||
EmptyAction emptyAction = service.getAction(EmptyAction.class);
|
EmptyAction emptyAction = service.getAction(EmptyAction.class);
|
||||||
emptyAction.getConsumer().accept(aggregate);
|
emptyAction.getConsumer().accept(aggregate);
|
||||||
@@ -107,7 +107,7 @@ public class ProviderTests {
|
|||||||
|
|
||||||
@Command(controller = EmptyController.class, method = "emptyAction")
|
@Command(controller = EmptyController.class, method = "emptyAction")
|
||||||
public void emptyAction() {
|
public void emptyAction() {
|
||||||
EmptyProvider emptyProvider = this.getProvider();
|
EmptyProvider emptyProvider = this.getModule();
|
||||||
emptyProvider.getEmptyService()
|
emptyProvider.getEmptyService()
|
||||||
.getAction(EmptyAction.class)
|
.getAction(EmptyAction.class)
|
||||||
.getConsumer()
|
.getConsumer()
|
||||||
|
|||||||
Reference in New Issue
Block a user