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 + 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! + + + + + + + + + + + + + +
+
+ +
+

Welcome to the Docker Java Shopfront!

+

Please select a product!

+
+ + + + + + + + + + + + + + + + + + + + + + +
Product NumSKUNameDescriptionPrice £Qty Available
112345678WidgetWidget1.192
+
+
+
+
+ + + + + + \ 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