Consistency testing
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,6 +9,8 @@ dump.rdb
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
.DS_Store
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
services:
|
||||
- rabbitmq
|
||||
- redis
|
||||
- docker
|
||||
install: mvn clean install -DskipDockerBuild
|
||||
before_install:
|
||||
- docker pull spotify/kafka
|
||||
- docker run -p 2181:2181 -p 9092:9092 spotify/kafka
|
||||
@@ -41,7 +41,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -9,10 +9,10 @@ import demo.account.event.AccountEventType;
|
||||
import demo.domain.Action;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.account.domain.AccountStatus.*;
|
||||
|
||||
@@ -22,38 +22,35 @@ import static demo.account.domain.AccountStatus.*;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ActivateAccount extends Action<Account> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Account, Account> getFunction() {
|
||||
return (account) -> {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_ACTIVE, "The account is already active");
|
||||
Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
|
||||
.contains(account.getStatus()), "The account cannot be activated");
|
||||
public Account apply(Account account) {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_ACTIVE, "The account is already active");
|
||||
Assert.isTrue(Arrays.asList(ACCOUNT_CONFIRMED, ACCOUNT_SUSPENDED, ACCOUNT_ARCHIVED)
|
||||
.contains(account.getStatus()), "The account cannot be activated");
|
||||
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
AccountStatus status = account.getStatus();
|
||||
AccountStatus status = account.getStatus();
|
||||
|
||||
// Activate the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_ACTIVE);
|
||||
// Activate the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_ACTIVE);
|
||||
account = accountService.update(account);
|
||||
|
||||
try {
|
||||
// Trigger the account activated event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be activated", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
account = accountService.update(account);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the account activated event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be activated", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
accountService.update(account);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ import demo.account.event.AccountEventType;
|
||||
import demo.domain.Action;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_ACTIVE;
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_ARCHIVED;
|
||||
|
||||
@@ -22,38 +21,35 @@ import static demo.account.domain.AccountStatus.ACCOUNT_ARCHIVED;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ArchiveAccount extends Action<Account> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Account, Account> getFunction() {
|
||||
return (account) -> {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_ARCHIVED, "The account is already archived");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
||||
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
public Account apply(Account account) {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_ARCHIVED, "The account is already archived");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
||||
|
||||
AccountStatus status = account.getStatus();
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
// Archive the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_ARCHIVED);
|
||||
AccountStatus status = account.getStatus();
|
||||
|
||||
// Archive the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_ARCHIVED);
|
||||
account = accountService.update(account);
|
||||
|
||||
try {
|
||||
// Trigger the account archived event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be archived", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
account = accountService.update(account);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the account archived event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be archived", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
accountService.update(account);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ import demo.account.event.AccountEventType;
|
||||
import demo.domain.Action;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_CONFIRMED;
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_PENDING;
|
||||
|
||||
@@ -22,38 +21,35 @@ import static demo.account.domain.AccountStatus.ACCOUNT_PENDING;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ConfirmAccount extends Action<Account> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Account, Account> getFunction() {
|
||||
return (account) -> {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_CONFIRMED, "The account has already been confirmed");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_PENDING, "The account has already been confirmed");
|
||||
public Account apply(Account account) {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_CONFIRMED, "The account has already been confirmed");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_PENDING, "The account has already been confirmed");
|
||||
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
AccountStatus status = account.getStatus();
|
||||
AccountStatus status = account.getStatus();
|
||||
|
||||
// Activate the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_CONFIRMED);
|
||||
// Activate the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_CONFIRMED);
|
||||
account = accountService.update(account);
|
||||
|
||||
try {
|
||||
// Trigger the account confirmed event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be confirmed", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
account = accountService.update(account);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the account confirmed event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be confirmed", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
accountService.update(account);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import demo.domain.Action;
|
||||
import demo.order.domain.OrderModule;
|
||||
import demo.order.domain.Orders;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.Function;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Query action to get {@link demo.order.domain.Order}s for an an {@link Account}
|
||||
@@ -14,6 +13,7 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class GetOrders extends Action<Account> {
|
||||
|
||||
private OrderModule orderModule;
|
||||
@@ -22,11 +22,9 @@ public class GetOrders extends Action<Account> {
|
||||
this.orderModule = orderModule;
|
||||
}
|
||||
|
||||
public Function<Account, Orders> getFunction() {
|
||||
return (account) -> {
|
||||
// Get orders from the order service
|
||||
return orderModule.getDefaultService()
|
||||
.findOrdersByAccountId(account.getIdentity());
|
||||
};
|
||||
public Orders apply(Account account) {
|
||||
// Get orders from the order service
|
||||
return orderModule.getDefaultService()
|
||||
.findOrdersByAccountId(account.getIdentity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
|
||||
@@ -16,7 +17,6 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_ACTIVE;
|
||||
|
||||
@@ -26,33 +26,32 @@ import static demo.account.domain.AccountStatus.ACCOUNT_ACTIVE;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class PostOrder extends Action<Account> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Account, Order, Order> getFunction() {
|
||||
return (account, order) -> {
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "Only active accounts can create an order");
|
||||
order = order.post();
|
||||
public Order apply(Account account, Order order) {
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "Only active accounts can create an order");
|
||||
order = order.post();
|
||||
|
||||
try {
|
||||
// Create traverson for the new order
|
||||
Traverson traverson = new Traverson(URI.create(order.getLink("self")
|
||||
.getHref()), MediaTypes.HAL_JSON);
|
||||
try {
|
||||
// Create traverson for the new order
|
||||
Traverson traverson = new Traverson(URI.create(order.getLink("self")
|
||||
.getHref()), MediaTypes.HAL_JSON);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("accountId", account.getIdentity());
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("accountId", account.getIdentity());
|
||||
|
||||
order = traverson.follow("commands", "connectAccount")
|
||||
.withTemplateParameters(params)
|
||||
.toObject(Order.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("New order could not be posted for the account", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex));
|
||||
}
|
||||
order = traverson.follow("commands", "connectAccount")
|
||||
.withTemplateParameters(params)
|
||||
.toObject(Order.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("New order could not be posted for the account", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex));
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
|
||||
private String getHttpStatusMessage(RestClientResponseException ex) {
|
||||
|
||||
@@ -9,10 +9,9 @@ import demo.account.event.AccountEventType;
|
||||
import demo.domain.Action;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_ACTIVE;
|
||||
import static demo.account.domain.AccountStatus.ACCOUNT_SUSPENDED;
|
||||
|
||||
@@ -22,38 +21,35 @@ import static demo.account.domain.AccountStatus.ACCOUNT_SUSPENDED;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class SuspendAccount extends Action<Account> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Account, Account> getFunction() {
|
||||
return (account) -> {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_SUSPENDED, "The account is already suspended");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
||||
public Account apply(Account account) {
|
||||
Assert.isTrue(account.getStatus() != ACCOUNT_SUSPENDED, "The account is already suspended");
|
||||
Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
||||
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
AccountService accountService = account.getModule(AccountModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
AccountStatus status = account.getStatus();
|
||||
AccountStatus status = account.getStatus();
|
||||
|
||||
// Suspend the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_SUSPENDED);
|
||||
// Suspend the account
|
||||
account.setStatus(AccountStatus.ACCOUNT_SUSPENDED);
|
||||
account = accountService.update(account);
|
||||
|
||||
try {
|
||||
// Trigger the account suspended event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be suspended", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
account = accountService.update(account);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the account suspended event
|
||||
account.sendAsyncEvent(new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED, account));
|
||||
} catch (Exception ex) {
|
||||
log.error("Account could not be suspended", ex);
|
||||
|
||||
// Rollback the operation
|
||||
account.setStatus(status);
|
||||
accountService.update(account);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,42 +88,36 @@ public class Account extends AbstractEntity<AccountEvent, Long> {
|
||||
@JsonIgnore
|
||||
public Orders getOrders() {
|
||||
return getAction(GetOrders.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "activate", controller = AccountController.class)
|
||||
public Account activate() {
|
||||
return getAction(ActivateAccount.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "archive", controller = AccountController.class)
|
||||
public Account archive() {
|
||||
return getAction(ArchiveAccount.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "confirm", controller = AccountController.class)
|
||||
public Account confirm() {
|
||||
return getAction(ConfirmAccount.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "suspend", controller = AccountController.class)
|
||||
public Account suspend() {
|
||||
return getAction(SuspendAccount.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "postOrder", controller = AccountController.class)
|
||||
public Order postOrder(Order order) {
|
||||
return getAction(PostOrder.class)
|
||||
.getFunction()
|
||||
.apply(this, order);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
output:
|
||||
destination: account
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -28,11 +28,12 @@ eureka:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
input:
|
||||
destination: account
|
||||
|
||||
@@ -10,10 +10,13 @@ redis:
|
||||
container_name: redis
|
||||
image: redis:latest
|
||||
net: host
|
||||
rabbit:
|
||||
container_name: rabbit
|
||||
image: rabbitmq:3-management
|
||||
kafka:
|
||||
container_name: kafka
|
||||
image: spotify/kafka:latest
|
||||
net: host
|
||||
ports:
|
||||
- 2181:2181
|
||||
- 9092:9092
|
||||
account-web:
|
||||
image: account-web
|
||||
environment:
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -12,49 +12,46 @@ import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects an {@link Order} to an Account.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class AddReservation extends Action<Order> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Order, Long, Order> getFunction() {
|
||||
return (order, reservationId) -> {
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.RESERVATION_PENDING, "Order must be in a pending reservation state");
|
||||
Assert.isTrue(!order.getReservationIds().contains(reservationId), "Reservation already added to order");
|
||||
public Order apply(Order order, Long reservationId) {
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.RESERVATION_PENDING, "Order must be in a pending reservation state");
|
||||
Assert.isTrue(!order.getReservationIds().contains(reservationId), "Reservation already added to order");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
order.getReservationIds().add(reservationId);
|
||||
order.getReservationIds().add(reservationId);
|
||||
order = orderService.update(order);
|
||||
|
||||
Link reservationLink = new Link(new UriTemplate("http://warehouse-web/v1/reservations/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(reservationId)
|
||||
.toString()).withRel("reservation");
|
||||
|
||||
try {
|
||||
// Trigger reservation added event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_ADDED, order), reservationLink);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not add reservation to order", ex);
|
||||
order.getReservationIds().remove(reservationId);
|
||||
order.setStatus(OrderStatus.RESERVATION_FAILED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_FAILED, order), reservationLink);
|
||||
}
|
||||
|
||||
Link reservationLink = new Link(new UriTemplate("http://warehouse-web/v1/reservations/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(reservationId)
|
||||
.toString()).withRel("reservation");
|
||||
|
||||
try {
|
||||
// Trigger reservation added event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_ADDED, order), reservationLink);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not add reservation to order", ex);
|
||||
order.getReservationIds().remove(reservationId);
|
||||
order.setStatus(OrderStatus.RESERVATION_FAILED);
|
||||
orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_FAILED, order), reservationLink);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Completes the {@link Order} and applies a final status
|
||||
@@ -20,39 +20,37 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class CompleteOrder extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(CompleteOrder.class);
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return (order) -> {
|
||||
Assert.isTrue(Arrays.asList(OrderStatus.PAYMENT_FAILED,
|
||||
OrderStatus.PAYMENT_SUCCEEDED,
|
||||
OrderStatus.RESERVATION_FAILED).contains(order.getStatus()), "Order must be in a terminal state");
|
||||
public Order apply(Order order) {
|
||||
Assert.isTrue(Arrays.asList(OrderStatus.PAYMENT_FAILED,
|
||||
OrderStatus.PAYMENT_SUCCEEDED,
|
||||
OrderStatus.RESERVATION_FAILED).contains(order.getStatus()), "Order must be in a terminal state");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
OrderStatus status = order.getStatus();
|
||||
OrderStatus status = order.getStatus();
|
||||
|
||||
try {
|
||||
if (order.getStatus() == OrderStatus.PAYMENT_SUCCEEDED) {
|
||||
order.setStatus(OrderStatus.ORDER_SUCCEEDED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_SUCCEEDED, order));
|
||||
} else {
|
||||
order.setStatus(OrderStatus.ORDER_FAILED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_FAILED, order));
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error completing the order", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
try {
|
||||
if (order.getStatus() == OrderStatus.PAYMENT_SUCCEEDED) {
|
||||
order.setStatus(OrderStatus.ORDER_SUCCEEDED);
|
||||
order = orderService.update(order);
|
||||
throw ex;
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_SUCCEEDED, order));
|
||||
} else {
|
||||
order.setStatus(OrderStatus.ORDER_FAILED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ORDER_FAILED, order));
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error completing the order", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import demo.reservation.domain.Reservation;
|
||||
import demo.reservation.domain.ReservationStatus;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static demo.order.domain.OrderStatus.RESERVATION_FAILED;
|
||||
@@ -26,56 +26,54 @@ import static demo.order.domain.OrderStatus.RESERVATION_SUCCEEDED;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class CompleteReservation extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(CompleteReservation.class);
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return (order) -> {
|
||||
if (order.getStatus() != RESERVATION_SUCCEEDED && order.getStatus() != RESERVATION_FAILED) {
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.RESERVATION_PENDING,
|
||||
"The order must be in a reservation pending state");
|
||||
} else {
|
||||
// Reservation has already completed
|
||||
return order;
|
||||
}
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
OrderStatus status = order.getStatus();
|
||||
|
||||
try {
|
||||
List<Reservation> reservations = order.getReservations().getContent().stream()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Check if all inventory has been reserved
|
||||
Boolean orderReserved = reservations.stream()
|
||||
.allMatch(r -> r.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
|
||||
// Check if any inventory reservations have failed
|
||||
Boolean reservationFailed = reservations.stream()
|
||||
.anyMatch(r -> r.getStatus() == ReservationStatus.RESERVATION_FAILED);
|
||||
|
||||
if (orderReserved && order.getStatus() == OrderStatus.RESERVATION_PENDING) {
|
||||
// Succeed the reservation and commit all inventory associated with order
|
||||
order.setStatus(RESERVATION_SUCCEEDED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_SUCCEEDED, order));
|
||||
} else if (reservationFailed && order.getStatus() == OrderStatus.RESERVATION_PENDING) {
|
||||
// Fail the reservation and release all inventory associated with order
|
||||
order.setStatus(RESERVATION_FAILED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_FAILED, order));
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error completing reservation", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
order = orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
public Order apply(Order order) {
|
||||
if (order.getStatus() != RESERVATION_SUCCEEDED && order.getStatus() != RESERVATION_FAILED) {
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.RESERVATION_PENDING,
|
||||
"The order must be in a reservation pending state");
|
||||
} else {
|
||||
// Reservation has already completed
|
||||
return order;
|
||||
};
|
||||
}
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
OrderStatus status = order.getStatus();
|
||||
|
||||
try {
|
||||
List<Reservation> reservations = order.getReservations().getContent().stream()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Check if all inventory has been reserved
|
||||
Boolean orderReserved = reservations.stream()
|
||||
.allMatch(r -> r.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
|
||||
// Check if any inventory reservations have failed
|
||||
Boolean reservationFailed = reservations.stream()
|
||||
.anyMatch(r -> r.getStatus() == ReservationStatus.RESERVATION_FAILED);
|
||||
|
||||
if (orderReserved && order.getStatus() == OrderStatus.RESERVATION_PENDING) {
|
||||
// Succeed the reservation and commit all inventory associated with order
|
||||
order.setStatus(RESERVATION_SUCCEEDED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_SUCCEEDED, order));
|
||||
} else if (reservationFailed && order.getStatus() == OrderStatus.RESERVATION_PENDING) {
|
||||
// Fail the reservation and release all inventory associated with order
|
||||
order.setStatus(RESERVATION_FAILED);
|
||||
order = orderService.update(order);
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.RESERVATION_FAILED, order));
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error completing reservation", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,43 +9,40 @@ import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects an {@link Order} to an Account.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ConnectAccount extends Action<Order> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Order, Long, Order> getFunction() {
|
||||
return (order, accountId) -> {
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.ORDER_CREATED, "Order must be in a created state");
|
||||
public Order apply(Order order, Long accountId) {
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.ORDER_CREATED, "Order must be in a created state");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Connect the account
|
||||
order.setAccountId(accountId);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
// Connect the account
|
||||
order.setAccountId(accountId);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
// Trigger the account connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ACCOUNT_CONNECTED, order));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect order to account", ex);
|
||||
order.setAccountId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the account connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.ACCOUNT_CONNECTED, order));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect order to account", ex);
|
||||
order.setAccountId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,43 +10,40 @@ import demo.order.event.OrderEventType;
|
||||
import demo.payment.domain.Payment;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects a {@link Payment} to an {@link Order}.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ConnectPayment extends Action<Order> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Order, Long, Order> getFunction() {
|
||||
return (order, paymentId) -> {
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.PAYMENT_CREATED, "Order must be in a payment created state");
|
||||
public Order apply(Order order, Long paymentId) {
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.PAYMENT_CREATED, "Order must be in a payment created state");
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Connect the payment
|
||||
order.setPaymentId(paymentId);
|
||||
order.setStatus(OrderStatus.PAYMENT_CONNECTED);
|
||||
// Connect the payment
|
||||
order.setPaymentId(paymentId);
|
||||
order.setStatus(OrderStatus.PAYMENT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
// Trigger the payment connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.PAYMENT_CONNECTED, order));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect payment to order", ex);
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the payment connected event
|
||||
order.sendAsyncEvent(new OrderEvent(OrderEventType.PAYMENT_CONNECTED, order));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect payment to order", ex);
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ORDER_CREATED);
|
||||
orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import demo.payment.domain.PaymentMethod;
|
||||
import demo.payment.domain.PaymentService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Creates a {@link Payment} for an {@link Order}.
|
||||
@@ -23,6 +23,7 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class CreatePayment extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
@@ -32,53 +33,49 @@ public class CreatePayment extends Action<Order> {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return order -> {
|
||||
Assert.isTrue(order.getPaymentId() == null, "Payment has already been created");
|
||||
Assert.isTrue(!Arrays.asList(OrderStatus.PAYMENT_CREATED,
|
||||
OrderStatus.PAYMENT_CONNECTED,
|
||||
OrderStatus.PAYMENT_SUCCEEDED,
|
||||
OrderStatus.PAYMENT_PENDING).contains(order.getStatus()), "Payment has already been created");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.RESERVATION_SUCCEEDED,
|
||||
"Inventory reservations for the order must be made first before creating a payment");
|
||||
public Order apply(Order order) {
|
||||
Assert.isTrue(order.getPaymentId() == null, "Payment has already been created");
|
||||
Assert.isTrue(!Arrays.asList(OrderStatus.PAYMENT_CREATED,
|
||||
OrderStatus.PAYMENT_CONNECTED,
|
||||
OrderStatus.PAYMENT_SUCCEEDED,
|
||||
OrderStatus.PAYMENT_PENDING).contains(order.getStatus()), "Payment has already been created");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.RESERVATION_SUCCEEDED,
|
||||
"Inventory reservations for the order must be made first before creating a payment");
|
||||
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_PENDING);
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_PENDING);
|
||||
order = orderService.update(order);
|
||||
|
||||
Payment payment = new Payment();
|
||||
payment.setAmount(order.calculateTotal());
|
||||
payment.setPaymentMethod(PaymentMethod.CREDIT_CARD);
|
||||
payment = paymentService.create(payment);
|
||||
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_CREATED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_CREATED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger payment created event
|
||||
order.sendAsyncEvent(event);
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be created", ex);
|
||||
|
||||
// Rollback the payment creation
|
||||
if (payment.getIdentity() != null)
|
||||
paymentService.delete(payment.getIdentity());
|
||||
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
Payment payment = new Payment();
|
||||
payment.setAmount(order.calculateTotal());
|
||||
payment.setPaymentMethod(PaymentMethod.CREDIT_CARD);
|
||||
payment = paymentService.create(payment);
|
||||
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_CREATED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_CREATED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger payment created event
|
||||
order.sendAsyncEvent(event);
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be created", ex);
|
||||
|
||||
// Rollback the payment creation
|
||||
if (payment.getIdentity() != null)
|
||||
paymentService.delete(payment.getIdentity());
|
||||
|
||||
order.setPaymentId(null);
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
orderService.update(order);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import demo.payment.domain.Payment;
|
||||
import demo.payment.domain.PaymentService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Processes a {@link Payment} for an {@link Order}.
|
||||
@@ -16,6 +15,7 @@ import java.util.function.Consumer;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class DeleteOrder extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
@@ -25,16 +25,14 @@ public class DeleteOrder extends Action<Order> {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Consumer<Order> getConsumer() {
|
||||
return (order) -> {
|
||||
// Delete payment
|
||||
if (order.getPaymentId() != null)
|
||||
paymentService.delete(order.getPaymentId());
|
||||
public void apply(Order order) {
|
||||
// Delete payment
|
||||
if (order.getPaymentId() != null)
|
||||
paymentService.delete(order.getPaymentId());
|
||||
|
||||
// Delete order
|
||||
order.getModule(OrderModule.class)
|
||||
.getDefaultService()
|
||||
.delete(order.getIdentity());
|
||||
};
|
||||
// Delete order
|
||||
order.getModule(OrderModule.class)
|
||||
.getDefaultService()
|
||||
.delete(order.getIdentity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import demo.order.domain.Order;
|
||||
import demo.reservation.domain.ReservationModule;
|
||||
import demo.reservation.domain.Reservations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.Function;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Query action to get {@link demo.order.domain.Order}s for an an {@link Order}
|
||||
@@ -14,6 +13,7 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class GetReservations extends Action<Order> {
|
||||
|
||||
private final ReservationModule reservationModule;
|
||||
@@ -22,11 +22,9 @@ public class GetReservations extends Action<Order> {
|
||||
this.reservationModule = reservationModule;
|
||||
}
|
||||
|
||||
public Function<Order, Reservations> getFunction() {
|
||||
return (order) -> {
|
||||
// Get orders from the order service
|
||||
return reservationModule.getDefaultService()
|
||||
.findReservationsByOrderId(order.getIdentity());
|
||||
};
|
||||
public Reservations apply(Order order) {
|
||||
// Get orders from the order service
|
||||
return reservationModule.getDefaultService()
|
||||
.findReservationsByOrderId(order.getIdentity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Processes a {@link Payment} for an {@link Order}.
|
||||
@@ -24,51 +24,55 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ProcessPayment extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return order -> {
|
||||
Assert.isTrue(!Arrays
|
||||
.asList(OrderStatus.PAYMENT_SUCCEEDED, OrderStatus.PAYMENT_PENDING, OrderStatus.PAYMENT_FAILED)
|
||||
.contains(order.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.PAYMENT_CONNECTED,
|
||||
"Order must be in a payment connected state");
|
||||
public Order apply(Order order) {
|
||||
Assert.isTrue(!Arrays
|
||||
.asList(OrderStatus.PAYMENT_SUCCEEDED, OrderStatus.PAYMENT_PENDING, OrderStatus.PAYMENT_FAILED)
|
||||
.contains(order.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(order.getStatus() == OrderStatus.PAYMENT_CONNECTED,
|
||||
"Order must be in a payment connected state");
|
||||
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
// Get entity services
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
// Get the payment
|
||||
Payment payment = order.getPayment();
|
||||
// Get the payment
|
||||
Payment payment = order.getPayment();
|
||||
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_PENDING);
|
||||
order = orderService.update(order);
|
||||
// Update the order status
|
||||
order.setStatus(OrderStatus.PAYMENT_PENDING);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
// Create traverson for the new order
|
||||
Traverson traverson = new Traverson(URI.create(payment.getLink("self").getHref()), MediaTypes.HAL_JSON);
|
||||
payment = traverson.follow("commands", "processPayment").toObject(Payment.class);
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be processed", ex);
|
||||
boolean paymentSuccess = false;
|
||||
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_FAILED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
try {
|
||||
// Create traverson for the new order
|
||||
Traverson traverson = new Traverson(URI.create(payment.getLink("self").getHref()), MediaTypes.HAL_JSON);
|
||||
payment = traverson.follow("commands", "processPayment").toObject(Payment.class);
|
||||
paymentSuccess = true;
|
||||
} catch (Exception ex) {
|
||||
log.error("The order's payment could not be processed", ex);
|
||||
|
||||
// Trigger payment failed event
|
||||
order.sendAsyncEvent(event);
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_FAILED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
throw ex;
|
||||
} finally {
|
||||
// Trigger payment failed event
|
||||
order.sendAsyncEvent(event);
|
||||
|
||||
paymentSuccess = false;
|
||||
} finally {
|
||||
if(paymentSuccess) {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.PAYMENT_SUCCEEDED, order);
|
||||
event.add(payment.getLink("self").withRel("payment"));
|
||||
|
||||
// Trigger payment succeeded event
|
||||
order.sendAsyncEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import demo.warehouse.domain.WarehouseService;
|
||||
import demo.warehouse.exception.WarehouseNotFoundException;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Reserves inventory for an {@link Order}.
|
||||
@@ -23,6 +23,7 @@ import java.util.function.Function;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ReserveInventory extends Action<Order> {
|
||||
|
||||
private final Logger log = Logger.getLogger(ReserveInventory.class);
|
||||
@@ -32,63 +33,61 @@ public class ReserveInventory extends Action<Order> {
|
||||
this.warehouseService = warehouseService;
|
||||
}
|
||||
|
||||
public Function<Order, Order> getFunction() {
|
||||
return (order) -> {
|
||||
Assert.isTrue(!Arrays
|
||||
.asList(OrderStatus.PAYMENT_SUCCEEDED, OrderStatus.PAYMENT_PENDING,
|
||||
OrderStatus.PAYMENT_FAILED, OrderStatus.INVENTORY_RESERVED,
|
||||
OrderStatus.RESERVATION_SUCCEEDED, OrderStatus.RESERVATION_PENDING,
|
||||
OrderStatus.RESERVATION_FAILED)
|
||||
.contains(order.getStatus()), "Inventory has already been reserved");
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "The order must be connected to an account");
|
||||
public Order apply(Order order) {
|
||||
Assert.isTrue(!Arrays
|
||||
.asList(OrderStatus.PAYMENT_SUCCEEDED, OrderStatus.PAYMENT_PENDING,
|
||||
OrderStatus.PAYMENT_FAILED, OrderStatus.INVENTORY_RESERVED,
|
||||
OrderStatus.RESERVATION_SUCCEEDED, OrderStatus.RESERVATION_PENDING,
|
||||
OrderStatus.RESERVATION_FAILED)
|
||||
.contains(order.getStatus()), "Inventory has already been reserved");
|
||||
Assert.isTrue(order
|
||||
.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "The order must be connected to an account");
|
||||
|
||||
Warehouse warehouse;
|
||||
Warehouse warehouse;
|
||||
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
OrderService orderService = order.getModule(OrderModule.class).getDefaultService();
|
||||
|
||||
OrderStatus status = order.getStatus();
|
||||
order.setStatus(OrderStatus.RESERVATION_PENDING);
|
||||
OrderStatus status = order.getStatus();
|
||||
order.setStatus(OrderStatus.RESERVATION_PENDING);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
warehouse = warehouseService.findWarehouseWithInventory(order);
|
||||
} catch (WarehouseNotFoundException ex) {
|
||||
log.error("The order contains items that are not available at any warehouse", ex);
|
||||
throw ex;
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error connecting to warehouse service", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
order = orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
try {
|
||||
// Reserve inventory for the order from the returned warehouse
|
||||
warehouse = warehouseService.reserveInventory(warehouse, order);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not reserve inventory for the order", ex);
|
||||
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
try {
|
||||
warehouse = warehouseService.findWarehouseWithInventory(order);
|
||||
} catch (WarehouseNotFoundException ex) {
|
||||
log.error("The order contains items that are not available at any warehouse", ex);
|
||||
throw ex;
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Error connecting to warehouse service", ex);
|
||||
// Rollback status change
|
||||
order.setStatus(status);
|
||||
order = orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
OrderEvent event = new OrderEvent(OrderEventType.RESERVATION_FAILED, order);
|
||||
event.add(warehouse.getLink("self").withRel("warehouse"));
|
||||
|
||||
try {
|
||||
// Reserve inventory for the order from the returned warehouse
|
||||
warehouse = warehouseService.reserveInventory(warehouse, order);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not reserve inventory for the order", ex);
|
||||
|
||||
order.setStatus(OrderStatus.ACCOUNT_CONNECTED);
|
||||
order = orderService.update(order);
|
||||
|
||||
OrderEvent event = new OrderEvent(OrderEventType.RESERVATION_FAILED, order);
|
||||
event.add(warehouse.getLink("self").withRel("warehouse"));
|
||||
|
||||
// Trigger reservation failed
|
||||
order.sendAsyncEvent(event);
|
||||
|
||||
throw ex;
|
||||
} finally {
|
||||
// Trigger reservation failed
|
||||
order.sendAsyncEvent(event);
|
||||
} finally {
|
||||
if(order.getStatus() != OrderStatus.ACCOUNT_CONNECTED) {
|
||||
OrderEvent event = new OrderEvent(OrderEventType.RESERVATION_PENDING, order);
|
||||
event.add(warehouse.getLink("self").withRel("warehouse"));
|
||||
|
||||
// Trigger reservation pending event
|
||||
order.sendAsyncEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import demo.order.domain.OrderService;
|
||||
import demo.order.domain.OrderStatus;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Updates the status of a {@link Order} entity.
|
||||
@@ -15,6 +14,7 @@ import java.util.function.BiFunction;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class UpdateOrderStatus extends Action<Order> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
@@ -24,24 +24,21 @@ public class UpdateOrderStatus extends Action<Order> {
|
||||
this.orderService = orderService;
|
||||
}
|
||||
|
||||
public BiFunction<Order, OrderStatus, Order> getFunction() {
|
||||
return (order, orderStatus) -> {
|
||||
public Order apply(Order order, OrderStatus orderStatus) {
|
||||
|
||||
// Save rollback status
|
||||
OrderStatus rollbackStatus = order.getStatus();
|
||||
// Save rollback status
|
||||
OrderStatus rollbackStatus = order.getStatus();
|
||||
|
||||
try {
|
||||
// Update status
|
||||
order.setStatus(orderStatus);
|
||||
order = orderService.update(order);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not update the status", ex);
|
||||
order.setStatus(rollbackStatus);
|
||||
order = orderService.update(order);
|
||||
throw ex;
|
||||
}
|
||||
try {
|
||||
// Update status
|
||||
order.setStatus(orderStatus);
|
||||
order = orderService.update(order);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not update the status", ex);
|
||||
order.setStatus(rollbackStatus);
|
||||
order = orderService.update(order);
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,77 +117,66 @@ public class Order extends AbstractEntity<OrderEvent, Long> {
|
||||
@JsonIgnore
|
||||
public Reservations getReservations() {
|
||||
return getAction(GetReservations.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "connectAccount", controller = OrderController.class)
|
||||
public Order connectAccount(Long accountId) {
|
||||
return getAction(ConnectAccount.class)
|
||||
.getFunction()
|
||||
.apply(this, accountId);
|
||||
}
|
||||
|
||||
@Command(method = "connectPayment", controller = OrderController.class)
|
||||
public Order connectPayment(Long paymentId) {
|
||||
return getAction(ConnectPayment.class)
|
||||
.getFunction()
|
||||
.apply(this, paymentId);
|
||||
}
|
||||
|
||||
@Command(method = "createPayment", controller = OrderController.class)
|
||||
public Order createPayment() {
|
||||
return getAction(CreatePayment.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "processPayment", controller = OrderController.class)
|
||||
public Order processPayment() {
|
||||
return getAction(ProcessPayment.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "reserveInventory", controller = OrderController.class)
|
||||
public Order reserveInventory() {
|
||||
return getAction(ReserveInventory.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "addReservation", controller = OrderController.class)
|
||||
public Order addReservation(Long reservationId) {
|
||||
return getAction(AddReservation.class)
|
||||
.getFunction()
|
||||
.apply(this, reservationId);
|
||||
}
|
||||
|
||||
@Command(method = "completeReservation", controller = OrderController.class)
|
||||
public Order completeReservation() {
|
||||
return getAction(CompleteReservation.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "completeOrder", controller = OrderController.class)
|
||||
public Order completeOrder() {
|
||||
return getAction(CompleteOrder.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "updateOrderStatus", controller = OrderController.class)
|
||||
public Order updateOrderStatus(OrderStatus orderStatus) {
|
||||
return getAction(UpdateOrderStatus.class)
|
||||
.getFunction()
|
||||
.apply(this, orderStatus);
|
||||
}
|
||||
|
||||
public boolean delete() {
|
||||
getAction(DeleteOrder.class)
|
||||
.getConsumer()
|
||||
.accept(this);
|
||||
.apply(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
output:
|
||||
destination: order
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -375,26 +375,33 @@ public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderS
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
Order order = traverson.follow("self", "commands", "completeOrder")
|
||||
.toEntity(Order.class)
|
||||
.getBody();
|
||||
|
||||
// Release the reservations
|
||||
Reservations reservations = traverson.follow("self", "reservations")
|
||||
.toObject(Reservations.class);
|
||||
|
||||
reservations.getContent().stream()
|
||||
.filter(r -> r.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED)
|
||||
.filter(r -> r.getStatus() != ReservationStatus.RESERVATION_FAILED)
|
||||
.parallel()
|
||||
.forEach(r -> {
|
||||
Traverson res = new Traverson(
|
||||
URI.create(r.getLink("self").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
try {
|
||||
Traverson res = new Traverson(
|
||||
URI.create(r.getLink("self").getHref()),
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
res.follow("self", "commands", "releaseInventory")
|
||||
.toObject(Reservation.class);
|
||||
res.follow("self", "commands", "releaseInventory")
|
||||
.toObject(Reservation.class);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not release inventory for reservation", ex);
|
||||
}
|
||||
});
|
||||
|
||||
return traverson.follow("self", "commands", "completeOrder")
|
||||
.toEntity(Order.class)
|
||||
.getBody();
|
||||
return order;
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
input:
|
||||
contentType: 'application/json'
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -9,42 +9,38 @@ import demo.payment.event.PaymentEvent;
|
||||
import demo.payment.event.PaymentEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ConnectOrder extends Action<Payment> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Payment, Long, Payment> getFunction() {
|
||||
return (payment, orderId) -> {
|
||||
Assert.isTrue(payment
|
||||
.getStatus() == PaymentStatus.PAYMENT_CREATED, "Payment has already been connected to an order");
|
||||
public Payment apply(Payment payment, Long orderId) {
|
||||
Assert.isTrue(payment
|
||||
.getStatus() == PaymentStatus.PAYMENT_CREATED, "Payment has already been connected to an order");
|
||||
|
||||
PaymentService paymentService = payment.getModule(PaymentModule.class)
|
||||
.getDefaultService();
|
||||
PaymentService paymentService = payment.getModule(PaymentModule.class)
|
||||
.getDefaultService();
|
||||
|
||||
// Connect the payment to the order
|
||||
payment.setOrderId(orderId);
|
||||
payment.setStatus(PaymentStatus.ORDER_CONNECTED);
|
||||
// Connect the payment to the order
|
||||
payment.setOrderId(orderId);
|
||||
payment.setStatus(PaymentStatus.ORDER_CONNECTED);
|
||||
payment = paymentService.update(payment);
|
||||
|
||||
try {
|
||||
// Trigger the payment connected
|
||||
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.ORDER_CONNECTED, payment));
|
||||
} catch (IllegalStateException ex) {
|
||||
log.error("Payment could not be connected to order", ex);
|
||||
|
||||
// Rollback operation
|
||||
payment.setStatus(PaymentStatus.PAYMENT_CREATED);
|
||||
payment.setOrderId(null);
|
||||
payment = paymentService.update(payment);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the payment connected
|
||||
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.ORDER_CONNECTED, payment));
|
||||
} catch (IllegalStateException ex) {
|
||||
log.error("Payment could not be connected to order", ex);
|
||||
|
||||
// Rollback operation
|
||||
payment.setStatus(PaymentStatus.PAYMENT_CREATED);
|
||||
payment.setOrderId(null);
|
||||
paymentService.update(payment);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return payment;
|
||||
};
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ import demo.payment.event.PaymentEvent;
|
||||
import demo.payment.event.PaymentEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.payment.domain.PaymentStatus.PAYMENT_FAILED;
|
||||
import static demo.payment.domain.PaymentStatus.PAYMENT_SUCCEEDED;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ProcessPayment extends Action<Payment> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final PaymentService paymentService;
|
||||
@@ -25,32 +26,31 @@ public class ProcessPayment extends Action<Payment> {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Function<Payment, Payment> getFunction() {
|
||||
return payment -> {
|
||||
// Validations
|
||||
Assert.isTrue(!Arrays.asList(PAYMENT_SUCCEEDED,
|
||||
PaymentStatus.PAYMENT_PENDING,
|
||||
PaymentStatus.PAYMENT_FAILED).contains(payment.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(payment.getStatus() == PaymentStatus.ORDER_CONNECTED,
|
||||
"Payment must be connected to an order");
|
||||
public Payment apply(Payment payment) {
|
||||
// Validations
|
||||
Assert.isTrue(!Arrays.asList(PAYMENT_SUCCEEDED,
|
||||
PaymentStatus.PAYMENT_PENDING,
|
||||
PaymentStatus.PAYMENT_FAILED).contains(payment.getStatus()), "Payment has already been processed");
|
||||
Assert.isTrue(payment.getStatus() == PaymentStatus.ORDER_CONNECTED,
|
||||
"Payment must be connected to an order");
|
||||
|
||||
payment.setStatus(PaymentStatus.PAYMENT_PROCESSED);
|
||||
payment = paymentService.update(payment);
|
||||
payment.setStatus(PaymentStatus.PAYMENT_PROCESSED);
|
||||
payment = paymentService.update(payment);
|
||||
|
||||
try {
|
||||
// Trigger the payment processed event
|
||||
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.PAYMENT_PROCESSED, payment));
|
||||
} catch (Exception ex) {
|
||||
log.error("Payment could not be processed", ex);
|
||||
finalizePayment(payment, PAYMENT_FAILED);
|
||||
throw ex;
|
||||
} finally {
|
||||
try {
|
||||
// Trigger the payment processed event
|
||||
payment.sendAsyncEvent(new PaymentEvent(PaymentEventType.PAYMENT_PROCESSED, payment));
|
||||
} catch (Exception ex) {
|
||||
log.error("Payment could not be processed", ex);
|
||||
payment = finalizePayment(payment, PAYMENT_FAILED);
|
||||
} finally {
|
||||
if(payment.getStatus() != PAYMENT_FAILED) {
|
||||
// Handle the result asynchronously
|
||||
finalizePayment(payment, PAYMENT_SUCCEEDED);
|
||||
payment = finalizePayment(payment, PAYMENT_SUCCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
return payment;
|
||||
};
|
||||
return payment;
|
||||
}
|
||||
|
||||
private Payment finalizePayment(Payment payment, PaymentStatus paymentStatus) {
|
||||
|
||||
@@ -91,14 +91,12 @@ public class Payment extends AbstractEntity<PaymentEvent, Long> {
|
||||
@Command(method = "connectOrder", controller = PaymentController.class)
|
||||
public Payment connectOrder(Long orderId) {
|
||||
return getAction(ConnectOrder.class)
|
||||
.getFunction()
|
||||
.apply(this, orderId);
|
||||
}
|
||||
|
||||
@Command(method = "processPayment", controller = PaymentController.class)
|
||||
public Payment processPayment() {
|
||||
return getAction(ProcessPayment.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
output:
|
||||
contentType: 'application/json'
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -22,11 +22,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
input:
|
||||
contentType: 'application/json'
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -12,8 +12,6 @@ import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static demo.inventory.domain.InventoryStatus.RESERVATION_CONNECTED;
|
||||
|
||||
/**
|
||||
@@ -33,34 +31,31 @@ public class ReserveInventory extends Action<Inventory> {
|
||||
this.inventoryService = inventoryService;
|
||||
}
|
||||
|
||||
public BiFunction<Inventory, Long, Inventory> getFunction() {
|
||||
return (inventory, reservationId) -> {
|
||||
Assert.isTrue(inventory.getStatus() == InventoryStatus.RESERVATION_CONNECTED,
|
||||
"Inventory must be in a reservation connected state");
|
||||
Assert.isTrue(inventory.getReservation() == null,
|
||||
"There is already a reservation attached to the inventory");
|
||||
public Inventory apply(Inventory inventory, Long reservationId) {
|
||||
Assert.isTrue(inventory.getStatus() == InventoryStatus.RESERVATION_CONNECTED,
|
||||
"Inventory must be in a reservation connected state");
|
||||
Assert.isTrue(inventory.getReservation() == null,
|
||||
"There is already a reservation attached to the inventory");
|
||||
|
||||
Reservation reservation = reservationService.get(reservationId);
|
||||
Assert.notNull(reservation, "Reserve inventory failed, the reservation does not exist");
|
||||
Reservation reservation = reservationService.get(reservationId);
|
||||
Assert.notNull(reservation, "Reserve inventory failed, the reservation does not exist");
|
||||
|
||||
try {
|
||||
// Trigger the reservation connected event
|
||||
inventory.sendAsyncEvent(new InventoryEvent(InventoryEventType.RESERVATION_CONNECTED, inventory));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to inventory", ex);
|
||||
inventory.setReservation(null);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_PENDING);
|
||||
inventoryService.update(inventory);
|
||||
throw ex;
|
||||
} finally {
|
||||
if (inventory.getStatus() == RESERVATION_CONNECTED && inventory.getReservation() != null) {
|
||||
inventory.setStatus(InventoryStatus.INVENTORY_RESERVED);
|
||||
inventory = inventoryService.update(inventory);
|
||||
inventory.sendAsyncEvent(new InventoryEvent(InventoryEventType.INVENTORY_RESERVED, inventory));
|
||||
}
|
||||
try {
|
||||
// Trigger the reservation connected event
|
||||
inventory.sendAsyncEvent(new InventoryEvent(InventoryEventType.RESERVATION_CONNECTED, inventory));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to inventory", ex);
|
||||
inventory.setReservation(null);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_PENDING);
|
||||
inventory = inventoryService.update(inventory);
|
||||
} finally {
|
||||
if (inventory.getStatus() == RESERVATION_CONNECTED && inventory.getReservation() != null) {
|
||||
inventory.setStatus(InventoryStatus.INVENTORY_RESERVED);
|
||||
inventory = inventoryService.update(inventory);
|
||||
inventory.sendAsyncEvent(new InventoryEvent(InventoryEventType.INVENTORY_RESERVED, inventory));
|
||||
}
|
||||
}
|
||||
|
||||
return inventory;
|
||||
};
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import demo.inventory.domain.InventoryStatus;
|
||||
import demo.reservation.domain.ReservationService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Updates the status of a {@link Inventory} entity.
|
||||
@@ -16,6 +15,7 @@ import java.util.function.BiFunction;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class UpdateInventoryStatus extends Action<Inventory> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
@@ -27,24 +27,20 @@ public class UpdateInventoryStatus extends Action<Inventory> {
|
||||
this.inventoryService = inventoryService;
|
||||
}
|
||||
|
||||
public BiFunction<Inventory, InventoryStatus, Inventory> getFunction() {
|
||||
return (inventory, inventoryStatus) -> {
|
||||
public Inventory apply(Inventory inventory, InventoryStatus inventoryStatus) {
|
||||
// Save rollback status
|
||||
InventoryStatus rollbackStatus = inventory.getStatus();
|
||||
|
||||
// Save rollback status
|
||||
InventoryStatus rollbackStatus = inventory.getStatus();
|
||||
try {
|
||||
// Update status
|
||||
inventory.setStatus(inventoryStatus);
|
||||
inventory = inventoryService.update(inventory);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not update the status", ex);
|
||||
inventory.setStatus(rollbackStatus);
|
||||
inventory = inventoryService.update(inventory);
|
||||
}
|
||||
|
||||
try {
|
||||
// Update status
|
||||
inventory.setStatus(inventoryStatus);
|
||||
inventory = inventoryService.update(inventory);
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not update the status", ex);
|
||||
inventory.setStatus(rollbackStatus);
|
||||
inventory = inventoryService.update(inventory);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return inventory;
|
||||
};
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +87,12 @@ public class Inventory extends AbstractEntity<InventoryEvent, Long> {
|
||||
@Command(method = "reserve", controller = InventoryController.class)
|
||||
public Inventory reserve(Long reservationId) {
|
||||
return getAction(ReserveInventory.class)
|
||||
.getFunction()
|
||||
.apply(this, reservationId);
|
||||
}
|
||||
|
||||
@Command(method = "updateInventoryStatus", controller = InventoryController.class)
|
||||
public Inventory updateStatus(InventoryStatus status) {
|
||||
return getAction(UpdateInventoryStatus.class)
|
||||
.getFunction()
|
||||
.apply(this, status);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ public class InventoryService extends Service<Inventory, Long> {
|
||||
Boolean lock = false;
|
||||
|
||||
try {
|
||||
lock = inventoryLock.tryLock(30, 5000, TimeUnit.MILLISECONDS);
|
||||
lock = inventoryLock.tryLock(1, 5000, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Interrupted while acquiring lock on inventory", e);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.reservation.event.ReservationEventType.*;
|
||||
|
||||
@@ -41,67 +40,73 @@ public class ConnectInventory extends Action<Reservation> {
|
||||
this.inventoryService = inventoryService;
|
||||
}
|
||||
|
||||
public Function<Reservation, Reservation> getFunction() {
|
||||
return (reservation) -> {
|
||||
Assert.isTrue(reservation.getStatus() == ReservationStatus.ORDER_CONNECTED,
|
||||
"Reservation must be in an order connected state");
|
||||
public Reservation apply(Reservation reservation) {
|
||||
Assert.isTrue(reservation.getStatus() == ReservationStatus.ORDER_CONNECTED,
|
||||
"Reservation must be in an order connected state");
|
||||
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
|
||||
// Set reservation to pending
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_PENDING);
|
||||
reservation = reservationService.update(reservation);
|
||||
// Set reservation to pending
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_PENDING);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
// Get available inventory and connect reservation in an atomic transaction
|
||||
Inventory inventory = inventoryService.findAvailableInventory(reservation);
|
||||
// Get available inventory and connect reservation in an atomic transaction
|
||||
Inventory inventory = inventoryService.findAvailableInventory(reservation);
|
||||
|
||||
try {
|
||||
if (inventory == null) {
|
||||
// Inventory is out of stock, fail the reservation process
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_FAILED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
// Trigger reservation failed event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_FAILED, reservation));
|
||||
|
||||
// Throw the out of stock exception
|
||||
throw new OutOfStockException("Inventory for reservation is unavailable in warehouse: "
|
||||
.concat(reservation.getId().toString()));
|
||||
}
|
||||
|
||||
// Set inventory on reservation and mark successful
|
||||
reservation.setInventory(inventory);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
try {
|
||||
if (inventory == null) {
|
||||
// Inventory is out of stock, fail the reservation process
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_FAILED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
// Trigger the inventory connected event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(INVENTORY_CONNECTED, reservation),
|
||||
reservation.getInventory().getId().withRel("inventory"));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to order", ex);
|
||||
if (reservation.getStatus() != ReservationStatus.RESERVATION_FAILED) {
|
||||
// Rollback the reservation attempt
|
||||
if (inventory != null) {
|
||||
inventory.setReservation(null);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_PENDING);
|
||||
inventory = inventoryService.update(inventory);
|
||||
}
|
||||
// Trigger reservation failed event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_FAILED, reservation));
|
||||
|
||||
reservation.setInventory(null);
|
||||
reservation.setStatus(ReservationStatus.ORDER_CONNECTED);
|
||||
reservation = reservationService.update(reservation);
|
||||
}
|
||||
throw ex;
|
||||
} finally {
|
||||
if (reservation.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED) {
|
||||
Link inventoryLink = reservation.getInventory().getId().withRel("inventory");
|
||||
Link orderLink = getRemoteLink("order-web", "/v1/orders/{id}", reservation.getOrderId(), "order");
|
||||
reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_SUCCEEDED, reservation), inventoryLink, orderLink);
|
||||
}
|
||||
// Throw the out of stock exception
|
||||
throw new OutOfStockException("Inventory for reservation is unavailable in warehouse: "
|
||||
.concat(reservation.getId().toString()));
|
||||
}
|
||||
|
||||
return reservation;
|
||||
};
|
||||
inventory.setReservation(reservation);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_CONNECTED);
|
||||
inventory = inventoryService.update(inventory);
|
||||
|
||||
// Set inventory on reservation and mark successful
|
||||
reservation.setInventory(inventory);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
|
||||
// Trigger the inventory connected event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(INVENTORY_CONNECTED, reservation),
|
||||
reservation.getInventory().getId().withRel("inventory"));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to order", ex);
|
||||
if (reservation.getStatus() != ReservationStatus.RESERVATION_FAILED) {
|
||||
// Rollback the reservation attempt
|
||||
if (inventory != null) {
|
||||
inventory.setReservation(null);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_PENDING);
|
||||
inventoryService.update(inventory);
|
||||
}
|
||||
|
||||
reservation.setInventory(null);
|
||||
reservation.setStatus(ReservationStatus.ORDER_CONNECTED);
|
||||
reservation = reservationService.update(reservation);
|
||||
}
|
||||
|
||||
throw ex;
|
||||
} finally {
|
||||
if (reservation.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED) {
|
||||
Link inventoryLink = reservation.getInventory().getId().withRel("inventory");
|
||||
Link orderLink = getRemoteLink("order-web", "/v1/orders/{id}", reservation.getOrderId(), "order");
|
||||
reservation
|
||||
.sendAsyncEvent(new ReservationEvent(RESERVATION_SUCCEEDED, reservation), inventoryLink,
|
||||
orderLink);
|
||||
}
|
||||
}
|
||||
|
||||
return reservation;
|
||||
}
|
||||
|
||||
private Link getRemoteLink(String service, String relative, Object identifier, String rel) {
|
||||
|
||||
@@ -9,42 +9,40 @@ import demo.reservation.event.ReservationEvent;
|
||||
import demo.reservation.event.ReservationEventType;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Connects an {@link Reservation} to an Order.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ConnectOrder extends Action<Reservation> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
public BiFunction<Reservation, Long, Reservation> getFunction() {
|
||||
return (reservation, orderId) -> {
|
||||
Assert.isTrue(reservation.getStatus() == ReservationStatus.RESERVATION_CREATED, "Reservation must be in a created state");
|
||||
public Reservation apply(Reservation reservation, Long orderId) {
|
||||
Assert.isTrue(reservation
|
||||
.getStatus() == ReservationStatus.RESERVATION_CREATED, "Reservation must be in a created state");
|
||||
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
|
||||
// Connect the order
|
||||
reservation.setOrderId(orderId);
|
||||
reservation.setStatus(ReservationStatus.ORDER_CONNECTED);
|
||||
// Connect the order
|
||||
reservation.setOrderId(orderId);
|
||||
reservation.setStatus(ReservationStatus.ORDER_CONNECTED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
try {
|
||||
// Trigger the order connected event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(ReservationEventType.ORDER_CONNECTED, reservation));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to order", ex);
|
||||
reservation.setOrderId(null);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_CREATED);
|
||||
reservation = reservationService.update(reservation);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the order connected event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(ReservationEventType.ORDER_CONNECTED, reservation));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not connect reservation to order", ex);
|
||||
reservation.setOrderId(null);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_CREATED);
|
||||
reservationService.update(reservation);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return reservation;
|
||||
};
|
||||
return reservation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@ import demo.reservation.domain.ReservationStatus;
|
||||
import demo.reservation.event.ReservationEvent;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static demo.inventory.event.InventoryEventType.INVENTORY_RELEASED;
|
||||
import static demo.reservation.event.ReservationEventType.RESERVATION_FAILED;
|
||||
|
||||
@@ -25,6 +24,7 @@ import static demo.reservation.event.ReservationEventType.RESERVATION_FAILED;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ReleaseInventory extends Action<Reservation> {
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final InventoryService inventoryService;
|
||||
@@ -33,34 +33,32 @@ public class ReleaseInventory extends Action<Reservation> {
|
||||
this.inventoryService = inventoryService;
|
||||
}
|
||||
|
||||
public Function<Reservation, Reservation> getFunction() {
|
||||
return (reservation) -> {
|
||||
Assert.isTrue(reservation.getStatus() == ReservationStatus.RESERVATION_SUCCEEDED,
|
||||
"Reservation must be in a succeeded state");
|
||||
Assert.notNull(reservation.getInventory(), "The reservation has no connected inventory");
|
||||
public Reservation apply(Reservation reservation) {
|
||||
Assert.isTrue(reservation.getStatus() != ReservationStatus.RESERVATION_FAILED,
|
||||
"Reservation is already in a failed state");
|
||||
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
ReservationService reservationService = reservation.getModule(ReservationModule.class).getDefaultService();
|
||||
|
||||
Inventory inventory = reservation.getInventory();
|
||||
Inventory inventory = reservation.getInventory();
|
||||
|
||||
try {
|
||||
// Remove the inventory and set the reservation to failed
|
||||
reservation.setInventory(null);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_FAILED);
|
||||
try {
|
||||
// Remove the inventory and set the reservation to failed
|
||||
reservation.setInventory(null);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_FAILED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
// Trigger the reservation failed event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_FAILED, reservation));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not release the reservation's inventory", ex);
|
||||
if (reservation.getStatus() == ReservationStatus.RESERVATION_FAILED) {
|
||||
// Rollback the attempt
|
||||
reservation.setInventory(inventory);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
reservation = reservationService.update(reservation);
|
||||
|
||||
// Trigger the reservation failed event
|
||||
reservation.sendAsyncEvent(new ReservationEvent(RESERVATION_FAILED, reservation));
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not release the reservation's inventory", ex);
|
||||
if (reservation.getStatus() == ReservationStatus.RESERVATION_FAILED) {
|
||||
// Rollback the attempt
|
||||
reservation.setInventory(inventory);
|
||||
reservation.setStatus(ReservationStatus.RESERVATION_SUCCEEDED);
|
||||
reservation = reservationService.update(reservation);
|
||||
}
|
||||
throw ex;
|
||||
} finally {
|
||||
}
|
||||
} finally {
|
||||
if (inventory != null && reservation.getStatus() != ReservationStatus.RESERVATION_SUCCEEDED) {
|
||||
// Release the inventory
|
||||
inventory.setReservation(null);
|
||||
inventory.setStatus(InventoryStatus.RESERVATION_PENDING);
|
||||
@@ -69,8 +67,8 @@ public class ReleaseInventory extends Action<Reservation> {
|
||||
// Trigger the inventory released event
|
||||
inventory.sendAsyncEvent(new InventoryEvent(INVENTORY_RELEASED, inventory));
|
||||
}
|
||||
}
|
||||
|
||||
return reservation;
|
||||
};
|
||||
return reservation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,21 +111,18 @@ public class Reservation extends AbstractEntity<ReservationEvent, Long> {
|
||||
@Command(method = "connectInventory", controller = ReservationController.class)
|
||||
public Reservation connectInventory() {
|
||||
return getAction(ConnectInventory.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "releaseInventory", controller = ReservationController.class)
|
||||
public Reservation releaseInventory() {
|
||||
return getAction(ReleaseInventory.class)
|
||||
.getFunction()
|
||||
.apply(this);
|
||||
}
|
||||
|
||||
@Command(method = "connectOrder", controller = ReservationController.class)
|
||||
public Reservation connectOrder(Long orderId) {
|
||||
return getAction(ConnectOrder.class)
|
||||
.getFunction()
|
||||
.apply(this, orderId);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import demo.reservation.event.ReservationEventType;
|
||||
import demo.warehouse.domain.Warehouse;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.stream.IntStream;
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class ReserveOrder extends Action<Warehouse> {
|
||||
|
||||
private final Logger log = Logger.getLogger(ReserveOrder.class);
|
||||
@@ -30,25 +31,22 @@ public class ReserveOrder extends Action<Warehouse> {
|
||||
this.reservationService = reservationService;
|
||||
}
|
||||
|
||||
public BiConsumer<Warehouse, Order> getConsumer() {
|
||||
return (warehouse, order) -> {
|
||||
public void apply(Warehouse warehouse, Order order) {
|
||||
// Create reservations for each order item
|
||||
List<Reservation> reservations = order.getLineItems().stream()
|
||||
.map(item -> IntStream.rangeClosed(1, item.getQuantity())
|
||||
.mapToObj(a -> new Reservation(item.getProductId(), order.getIdentity(), warehouse)))
|
||||
.flatMap(a -> a)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Create reservations for each order item
|
||||
List<Reservation> reservations = order.getLineItems().stream()
|
||||
.map(item -> IntStream.rangeClosed(1, item.getQuantity())
|
||||
.mapToObj(a -> new Reservation(item.getProductId(), order.getIdentity(), warehouse)))
|
||||
.flatMap(a -> a)
|
||||
.collect(Collectors.toList());
|
||||
// Save the reservations
|
||||
reservations = reservationService.create(reservations);
|
||||
|
||||
// Save the reservations
|
||||
reservations = reservationService.create(reservations);
|
||||
|
||||
// Trigger reservation requests for each order item
|
||||
reservations.forEach(r -> {
|
||||
ReservationEvent event = new ReservationEvent(ReservationEventType.RESERVATION_REQUESTED, r);
|
||||
event.add(order.getLink("self").withRel("order"));
|
||||
r.sendAsyncEvent(event);
|
||||
});
|
||||
};
|
||||
// Trigger reservation requests for each order item
|
||||
reservations.forEach(r -> {
|
||||
ReservationEvent event = new ReservationEvent(ReservationEventType.RESERVATION_REQUESTED, r);
|
||||
event.add(order.getLink("self").withRel("order"));
|
||||
r.sendAsyncEvent(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@ public class Warehouse extends AbstractEntity<WarehouseEvent, Long> {
|
||||
@Command(method = "reserveOrder", controller = WarehouseController.class)
|
||||
public Warehouse reserveOrder(Order order) {
|
||||
getAction(ReserveOrder.class)
|
||||
.getConsumer()
|
||||
.accept(this, order);
|
||||
.apply(this, order);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static demo.order.domain.OrderStatus.ORDER_FAILED;
|
||||
import static demo.order.domain.OrderStatus.RESERVATION_FAILED;
|
||||
import static demo.order.domain.OrderStatus.RESERVATION_PENDING;
|
||||
|
||||
/**
|
||||
@@ -243,12 +245,24 @@ public class ReservationStateMachineConfig extends EnumStateMachineConfigurerAda
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
traverson.follow("self", "order", "commands", "completeReservation")
|
||||
.toObject(Order.class);
|
||||
Order order = traverson.follow("self", "order").toObject(Order.class);
|
||||
Reservation reservation = null;
|
||||
|
||||
return traverson.follow("self")
|
||||
.toEntity(Reservation.class)
|
||||
.getBody();
|
||||
// Check order status and release inventory if it has failed
|
||||
if (order.getStatus() == RESERVATION_FAILED || order.getStatus() == ORDER_FAILED) {
|
||||
reservation = traverson.follow("self", "commands", "releaseInventory")
|
||||
.toObject(Reservation.class);
|
||||
} else if (order.getStatus() == RESERVATION_PENDING) {
|
||||
traverson.follow("self", "order", "commands", "completeReservation")
|
||||
.toObject(Order.class);
|
||||
}
|
||||
|
||||
if (reservation == null)
|
||||
reservation = traverson.follow("self")
|
||||
.toEntity(Reservation.class)
|
||||
.getBody();
|
||||
|
||||
return reservation;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,12 @@ spring:
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
rabbitmq:
|
||||
host: ${DOCKER_IP:192.168.99.100}
|
||||
port: 5672
|
||||
cloud:
|
||||
stream:
|
||||
kafka:
|
||||
binder:
|
||||
brokers: ${DOCKER_IP:192.168.99.100}
|
||||
zk-nodes: ${DOCKER_IP:192.168.99.100}
|
||||
bindings:
|
||||
warehouse:
|
||||
contentType: 'application/json'
|
||||
|
||||
Reference in New Issue
Block a user