Load simulation module
This commit is contained in:
@@ -2,20 +2,22 @@ package demo.account.controller;
|
||||
|
||||
import demo.account.domain.Account;
|
||||
import demo.account.domain.AccountService;
|
||||
import demo.account.domain.Accounts;
|
||||
import demo.account.event.AccountEvent;
|
||||
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;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
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.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
@@ -32,6 +34,11 @@ public class AccountController {
|
||||
this.eventService = eventService;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts")
|
||||
public ResponseEntity getAccounts(@RequestBody(required = false) PageRequest pageRequest) {
|
||||
return new ResponseEntity<>(getAccountsResource(pageRequest), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping(path = "/accounts")
|
||||
public ResponseEntity createAccount(@RequestBody Account account) {
|
||||
return Optional.ofNullable(createAccountResource(account))
|
||||
@@ -89,6 +96,13 @@ public class AccountController {
|
||||
.orElseThrow(() -> new RuntimeException("Could not get account events"));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/accounts/{id}/orders/{orderId}")
|
||||
public ResponseEntity getAccountOrders(@PathVariable Long id, @PathVariable Long orderId) {
|
||||
return Optional.of(getAccountOrderResource(id, orderId))
|
||||
.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))
|
||||
@@ -136,7 +150,7 @@ public class AccountController {
|
||||
public ResponseEntity postOrder(@PathVariable Long id, @RequestBody Order order) {
|
||||
return Optional.ofNullable(accountService.get(id))
|
||||
.map(a -> a.postOrder(order))
|
||||
.map(o -> new ResponseEntity<>(o, HttpStatus.CREATED))
|
||||
.map(o -> new ResponseEntity<>(getAccountOrderResource(id, o.getIdentity()), HttpStatus.CREATED))
|
||||
.orElseThrow(() -> new RuntimeException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@@ -170,6 +184,50 @@ public class AccountController {
|
||||
return getAccountResource(accountService.update(account));
|
||||
}
|
||||
|
||||
private Accounts getAccountsResource(PageRequest pageRequest) {
|
||||
if (pageRequest == null) {
|
||||
pageRequest = new PageRequest(1, 20);
|
||||
}
|
||||
|
||||
Page<Account> accountPage = accountService.findAll(pageRequest.first());
|
||||
Accounts accounts = new Accounts(accountPage);
|
||||
|
||||
accounts.add(new Link(new UriTemplate(linkTo(AccountController.class).slash("accounts").toUri().toString())
|
||||
.with("page", TemplateVariable.VariableType.REQUEST_PARAM)
|
||||
.with("size", TemplateVariable.VariableType.REQUEST_PARAM), "self"));
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private Order getAccountOrderResource(Long accountId, Long orderId) {
|
||||
Account account = accountService.get(accountId);
|
||||
Assert.notNull(account, "Account could not be found");
|
||||
|
||||
Order order = account.getOrders().getContent().stream().filter(o -> Objects
|
||||
.equals(o.getIdentity(), orderId))
|
||||
.findFirst()
|
||||
.orElseGet(null);
|
||||
|
||||
Assert.notNull(order, "The order for the account could not be found");
|
||||
|
||||
order.removeLinks();
|
||||
|
||||
order.add(
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.slash("orders")
|
||||
.slash(orderId)
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.withRel("account")
|
||||
);
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private Orders getAccountOrdersResource(Long accountId) {
|
||||
Account account = accountService.get(accountId);
|
||||
Assert.notNull(account, "Account could not be found");
|
||||
|
||||
@@ -5,6 +5,8 @@ import demo.account.event.AccountEventType;
|
||||
import demo.account.repository.AccountRepository;
|
||||
import demo.domain.Service;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
@@ -17,6 +19,10 @@ public class AccountService extends Service<Account, Long> {
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
||||
|
||||
public Page<Account> findAll(Pageable pageable) {
|
||||
return accountRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new {@link Account} and handle a synchronous event flow for account creation
|
||||
*
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package demo.account.domain;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Accounts extends Resources<Account> {
|
||||
|
||||
private PageModel page;
|
||||
|
||||
public Accounts(Page<Account> accountPage) {
|
||||
super(accountPage.getContent());
|
||||
page = new PageModel(accountPage);
|
||||
}
|
||||
|
||||
public Accounts(Iterable<Account> content, Link... links) {
|
||||
super(content, links);
|
||||
}
|
||||
|
||||
public Accounts(Iterable<Account> content, Iterable<Link> links) {
|
||||
super(content, links);
|
||||
}
|
||||
|
||||
public PageModel getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
class PageModel implements Serializable {
|
||||
|
||||
private int number;
|
||||
private int size;
|
||||
private int totalPages;
|
||||
private long totalElements;
|
||||
|
||||
public PageModel() {
|
||||
}
|
||||
|
||||
public PageModel(Page page) {
|
||||
number = page.getNumber();
|
||||
size = page.getSize();
|
||||
totalPages = page.getTotalPages();
|
||||
totalElements = page.getTotalElements();
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public long getTotalElements() {
|
||||
return totalElements;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package demo.order.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.Aggregate;
|
||||
import demo.domain.Module;
|
||||
import demo.order.event.OrderEvent;
|
||||
@@ -105,6 +106,7 @@ public class Order extends Aggregate<OrderEvent, Long> {
|
||||
return orderEvents;
|
||||
}
|
||||
|
||||
@JsonProperty("orderId")
|
||||
@Override
|
||||
public Long getIdentity() {
|
||||
return id;
|
||||
|
||||
@@ -72,6 +72,12 @@ discovery:
|
||||
image: discovery
|
||||
ports:
|
||||
- 8761:8761
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=docker
|
||||
- DOCKER_IP=$DOCKER_IP
|
||||
net: host
|
||||
dashboard:
|
||||
image: dashboard
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=docker
|
||||
- DOCKER_IP=$DOCKER_IP
|
||||
|
||||
@@ -3,8 +3,10 @@ package demo;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@@ -13,4 +15,9 @@ public class OrderWorker {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OrderWorker.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import demo.order.event.OrderEvent;
|
||||
import demo.order.event.OrderEventType;
|
||||
import demo.order.event.OrderEvents;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
@@ -14,31 +16,57 @@ import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class StateFactory {
|
||||
|
||||
final private Logger log = Logger.getLogger(StateFactory.class);
|
||||
final private StateService stateService;
|
||||
final private RestTemplate restTemplate;
|
||||
final private DiscoveryClient discoveryClient;
|
||||
|
||||
public StateFactory(StateService stateService) {
|
||||
public StateFactory(StateService stateService, RestTemplate restTemplate, DiscoveryClient discoveryClient) {
|
||||
this.stateService = stateService;
|
||||
this.restTemplate = restTemplate;
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
public Order apply(OrderEvent orderEvent) {
|
||||
Assert.notNull(orderEvent, "Cannot apply a null event");
|
||||
Assert.notNull(orderEvent.getId(), "The event payload's identity link was not found");
|
||||
|
||||
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("order-web");
|
||||
Assert.notEmpty(serviceInstances, "No instances available for order-web");
|
||||
Integer instanceNum = new Random().nextInt(serviceInstances.size());
|
||||
ServiceInstance orderService = serviceInstances.get(instanceNum);
|
||||
|
||||
URI orderHref = getLoadBalanceUri(orderService, URI.create(orderEvent.getLink("order").getHref()));
|
||||
URI selfHref = getLoadBalanceUri(orderService, URI.create(orderEvent.getLink("self").getHref()));
|
||||
|
||||
orderEvent.getLinks()
|
||||
.replaceAll(a -> Objects.equals(a.getRel(), "order") ? new Link(orderHref
|
||||
.toString(), "order") : a);
|
||||
|
||||
orderEvent.getLinks()
|
||||
.replaceAll(a -> Objects.equals(a.getRel(), "self") ? new Link(selfHref
|
||||
.toString(), "self") : a);
|
||||
|
||||
StateMachine<OrderStatus, OrderEventType> stateMachine = getStateMachine(orderEvent);
|
||||
stateMachine.stop();
|
||||
|
||||
return stateMachine.getExtendedState().get("order", Order.class);
|
||||
}
|
||||
|
||||
private URI getLoadBalanceUri(ServiceInstance serviceInstance, URI uri) {
|
||||
return URI.create(uri.toString()
|
||||
.replace(uri.getHost(), serviceInstance.getHost())
|
||||
.replace(":" + uri.getPort(), ":" + String.valueOf(serviceInstance.getPort())));
|
||||
}
|
||||
|
||||
private StateMachine<OrderStatus, OrderEventType> getStateMachine(OrderEvent orderEvent) {
|
||||
Link eventId = orderEvent.getId();
|
||||
log.info(String.format("Order event received: %s", eventId));
|
||||
@@ -67,13 +95,18 @@ public class StateFactory {
|
||||
}
|
||||
|
||||
private OrderEvents getEventLog(OrderEvent event) {
|
||||
|
||||
// Replace the host and port of the link with an available instance
|
||||
URI orderHref = URI.create(event.getLink("order")
|
||||
.getHref());
|
||||
|
||||
// Follow the hypermedia link to fetch the attached order
|
||||
Traverson traverson = new Traverson(
|
||||
URI.create(event.getLink("order")
|
||||
.getHref()),
|
||||
Traverson traverson = new Traverson(orderHref,
|
||||
MediaTypes.HAL_JSON
|
||||
);
|
||||
|
||||
traverson.setRestOperations(restTemplate);
|
||||
|
||||
// Get the event log for the attached order resource
|
||||
return traverson.follow("events")
|
||||
.toEntity(OrderEvents.class)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package demo.order.event;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import demo.domain.AbstractEntity;
|
||||
import org.springframework.hateoas.Link;
|
||||
|
||||
public class OrderEvent extends AbstractEntity {
|
||||
|
||||
@@ -21,6 +23,12 @@ public class OrderEvent extends AbstractEntity {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Link getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrderEvent{" +
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dataflow-dependencies</artifactId>
|
||||
<version>1.1.0.RELEASE</version>
|
||||
<version>1.1.2.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -33,19 +33,51 @@ public class ImportResources implements ApplicationListener<ApplicationReadyEven
|
||||
|
||||
// Deploy a set of event stream definitions
|
||||
List<StreamApp> streams = Arrays.asList(
|
||||
new StreamApp("account-stream",
|
||||
"account-web: account-web | account-worker: account-worker"),
|
||||
new StreamApp("order-stream",
|
||||
"order-web: order-web | order-worker: order-worker"),
|
||||
new StreamApp("payment-stream",
|
||||
"payment-web: payment-web | payment-worker: payment-worker"),
|
||||
new StreamApp("warehouse-stream",
|
||||
"warehouse-web: warehouse-web | warehouse-worker: warehouse-worker"));
|
||||
new StreamApp("account-event-stream",
|
||||
"account-web > :account-stream"),
|
||||
new StreamApp("account-event-processor",
|
||||
":account-stream > account-worker"),
|
||||
new StreamApp("account-event-counter",
|
||||
":account-stream > field-value-counter --field-name=type --name=account-events"),
|
||||
new StreamApp("order-event-stream",
|
||||
"order-web > :order-stream"),
|
||||
new StreamApp("order-event-processor",
|
||||
":order-stream > order-worker"),
|
||||
new StreamApp("order-event-counter",
|
||||
":order-stream > field-value-counter --field-name=type --name=order-events"),
|
||||
new StreamApp("payment-event-stream",
|
||||
"payment-web > :payment-stream"),
|
||||
new StreamApp("payment-event-processor",
|
||||
":payment-stream > payment-worker"),
|
||||
new StreamApp("payment-event-counter",
|
||||
":payment-stream > field-value-counter --field-name=type --name=payment-events"),
|
||||
new StreamApp("warehouse-event-stream",
|
||||
"warehouse-web > :warehouse-stream"),
|
||||
new StreamApp("warehouse-event-processor",
|
||||
":warehouse-stream > warehouse-worker"),
|
||||
new StreamApp("warehouse-event-counter",
|
||||
":warehouse-stream > field-value-counter --field-name=type --name=warehouse-events"),
|
||||
new StreamApp("account-load-simulator", "time --time-unit=SECONDS --initial-delay=60 " +
|
||||
"--fixed-delay=30 | " +
|
||||
"load-simulator --domain=ACCOUNT --operation=CREATE > :load-log"),
|
||||
new StreamApp("inventory-load-simulator", "time --time-unit=SECONDS --initial-delay=60 " +
|
||||
"--fixed-delay=1 | " +
|
||||
"load-simulator --domain=INVENTORY --operation=CREATE --range=10 > :load-log"),
|
||||
new StreamApp("order-load-simulator", "time --time-unit=SECONDS --initial-delay=80 " +
|
||||
"--fixed-delay=5 | " +
|
||||
"load-simulator --command=POST_ORDER --domain=ACCOUNT --operation=CREATE --range=5 > :load-log"),
|
||||
new StreamApp("account-counter",
|
||||
":account-stream > counter --name-expression=payload.type.toString()"),
|
||||
new StreamApp("order-counter",
|
||||
":order-stream > counter --name-expression=payload.type.toString()"),
|
||||
new StreamApp("warehouse-counter",
|
||||
":warehouse-stream > counter --name-expression=payload.type.toString()"));
|
||||
|
||||
// Deploy the streams in parallel
|
||||
streams.parallelStream()
|
||||
.forEach(stream -> dataFlowTemplate.streamOperations()
|
||||
.createStream(stream.getName(), stream.getDefinition(), true));
|
||||
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ sink.order-worker=maven://org.kbastani:order-worker:0.0.1-SNAPSHOT
|
||||
source.payment-web=maven://org.kbastani:payment-web:0.0.1-SNAPSHOT
|
||||
sink.payment-worker=maven://org.kbastani:payment-worker:0.0.1-SNAPSHOT
|
||||
source.warehouse-web=maven://org.kbastani:warehouse-web:0.0.1-SNAPSHOT
|
||||
sink.warehouse-worker=maven://org.kbastani:warehouse-worker:0.0.1-SNAPSHOT
|
||||
sink.warehouse-worker=maven://org.kbastani:warehouse-worker:0.0.1-SNAPSHOT
|
||||
processor.load-simulator=maven://org.kbastani:load-simulator:1.0-SNAPSHOT
|
||||
@@ -26,5 +26,6 @@
|
||||
<modules>
|
||||
<module>discovery</module>
|
||||
<module>data-flow-server</module>
|
||||
<module>stream-modules</module>
|
||||
</modules>
|
||||
</project>
|
||||
67
platform-services/stream-modules/load-simulator/pom.xml
Normal file
67
platform-services/stream-modules/load-simulator/pom.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>load-simulator</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>load-simulator</name>
|
||||
<description>Simulates load on event-driven microservices</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.kbastani</groupId>
|
||||
<artifactId>stream-modules</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-eureka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.javafaker</groupId>
|
||||
<artifactId>javafaker</artifactId>
|
||||
<version>0.12</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,5 @@
|
||||
package demo;
|
||||
|
||||
public enum Command {
|
||||
POST_ORDER
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package demo;
|
||||
|
||||
public enum Domain {
|
||||
ACCOUNT,
|
||||
ORDER,
|
||||
WAREHOUSE,
|
||||
INVENTORY
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package demo;
|
||||
|
||||
import com.github.javafaker.Faker;
|
||||
import demo.account.domain.Account;
|
||||
import demo.account.domain.AccountStatus;
|
||||
import demo.account.service.AccountService;
|
||||
import demo.domain.Address;
|
||||
import demo.domain.AddressType;
|
||||
import demo.inventory.domain.Inventory;
|
||||
import demo.inventory.domain.InventoryStatus;
|
||||
import demo.order.domain.LineItem;
|
||||
import demo.order.domain.Order;
|
||||
import demo.warehouse.domain.Warehouse;
|
||||
import demo.warehouse.service.WarehouseService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
@MessageEndpoint
|
||||
public class LoadProcessor {
|
||||
|
||||
private static final Logger log = Logger.getLogger(LoadProcessor.class);
|
||||
private final LoadSimulatorProperties properties;
|
||||
private final AccountService accountService;
|
||||
private final WarehouseService warehouseService;
|
||||
|
||||
public LoadProcessor(LoadSimulatorProperties properties, AccountService accountService,
|
||||
WarehouseService warehouseService) {
|
||||
this.properties = properties;
|
||||
this.accountService = accountService;
|
||||
this.warehouseService = warehouseService;
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
|
||||
public Object process(Message<?> message) {
|
||||
return handleRequest(message);
|
||||
}
|
||||
|
||||
private String handleRequest(Message<?> message) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
switch (properties.getDomain()) {
|
||||
case ACCOUNT:
|
||||
accountOperation(properties.getOperation(), sb);
|
||||
break;
|
||||
case ORDER:
|
||||
break;
|
||||
case WAREHOUSE:
|
||||
break;
|
||||
case INVENTORY:
|
||||
inventoryOperation(properties.getOperation(), sb);
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void inventoryOperation(Operation operation, StringBuffer sb) {
|
||||
Faker faker = new Faker();
|
||||
|
||||
switch (operation) {
|
||||
case CREATE:
|
||||
Warehouse warehouse = null;
|
||||
|
||||
try {
|
||||
warehouse = warehouseService.get(1L);
|
||||
} catch (Exception ex) {
|
||||
log.error(ex);
|
||||
}
|
||||
|
||||
if (warehouse == null) {
|
||||
// Create the first warehouse
|
||||
warehouse = warehouseService.create(new Warehouse(new Address(faker.address().streetAddress(),
|
||||
faker.address().buildingNumber(),
|
||||
faker.address().state(),
|
||||
faker.address().city(),
|
||||
faker.address().country(),
|
||||
Integer.parseInt(faker.address().zipCode()))));
|
||||
|
||||
sb.append("[Warehouse created]\n");
|
||||
}
|
||||
|
||||
List<Inventory> inventory = new ArrayList<>();
|
||||
|
||||
LongStream.range(0, properties.getRange())
|
||||
.forEach(a -> inventory.add(new Inventory(InventoryStatus.INVENTORY_CREATED, "SKU-" + a)));
|
||||
|
||||
List<Inventory> results = warehouseService.addInventory(inventory, 1L);
|
||||
|
||||
sb.append(String.format("[%s inventory added to warehouse]\n", results.size()));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void accountOperation(Operation operation, StringBuffer sb) {
|
||||
|
||||
Faker faker = new Faker();
|
||||
|
||||
switch (operation) {
|
||||
case CREATE:
|
||||
if (properties.getCommand() == Command.POST_ORDER) {
|
||||
// Post new order to the accounts
|
||||
LongStream.range(1, properties.getRange() + 1)
|
||||
.forEach(a -> {
|
||||
try {
|
||||
Order order = new Order(a, new Address(faker.address().streetAddress(),
|
||||
faker.address().buildingNumber(),
|
||||
faker.address().state(),
|
||||
faker.address().city(),
|
||||
faker.address().country(),
|
||||
Integer.parseInt(faker.address().zipCode().substring(0, 4)),
|
||||
AddressType.SHIPPING));
|
||||
|
||||
IntStream.range(0, new Random().nextInt(5))
|
||||
.forEach(i -> order.getLineItems()
|
||||
.add(new LineItem(faker.commerce().productName(),
|
||||
"SKU-" + i, new Random().nextInt(3), Double
|
||||
.parseDouble(faker.commerce().price(.99, 50.0)), .06)));
|
||||
|
||||
Order result = accountService.postOrder(a, order);
|
||||
|
||||
sb.append(String.format("[New order posted to account %s]\n", a));
|
||||
} catch (Exception ex) {
|
||||
log.error(ex);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LongStream.range(0, properties.getRange())
|
||||
.forEach(a -> accountService
|
||||
.create(new Account(faker.name().firstName(), faker.name().lastName(), faker
|
||||
.internet()
|
||||
.emailAddress(), AccountStatus.ACCOUNT_CREATED)));
|
||||
|
||||
sb.append(String.format("[%s new accounts created]\n", properties.getRange()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||
|
||||
@EnableEurekaClient
|
||||
@SpringBootApplication
|
||||
public class LoadSimulatorApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LoadSimulatorApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package demo;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.config.SpelExpressionConverterConfiguration;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
@EnableBinding(Processor.class)
|
||||
@Import(SpelExpressionConverterConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadSimulatorProperties.class)
|
||||
public class LoadSimulatorConfiguration {
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package demo;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties
|
||||
public class LoadSimulatorProperties {
|
||||
private Domain domain = Domain.ACCOUNT;
|
||||
private Operation operation = Operation.CREATE;
|
||||
private Command command;
|
||||
private Long range = 1L;
|
||||
|
||||
public Domain getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(Domain domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public void setOperation(Operation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public Command getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(Command command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public Long getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public void setRange(Long range) {
|
||||
this.range = range;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package demo;
|
||||
|
||||
public enum Operation {
|
||||
CREATE
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package demo.account.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Account {
|
||||
private Long id;
|
||||
private List<AccountEvent> accountEvents = new ArrayList<>();
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private AccountStatus status;
|
||||
|
||||
public Account() {
|
||||
}
|
||||
|
||||
public Account(String firstName, String lastName, String email, AccountStatus status) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public AccountStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(AccountStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<AccountEvent> getEvents() {
|
||||
return accountEvents;
|
||||
}
|
||||
|
||||
@JsonProperty("accountId")
|
||||
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}.
|
||||
*/
|
||||
public Link getId() {
|
||||
return new Link(new UriTemplate("http://account-web/v1/accounts/{id}").with("id", TemplateVariable.VariableType
|
||||
.PATH_VARIABLE)
|
||||
.expand(getIdentity())
|
||||
.toString()).withSelfRel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package demo.account.domain;
|
||||
|
||||
/**
|
||||
* The domain event {@link AccountEvent} tracks the type and state of events as
|
||||
* applied to the {@link Account} domain object. This event resource can be used
|
||||
* to event source the aggregate state of {@link Account}.
|
||||
* <p>
|
||||
* This event resource also provides a transaction log that can be used to append
|
||||
* actions to the event.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public class AccountEvent {
|
||||
|
||||
private AccountEventType type;
|
||||
|
||||
public AccountEvent() {
|
||||
}
|
||||
|
||||
public AccountEvent(AccountEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public AccountEventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(AccountEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AccountEvent{" +
|
||||
"type=" + type +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package demo.account.domain;
|
||||
|
||||
/**
|
||||
* The {@link AccountEventType} represents a collection of possible events that describe
|
||||
* state transitions of {@link AccountStatus} on the {@link Account} aggregate.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum AccountEventType {
|
||||
ACCOUNT_CREATED,
|
||||
ACCOUNT_CONFIRMED,
|
||||
ACCOUNT_ACTIVATED,
|
||||
ACCOUNT_SUSPENDED,
|
||||
ACCOUNT_ARCHIVED
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package demo.account.domain;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public enum AccountStatus {
|
||||
ACCOUNT_CREATED,
|
||||
ACCOUNT_PENDING,
|
||||
ACCOUNT_CONFIRMED,
|
||||
ACCOUNT_ACTIVE,
|
||||
ACCOUNT_SUSPENDED,
|
||||
ACCOUNT_ARCHIVED
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package demo.account.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import demo.account.domain.Account;
|
||||
import demo.order.domain.Order;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class AccountService {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public AccountService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public Account get(Long accountId) {
|
||||
Account result;
|
||||
try {
|
||||
result = restTemplate.getForObject(new UriTemplate("http://account-web/v1/accounts/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(accountId), Account.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Get account failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Account create(Account account) {
|
||||
Account result;
|
||||
try {
|
||||
result = restTemplate.postForObject(new UriTemplate("http://account-web/v1/accounts").expand(),
|
||||
account, Account.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Create account failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Account update(Account account) {
|
||||
Account result;
|
||||
try {
|
||||
result = restTemplate.exchange(new RequestEntity<>(account, HttpMethod.PUT,
|
||||
new UriTemplate("http://account-web/v1/accounts/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(account.getIdentity())), Account.class).getBody();
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Update account failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean delete(Long accountId) {
|
||||
try {
|
||||
restTemplate.delete(new UriTemplate("http://account-web/v1/accounts/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE).expand(accountId));
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Delete account failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Order postOrder(Long accountId, Order order) {
|
||||
Order result;
|
||||
try {
|
||||
result = restTemplate.postForObject(new UriTemplate(String
|
||||
.format("http://account-web/v1/accounts/%s/commands/postOrder", accountId)).expand(),
|
||||
order, Order.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Post order to account has failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getHttpStatusMessage(RestClientResponseException ex) {
|
||||
Map<String, String> errorMap = new HashMap<>();
|
||||
try {
|
||||
errorMap = new ObjectMapper()
|
||||
.readValue(ex.getResponseBodyAsString(), errorMap
|
||||
.getClass());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return errorMap.getOrDefault("message", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package demo.domain;
|
||||
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
|
||||
public class AbstractEntity extends ResourceSupport {
|
||||
|
||||
private Long createdAt;
|
||||
private Long lastModified;
|
||||
|
||||
public AbstractEntity() {
|
||||
}
|
||||
|
||||
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 "BaseEntity{" +
|
||||
"createdAt=" + createdAt +
|
||||
", lastModified=" + lastModified +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package demo.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 Address(String street1, String street2, String state,
|
||||
String city, String country, Integer zipCode, AddressType addressType) {
|
||||
this.street1 = street1;
|
||||
this.street2 = street2;
|
||||
this.state = state;
|
||||
this.city = city;
|
||||
this.country = country;
|
||||
this.zipCode = zipCode;
|
||||
this.addressType = addressType;
|
||||
}
|
||||
|
||||
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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package demo.domain;
|
||||
|
||||
public enum AddressType {
|
||||
SHIPPING,
|
||||
BILLING
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package demo.inventory.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.AbstractEntity;
|
||||
import org.springframework.hateoas.Link;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Inventory extends AbstractEntity {
|
||||
private Long id;
|
||||
private InventoryStatus status;
|
||||
private List<InventoryEvent> events = new ArrayList<>();
|
||||
private String productId;
|
||||
|
||||
public Inventory() {
|
||||
}
|
||||
|
||||
public Inventory(InventoryStatus status, String productId) {
|
||||
this.status = status;
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
@JsonProperty("inventoryId")
|
||||
public Long getIdentity() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setIdentity(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public InventoryStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(InventoryStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public List<InventoryEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public void setEvents(List<InventoryEvent> events) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public String getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(String productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||
*/
|
||||
@Override
|
||||
public Link getId() {
|
||||
return getLink("self");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package demo.inventory.domain;
|
||||
|
||||
import demo.domain.AbstractEntity;
|
||||
|
||||
/**
|
||||
* The domain event {@link InventoryEvent} tracks the type and state of events as applied to the {@link Inventory} domain
|
||||
* object. This event resource can be used to event source the aggregate state of {@link Inventory}.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public class InventoryEvent extends AbstractEntity {
|
||||
|
||||
private InventoryEventType type;
|
||||
|
||||
public InventoryEvent() {
|
||||
}
|
||||
|
||||
public InventoryEvent(InventoryEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public InventoryEventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(InventoryEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InventoryEvent{" +
|
||||
"type=" + type +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package demo.inventory.domain;
|
||||
|
||||
/**
|
||||
* The {@link InventoryEventType} represents a collection of possible events that describe state transitions of
|
||||
* {@link InventoryStatus} on the {@link Inventory} aggregate.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
public enum InventoryEventType {
|
||||
INVENTORY_CREATED,
|
||||
RESERVATION_PENDING,
|
||||
RESERVATION_CONNECTED,
|
||||
INVENTORY_RESERVED,
|
||||
INVENTORY_RELEASED
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package demo.inventory.domain;
|
||||
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
public class InventoryEvents extends Resources<InventoryEvent> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package demo.inventory.domain;
|
||||
|
||||
public enum InventoryStatus {
|
||||
INVENTORY_CREATED,
|
||||
RESERVATION_PENDING,
|
||||
RESERVATION_CONNECTED,
|
||||
INVENTORY_RESERVED,
|
||||
INVENTORY_RELEASED
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package demo.order.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.Address;
|
||||
import demo.domain.AddressType;
|
||||
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 {
|
||||
|
||||
private Long id;
|
||||
private Long createdAt;
|
||||
private Long lastModified;
|
||||
private List<OrderEvent> orderEvents = new ArrayList<>();
|
||||
private OrderStatus status;
|
||||
private Set<LineItem> 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 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;
|
||||
}
|
||||
|
||||
public OrderStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(OrderStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Set<LineItem> getLineItems() {
|
||||
return lineItems;
|
||||
}
|
||||
|
||||
public void setLineItems(Set<LineItem> 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;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<OrderEvent> getEvents() {
|
||||
return orderEvents;
|
||||
}
|
||||
|
||||
@JsonProperty("orderId")
|
||||
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}.
|
||||
*/
|
||||
public Link getId() {
|
||||
return new Link(new UriTemplate("http://order-web/v1/orders/{id}").with("id", TemplateVariable.VariableType
|
||||
.PATH_VARIABLE)
|
||||
.expand(getIdentity())
|
||||
.toString()).withSelfRel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package demo.order.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* <p>
|
||||
* This event resource also provides a transaction log that can be used to append actions to the event.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
public class OrderEvent {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package demo.order.domain;
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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,
|
||||
ORDER_SUCCEEDED,
|
||||
ORDER_FAILED
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package demo.order.domain;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
public class Orders extends Resources<Order> {
|
||||
|
||||
/**
|
||||
* 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<Order> content, Link... links) {
|
||||
super(content, links);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package demo.order.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import demo.order.domain.Order;
|
||||
import demo.order.domain.Orders;
|
||||
import org.apache.log4j.Logger;
|
||||
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.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
public class OrderService {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public OrderService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public Order get(Long orderId) {
|
||||
Order result;
|
||||
try {
|
||||
result = restTemplate.getForObject(new UriTemplate("http://order-web/v1/orders/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(orderId), Order.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Get order failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Order create(Order order) {
|
||||
Order result;
|
||||
try {
|
||||
result = restTemplate.postForObject(new UriTemplate("http://order-web/v1/orders").expand(),
|
||||
order, Order.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Create order failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Order update(Order order) {
|
||||
Order result;
|
||||
try {
|
||||
result = 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();
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Update order failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean delete(Long orderId) {
|
||||
try {
|
||||
restTemplate.delete(new UriTemplate("http://order-web/v1/orders/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE).expand(orderId));
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Delete order failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Orders findOrdersByAccountId(Long accountId) {
|
||||
Orders result;
|
||||
try {
|
||||
result = restTemplate
|
||||
.getForObject(new UriTemplate("http://order-web/v1/orders/search/findOrdersByAccountId")
|
||||
.with("accountId", TemplateVariable.VariableType.REQUEST_PARAM)
|
||||
.expand(accountId), Orders.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Delete order failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getHttpStatusMessage(RestClientResponseException ex) {
|
||||
Map<String, String> errorMap = new HashMap<>();
|
||||
try {
|
||||
errorMap = new ObjectMapper()
|
||||
.readValue(ex.getResponseBodyAsString(), errorMap
|
||||
.getClass());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return errorMap.getOrDefault("message", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package demo.warehouse.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import demo.domain.AbstractEntity;
|
||||
import demo.domain.Address;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Warehouse extends AbstractEntity {
|
||||
|
||||
private Long id;
|
||||
private List<WarehouseEvent> events = new ArrayList<>();
|
||||
private Address address;
|
||||
private WarehouseStatus status;
|
||||
|
||||
public Warehouse() {
|
||||
}
|
||||
|
||||
public Warehouse(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public WarehouseStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(WarehouseStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@JsonProperty("warehouseId")
|
||||
public Long getIdentity() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setIdentity(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<WarehouseEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} with a rel of {@link Link#REL_SELF}.
|
||||
*/
|
||||
@Override
|
||||
public Link getId() {
|
||||
return new Link(new UriTemplate("http://warehouse-web/v1/warehouses/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(getIdentity())
|
||||
.toString()).withSelfRel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Warehouse{" +
|
||||
"id=" + id +
|
||||
", events=" + events +
|
||||
", address=" + address +
|
||||
", status=" + status +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package demo.warehouse.domain;
|
||||
|
||||
/**
|
||||
* The domain event {@link WarehouseEvent} tracks the type and state of events as applied to the {@link Warehouse}
|
||||
* domain
|
||||
* object. This event resource can be used to event source the aggregate state of {@link Warehouse}.
|
||||
*
|
||||
* @author kbastani
|
||||
*/
|
||||
public class WarehouseEvent {
|
||||
|
||||
private Long eventId;
|
||||
private WarehouseEventType type;
|
||||
private Warehouse entity;
|
||||
private Long createdAt;
|
||||
private Long lastModified;
|
||||
|
||||
public WarehouseEvent() {
|
||||
}
|
||||
|
||||
public WarehouseEvent(WarehouseEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public WarehouseEvent(WarehouseEventType type, Warehouse entity) {
|
||||
this.type = type;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public Long getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public void setEventId(Long id) {
|
||||
eventId = id;
|
||||
}
|
||||
|
||||
public WarehouseEventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(WarehouseEventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Warehouse getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void setEntity(Warehouse entity) {
|
||||
this.entity = 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;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "WarehouseEvent{" +
|
||||
"eventId=" + eventId +
|
||||
", type=" + type +
|
||||
", entity=" + entity +
|
||||
", createdAt=" + createdAt +
|
||||
", lastModified=" + lastModified +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package demo.warehouse.domain;
|
||||
|
||||
/**
|
||||
* The {@link WarehouseEventType} represents a collection of possible events that describe state transitions of
|
||||
* {@link WarehouseStatus} on the {@link Warehouse} aggregate.
|
||||
*
|
||||
* @author Kenny Bastani
|
||||
*/
|
||||
public enum WarehouseEventType {
|
||||
WAREHOUSE_CREATED
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package demo.warehouse.domain;
|
||||
|
||||
public enum WarehouseStatus {
|
||||
WAREHOUSE_CREATED
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package demo.warehouse.domain;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
public class Warehouses extends Resources<Warehouse> {
|
||||
|
||||
/**
|
||||
* Creates an empty {@link Resources} instance.
|
||||
*/
|
||||
public Warehouses() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Warehouses(Iterable<Warehouse> content, Link... links) {
|
||||
super(content, links);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package demo.warehouse.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import demo.inventory.domain.Inventory;
|
||||
import demo.warehouse.domain.Warehouse;
|
||||
import org.apache.log4j.Logger;
|
||||
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.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@org.springframework.stereotype.Service
|
||||
public class WarehouseService {
|
||||
|
||||
private final Logger log = Logger.getLogger(this.getClass());
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public WarehouseService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public Warehouse get(Long warehouseId) {
|
||||
Warehouse result;
|
||||
try {
|
||||
result = restTemplate.getForObject(new UriTemplate("http://warehouse-web/v1/warehouses/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(warehouseId), Warehouse.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Get warehouse failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Warehouse create(Warehouse warehouse) {
|
||||
Warehouse result;
|
||||
try {
|
||||
result = restTemplate.postForObject(new UriTemplate("http://warehouse-web/v1/warehouses").expand(),
|
||||
warehouse, Warehouse.class);
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Create warehouse failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Warehouse update(Warehouse warehouse) {
|
||||
Warehouse result;
|
||||
try {
|
||||
result = restTemplate.exchange(new RequestEntity<>(warehouse, HttpMethod.PUT,
|
||||
new UriTemplate("http://warehouse-web/v1/warehouses/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE)
|
||||
.expand(warehouse.getIdentity())), Warehouse.class).getBody();
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Update warehouse failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean delete(Long warehouseId) {
|
||||
try {
|
||||
restTemplate.delete(new UriTemplate("http://warehouse-web/v1/warehouses/{id}")
|
||||
.with("id", TemplateVariable.VariableType.PATH_VARIABLE).expand(warehouseId));
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Delete warehouse failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<Inventory> addInventory(List<Inventory> inventory, Long warehouseId) {
|
||||
List<Inventory> result = new ArrayList<>();
|
||||
try {
|
||||
inventory.parallelStream().forEach(item -> {
|
||||
result.add(restTemplate.postForObject(new UriTemplate(String
|
||||
.format("http://warehouse-web/v1/warehouses/%s/inventory", warehouseId))
|
||||
.expand(), item, Inventory.class));
|
||||
});
|
||||
} catch (RestClientResponseException ex) {
|
||||
log.error("Add warehouse inventory failed", ex);
|
||||
throw new IllegalStateException(getHttpStatusMessage(ex), ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getHttpStatusMessage(RestClientResponseException ex) {
|
||||
Map<String, String> errorMap = new HashMap<>();
|
||||
try {
|
||||
errorMap = new ObjectMapper()
|
||||
.readValue(ex.getResponseBodyAsString(), errorMap
|
||||
.getClass());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return errorMap.getOrDefault("message", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: development
|
||||
server:
|
||||
port: 0
|
||||
---
|
||||
spring:
|
||||
profiles: development
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://${DOCKER_IP:192.168.99.100}:8761/eureka
|
||||
registryFetchIntervalSeconds: 5
|
||||
instance:
|
||||
hostname: ${DOCKER_IP:192.168.99.100}
|
||||
instance-id: ${spring.application.name}:${random.int}
|
||||
leaseRenewalIntervalInSeconds: 5
|
||||
---
|
||||
spring:
|
||||
profiles: test
|
||||
eureka:
|
||||
client:
|
||||
enabled: false
|
||||
---
|
||||
spring:
|
||||
profiles: cloud
|
||||
eureka:
|
||||
instance:
|
||||
hostname: ${vcap.application.uris[0]:localhost}
|
||||
nonSecurePort: 80
|
||||
metadataMap:
|
||||
instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
|
||||
leaseRenewalIntervalInSeconds: 5
|
||||
client:
|
||||
region: default
|
||||
registryFetchIntervalSeconds: 5
|
||||
serviceUrl:
|
||||
defaultZone: ${vcap.services.discovery-service.credentials.uri:http://localhost:8761}/eureka/
|
||||
@@ -0,0 +1,4 @@
|
||||
spring:
|
||||
application:
|
||||
name: load-simulator
|
||||
---
|
||||
@@ -0,0 +1,26 @@
|
||||
package demo;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.junit.Test;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
public class DateTimeTests {
|
||||
|
||||
@Test
|
||||
public void testDateTime() throws ParseException {
|
||||
DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
|
||||
|
||||
System.out.print(new SpelExpressionParser().parseRaw("new org.joda.time.DateTime(new java.lang.Long('1487145898898')).toLocalDateTime().toString()").getValue());
|
||||
dateFormatter.parseDateTime(new DateTime(new Long("1487145898898")).toLocalDateTime().toString());
|
||||
|
||||
JsonPropertyAccessor.ToStringFriendlyJsonNode jsonNode = new JsonPropertyAccessor.ToStringFriendlyJsonNode(JsonNodeFactory.instance.textNode("test"));
|
||||
|
||||
new SpelExpressionParser().parseExpression("toString()").getValue(jsonNode, CharSequence.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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 LoadSimulatorApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
29
platform-services/stream-modules/pom.xml
Normal file
29
platform-services/stream-modules/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>stream-modules</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>stream-modules</name>
|
||||
|
||||
<parent>
|
||||
<groupId>org.kbastani</groupId>
|
||||
<artifactId>platform-services</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>load-simulator</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -2,8 +2,11 @@ package demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableEurekaClient
|
||||
@@ -13,4 +16,10 @@ public class DashboardApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DashboardApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ zuul:
|
||||
account-web: /account/**
|
||||
warehouse-web: /warehouse/**
|
||||
order-web: /order/**
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka
|
||||
---
|
||||
spring:
|
||||
profiles: docker
|
||||
@@ -20,6 +24,7 @@ zuul:
|
||||
routes:
|
||||
account-web: /account/**
|
||||
warehouse-web: /warehouse/**
|
||||
order-web: /order/**
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<script src="src/js/client/api/warehouseApi.js"></script>
|
||||
<script src="src/js/client/api/orderApi.js"></script>
|
||||
<script src="src/js/client/api/accountApi.js"></script>
|
||||
<script src="src/js/util/faker.js"></script>
|
||||
<script src="src/js/root.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -117,12 +117,17 @@ var loadAccount = function (id, callback) {
|
||||
$.each(document._links, function (k, v) {
|
||||
var commandBtn = $("<div class='command-btn'>" + "<input class='command-href' type='hidden' value='" + v.href + "'>" + "<input type='button' class='btn btn-default' value='" + k + "'></div>");
|
||||
$(commandBtn).click(function () {
|
||||
appDispatcher.handle(generate("GET", null, $(this).find(".command-href").val()), function (cmd) {
|
||||
accountStatus = cmd.status;
|
||||
updateAccountStatus(accountStatus);
|
||||
$(formSelect).text('');
|
||||
$(formSelect).jsonForm(accountForm.apply(formSelect, cmd));
|
||||
});
|
||||
if (k != "postOrder") {
|
||||
appDispatcher.handle(generate("GET", null, $(this).find(".command-href").val()), function (cmd) {
|
||||
accountStatus = cmd.status;
|
||||
updateAccountStatus(accountStatus);
|
||||
$(formSelect).text('');
|
||||
$(formSelect).jsonForm(accountForm.apply(formSelect, cmd));
|
||||
});
|
||||
} else {
|
||||
postOrderCommand($(this).find(".command-href").val());
|
||||
}
|
||||
|
||||
});
|
||||
$(commandSelect).append(commandBtn);
|
||||
});
|
||||
|
||||
@@ -227,7 +227,9 @@ var loadOrder = function (id, callback) {
|
||||
updateOrderStatus(eventList[0].type);
|
||||
callback();
|
||||
} else if (lastOrderSize < eventList.length) {
|
||||
appendRow(".order-events", eventList.filter(function (x) {
|
||||
appendRow(".order-events", eventList.sort(function (a, b) {
|
||||
return a.createdAt - b.createdAt;
|
||||
}).filter(function (x) {
|
||||
return eventList.indexOf(x) > lastOrderSize - 1;
|
||||
}), true, updateOrderStatus);
|
||||
lastOrderSize = eventList.length;
|
||||
@@ -318,12 +320,14 @@ var renderOrderFlow = function (callback, orderStatus) {
|
||||
});
|
||||
g.setEdge(2, 3, {
|
||||
label: "RESERVATION_ADDED",
|
||||
minlen: 2
|
||||
minlen: 1
|
||||
});
|
||||
g.setEdge(3, 4, {
|
||||
label: "RESERVATION_SUCCEEDED"
|
||||
label: "RESERVATION_SUCCEEDED",
|
||||
labelpos: 'r',
|
||||
minlen: 2
|
||||
});
|
||||
g.setEdge(2, 8, {
|
||||
g.setEdge(3, 8, {
|
||||
label: "RESERVATION_FAILED",
|
||||
labelpos: 'l'
|
||||
});
|
||||
@@ -366,7 +370,7 @@ var renderOrderFlow = function (callback, orderStatus) {
|
||||
inner.call(render, g);
|
||||
var draw = function (isUpdate) {
|
||||
var graphWidth = g.graph().width + 35;
|
||||
var graphHeight = g.graph().height + 200;
|
||||
var graphHeight = g.graph().height + 100;
|
||||
var width = parseInt(svg.style("width").replace(/px/, ""));
|
||||
var height = parseInt(svg.style("height").replace(/px/, ""));
|
||||
var zoomScale = Math.min(width / graphWidth, height / graphHeight);
|
||||
|
||||
@@ -13,6 +13,15 @@ var warehouseApi = {
|
||||
}
|
||||
};
|
||||
|
||||
var inventoryApi = {
|
||||
get: function (id, callback) {
|
||||
appDispatcher.handle(generate("GET", null, "/warehouse/v1/warehouses/" + id + "/inventory"), callback);
|
||||
},
|
||||
post: function (id, body, callback) {
|
||||
appDispatcher.handle(generate("POST", body, "/warehouse/v1/warehouses/" + id + "/inventory"), callback);
|
||||
}
|
||||
};
|
||||
|
||||
var warehouseForm = {
|
||||
apply: function (selector, value) {
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,12 @@ var appDispatcher = {
|
||||
.success(callback)
|
||||
.error(function (err) {
|
||||
$(".modal").modal();
|
||||
$(".modal-body").text($.parseJSON(err.responseText).message);
|
||||
if (err.statusCode().status == 404) {
|
||||
$(".modal-body").text("The requested resource was not found");
|
||||
callback(err);
|
||||
} else {
|
||||
$(".modal-body").text($.parseJSON(err.responseText).message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
var app = angular.module("myApp", ["ngRoute"]);
|
||||
var polling = true;
|
||||
var pageId;
|
||||
var windowService;
|
||||
var locationService;
|
||||
var scopeService;
|
||||
app.config(function ($routeProvider) {
|
||||
$routeProvider.when("/", {
|
||||
templateUrl: "/src/partials/main.html",
|
||||
@@ -15,9 +18,17 @@ app.config(function ($routeProvider) {
|
||||
templateUrl: "/src/partials/warehouse.html",
|
||||
cache: false
|
||||
});
|
||||
}).directive('loader', ['$routeParams', function ($routeParams) {
|
||||
}).directive('loader', ['$routeParams', '$window', '$location', function ($routeParams, $window, $location) {
|
||||
return {
|
||||
link: function (scope, element, attr) {
|
||||
|
||||
scope.domain = attr.domain;
|
||||
|
||||
pageId = $routeParams.id;
|
||||
windowService = $window;
|
||||
locationService = $location;
|
||||
scopeService = scope;
|
||||
|
||||
$('#accordion').collapse({
|
||||
toggle: false
|
||||
});
|
||||
@@ -29,7 +40,7 @@ app.config(function ($routeProvider) {
|
||||
}).on('hidden.bs.collapse', function () {
|
||||
$(this).parent().find('.panel-title').removeClass("expanded").addClass("expandable");
|
||||
});
|
||||
pageId = $routeParams.id;
|
||||
|
||||
var selector = ".item-title";
|
||||
var handleRoute = function (after) {
|
||||
switch (attr.domain) {
|
||||
@@ -62,24 +73,75 @@ app.config(function ($routeProvider) {
|
||||
break;
|
||||
case "home":
|
||||
$(selector).text("Warehouse " + 1);
|
||||
loadWarehouse(1, after);
|
||||
|
||||
var loadHomeAccount = function () {
|
||||
accountApi.get(1, function (acctRes) {
|
||||
if (acctRes.statusCode != null) {
|
||||
if (acctRes.statusCode().status == 404) {
|
||||
setupAccounts(1, function (result) {
|
||||
if (result.statusCode == null) {
|
||||
accountApi.get(1, function (newRes) {
|
||||
$(".account-form").jsonForm(accountForm.apply(".account-form", newRes));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$(".account-form").jsonForm(accountForm.apply(".account-form", acctRes));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loadHomeWarehouse = function () {
|
||||
loadWarehouse(1, after, function (res) {
|
||||
$(".modal").modal('hide');
|
||||
|
||||
if (res.statusCode().status == 404) {
|
||||
// Create the first warehouse and reload page
|
||||
setupWarehouses(1, function (result) {
|
||||
if (result.statusCode == null) {
|
||||
console.log(result);
|
||||
addInventory(1, 5, 20, function () {
|
||||
console.log("Inventory added");
|
||||
loadHomeWarehouse();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadHomeAccount();
|
||||
loadHomeWarehouse();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var doPoll = function () {
|
||||
if (polling) {
|
||||
setTimeout(function () {
|
||||
handleRoute(doPoll);
|
||||
if (scope.domain == scopeService.domain) {
|
||||
handleRoute(doPoll);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
if (polling) {
|
||||
polling = false;
|
||||
handleRoute(function () {
|
||||
setTimeout(function () {
|
||||
polling = true;
|
||||
doPoll();
|
||||
}, 2000);
|
||||
if (attr.domain != "home") {
|
||||
if (polling) {
|
||||
polling = false;
|
||||
if (scope.domain == scopeService.domain) {
|
||||
handleRoute(function () {
|
||||
setTimeout(function () {
|
||||
polling = true;
|
||||
doPoll();
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleRoute(function() {
|
||||
|
||||
});
|
||||
}
|
||||
return attr;
|
||||
@@ -89,49 +151,168 @@ app.config(function ($routeProvider) {
|
||||
var lastWarehouseSize = 0;
|
||||
var warehouseHref = null;
|
||||
var warehouseId = null;
|
||||
var loadWarehouse = function (id, callback) {
|
||||
warehouseId = warehouseId || id;
|
||||
var getWarehouse = function (after) {
|
||||
warehouseApi.get(id, function (res) {
|
||||
var selector = ".warehouse-form";
|
||||
$(selector).text('');
|
||||
$(selector).jsonForm(warehouseForm.apply(selector, res));
|
||||
warehouseHref = res._links.inventory.href;
|
||||
if (after != null) after();
|
||||
});
|
||||
};
|
||||
var loadWarehouseEvents = function () {
|
||||
traverson.from(warehouseHref).getResource(function (error, document) {
|
||||
if (error) {
|
||||
console.error('Could not fetch events for the warehouse')
|
||||
callback();
|
||||
} else {
|
||||
var inventoryList = document._embedded.inventoryList;
|
||||
if (lastWarehouseSize < inventoryList.length && lastWarehouseSize == 0) {
|
||||
lastWarehouseSize = inventoryList.length;
|
||||
createTable(".warehouse-inventory", inventoryList);
|
||||
callback();
|
||||
} else if (lastWarehouseSize < inventoryList.length) {
|
||||
appendRow(".warehouse-inventory", inventoryList.filter(function (x) {
|
||||
return inventoryList.indexOf(x) > lastWarehouseSize - 1;
|
||||
}));
|
||||
lastWarehouseSize = inventoryList.length;
|
||||
getWarehouse(function () {
|
||||
callback();
|
||||
});
|
||||
var loadWarehouse = function (id, callback, err) {
|
||||
warehouseId = warehouseId || id;
|
||||
var getWarehouse = function (after) {
|
||||
warehouseApi.get(id, function (res) {
|
||||
console.log(res);
|
||||
if (err != null ? res.statusCode != null : res != null) {
|
||||
err(res);
|
||||
} else {
|
||||
callback();
|
||||
var selector = ".warehouse-form";
|
||||
$(selector).text('');
|
||||
$(selector).jsonForm(warehouseForm.apply(selector, res));
|
||||
warehouseHref = res._links.inventory.href;
|
||||
if (after != null) after();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (warehouseHref == null) {
|
||||
getWarehouse(function () {
|
||||
});
|
||||
};
|
||||
var loadWarehouseEvents = function () {
|
||||
traverson.from(warehouseHref).getResource(function (error, document) {
|
||||
if (error) {
|
||||
console.error('Could not fetch events for the warehouse')
|
||||
callback();
|
||||
} else {
|
||||
var inventoryList = document._embedded.inventoryList;
|
||||
if (lastWarehouseSize < inventoryList.length && lastWarehouseSize == 0) {
|
||||
lastWarehouseSize = inventoryList.length;
|
||||
createTable(".warehouse-inventory", inventoryList);
|
||||
callback();
|
||||
} else if (lastWarehouseSize < inventoryList.length) {
|
||||
appendRow(".warehouse-inventory", inventoryList.filter(function (x) {
|
||||
return inventoryList.indexOf(x) > lastWarehouseSize - 1;
|
||||
}).sort(function (a, b) {
|
||||
return a.createdAt - b.createdAt;
|
||||
}));
|
||||
lastWarehouseSize = inventoryList.length;
|
||||
getWarehouse(function () {
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (warehouseHref == null) {
|
||||
getWarehouse(function () {
|
||||
loadWarehouseEvents();
|
||||
});
|
||||
} else {
|
||||
loadWarehouseEvents();
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
var setupAccounts = function (num) {
|
||||
for (var i = 0; i < num; i++) {
|
||||
accountApi.post(JSON.stringify(getFakeAccount()), function (res) {
|
||||
console.log(res);
|
||||
});
|
||||
} else {
|
||||
loadWarehouseEvents();
|
||||
}
|
||||
};
|
||||
|
||||
var getFakeAccount = function () {
|
||||
return {
|
||||
firstName: faker.name.firstName(),
|
||||
lastName: faker.name.lastName(),
|
||||
email: faker.internet.email()
|
||||
};
|
||||
};
|
||||
|
||||
var setupWarehouses = function (num, callback) {
|
||||
var counter = 0;
|
||||
for (var i = 0; i < num; i++) {
|
||||
var item = getFakeWarehouse();
|
||||
warehouseApi.post(JSON.stringify(item), function (result) {
|
||||
console.log(result);
|
||||
counter += 1;
|
||||
if (counter == num) {
|
||||
callback(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var addInventory = function (warehouseId, seed, num, callback) {
|
||||
var counter = 0;
|
||||
for (var i = 0; i < num; i++) {
|
||||
var item = getFakeInventory(seed);
|
||||
inventoryApi.post(warehouseId, JSON.stringify(item), function () {
|
||||
counter += 1;
|
||||
|
||||
if (counter == num) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var getFakeAddress = function () {
|
||||
return {
|
||||
street1: faker.address.streetAddress(),
|
||||
state: faker.address.state(),
|
||||
city: faker.address.city(),
|
||||
country: faker.address.country(),
|
||||
zipCode: parseInt(faker.address.zipCode().substring(0, 5))
|
||||
};
|
||||
};
|
||||
|
||||
var getFakeWarehouse = function () {
|
||||
return {
|
||||
address: getFakeAddress(),
|
||||
status: "WAREHOUSE_CREATED"
|
||||
};
|
||||
};
|
||||
|
||||
var getFakeInventory = function (seed) {
|
||||
return {
|
||||
productId: "SKU-" + String("00" + Math.floor((Math.random() * seed) + 1)).slice(-5)
|
||||
};
|
||||
};
|
||||
|
||||
var getFakeLineItem = function (seed) {
|
||||
return {
|
||||
name: faker.commerce.productName(),
|
||||
productId: "SKU-" + String("00" + seed).slice(-5),
|
||||
quantity: Math.floor((Math.random() * 5) + 1),
|
||||
price: faker.commerce.price(),
|
||||
tax: 0.06
|
||||
};
|
||||
};
|
||||
|
||||
var postOrderCommand = function (href) {
|
||||
appDispatcher.handle(generate("POST", JSON.stringify(getFakeOrder(5)), href), function (res) {
|
||||
console.log(res);
|
||||
if (res.statusCode == null) {
|
||||
traverson.from(res._links.self.href).getResource(function (error, document) {
|
||||
if (error == null) {
|
||||
if (scopeService != null) {
|
||||
scopeService.$apply(function () {
|
||||
locationService.path("/orders/" + document.orderId);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$(".modal").modal();
|
||||
$(".modal-body").text($.parseJSON(document.responseText).message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var getFakeOrder = function (numProducts) {
|
||||
var shippingAddress = getFakeAddress();
|
||||
shippingAddress.addressType = "SHIPPING";
|
||||
|
||||
var lineItems = [];
|
||||
var numLineItems = Math.floor((Math.random() * numProducts) + 1);
|
||||
for (var i = 0; i < numLineItems; i++) {
|
||||
lineItems.push(getFakeLineItem(i + 1));
|
||||
}
|
||||
|
||||
return {
|
||||
shippingAddress: shippingAddress,
|
||||
lineItems: lineItems
|
||||
}
|
||||
};
|
||||
52714
ui/dashboard/src/main/resources/static/src/js/util/faker.js
Normal file
52714
ui/dashboard/src/main/resources/static/src/js/util/faker.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ function appendRow(selector, array, replay, statusCallback) {
|
||||
var tbl_body = "";
|
||||
|
||||
var tbl_row = "";
|
||||
|
||||
$.each(item, function (k, v) {
|
||||
if (k != "_links" && k != "lastModified") {
|
||||
tbl_row += "<td>" + ((k == "createdAt") ? new Date(v).toLocaleString() : v) + "</td>";
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
<div class="row">
|
||||
<div class="well">
|
||||
<div loader domain="home">
|
||||
<fieldset class="control-group">
|
||||
<legend class="item-title">Warehouse</legend>
|
||||
<form class="warehouse-form"></form>
|
||||
</fieldset>
|
||||
<fieldset class="control-group">
|
||||
<legend>Inventory</legend>
|
||||
<div class="warehouse-inventory"></div>
|
||||
</fieldset>
|
||||
<div class="col-md-6">
|
||||
<div class="well">
|
||||
<div loader domain="home">
|
||||
<fieldset class="control-group">
|
||||
<legend class="item-title">Warehouse</legend>
|
||||
<form class="warehouse-form"></form>
|
||||
</fieldset>
|
||||
<fieldset class="control-group">
|
||||
<legend>Inventory</legend>
|
||||
<div class="warehouse-inventory"></div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="well">
|
||||
<div>
|
||||
<fieldset class="control-group">
|
||||
<legend class="page-title header-title">Account 1</legend>
|
||||
<form class="account-form"></form>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user