diff --git a/account/account-web/pom.xml b/account/account-web/pom.xml index 398da12..652ad07 100644 --- a/account/account-web/pom.xml +++ b/account/account-web/pom.xml @@ -51,13 +51,26 @@ org.springframework.boot spring-boot-starter-integration + + io.projectreactor + reactor-core + 3.0.3.RELEASE + + + org.springframework.cloud + spring-cloud-starter-eureka + org.kbastani spring-boot-starter-data-events 1.0-SNAPSHOT - + + com.jayway.jsonpath + json-path + ${json-path.version} + com.h2database h2 diff --git a/account/account-web/src/main/java/demo/AccountServiceApplication.java b/account/account-web/src/main/java/demo/AccountServiceApplication.java index 9e17f64..3eb44d0 100644 --- a/account/account-web/src/main/java/demo/AccountServiceApplication.java +++ b/account/account-web/src/main/java/demo/AccountServiceApplication.java @@ -2,9 +2,11 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = {EnableHypermediaSupport.HypermediaType.HAL}) public class AccountServiceApplication { diff --git a/account/account-web/src/main/java/demo/account/Account.java b/account/account-web/src/main/java/demo/account/Account.java index a740f95..cc1586c 100644 --- a/account/account-web/src/main/java/demo/account/Account.java +++ b/account/account-web/src/main/java/demo/account/Account.java @@ -1,14 +1,16 @@ package demo.account; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import demo.account.action.ActivateAccount; -import demo.account.action.ArchiveAccount; -import demo.account.action.ConfirmAccount; -import demo.account.action.SuspendAccount; +import demo.account.action.*; import demo.account.controller.AccountController; +import demo.account.event.AccountEvent; import demo.domain.AbstractEntity; +import demo.domain.Aggregate; import demo.domain.Command; -import demo.event.AccountEvent; +import demo.domain.Provider; +import demo.order.domain.Order; +import demo.order.domain.Orders; import org.springframework.hateoas.Link; import javax.persistence.*; @@ -83,6 +85,13 @@ public class Account extends AbstractEntity { this.status = status; } + @JsonIgnore + public Orders getOrders() { + return getAction(GetOrders.class) + .getFunction() + .apply(this); + } + @Command(method = "activate", controller = AccountController.class) public Account activate() { getAction(ActivateAccount.class) @@ -115,6 +124,28 @@ public class Account extends AbstractEntity { return this; } + @Command(method = "postOrder", controller = AccountController.class) + public Account postOrder(Order order) { + getAction(PostOrder.class) + .getFunction() + .apply(this, order); + return this; + } + + /** + * Retrieves an instance of the {@link Provider} for this instance + * + * @return the provider for this instance + * @throws IllegalArgumentException if the application context is unavailable or the provider does not exist + */ + @Override + @SuppressWarnings("unchecked") + public , A extends Aggregate> T getProvider() throws + IllegalArgumentException { + AccountProvider accountProvider = getProvider(AccountProvider.class); + return (T) accountProvider; + } + /** * Returns the {@link Link} with a rel of {@link Link#REL_SELF}. */ diff --git a/account/account-web/src/main/java/demo/account/AccountProvider.java b/account/account-web/src/main/java/demo/account/AccountProvider.java index f7c3420..c2b00ea 100644 --- a/account/account-web/src/main/java/demo/account/AccountProvider.java +++ b/account/account-web/src/main/java/demo/account/AccountProvider.java @@ -1,7 +1,7 @@ package demo.account; import demo.domain.Provider; -import demo.event.AccountEvent; +import demo.account.event.AccountEvent; import demo.event.EventService; @org.springframework.stereotype.Service diff --git a/account/account-web/src/main/java/demo/account/AccountService.java b/account/account-web/src/main/java/demo/account/AccountService.java index f2104e3..3f8fdf5 100644 --- a/account/account-web/src/main/java/demo/account/AccountService.java +++ b/account/account-web/src/main/java/demo/account/AccountService.java @@ -1,8 +1,8 @@ package demo.account; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import demo.domain.Service; -import demo.event.AccountEvent; -import demo.event.AccountEventType; import org.springframework.util.Assert; @org.springframework.stereotype.Service @@ -58,6 +58,7 @@ public class AccountService extends Service { * @param account is the {@link Account} containing updated fields * @return the updated {@link Account} entity */ + @Override public Account update(Account account) { Assert.notNull(account.getIdentity(), "Account id must be present in the resource URL"); Assert.notNull(account, "Account request body cannot be null"); diff --git a/account/account-web/src/main/java/demo/account/AccountStatus.java b/account/account-web/src/main/java/demo/account/AccountStatus.java index 9bb0518..246b817 100644 --- a/account/account-web/src/main/java/demo/account/AccountStatus.java +++ b/account/account-web/src/main/java/demo/account/AccountStatus.java @@ -1,9 +1,11 @@ package demo.account; +import demo.account.event.AccountEvent; + /** * The {@link AccountStatus} describes the state of an {@link Account}. * The aggregate state of a {@link Account} is sourced from attached domain - * events in the form of {@link demo.event.AccountEvent}. + * events in the form of {@link AccountEvent}. * * @author kbastani */ diff --git a/account/account-web/src/main/java/demo/account/action/ActivateAccount.java b/account/account-web/src/main/java/demo/account/action/ActivateAccount.java index bcadd7f..cd481c1 100644 --- a/account/account-web/src/main/java/demo/account/action/ActivateAccount.java +++ b/account/account-web/src/main/java/demo/account/action/ActivateAccount.java @@ -5,8 +5,8 @@ import demo.account.AccountProvider; import demo.account.AccountService; import demo.account.AccountStatus; import demo.domain.Action; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -16,7 +16,7 @@ import java.util.function.Consumer; import static demo.account.AccountStatus.*; /** - * Connects an {@link Account} to an Account. + * Activates an {@link Account} * * @author Kenny Bastani */ diff --git a/account/account-web/src/main/java/demo/account/action/ArchiveAccount.java b/account/account-web/src/main/java/demo/account/action/ArchiveAccount.java index bd7f0b6..0be919b 100644 --- a/account/account-web/src/main/java/demo/account/action/ArchiveAccount.java +++ b/account/account-web/src/main/java/demo/account/action/ArchiveAccount.java @@ -5,8 +5,8 @@ import demo.account.AccountProvider; import demo.account.AccountService; import demo.account.AccountStatus; import demo.domain.Action; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -15,7 +15,7 @@ import java.util.function.Consumer; import static demo.account.AccountStatus.ACCOUNT_ACTIVE; /** - * Connects an {@link Account} to an Account. + * Archives an {@link Account} * * @author Kenny Bastani */ diff --git a/account/account-web/src/main/java/demo/account/action/ConfirmAccount.java b/account/account-web/src/main/java/demo/account/action/ConfirmAccount.java index 690ea42..4ee2a86 100644 --- a/account/account-web/src/main/java/demo/account/action/ConfirmAccount.java +++ b/account/account-web/src/main/java/demo/account/action/ConfirmAccount.java @@ -5,8 +5,8 @@ import demo.account.AccountProvider; import demo.account.AccountService; import demo.account.AccountStatus; import demo.domain.Action; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -15,7 +15,7 @@ import java.util.function.Consumer; import static demo.account.AccountStatus.ACCOUNT_PENDING; /** - * Connects an {@link Account} to an Account. + * Confirms an {@link Account} * * @author Kenny Bastani */ diff --git a/account/account-web/src/main/java/demo/account/action/GetOrders.java b/account/account-web/src/main/java/demo/account/action/GetOrders.java new file mode 100644 index 0000000..beba972 --- /dev/null +++ b/account/account-web/src/main/java/demo/account/action/GetOrders.java @@ -0,0 +1,27 @@ +package demo.account.action; + +import demo.account.Account; +import demo.domain.Action; +import demo.order.OrderProvider; +import demo.order.domain.Orders; +import org.springframework.stereotype.Service; + +import java.util.function.Function; + +@Service +public class GetOrders extends Action { + + private OrderProvider orderProvider; + + public GetOrders(OrderProvider orderProvider) { + this.orderProvider = orderProvider; + } + + public Function getFunction() { + return (account) -> { + // Get orders from the order service + return orderProvider.getDefaultService() + .findOrdersByAccountId(account.getIdentity()); + }; + } +} diff --git a/account/account-web/src/main/java/demo/account/action/PostOrder.java b/account/account-web/src/main/java/demo/account/action/PostOrder.java new file mode 100644 index 0000000..813ef02 --- /dev/null +++ b/account/account-web/src/main/java/demo/account/action/PostOrder.java @@ -0,0 +1,46 @@ +package demo.account.action; + + +import demo.account.Account; +import demo.domain.Action; +import demo.order.domain.Order; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.client.Traverson; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static demo.account.AccountStatus.ACCOUNT_ACTIVE; + +/** + * Confirms an {@link Account} + * + * @author Kenny Bastani + */ +@Service +public class PostOrder extends Action { + + public BiFunction getFunction() { + return (account, order) -> { + Assert.isTrue(account.getStatus() == ACCOUNT_ACTIVE, "Only active accounts can create an order"); + order = order.post(); + + // Create traverson for the new order + Traverson traverson = new Traverson(URI.create(order.getLink("self") + .getHref()), MediaTypes.HAL_JSON); + + Map params = new HashMap<>(); + params.put("accountId", account.getIdentity()); + + order = traverson.follow("commands", "connectAccount") + .withTemplateParameters(params) + .toObject(Order.class); + + return account; + }; + } +} diff --git a/account/account-web/src/main/java/demo/account/action/SuspendAccount.java b/account/account-web/src/main/java/demo/account/action/SuspendAccount.java index b9c6e41..a445f60 100644 --- a/account/account-web/src/main/java/demo/account/action/SuspendAccount.java +++ b/account/account-web/src/main/java/demo/account/action/SuspendAccount.java @@ -5,8 +5,8 @@ import demo.account.AccountProvider; import demo.account.AccountService; import demo.account.AccountStatus; import demo.domain.Action; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -15,7 +15,7 @@ import java.util.function.Consumer; import static demo.account.AccountStatus.ACCOUNT_ACTIVE; /** - * Connects an {@link Account} to an Account. + * Suspends an {@link Account} * * @author Kenny Bastani */ diff --git a/account/account-web/src/main/java/demo/account/controller/AccountController.java b/account/account-web/src/main/java/demo/account/controller/AccountController.java index adf9e7a..ac77d2e 100644 --- a/account/account-web/src/main/java/demo/account/controller/AccountController.java +++ b/account/account-web/src/main/java/demo/account/controller/AccountController.java @@ -2,10 +2,12 @@ package demo.account.controller; import demo.account.Account; import demo.account.AccountService; -import demo.event.AccountEvent; +import demo.account.event.AccountEvent; import demo.event.EventController; import demo.event.EventService; import demo.event.Events; +import demo.order.domain.Order; +import demo.order.domain.Orders; import org.springframework.hateoas.LinkBuilder; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; @@ -73,6 +75,13 @@ public class AccountController { .orElseThrow(() -> new RuntimeException("Append account event failed")); } + @RequestMapping(path = "/accounts/{id}/orders") + public ResponseEntity getAccountOrders(@PathVariable Long id) { + return Optional.of(getAccountOrdersResource(id)) + .map(e -> new ResponseEntity<>(e, HttpStatus.OK)) + .orElseThrow(() -> new RuntimeException("Could not get account events")); + } + @RequestMapping(path = "/accounts/{id}/commands") public ResponseEntity getCommands(@PathVariable Long id) { return Optional.ofNullable(getCommandsResource(id)) @@ -112,6 +121,14 @@ public class AccountController { .orElseThrow(() -> new RuntimeException("The command could not be applied")); } + @RequestMapping(path = "/accounts/{id}/commands/postOrder", method = RequestMethod.POST) + public ResponseEntity postOrder(@PathVariable Long id, @RequestBody Order order) { + return Optional.ofNullable(getAccountResource(accountService.get(id) + .postOrder(order))) + .map(e -> new ResponseEntity<>(e, HttpStatus.OK)) + .orElseThrow(() -> new RuntimeException("The command could not be applied")); + } + /** * Retrieves a hypermedia resource for {@link Account} with the specified identifier. * @@ -155,6 +172,27 @@ public class AccountController { return getAccountResource(accountService.update(account)); } + private Orders getAccountOrdersResource(Long accountId) { + Account account = accountService.get(accountId); + Assert.notNull(account, "Account could not be found"); + + Orders accountOrders = account.getOrders(); + + accountOrders.add( + linkTo(AccountController.class) + .slash("accounts") + .slash(accountId) + .slash("orders") + .withSelfRel(), + linkTo(AccountController.class) + .slash("accounts") + .slash(accountId) + .withRel("account") + ); + + return accountOrders; + } + /** * Appends an {@link AccountEvent} domain event to the event log of the {@link Account} * aggregate with the specified accountId. @@ -165,7 +203,7 @@ public class AccountController { */ private Resource appendEventResource(Long accountId, AccountEvent event) { Assert.notNull(event, "Event body must be provided"); - + Account account = accountService.get(accountId); Assert.notNull(account, "Account could not be found"); @@ -215,6 +253,9 @@ public class AccountController { // Add get events link account.add(linkBuilder("getAccountEvents", account.getIdentity()).withRel("events")); + // Add orders link + account.add(linkBuilder("getAccountOrders", account.getIdentity()).withRel("orders")); + return new Resource<>(account); } diff --git a/account/account-web/src/main/java/demo/event/AccountEvent.java b/account/account-web/src/main/java/demo/account/event/AccountEvent.java similarity index 98% rename from account/account-web/src/main/java/demo/event/AccountEvent.java rename to account/account-web/src/main/java/demo/account/event/AccountEvent.java index b5fde9a..dc036c4 100644 --- a/account/account-web/src/main/java/demo/event/AccountEvent.java +++ b/account/account-web/src/main/java/demo/account/event/AccountEvent.java @@ -1,7 +1,8 @@ -package demo.event; +package demo.account.event; import com.fasterxml.jackson.annotation.JsonIgnore; import demo.account.Account; +import demo.event.Event; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/account/account-web/src/main/java/demo/event/AccountEventRepository.java b/account/account-web/src/main/java/demo/account/event/AccountEventRepository.java similarity index 58% rename from account/account-web/src/main/java/demo/event/AccountEventRepository.java rename to account/account-web/src/main/java/demo/account/event/AccountEventRepository.java index 465d40d..e6db4f4 100644 --- a/account/account-web/src/main/java/demo/event/AccountEventRepository.java +++ b/account/account-web/src/main/java/demo/account/event/AccountEventRepository.java @@ -1,4 +1,6 @@ -package demo.event; +package demo.account.event; + +import demo.event.EventRepository; public interface AccountEventRepository extends EventRepository { } diff --git a/account/account-web/src/main/java/demo/event/AccountEventType.java b/account/account-web/src/main/java/demo/account/event/AccountEventType.java similarity index 93% rename from account/account-web/src/main/java/demo/event/AccountEventType.java rename to account/account-web/src/main/java/demo/account/event/AccountEventType.java index 472b23f..19fb9a0 100644 --- a/account/account-web/src/main/java/demo/event/AccountEventType.java +++ b/account/account-web/src/main/java/demo/account/event/AccountEventType.java @@ -1,4 +1,4 @@ -package demo.event; +package demo.account.event; import demo.account.Account; import demo.account.AccountStatus; diff --git a/account/account-web/src/main/java/demo/config/WebMvcConfig.java b/account/account-web/src/main/java/demo/config/WebMvcConfig.java index 9282e23..003cb08 100644 --- a/account/account-web/src/main/java/demo/config/WebMvcConfig.java +++ b/account/account-web/src/main/java/demo/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package demo.config; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; @@ -28,6 +29,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter { } @Bean + @LoadBalanced protected RestTemplate restTemplate(ObjectMapper objectMapper) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); diff --git a/account/account-web/src/main/java/demo/order/OrderProvider.java b/account/account-web/src/main/java/demo/order/OrderProvider.java new file mode 100644 index 0000000..634cd87 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/OrderProvider.java @@ -0,0 +1,36 @@ +package demo.order; + +import demo.account.event.AccountEvent; +import demo.domain.Provider; +import demo.event.EventService; +import demo.order.domain.Order; + +@org.springframework.stereotype.Service +public class OrderProvider extends Provider { + + private final OrderService orderService; + private final EventService eventService; + + public OrderProvider(OrderService orderService, EventService eventService) { + this.orderService = orderService; + this.eventService = eventService; + } + + public OrderService getOrderService() { + return orderService; + } + + public EventService getEventService() { + return eventService; + } + + @Override + public OrderService getDefaultService() { + return orderService; + } + + @Override + public EventService getDefaultEventService() { + return eventService; + } +} diff --git a/account/account-web/src/main/java/demo/order/OrderService.java b/account/account-web/src/main/java/demo/order/OrderService.java new file mode 100644 index 0000000..2ce65dc --- /dev/null +++ b/account/account-web/src/main/java/demo/order/OrderService.java @@ -0,0 +1,55 @@ +package demo.order; + +import demo.domain.Service; +import demo.order.domain.Order; +import demo.order.domain.Orders; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.UriTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; + +@org.springframework.stereotype.Service +public class OrderService extends Service { + + private RestTemplate restTemplate; + + public OrderService(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public Order get(Long orderId) { + return restTemplate.getForObject(new UriTemplate("http://order-web/v1/orders/{id}") + .with("id", TemplateVariable.VariableType.PATH_VARIABLE) + .expand(orderId), Order.class); + } + + @Override + public Order create(Order order) { + return restTemplate.postForObject(new UriTemplate("http://order-web/v1/orders").expand(), + order, Order.class); + } + + @Override + public Order update(Order order) { + return restTemplate.exchange(new RequestEntity<>(order, HttpMethod.PUT, new UriTemplate + ("http://order-web/v1/orders/{id}").with("id", TemplateVariable.VariableType.PATH_VARIABLE) + .expand(order.getIdentity())), Order.class) + .getBody(); + } + + @Override + public boolean delete(Long orderId) { + restTemplate.delete(new UriTemplate("http://order-web/v1/orders/{id}").with("id", TemplateVariable + .VariableType.PATH_VARIABLE) + .expand(orderId)); + return true; + } + + public Orders findOrdersByAccountId(Long accountId) { + return restTemplate.getForObject(new UriTemplate("http://order-web/v1/orders/search/findOrdersByAccountId") + .with("accountId", TemplateVariable.VariableType.REQUEST_PARAM) + .expand(accountId), Orders.class); + } +} diff --git a/account/account-web/src/main/java/demo/order/domain/Address.java b/account/account-web/src/main/java/demo/order/domain/Address.java new file mode 100644 index 0000000..47a9f42 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/Address.java @@ -0,0 +1,93 @@ +package demo.order.domain; + +import java.io.Serializable; + +public class Address implements Serializable { + + private String street1, street2, state, city, country; + private Integer zipCode; + + private AddressType addressType; + + public Address() { + } + + public Address(String street1, String street2, String state, + String city, String country, Integer zipCode) { + this.street1 = street1; + this.street2 = street2; + this.state = state; + this.city = city; + this.country = country; + this.zipCode = zipCode; + } + + public String getStreet1() { + return street1; + } + + public void setStreet1(String street1) { + this.street1 = street1; + } + + public String getStreet2() { + return street2; + } + + public void setStreet2(String street2) { + this.street2 = street2; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Integer getZipCode() { + return zipCode; + } + + public void setZipCode(Integer zipCode) { + this.zipCode = zipCode; + } + + public AddressType getAddressType() { + return addressType; + } + + public void setAddressType(AddressType addressType) { + this.addressType = addressType; + } + + @Override + public String toString() { + return "Address{" + + "street1='" + street1 + '\'' + + ", street2='" + street2 + '\'' + + ", state='" + state + '\'' + + ", city='" + city + '\'' + + ", country='" + country + '\'' + + ", zipCode=" + zipCode + + ", addressType=" + addressType + + '}'; + } +} diff --git a/account/account-web/src/main/java/demo/order/domain/AddressType.java b/account/account-web/src/main/java/demo/order/domain/AddressType.java new file mode 100644 index 0000000..79b5424 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/AddressType.java @@ -0,0 +1,6 @@ +package demo.order.domain; + +public enum AddressType { + SHIPPING, + BILLING +} diff --git a/account/account-web/src/main/java/demo/order/domain/LineItem.java b/account/account-web/src/main/java/demo/order/domain/LineItem.java new file mode 100644 index 0000000..f63f73d --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/LineItem.java @@ -0,0 +1,71 @@ +package demo.order.domain; + +public class LineItem { + + private String name, productId; + private Integer quantity; + private Double price, tax; + + public LineItem() { + } + + public LineItem(String name, String productId, Integer quantity, + Double price, Double tax) { + this.name = name; + this.productId = productId; + this.quantity = quantity; + this.price = price; + this.tax = tax; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProductId() { + return productId; + } + + public void setProductId(String productId) { + this.productId = productId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Double getTax() { + return tax; + } + + public void setTax(Double tax) { + this.tax = tax; + } + + @Override + public String toString() { + return "LineItem{" + + "name='" + name + '\'' + + ", productId='" + productId + '\'' + + ", quantity=" + quantity + + ", price=" + price + + ", tax=" + tax + + '}'; + } +} \ No newline at end of file diff --git a/account/account-web/src/main/java/demo/order/domain/Order.java b/account/account-web/src/main/java/demo/order/domain/Order.java new file mode 100644 index 0000000..82814d0 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/Order.java @@ -0,0 +1,137 @@ +package demo.order.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import demo.domain.Aggregate; +import demo.domain.Provider; +import demo.order.OrderProvider; +import demo.order.event.OrderEvent; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.UriTemplate; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Order extends Aggregate { + + private Long id; + private List orderEvents = new ArrayList<>(); + private OrderStatus status; + private Set lineItems = new HashSet<>(); + private Address shippingAddress; + + private Long accountId, paymentId; + + public Order() { + this.status = OrderStatus.ORDER_CREATED; + } + + public Order(Long accountId, Address shippingAddress) { + this(); + this.accountId = accountId; + this.shippingAddress = shippingAddress; + if (shippingAddress.getAddressType() == null) + this.shippingAddress.setAddressType(AddressType.SHIPPING); + } + + public OrderStatus getStatus() { + return status; + } + + public void setStatus(OrderStatus status) { + this.status = status; + } + + public Set getLineItems() { + return lineItems; + } + + public void setLineItems(Set lineItems) { + this.lineItems = lineItems; + } + + public Address getShippingAddress() { + return shippingAddress; + } + + public void setShippingAddress(Address shippingAddress) { + this.shippingAddress = shippingAddress; + } + + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public Long getPaymentId() { + return paymentId; + } + + public void setPaymentId(Long paymentId) { + this.paymentId = paymentId; + } + + public Order post() { + OrderProvider orderProvider = getProvider(); + return orderProvider.getDefaultService() + .create(this); + } + + @Override + @JsonIgnore + public List getEvents() { + return orderEvents; + } + + @Override + public Long getIdentity() { + return id; + } + + public void setIdentity(Long id) { + this.id = id; + } + + /** + * Returns the {@link Link} with a rel of {@link Link#REL_SELF}. + */ + @Override + public Link getId() { + return new Link(new UriTemplate("http://order-web/v1/orders/{id}").with("id", TemplateVariable.VariableType + .PATH_VARIABLE) + .expand(getIdentity()) + .toString()).withSelfRel(); + } + + /** + * Retrieves an instance of the {@link Provider} for this instance + * + * @return the provider for this instance + * @throws IllegalArgumentException if the application context is unavailable or the provider does not exist + */ + @Override + @SuppressWarnings("unchecked") + public , A extends Aggregate> T getProvider() throws + IllegalArgumentException { + OrderProvider orderProvider = getProvider(OrderProvider.class); + return (T) orderProvider; + } + + @Override + public String toString() { + return "Order{" + + "id=" + id + + ", orderEvents=" + orderEvents + + ", status=" + status + + ", lineItems=" + lineItems + + ", shippingAddress=" + shippingAddress + + ", accountId=" + accountId + + ", paymentId=" + paymentId + + "} " + super.toString(); + } +} diff --git a/account/account-web/src/main/java/demo/order/domain/OrderStatus.java b/account/account-web/src/main/java/demo/order/domain/OrderStatus.java new file mode 100644 index 0000000..c1910bb --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/OrderStatus.java @@ -0,0 +1,15 @@ +package demo.order.domain; + +public enum OrderStatus { + ORDER_CREATED, + ACCOUNT_CONNECTED, + RESERVATION_PENDING, + INVENTORY_RESERVED, + RESERVATION_SUCCEEDED, + RESERVATION_FAILED, + PAYMENT_CREATED, + PAYMENT_CONNECTED, + PAYMENT_PENDING, + PAYMENT_SUCCEEDED, + PAYMENT_FAILED +} diff --git a/account/account-web/src/main/java/demo/order/domain/Orders.java b/account/account-web/src/main/java/demo/order/domain/Orders.java new file mode 100644 index 0000000..4e1e8e8 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/domain/Orders.java @@ -0,0 +1,23 @@ +package demo.order.domain; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resources; + +public class Orders extends Resources { + + /** + * Creates an empty {@link Resources} instance. + */ + public Orders() { + } + + /** + * Creates a {@link Resources} instance with the given content and {@link Link}s (optional). + * + * @param content must not be {@literal null}. + * @param links the links to be added to the {@link Resources}. + */ + public Orders(Iterable content, Link... links) { + super(content, links); + } +} diff --git a/account/account-web/src/main/java/demo/order/event/OrderEvent.java b/account/account-web/src/main/java/demo/order/event/OrderEvent.java new file mode 100644 index 0000000..b9bffac --- /dev/null +++ b/account/account-web/src/main/java/demo/order/event/OrderEvent.java @@ -0,0 +1,85 @@ +package demo.order.event; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import demo.event.Event; +import demo.order.domain.Order; + +/** + * The domain event {@link OrderEvent} tracks the type and state of events as applied to the {@link Order} domain + * object. This event resource can be used to event source the aggregate state of {@link Order}. + *

+ * This event resource also provides a transaction log that can be used to append actions to the event. + * + * @author Kenny Bastani + */ +public class OrderEvent extends Event { + + private Long eventId; + private OrderEventType type; + @JsonIgnore + private Order order; + private Long createdAt, lastModified; + + public OrderEvent() { + } + + public OrderEvent(OrderEventType type) { + this.type = type; + } + + public OrderEvent(OrderEventType type, Order order) { + this.type = type; + this.order = order; + } + + public Long getEventId() { + return eventId; + } + + public void setEventId(Long id) { + eventId = id; + } + + public OrderEventType getType() { + return type; + } + + public void setType(OrderEventType type) { + this.type = type; + } + + public Order getEntity() { + return order; + } + + public void setEntity(Order entity) { + this.order = entity; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } + + public Long getLastModified() { + return lastModified; + } + + public void setLastModified(Long lastModified) { + this.lastModified = lastModified; + } + + @Override + public String toString() { + return "OrderEvent{" + + "eventId=" + eventId + + ", type=" + type + + ", order=" + order + + ", createdAt=" + createdAt + + ", lastModified=" + lastModified + + "} " + super.toString(); + } +} diff --git a/account/account-web/src/main/java/demo/order/event/OrderEventType.java b/account/account-web/src/main/java/demo/order/event/OrderEventType.java new file mode 100644 index 0000000..ede43c9 --- /dev/null +++ b/account/account-web/src/main/java/demo/order/event/OrderEventType.java @@ -0,0 +1,24 @@ +package demo.order.event; + +import demo.order.domain.Order; +import demo.order.domain.OrderStatus; + +/** + * The {@link OrderEventType} represents a collection of possible events that describe state transitions of + * {@link OrderStatus} on the {@link Order} aggregate. + * + * @author Kenny Bastani + */ +public enum OrderEventType { + ORDER_CREATED, + ACCOUNT_CONNECTED, + RESERVATION_PENDING, + INVENTORY_RESERVED, + RESERVATION_SUCCEEDED, + RESERVATION_FAILED, + PAYMENT_CREATED, + PAYMENT_CONNECTED, + PAYMENT_PENDING, + PAYMENT_SUCCEEDED, + PAYMENT_FAILED +} diff --git a/account/account-web/src/main/resources/application.yml b/account/account-web/src/main/resources/application.yml index 3210601..dbbf0c2 100644 --- a/account/account-web/src/main/resources/application.yml +++ b/account/account-web/src/main/resources/application.yml @@ -11,4 +11,22 @@ spring: destination: account contentType: 'application/json' server: - port: 8080 \ No newline at end of file + port: 0 +events: + worker: http://account-worker/v1/events +--- +spring: + profiles: test + cloud: + stream: + bindings: + output: + destination: account + contentType: 'application/json' +server: + port: 0 +events: + worker: http://account-worker/v1/events +eureka: + client: + enabled: false \ No newline at end of file diff --git a/account/account-web/src/test/java/demo/account/AccountControllerTest.java b/account/account-web/src/test/java/demo/account/AccountControllerTest.java index ffe5c8f..2171f43 100644 --- a/account/account-web/src/test/java/demo/account/AccountControllerTest.java +++ b/account/account-web/src/test/java/demo/account/AccountControllerTest.java @@ -1,8 +1,8 @@ package demo.account; import demo.account.controller.AccountController; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import demo.event.EventService; import demo.event.Events; import org.junit.Test; @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -23,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) @WebMvcTest(AccountController.class) +@ActiveProfiles("test") public class AccountControllerTest { @Autowired diff --git a/account/account-web/src/test/java/demo/account/AccountServiceTests.java b/account/account-web/src/test/java/demo/account/AccountServiceTests.java index c3511d0..b4efd31 100644 --- a/account/account-web/src/test/java/demo/account/AccountServiceTests.java +++ b/account/account-web/src/test/java/demo/account/AccountServiceTests.java @@ -1,13 +1,15 @@ package demo.account; -import demo.event.AccountEvent; -import demo.event.AccountEventType; +import demo.account.event.AccountEvent; +import demo.account.event.AccountEventType; import demo.event.EventService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -15,6 +17,7 @@ import static org.mockito.BDDMockito.given; @RunWith(SpringRunner.class) @SpringBootTest +@ActiveProfiles("test") public class AccountServiceTests { @MockBean @@ -23,6 +26,9 @@ public class AccountServiceTests { @MockBean private AccountRepository accountRepository; + @MockBean + private DiscoveryClient discoveryClient; + @Autowired private AccountService accountService; diff --git a/account/account-worker/pom.xml b/account/account-worker/pom.xml index 4e76279..1eef4b5 100644 --- a/account/account-worker/pom.xml +++ b/account/account-worker/pom.xml @@ -43,6 +43,10 @@ org.springframework.boot spring-boot-starter-integration + + org.springframework.cloud + spring-cloud-starter-eureka + org.springframework.statemachine spring-statemachine-core diff --git a/account/account-worker/src/main/java/demo/AccountStreamModuleApplication.java b/account/account-worker/src/main/java/demo/AccountStreamModuleApplication.java index 53ac1ba..eb0b3b1 100644 --- a/account/account-worker/src/main/java/demo/AccountStreamModuleApplication.java +++ b/account/account-worker/src/main/java/demo/AccountStreamModuleApplication.java @@ -2,10 +2,12 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = {HypermediaType.HAL}) public class AccountStreamModuleApplication { public static void main(String[] args) { diff --git a/account/account-worker/src/main/resources/application.yml b/account/account-worker/src/main/resources/application.yml index 37237e4..8c05513 100644 --- a/account/account-worker/src/main/resources/application.yml +++ b/account/account-worker/src/main/resources/application.yml @@ -21,4 +21,7 @@ amazon: access-key-secret: replace --- spring: - profiles: test \ No newline at end of file + profiles: test +eureka: + client: + enabled: false \ No newline at end of file diff --git a/order/order-web/pom.xml b/order/order-web/pom.xml index b2d6d44..7abf34d 100644 --- a/order/order-web/pom.xml +++ b/order/order-web/pom.xml @@ -51,6 +51,10 @@ org.springframework.boot spring-boot-starter-integration + + org.springframework.cloud + spring-cloud-starter-eureka + org.kbastani spring-boot-starter-data-events diff --git a/order/order-web/src/main/java/demo/OrderServiceApplication.java b/order/order-web/src/main/java/demo/OrderServiceApplication.java index e1754ab..d6a5404 100644 --- a/order/order-web/src/main/java/demo/OrderServiceApplication.java +++ b/order/order-web/src/main/java/demo/OrderServiceApplication.java @@ -2,9 +2,11 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) public class OrderServiceApplication { diff --git a/order/order-web/src/main/java/demo/config/CacheConfig.java b/order/order-web/src/main/java/demo/config/CacheConfig.java deleted file mode 100644 index e84e06c..0000000 --- a/order/order-web/src/main/java/demo/config/CacheConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package demo.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.CacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; - -import java.util.Arrays; - -public class CacheConfig { - - @Bean - public JedisConnectionFactory redisConnectionFactory( - @Value("${spring.redis.port}") Integer redisPort, - @Value("${spring.redis.host}") String redisHost) { - JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(); - - redisConnectionFactory.setHostName(redisHost); - redisConnectionFactory.setPort(redisPort); - - return redisConnectionFactory; - } - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory cf) { - RedisTemplate redisTemplate = new RedisTemplate(); - redisTemplate.setConnectionFactory(cf); - return redisTemplate; - } - - @Bean - public CacheManager cacheManager(RedisTemplate redisTemplate) { - RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); - cacheManager.setDefaultExpiration(50000); - cacheManager.setCacheNames(Arrays.asList("orders", "order-events")); - cacheManager.setUsePrefix(true); - return cacheManager; - } -} \ No newline at end of file diff --git a/order/order-web/src/main/java/demo/config/WebMvcConfig.java b/order/order-web/src/main/java/demo/config/WebMvcConfig.java index 9282e23..003cb08 100644 --- a/order/order-web/src/main/java/demo/config/WebMvcConfig.java +++ b/order/order-web/src/main/java/demo/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package demo.config; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; @@ -28,6 +29,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter { } @Bean + @LoadBalanced protected RestTemplate restTemplate(ObjectMapper objectMapper) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); diff --git a/order/order-web/src/main/java/demo/order/Order.java b/order/order-web/src/main/java/demo/order/Order.java index 410a2d1..54a25c8 100644 --- a/order/order-web/src/main/java/demo/order/Order.java +++ b/order/order-web/src/main/java/demo/order/Order.java @@ -1,5 +1,6 @@ package demo.order; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import demo.address.Address; import demo.address.AddressType; @@ -136,6 +137,14 @@ public class Order extends AbstractEntity { return this; } + @JsonIgnore + public Double calculateTotal() { + return getLineItems() + .stream() + .mapToDouble(a -> (a.getPrice() + a.getTax()) * a.getQuantity()) + .sum(); + } + /** * Returns the {@link Link} with a rel of {@link Link#REL_SELF}. */ diff --git a/order/order-web/src/main/java/demo/order/OrderRepository.java b/order/order-web/src/main/java/demo/order/OrderRepository.java index 24baa90..dbca9a7 100644 --- a/order/order-web/src/main/java/demo/order/OrderRepository.java +++ b/order/order-web/src/main/java/demo/order/OrderRepository.java @@ -1,7 +1,10 @@ package demo.order; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface OrderRepository extends JpaRepository { - + List findOrdersByAccountId(@Param("accountId") Long accountId); } diff --git a/order/order-web/src/main/java/demo/order/OrderService.java b/order/order-web/src/main/java/demo/order/OrderService.java index b931bfa..bcce7bc 100644 --- a/order/order-web/src/main/java/demo/order/OrderService.java +++ b/order/order-web/src/main/java/demo/order/OrderService.java @@ -82,4 +82,8 @@ public class OrderService extends Service { this.orderRepository.delete(id); return true; } + + public Orders findOrdersByAccountId(Long accountId) { + return new Orders(orderRepository.findOrdersByAccountId(accountId)); + } } diff --git a/order/order-web/src/main/java/demo/order/Orders.java b/order/order-web/src/main/java/demo/order/Orders.java new file mode 100644 index 0000000..8d97327 --- /dev/null +++ b/order/order-web/src/main/java/demo/order/Orders.java @@ -0,0 +1,23 @@ +package demo.order; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resources; + +public class Orders extends Resources { + + /** + * Creates an empty {@link Resources} instance. + */ + public Orders() { + } + + /** + * Creates a {@link Resources} instance with the given content and {@link Link}s (optional). + * + * @param content must not be {@literal null}. + * @param links the links to be added to the {@link Resources}. + */ + public Orders(Iterable content, Link... links) { + super(content, links); + } +} diff --git a/order/order-web/src/main/java/demo/order/action/CreatePayment.java b/order/order-web/src/main/java/demo/order/action/CreatePayment.java index 704951b..9e5815d 100644 --- a/order/order-web/src/main/java/demo/order/action/CreatePayment.java +++ b/order/order-web/src/main/java/demo/order/action/CreatePayment.java @@ -15,6 +15,7 @@ import org.springframework.hateoas.Resource; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; import java.net.URI; @@ -38,24 +39,23 @@ public class CreatePayment extends Action { public Consumer getConsumer() { return order -> { + Assert.isTrue(order.getPaymentId() == null, "Payment has already been created"); + Assert.isTrue(order.getStatus() == OrderStatus.ACCOUNT_CONNECTED, "Account must be connected first"); - OrderService orderService = (OrderService) order.getProvider(OrderProvider.class) + OrderService orderService = order.getProvider(OrderProvider.class) .getDefaultService(); Payment payment = new Payment(); // Calculate payment amount - payment.setAmount(order.getLineItems() - .stream() - .mapToDouble(a -> (a.getPrice() + a.getTax()) * a.getQuantity()) - .sum()); + payment.setAmount(order.calculateTotal()); // Set payment method payment.setPaymentMethod(PaymentMethod.CREDIT_CARD); // Create a new request entity RequestEntity> requestEntity = RequestEntity.post( - URI.create("http://localhost:8082/v1/payments")) + URI.create("http://payment-web/v1/payments")) .contentType(MediaType.APPLICATION_JSON) .accept(MediaTypes.HAL_JSON) .body(new Resource<>(payment), Resource.class); diff --git a/order/order-web/src/main/java/demo/order/controller/OrderController.java b/order/order-web/src/main/java/demo/order/controller/OrderController.java index 5ce4421..e371942 100644 --- a/order/order-web/src/main/java/demo/order/controller/OrderController.java +++ b/order/order-web/src/main/java/demo/order/controller/OrderController.java @@ -6,17 +6,18 @@ import demo.event.Events; import demo.event.OrderEvent; import demo.order.Order; import demo.order.OrderService; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.LinkBuilder; -import org.springframework.hateoas.Resource; -import org.springframework.hateoas.ResourceSupport; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.hateoas.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; +import java.util.Random; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; @@ -26,10 +27,13 @@ public class OrderController { private final OrderService orderService; private final EventService eventService; + private final DiscoveryClient discoveryClient; - public OrderController(OrderService orderService, EventService eventService) { + public OrderController(OrderService orderService, EventService eventService, DiscoveryClient + discoveryClient) { this.orderService = orderService; this.eventService = eventService; + this.discoveryClient = discoveryClient; } @PostMapping(path = "/orders") @@ -121,6 +125,13 @@ public class OrderController { .orElseThrow(() -> new RuntimeException("The command could not be applied")); } + @RequestMapping(path = "/orders/search/findOrdersByAccountId") + public ResponseEntity findOrdersByAccountId(@RequestParam("accountId") Long accountId) { + return Optional.ofNullable(orderService.findOrdersByAccountId(accountId)) + .map(e -> new ResponseEntity<>(new Resources(e), HttpStatus.OK)) + .orElseThrow(() -> new RuntimeException("The command could not be applied")); + } + /** * Retrieves a hypermedia resource for {@link Order} with the specified identifier. * @@ -222,15 +233,38 @@ public class OrderController { // Add get events link order.add(linkBuilder("getOrderEvents", order.getIdentity()).withRel("events")); - if (order.getAccountId() != null) - order.add(new Link("http://account-service/v1/accounts/" + order.getAccountId(), "account")); + // Add remote account link + if (order.getAccountId() != null) { + Link result = getRemoteLink("account-web", "/v1/accounts/{id}", order.getAccountId(), "account"); + if (result != null) + order.add(result); + } - if (order.getPaymentId() != null) - order.add(new Link("http://localhost:8082/v1/payments/" + order.getPaymentId(), "payment")); + // Add remote payment link + if (order.getPaymentId() != null) { + Link result = getRemoteLink("payment-web", "/v1/payments/{id}", order.getPaymentId(), "payment"); + if (result != null) + order.add(result); + } return new Resource<>(order); } + private Link getRemoteLink(String service, String relative, Object identifier, String rel) { + Link result = null; + List serviceInstances = discoveryClient.getInstances(service); + if (serviceInstances.size() > 0) { + ServiceInstance serviceInstance = serviceInstances.get(new Random().nextInt(serviceInstances.size())); + result = new Link(new UriTemplate(serviceInstance.getUri() + .toString() + .concat(relative)).with("id", TemplateVariable.VariableType.PATH_VARIABLE) + .expand(identifier) + .toString()) + .withRel(rel); + } + return result; + } + private ResourceSupport getCommandsResources(Long id) { Order order = new Order(); order.setIdentity(id); diff --git a/order/order-web/src/main/resources/application.yml b/order/order-web/src/main/resources/application.yml index ff1a082..2d0e424 100644 --- a/order/order-web/src/main/resources/application.yml +++ b/order/order-web/src/main/resources/application.yml @@ -10,8 +10,23 @@ spring: output: destination: order contentType: 'application/json' - redis: - host: localhost - port: 6379 server: - port: 8080 \ No newline at end of file + port: 0 +events: + worker: http://order-worker/v1/events +--- +spring: + profiles: test + cloud: + stream: + bindings: + output: + destination: order + contentType: 'application/json' +server: + port: 0 +events: + worker: http://order-worker/v1/events +eureka: + client: + enabled: false \ No newline at end of file diff --git a/order/order-web/src/test/java/demo/OrderServiceApplicationTests.java b/order/order-web/src/test/java/demo/OrderServiceApplicationTests.java index 036375b..1fa2fa6 100644 --- a/order/order-web/src/test/java/demo/OrderServiceApplicationTests.java +++ b/order/order-web/src/test/java/demo/OrderServiceApplicationTests.java @@ -3,10 +3,12 @@ package demo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest +@ActiveProfiles("test") public class OrderServiceApplicationTests { @Test diff --git a/order/order-worker/pom.xml b/order/order-worker/pom.xml index 99973c2..39b7b5c 100644 --- a/order/order-worker/pom.xml +++ b/order/order-worker/pom.xml @@ -43,6 +43,10 @@ org.springframework.boot spring-boot-starter-integration + + org.springframework.cloud + spring-cloud-starter-eureka + org.springframework.statemachine spring-statemachine-core diff --git a/order/order-worker/src/main/java/demo/OrderWorkerApplication.java b/order/order-worker/src/main/java/demo/OrderWorkerApplication.java index f21b5c1..e0619b2 100644 --- a/order/order-worker/src/main/java/demo/OrderWorkerApplication.java +++ b/order/order-worker/src/main/java/demo/OrderWorkerApplication.java @@ -2,10 +2,12 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = {HypermediaType.HAL}) public class OrderWorkerApplication { public static void main(String[] args) { diff --git a/order/order-worker/src/main/resources/application.yml b/order/order-worker/src/main/resources/application.yml index 9f7428b..7efcdf6 100644 --- a/order/order-worker/src/main/resources/application.yml +++ b/order/order-worker/src/main/resources/application.yml @@ -14,11 +14,14 @@ spring: consumer: durableSubscription: true server: - port: 8081 + port: 0 amazon: aws: access-key-id: replace access-key-secret: replace --- spring: - profiles: test \ No newline at end of file + profiles: test +eureka: + client: + enabled: false \ No newline at end of file diff --git a/payment/payment-web/pom.xml b/payment/payment-web/pom.xml index c7b98fa..58c2ac6 100644 --- a/payment/payment-web/pom.xml +++ b/payment/payment-web/pom.xml @@ -56,6 +56,10 @@ spring-boot-starter-data-events 1.0-SNAPSHOT + + org.springframework.cloud + spring-cloud-starter-eureka + org.projectlombok diff --git a/payment/payment-web/src/main/java/demo/PaymentServiceApplication.java b/payment/payment-web/src/main/java/demo/PaymentServiceApplication.java index 3563fb0..e1f3724 100644 --- a/payment/payment-web/src/main/java/demo/PaymentServiceApplication.java +++ b/payment/payment-web/src/main/java/demo/PaymentServiceApplication.java @@ -2,13 +2,15 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = {EnableHypermediaSupport.HypermediaType.HAL}) public class PaymentServiceApplication { - public static void main(String[] args) { - SpringApplication.run(PaymentServiceApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(PaymentServiceApplication.class, args); + } } diff --git a/payment/payment-web/src/main/java/demo/config/CacheConfig.java b/payment/payment-web/src/main/java/demo/config/CacheConfig.java deleted file mode 100644 index e8b0e39..0000000 --- a/payment/payment-web/src/main/java/demo/config/CacheConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package demo.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.CacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; - -import java.util.Arrays; - -public class CacheConfig { - - @Bean - public JedisConnectionFactory redisConnectionFactory( - @Value("${spring.redis.port}") Integer redisPort, - @Value("${spring.redis.host}") String redisHost) { - JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(); - - redisConnectionFactory.setHostName(redisHost); - redisConnectionFactory.setPort(redisPort); - - return redisConnectionFactory; - } - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory cf) { - RedisTemplate redisTemplate = new RedisTemplate(); - redisTemplate.setConnectionFactory(cf); - return redisTemplate; - } - - @Bean - public CacheManager cacheManager(RedisTemplate redisTemplate) { - RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); - cacheManager.setDefaultExpiration(50000); - cacheManager.setCacheNames(Arrays.asList("payments", "payment-events")); - cacheManager.setUsePrefix(true); - return cacheManager; - } -} \ No newline at end of file diff --git a/payment/payment-web/src/main/java/demo/config/WebMvcConfig.java b/payment/payment-web/src/main/java/demo/config/WebMvcConfig.java index 9282e23..003cb08 100644 --- a/payment/payment-web/src/main/java/demo/config/WebMvcConfig.java +++ b/payment/payment-web/src/main/java/demo/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package demo.config; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; @@ -28,6 +29,7 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter { } @Bean + @LoadBalanced protected RestTemplate restTemplate(ObjectMapper objectMapper) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); diff --git a/payment/payment-web/src/main/resources/application.yml b/payment/payment-web/src/main/resources/application.yml index 5e66ac4..00f5e53 100644 --- a/payment/payment-web/src/main/resources/application.yml +++ b/payment/payment-web/src/main/resources/application.yml @@ -14,4 +14,22 @@ spring: host: localhost port: 6379 server: - port: 8082 \ No newline at end of file + port: 0 +events: + worker: http://payment-worker/v1/events +--- +spring: + profiles: test + cloud: + stream: + bindings: + output: + destination: payment + contentType: 'application/json' +server: + port: 0 +events: + worker: http://payment-worker/v1/events +eureka: + client: + enabled: false \ No newline at end of file diff --git a/payment/payment-web/src/test/java/demo/payment/EventServiceTests.java b/payment/payment-web/src/test/java/demo/payment/EventServiceTests.java index 3bc393d..8e1eac0 100644 --- a/payment/payment-web/src/test/java/demo/payment/EventServiceTests.java +++ b/payment/payment-web/src/test/java/demo/payment/EventServiceTests.java @@ -5,11 +5,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.Assert; @RunWith(SpringRunner.class) @SpringBootTest +@ActiveProfiles("test") public class EventServiceTests { @Autowired diff --git a/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java b/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java index a08e2fe..a7201ae 100644 --- a/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java +++ b/payment/payment-web/src/test/java/demo/payment/PaymentControllerTest.java @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -23,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) @WebMvcTest(PaymentController.class) +@ActiveProfiles("test") public class PaymentControllerTest { @Autowired diff --git a/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java b/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java index 78ba5ef..be1860c 100644 --- a/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java +++ b/payment/payment-web/src/test/java/demo/payment/PaymentServiceTests.java @@ -6,12 +6,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @RunWith(SpringRunner.class) +@ActiveProfiles("test") public class PaymentServiceTests { @MockBean diff --git a/payment/payment-worker/pom.xml b/payment/payment-worker/pom.xml index 01b16a2..0076816 100644 --- a/payment/payment-worker/pom.xml +++ b/payment/payment-worker/pom.xml @@ -43,6 +43,10 @@ org.springframework.boot spring-boot-starter-integration + + org.springframework.cloud + spring-cloud-starter-eureka + org.springframework.statemachine spring-statemachine-core diff --git a/payment/payment-worker/src/main/java/demo/PaymentStreamModuleApplication.java b/payment/payment-worker/src/main/java/demo/PaymentStreamModuleApplication.java index aa5db35..9b7912b 100644 --- a/payment/payment-worker/src/main/java/demo/PaymentStreamModuleApplication.java +++ b/payment/payment-worker/src/main/java/demo/PaymentStreamModuleApplication.java @@ -2,10 +2,12 @@ package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; @SpringBootApplication +@EnableDiscoveryClient @EnableHypermediaSupport(type = {HypermediaType.HAL}) public class PaymentStreamModuleApplication { public static void main(String[] args) { diff --git a/payment/payment-worker/src/main/resources/application.yml b/payment/payment-worker/src/main/resources/application.yml index becdec6..5f19ca0 100644 --- a/payment/payment-worker/src/main/resources/application.yml +++ b/payment/payment-worker/src/main/resources/application.yml @@ -14,11 +14,14 @@ spring: consumer: durableSubscription: true server: - port: 8083 + port: 0 amazon: aws: access-key-id: replace access-key-secret: replace --- spring: - profiles: test \ No newline at end of file + profiles: test +eureka: + client: + enabled: false \ No newline at end of file diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java index 427f555..def4fe2 100644 --- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java +++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Aggregate.java @@ -31,7 +31,7 @@ public abstract class Aggregate extend Value { @JsonProperty("id") - abstract ID getIdentity(); + public abstract ID getIdentity(); private final ApplicationContext applicationContext = Optional.ofNullable(Provider.getApplicationContext()) .orElse(null); @@ -123,7 +123,7 @@ public abstract class Aggregate extend .stream() .collect(Collectors.toList()); - if(!super.hasLink("self")) + if (!super.hasLink("self")) links.add(getId()); return links; @@ -165,12 +165,14 @@ public abstract class Aggregate extend } @SuppressWarnings("unchecked") - private Service getEntityService() { - return (Service) getProvider().getDefaultService(); + @JsonIgnore + protected Service, ID> getEntityService() { + return (Service, ID>) getProvider().getDefaultService(); } @SuppressWarnings("unchecked") - private EventService getEventService() { + @JsonIgnore + protected EventService getEventService() { return (EventService) getProvider().getDefaultEventService(); } diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java index 82d227f..1479efe 100644 --- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java +++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/domain/Provider.java @@ -25,7 +25,7 @@ public abstract class Provider implements ApplicationContex Provider.applicationContext = applicationContext; } - public abstract Service getDefaultService(); + public abstract Service getDefaultService(); public abstract EventService getDefaultEventService(); } diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java index 933477d..c4ebd5a 100755 --- a/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java +++ b/spring-boot-starters/spring-boot-starter-data-events/src/main/java/demo/event/EventServiceImpl.java @@ -2,6 +2,7 @@ package demo.event; import demo.domain.Aggregate; import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.stream.messaging.Source; import org.springframework.data.domain.PageRequest; import org.springframework.hateoas.Link; @@ -27,7 +28,9 @@ import java.net.URI; class EventServiceImpl implements EventService { private static final Logger log = Logger.getLogger(EventServiceImpl.class); - private static final String EVENT_PROCESSOR_URL = "http://localhost:8083/v1/events"; + + @Value("${events.worker:http://localhost:8080/v1/events}") + private String eventsWorker; private final EventRepository eventRepository; private final Source eventStream; @@ -41,7 +44,7 @@ class EventServiceImpl implements Even public S send(S event, Link... links) { // Assemble request to the event stream processor - RequestEntity> requestEntity = RequestEntity.post(URI.create(EVENT_PROCESSOR_URL)) + RequestEntity> requestEntity = RequestEntity.post(URI.create(eventsWorker)) .contentType(MediaTypes.HAL_JSON) .body(new Resource(event), Resource.class); diff --git a/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java index 98e9dae..667e6f0 100644 --- a/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java +++ b/spring-boot-starters/spring-boot-starter-data-events/src/test/java/demo/domain/ProviderTests.java @@ -120,7 +120,7 @@ public class ProviderTests { } @Override - Long getIdentity() { + public Long getIdentity() { return this.id; }