diff --git a/kubernetes/productcatalogue-service.yaml b/kubernetes/productcatalogue-service.yaml
new file mode 100644
index 0000000..71bd17f
--- /dev/null
+++ b/kubernetes/productcatalogue-service.yaml
@@ -0,0 +1,42 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: productcatalogue
+ labels:
+ app: productcatalogue
+spec:
+ type: NodePort
+ selector:
+ app: productcatalogue
+ ports:
+ - protocol: TCP
+ port: 8020
+ name: http
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: productcatalogue
+spec:
+ selector:
+ matchLabels:
+ app: productcatalogue
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: productcatalogue
+ spec:
+ containers:
+ - name: productcatalogue
+ image: danielbryantuk/djproductcatalogue:latest
+ ports:
+ - containerPort: 8020
+ livenessProbe:
+ httpGet:
+ path: /healthcheck
+ port: 8025
+ initialDelaySeconds: 30
+ timeoutSeconds: 1
diff --git a/kubernetes/shopfront-service.yaml b/kubernetes/shopfront-service.yaml
new file mode 100644
index 0000000..cc26aee
--- /dev/null
+++ b/kubernetes/shopfront-service.yaml
@@ -0,0 +1,42 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: shopfront
+ labels:
+ app: shopfront
+spec:
+ type: NodePort
+ selector:
+ app: shopfront
+ ports:
+ - protocol: TCP
+ port: 8010
+ name: http
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: shopfront
+spec:
+ selector:
+ matchLabels:
+ app: shopfront
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: shopfront
+ spec:
+ containers:
+ - name: djshopfront
+ image: danielbryantuk/djshopfront:latest
+ ports:
+ - containerPort: 8010
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 8010
+ initialDelaySeconds: 30
+ timeoutSeconds: 1
diff --git a/kubernetes/stockmanager-service.yaml b/kubernetes/stockmanager-service.yaml
new file mode 100644
index 0000000..d3f58bb
--- /dev/null
+++ b/kubernetes/stockmanager-service.yaml
@@ -0,0 +1,43 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: stockmanager
+ labels:
+ app: stockmanager
+spec:
+ type: NodePort
+ selector:
+ app: stockmanager
+ ports:
+ - protocol: TCP
+ port: 8030
+ name: http
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: stockmanager
+spec:
+ spec:
+ selector:
+ matchLabels:
+ app: stockmanager
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: stockmanager
+ spec:
+ containers:
+ - name: stockmanager
+ image: danielbryantuk/djstockmanager:latest
+ ports:
+ - containerPort: 8030
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 8030
+ initialDelaySeconds: 30
+ timeoutSeconds: 1
diff --git a/productcatalogue/Dockerfile b/productcatalogue/Dockerfile
new file mode 100644
index 0000000..2368d55
--- /dev/null
+++ b/productcatalogue/Dockerfile
@@ -0,0 +1,5 @@
+FROM openjdk:8-jre
+ADD target/productcatalogue-0.0.1-SNAPSHOT.jar app.jar
+ADD product-catalogue.yml app-config.yml
+EXPOSE 8020
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar", "server", "app-config.yml"]
diff --git a/productcatalogue/README.md b/productcatalogue/README.md
new file mode 100644
index 0000000..e41fecd
--- /dev/null
+++ b/productcatalogue/README.md
@@ -0,0 +1,9 @@
+product-catalogue
+=================
+
+java -jar target/product-1.0-SNAPSHOT.jar server product-catalogue.yml
+
+docker build -t danielbryantuk/product .
+docker run -p 9010:9010 -p 9011:9011 -d danielbryantuk/product
+
+Update
\ No newline at end of file
diff --git a/productcatalogue/pom.xml b/productcatalogue/pom.xml
new file mode 100644
index 0000000..14b6206
--- /dev/null
+++ b/productcatalogue/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+ uk.co.danielbryant.djshopping
+ productcatalogue
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.3.27
+ 4.2.3
+
+
+
+
+ io.dropwizard
+ dropwizard-core
+ ${dropwizard.version}
+
+
+ com.google.inject
+ guice
+ ${guice.version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 1.6
+
+ true
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ uk.co.danielbryant.djshopping.productcatalogue.ProductServiceApplication
+
+
+
+
+
+
+
+
+
diff --git a/productcatalogue/product-catalogue.yml b/productcatalogue/product-catalogue.yml
new file mode 100644
index 0000000..e5ee50c
--- /dev/null
+++ b/productcatalogue/product-catalogue.yml
@@ -0,0 +1,10 @@
+version: 1.0-SNAPSHOT
+
+
+server:
+ applicationConnectors:
+ - type: http
+ port: 8020
+ adminConnectors:
+ - type: http
+ port: 8025
\ No newline at end of file
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/ProductServiceApplication.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/ProductServiceApplication.java
new file mode 100644
index 0000000..e43e6c2
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/ProductServiceApplication.java
@@ -0,0 +1,36 @@
+package uk.co.danielbryant.djshopping.productcatalogue;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import io.dropwizard.Application;
+import io.dropwizard.setup.Bootstrap;
+import io.dropwizard.setup.Environment;
+import uk.co.danielbryant.djshopping.productcatalogue.healthchecks.BasicHealthCheck;
+import uk.co.danielbryant.djshopping.productcatalogue.configuration.ProductServiceConfiguration;
+import uk.co.danielbryant.djshopping.productcatalogue.resources.ProductResource;
+
+public class ProductServiceApplication extends Application {
+ public static void main(String[] args) throws Exception {
+ new ProductServiceApplication().run(args);
+ }
+
+ @Override
+ public String getName() {
+ return "product-list-service";
+ }
+
+ @Override
+ public void initialize(Bootstrap bootstrap) {
+ // nothing to do yet
+ }
+
+ @Override
+ public void run(ProductServiceConfiguration config,
+ Environment environment) {
+ final BasicHealthCheck healthCheck = new BasicHealthCheck(config.getVersion());
+ environment.healthChecks().register("template", healthCheck);
+
+ Injector injector = Guice.createInjector();
+ environment.jersey().register(injector.getInstance(ProductResource.class));
+ }
+}
\ No newline at end of file
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/configuration/ProductServiceConfiguration.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/configuration/ProductServiceConfiguration.java
new file mode 100644
index 0000000..62cabd1
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/configuration/ProductServiceConfiguration.java
@@ -0,0 +1,21 @@
+package uk.co.danielbryant.djshopping.productcatalogue.configuration;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.dropwizard.Configuration;
+import org.hibernate.validator.constraints.NotEmpty;
+
+public class ProductServiceConfiguration extends Configuration {
+
+ @NotEmpty
+ private String version;
+
+ @JsonProperty
+ public String getVersion() {
+ return version;
+ }
+
+ @JsonProperty
+ public void setVersion(String version) {
+ this.version = version;
+ }
+}
\ No newline at end of file
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/healthchecks/BasicHealthCheck.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/healthchecks/BasicHealthCheck.java
new file mode 100644
index 0000000..063fabe
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/healthchecks/BasicHealthCheck.java
@@ -0,0 +1,17 @@
+package uk.co.danielbryant.djshopping.productcatalogue.healthchecks;
+
+import com.codahale.metrics.health.HealthCheck;
+
+public class BasicHealthCheck extends HealthCheck {
+
+ private final String version;
+
+ public BasicHealthCheck(String version) {
+ this.version = version;
+ }
+
+ @Override
+ protected Result check() throws Exception {
+ return Result.healthy("Ok with version: " + version);
+ }
+}
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/model/Product.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/model/Product.java
new file mode 100644
index 0000000..b433a1f
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/model/Product.java
@@ -0,0 +1,43 @@
+package uk.co.danielbryant.djshopping.productcatalogue.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.math.BigDecimal;
+
+public class Product {
+ private String id;
+ private String name;
+ private String description;
+ private BigDecimal price;
+
+ public Product() {
+ // Needed for Jackson deserialization
+ }
+
+ public Product(String id, String name, String description, BigDecimal price) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.price = price;
+ }
+
+ @JsonProperty
+ public String getId() {
+ return id;
+ }
+
+ @JsonProperty
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty
+ public String getDescription() {
+ return description;
+ }
+
+ @JsonProperty
+ public BigDecimal getPrice() {
+ return price;
+ }
+}
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/resources/ProductResource.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/resources/ProductResource.java
new file mode 100644
index 0000000..bb6a264
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/resources/ProductResource.java
@@ -0,0 +1,50 @@
+package uk.co.danielbryant.djshopping.productcatalogue.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.inject.Inject;
+import uk.co.danielbryant.djshopping.productcatalogue.services.ProductService;
+import uk.co.danielbryant.djshopping.productcatalogue.model.Product;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Optional;
+
+@Path("/products")
+@Produces(MediaType.APPLICATION_JSON)
+public class ProductResource {
+
+ private ProductService productService;
+
+ @Inject
+ public ProductResource(ProductService productService) {
+ this.productService = productService;
+ }
+
+ @GET
+ @Timed
+ public Response getAllProducts() {
+ return Response.status(200)
+ .entity(productService.getAllProducts())
+ .build();
+ }
+
+ @GET
+ @Timed
+ @Path("{id}")
+ public Response getProduct(@PathParam("id") String id) {
+ Optional result = productService.getProduct(id);
+
+ if (result.isPresent()) {
+ return Response.status(Response.Status.OK)
+ .entity(result.get())
+ .build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND)
+ .build();
+ }
+ }
+}
diff --git a/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/services/ProductService.java b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/services/ProductService.java
new file mode 100644
index 0000000..4bbd411
--- /dev/null
+++ b/productcatalogue/src/main/java/uk/co/danielbryant/djshopping/productcatalogue/services/ProductService.java
@@ -0,0 +1,28 @@
+package uk.co.danielbryant.djshopping.productcatalogue.services;
+
+import uk.co.danielbryant.djshopping.productcatalogue.model.Product;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+public class ProductService {
+
+ //{productId, Product}
+ private Map fakeProductDAO = new HashMap<>();
+
+ public ProductService() {
+ fakeProductDAO.put("1", new Product("1", "Widget", "Premium ACME Widgets", new BigDecimal(1.20)));
+ fakeProductDAO.put("2", new Product("2", "Sprocket", "Grade B sprockets", new BigDecimal(4.10)));
+ fakeProductDAO.put("3", new Product("3", "Anvil", "Large Anvils", new BigDecimal(45.50)));
+ fakeProductDAO.put("4", new Product("4", "Cogs", "Grade Y cogs", new BigDecimal(1.80)));
+ fakeProductDAO.put("5", new Product("5", "Multitool", "Multitools", new BigDecimal(154.10)));
+ }
+
+ public List getAllProducts() {
+ return new ArrayList<>(fakeProductDAO.values());
+ }
+
+ public Optional getProduct(String id) {
+ return Optional.ofNullable(fakeProductDAO.get(id));
+ }
+}
diff --git a/shopfront/Dockerfile b/shopfront/Dockerfile
new file mode 100644
index 0000000..c0577de
--- /dev/null
+++ b/shopfront/Dockerfile
@@ -0,0 +1,4 @@
+FROM openjdk:8-jre
+ADD target/shopfront-0.0.1-SNAPSHOT.jar app.jar
+EXPOSE 8010
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
diff --git a/shopfront/pom.xml b/shopfront/pom.xml
new file mode 100644
index 0000000..1535a9a
--- /dev/null
+++ b/shopfront/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+ uk.co.danielbryant.djshopping
+ shopfront
+ 0.0.1-SNAPSHOT
+ jar
+
+ shopfront
+ Docker Java application Shopfront
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.22.RELEASE
+
+
+
+ UTF-8
+ 1.8
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ Dalston.SR5
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.cloud
+ spring-cloud-starter-hystrix
+
+
+ org.springframework.cloud
+ spring-cloud-starter-eureka
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+
+
+
+
+
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplication.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplication.java
new file mode 100644
index 0000000..f1d2a5b
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplication.java
@@ -0,0 +1,21 @@
+package uk.co.danielbryant.djshopping.shopfront;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.hystrix.EnableHystrix;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootApplication
+@EnableHystrix
+public class ShopfrontApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ShopfrontApplication.class, args);
+ }
+
+ @Bean(name = "stdRestTemplate")
+ public RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/controllers/HomeController.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/controllers/HomeController.java
new file mode 100644
index 0000000..bc704fb
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/controllers/HomeController.java
@@ -0,0 +1,20 @@
+package uk.co.danielbryant.djshopping.shopfront.controllers;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import uk.co.danielbryant.djshopping.shopfront.services.ProductService;
+
+@Controller
+public class HomeController {
+
+ @Autowired
+ private ProductService productService;
+
+ @RequestMapping("/")
+ public String index(Model model) {
+ model.addAttribute("products", productService.getProducts());
+ return "index";
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/model/Product.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/model/Product.java
new file mode 100644
index 0000000..31d1a53
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/model/Product.java
@@ -0,0 +1,52 @@
+package uk.co.danielbryant.djshopping.shopfront.model;
+
+import java.math.BigDecimal;
+
+public class Product {
+ private String id;
+ private String sku;
+ private String name;
+ private String description;
+ private BigDecimal price;
+ private int amountAvailable;
+
+ public Product(String id, String name, String description, BigDecimal price) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.price = price;
+ }
+
+ public Product(String id, String sku, String name, String description, BigDecimal price, int amountAvailable) {
+ this.id = id;
+ this.sku = sku;
+ this.name = name;
+ this.description = description;
+ this.price = price;
+ this.amountAvailable = amountAvailable;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getSku() {
+ return sku;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public BigDecimal getPrice() {
+ return price;
+ }
+
+ public int getAmountAvailable() {
+ return amountAvailable;
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/ProductRepo.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/ProductRepo.java
new file mode 100644
index 0000000..7dfd1d3
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/ProductRepo.java
@@ -0,0 +1,39 @@
+package uk.co.danielbryant.djshopping.shopfront.repo;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import uk.co.danielbryant.djshopping.shopfront.services.dto.ProductDTO;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Component
+public class ProductRepo {
+
+ @Value("${productCatalogueUri}")
+ private String productCatalogueUri;
+
+ @Autowired
+ @Qualifier(value = "stdRestTemplate")
+ private RestTemplate restTemplate;
+
+
+ public Map getProductDTOs() {
+ ResponseEntity> productCatalogueResponse =
+ restTemplate.exchange(productCatalogueUri + "/products",
+ HttpMethod.GET, null, new ParameterizedTypeReference>() {
+ });
+ List productDTOs = productCatalogueResponse.getBody();
+
+ return productDTOs.stream()
+ .collect(Collectors.toMap(ProductDTO::getId, Function.identity()));
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/StockRepo.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/StockRepo.java
new file mode 100644
index 0000000..0fae57e
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/repo/StockRepo.java
@@ -0,0 +1,51 @@
+package uk.co.danielbryant.djshopping.shopfront.repo;
+
+import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import uk.co.danielbryant.djshopping.shopfront.services.dto.StockDTO;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Component
+public class StockRepo {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StockRepo.class);
+
+ @Value("${stockManagerUri}")
+ private String stockManagerUri;
+
+ @Autowired
+ @Qualifier(value = "stdRestTemplate")
+ private RestTemplate restTemplate;
+
+ @HystrixCommand(fallbackMethod = "stocksNotFound") // Hystrix circuit breaker for fault-tolernace demo
+ public Map getStockDTOs() {
+ LOGGER.info("getStocksDTOs");
+ ResponseEntity> stockManagerResponse =
+ restTemplate.exchange(stockManagerUri + "/stocks",
+ HttpMethod.GET, null, new ParameterizedTypeReference>() {
+ });
+ List stockDTOs = stockManagerResponse.getBody();
+
+ return stockDTOs.stream()
+ .collect(Collectors.toMap(StockDTO::getProductId, Function.identity()));
+ }
+
+ public Map stocksNotFound() {
+ LOGGER.info("stocksNotFound *** FALLBACK ***");
+ return Collections.EMPTY_MAP;
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/resources/ProductResource.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/resources/ProductResource.java
new file mode 100644
index 0000000..0742eee
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/resources/ProductResource.java
@@ -0,0 +1,22 @@
+package uk.co.danielbryant.djshopping.shopfront.resources;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import uk.co.danielbryant.djshopping.shopfront.model.Product;
+import uk.co.danielbryant.djshopping.shopfront.services.ProductService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/products")
+public class ProductResource {
+
+ @Autowired
+ private ProductService productService;
+
+ @RequestMapping()
+ public List getProducts() {
+ return productService.getProducts();
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/ProductService.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/ProductService.java
new file mode 100644
index 0000000..6fd0564
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/ProductService.java
@@ -0,0 +1,45 @@
+package uk.co.danielbryant.djshopping.shopfront.services;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import uk.co.danielbryant.djshopping.shopfront.model.Product;
+import uk.co.danielbryant.djshopping.shopfront.repo.StockRepo;
+import uk.co.danielbryant.djshopping.shopfront.repo.ProductRepo;
+import uk.co.danielbryant.djshopping.shopfront.services.dto.ProductDTO;
+import uk.co.danielbryant.djshopping.shopfront.services.dto.StockDTO;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+public class ProductService {
+
+ @Autowired
+ private StockRepo stockRepo;
+
+ @Autowired
+ private ProductRepo productRepo;
+
+
+ public List getProducts() {
+ Map productDTOs = productRepo.getProductDTOs();
+ Map stockDTOMap = stockRepo.getStockDTOs();
+
+ // Merge productDTOs and stockDTOs to a List of Products
+ return productDTOs.values().stream()
+ .map(productDTO -> {
+ StockDTO stockDTO = stockDTOMap.get(productDTO.getId());
+ if (stockDTO == null) {
+ stockDTO = StockDTO.DEFAULT_STOCK_DTO;
+ }
+ return new Product(productDTO.getId(), stockDTO.getSku(), productDTO.getName(), productDTO.getDescription(), productDTO.getPrice(), stockDTO.getAmountAvailable());
+ })
+ .collect(Collectors.toList());
+ }
+
+ public List productsNotFound() {
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/ProductDTO.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/ProductDTO.java
new file mode 100644
index 0000000..67597ce
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/ProductDTO.java
@@ -0,0 +1,36 @@
+package uk.co.danielbryant.djshopping.shopfront.services.dto;
+
+import java.math.BigDecimal;
+
+public class ProductDTO {
+ private String id;
+ private String name;
+ private String description;
+ private BigDecimal price;
+
+ public ProductDTO() {
+ }
+
+ public ProductDTO(String id, String name, String description, BigDecimal price) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.price = price;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public BigDecimal getPrice() {
+ return price;
+ }
+}
diff --git a/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/StockDTO.java b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/StockDTO.java
new file mode 100644
index 0000000..35591cd
--- /dev/null
+++ b/shopfront/src/main/java/uk/co/danielbryant/djshopping/shopfront/services/dto/StockDTO.java
@@ -0,0 +1,30 @@
+package uk.co.danielbryant.djshopping.shopfront.services.dto;
+
+public class StockDTO {
+ private String productId;
+ private String sku;
+ private int amountAvailable;
+
+ public static final StockDTO DEFAULT_STOCK_DTO = new StockDTO("", "default", 999);
+
+ public StockDTO() {
+ }
+
+ public StockDTO(String productId, String sku, int amountAvailable) {
+ this.productId = productId;
+ this.sku = sku;
+ this.amountAvailable = amountAvailable;
+ }
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public String getSku() {
+ return sku;
+ }
+
+ public int getAmountAvailable() {
+ return amountAvailable;
+ }
+}
diff --git a/shopfront/src/main/resources/application.properties b/shopfront/src/main/resources/application.properties
new file mode 100644
index 0000000..15d5c7b
--- /dev/null
+++ b/shopfront/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+server.port = 8010
+productCatalogueUri = http://productcatalogue:8020
+stockManagerUri = http://stockmanager:8030
\ No newline at end of file
diff --git a/shopfront/src/main/resources/templates/index.html b/shopfront/src/main/resources/templates/index.html
new file mode 100644
index 0000000..70f90a6
--- /dev/null
+++ b/shopfront/src/main/resources/templates/index.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+ Welcome to the Docker Java Shopfront!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Product Num |
+ SKU |
+ Name |
+ Description |
+ Price £ |
+ Qty Available |
+
+
+
+
+ 1 |
+ 12345678 |
+ Widget |
+ Widget |
+ 1.19 |
+ 2 |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shopfront/src/test/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplicationTests.java b/shopfront/src/test/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplicationTests.java
new file mode 100644
index 0000000..b933216
--- /dev/null
+++ b/shopfront/src/test/java/uk/co/danielbryant/djshopping/shopfront/ShopfrontApplicationTests.java
@@ -0,0 +1,16 @@
+package uk.co.danielbryant.djshopping.shopfront;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ShopfrontApplication.class)
+public class ShopfrontApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/stockmanager/Dockerfile b/stockmanager/Dockerfile
new file mode 100644
index 0000000..2802e89
--- /dev/null
+++ b/stockmanager/Dockerfile
@@ -0,0 +1,4 @@
+FROM openjdk:8-jre
+ADD target/stockmanager-0.0.1-SNAPSHOT.jar app.jar
+EXPOSE 8030
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
diff --git a/stockmanager/pom.xml b/stockmanager/pom.xml
new file mode 100644
index 0000000..ca2beaf
--- /dev/null
+++ b/stockmanager/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ uk.co.danielbryant.djshopping
+ stockmanager
+ 0.0.1-SNAPSHOT
+ jar
+
+ stockmanager
+ Docker Java application stock manager
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.3.7.RELEASE
+
+
+
+ UTF-8
+ 1.8
+ 1.2.6
+ 2.2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ ${hamcrest-core.version}
+
+
+ info.cukes
+ cucumber-java
+ ${cucumber.version}
+
+
+ info.cukes
+ cucumber-junit
+ ${cucumber.version}
+
+
+ info.cukes
+ cucumber-spring
+ ${cucumber.version}
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.7
+
+
+
+
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/StockManagerApplication.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/StockManagerApplication.java
new file mode 100644
index 0000000..8bd6b81
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/StockManagerApplication.java
@@ -0,0 +1,12 @@
+package uk.co.danielbryant.djshopping.stockmanager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class StockManagerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(StockManagerApplication.class, args);
+ }
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/config/DataGenerator.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/config/DataGenerator.java
new file mode 100644
index 0000000..48aa6f4
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/config/DataGenerator.java
@@ -0,0 +1,38 @@
+package uk.co.danielbryant.djshopping.stockmanager.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
+import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
+
+import javax.annotation.PostConstruct;
+
+@Component
+public class DataGenerator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DataGenerator.class);
+
+ private StockRepository stockRepository;
+
+ @Autowired
+ protected DataGenerator(StockRepository stockRepository) {
+ this.stockRepository = stockRepository;
+ }
+
+ @PostConstruct
+ @Transactional
+ public void init() {
+ LOGGER.info("Generating synthetic data for demonstration purposes...");
+
+ stockRepository.save(new Stock("1", "12345678", 5));
+ stockRepository.save(new Stock("2", "34567890", 2));
+ stockRepository.save(new Stock("3", "54326745", 999));
+ stockRepository.save(new Stock("4", "93847614", 0));
+ stockRepository.save(new Stock("5", "11856388", 1));
+
+ LOGGER.info("... data generation complete");
+ }
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/exceptions/StockNotFoundException.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/exceptions/StockNotFoundException.java
new file mode 100644
index 0000000..80af494
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/exceptions/StockNotFoundException.java
@@ -0,0 +1,15 @@
+package uk.co.danielbryant.djshopping.stockmanager.exceptions;
+
+public class StockNotFoundException extends Exception {
+
+ public StockNotFoundException() {
+ }
+
+ public StockNotFoundException(String message) {
+ super(message);
+ }
+
+ public StockNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/model/Stock.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/model/Stock.java
new file mode 100644
index 0000000..db26470
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/model/Stock.java
@@ -0,0 +1,34 @@
+package uk.co.danielbryant.djshopping.stockmanager.model;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class Stock {
+
+ @Id
+ private String productId;
+ private String sku;
+ private int amountAvailable;
+
+ protected Stock() {
+ }
+
+ public Stock(String productId, String sku, int amountAvailable) {
+ this.productId = productId;
+ this.sku = sku;
+ this.amountAvailable = amountAvailable;
+ }
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public String getSku() {
+ return sku;
+ }
+
+ public int getAmountAvailable() {
+ return amountAvailable;
+ }
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/repositories/StockRepository.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/repositories/StockRepository.java
new file mode 100644
index 0000000..77dbafd
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/repositories/StockRepository.java
@@ -0,0 +1,7 @@
+package uk.co.danielbryant.djshopping.stockmanager.repositories;
+
+import org.springframework.data.repository.CrudRepository;
+import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
+
+public interface StockRepository extends CrudRepository {
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/resources/StockResource.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/resources/StockResource.java
new file mode 100644
index 0000000..9648d19
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/resources/StockResource.java
@@ -0,0 +1,39 @@
+package uk.co.danielbryant.djshopping.stockmanager.resources;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
+import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
+import uk.co.danielbryant.djshopping.stockmanager.services.StockService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/stocks")
+public class StockResource {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StockResource.class);
+
+ @Autowired
+ private StockService stockService;
+
+ @RequestMapping()
+ public List getStocks() {
+ LOGGER.info("getStocks (All stocks)");
+ return stockService.getStocks();
+ }
+
+ @RequestMapping("{productId}")
+ public Stock getStock(@PathVariable("productId") String productId) throws StockNotFoundException {
+ LOGGER.info("getStock with productId: {}", productId);
+ return stockService.getStock(productId);
+ }
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public void handleStockNotFound(StockNotFoundException snfe) {
+ }
+}
diff --git a/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/services/StockService.java b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/services/StockService.java
new file mode 100644
index 0000000..53864e8
--- /dev/null
+++ b/stockmanager/src/main/java/uk/co/danielbryant/djshopping/stockmanager/services/StockService.java
@@ -0,0 +1,33 @@
+package uk.co.danielbryant.djshopping.stockmanager.services;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
+import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
+import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+@Service
+public class StockService {
+
+ private StockRepository stockRepository;
+
+ @Autowired
+ public StockService(StockRepository stockRepository) {
+ this.stockRepository = stockRepository;
+ }
+
+ public List getStocks() {
+ return StreamSupport.stream(stockRepository.findAll().spliterator(), false)
+ .collect(Collectors.toList());
+ }
+
+ public Stock getStock(String productId) throws StockNotFoundException {
+ return stockRepository.findById(productId)
+ .orElseThrow(() -> new StockNotFoundException("Stock not found with productId: " + productId));
+ }
+}
diff --git a/stockmanager/src/main/resources/application.properties b/stockmanager/src/main/resources/application.properties
new file mode 100644
index 0000000..bd20a40
--- /dev/null
+++ b/stockmanager/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port = 8030
\ No newline at end of file
diff --git a/stockmanager/src/test/java/functional/FunctionalTests.java b/stockmanager/src/test/java/functional/FunctionalTests.java
new file mode 100644
index 0000000..5c6dee4
--- /dev/null
+++ b/stockmanager/src/test/java/functional/FunctionalTests.java
@@ -0,0 +1,15 @@
+package functional;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.junit.Cucumber;
+import org.junit.runner.RunWith;
+
+@RunWith(Cucumber.class)
+@CucumberOptions(monochrome = true,
+ features = "classpath:features/",
+ plugin = "html:build/reports/cucumber",
+ glue = "functional",
+ strict = true)
+public class FunctionalTests {
+
+}
diff --git a/stockmanager/src/test/java/functional/RestStepDefs.java b/stockmanager/src/test/java/functional/RestStepDefs.java
new file mode 100644
index 0000000..94f8dec
--- /dev/null
+++ b/stockmanager/src/test/java/functional/RestStepDefs.java
@@ -0,0 +1,52 @@
+package functional;
+
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+import cucumber.api.java.en.When;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import uk.co.danielbryant.djshopping.stockmanager.StockManagerApplication;
+import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.core.Is.is;
+
+@ContextConfiguration
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = StockManagerApplication.class)
+public class RestStepDefs {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ private List stocks;
+
+ @Given("^the application has been initialised with test data$")
+ public void init() {
+ //the default profile loads synthetic stocks
+ }
+
+ @When("^the client gets all stocks$")
+ public void getAllStocks() {
+ Stock[] stockArray = restTemplate.getForObject("/stocks", Stock[].class);
+ stocks = Arrays.asList(stockArray);
+ }
+
+ @Then("^a list of (.*) stocks will be returned$")
+ public void assertListOfStocksLength(int length) {
+ assertThat(stocks, hasSize(length));
+ }
+
+ @Then("^the stock at index (.*) will have the sku (.*)$")
+ public void assertStockHasSku(int stockIndex, String sku) {
+ assertThat(stocks.get(stockIndex).getSku(), is(sku));
+ }
+}
diff --git a/stockmanager/src/test/java/uk/co/danielbryant/djshopping/stockmanager/ShopfrontApplicationTests.java b/stockmanager/src/test/java/uk/co/danielbryant/djshopping/stockmanager/ShopfrontApplicationTests.java
new file mode 100644
index 0000000..1861ef0
--- /dev/null
+++ b/stockmanager/src/test/java/uk/co/danielbryant/djshopping/stockmanager/ShopfrontApplicationTests.java
@@ -0,0 +1,20 @@
+package uk.co.danielbryant.djshopping.stockmanager;
+
+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.web.client.TestRestTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = StockManagerApplication.class)
+public class ShopfrontApplicationTests {
+
+ @Autowired
+ private TestRestTemplate testRestTemplate;
+
+ @Test
+ public void contextLoads() {
+ }
+}
diff --git a/stockmanager/src/test/resources/features/Stocks.feature b/stockmanager/src/test/resources/features/Stocks.feature
new file mode 100644
index 0000000..8e30975
--- /dev/null
+++ b/stockmanager/src/test/resources/features/Stocks.feature
@@ -0,0 +1,11 @@
+Feature: Retrieving Stocks
+
+ Scenario: Should be able to get a list of all stocks
+ Given the application has been initialised with test data
+ When the client gets all stocks
+ Then a list of 5 stocks will be returned
+
+ Scenario: Should be able to get the correct SKU for the first stock
+ Given the application has been initialised with test data
+ When the client gets all stocks
+ Then the stock at index 0 will have the sku 12345678
\ No newline at end of file