diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1e4292..8e776f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ jobs: - inventory - membership - pricing + - shipping runs-on: depot-ubuntu-24.04-8 steps: - name: Store new version in env diff --git a/Makefile b/Makefile index a781dc3..13dea3a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PROJECT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) # Apps that have a Docker image (build + dev-deploy) -APPS = coupon currency frontend geolocation inventory membership pricing load-generator +APPS = coupon currency frontend geolocation inventory membership pricing shipping load-generator # All apps that produce Linux packages (includes demo meta-package) PACKAGE_APPS = $(APPS) demo diff --git a/README.md b/README.md index bddf946..ba5610c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Simple Demo contains the following services: | Inventory | Python | 3.11 | | Membership | Go | 1.21 | | Pricing | .NET | 8.0 | +| Shipping | C++ | 17 (GCC) | ## Running locally diff --git a/demo/distribution/systemd.service b/demo/distribution/systemd.service index 0eb26f4..3a58fa0 100644 --- a/demo/distribution/systemd.service +++ b/demo/distribution/systemd.service @@ -6,9 +6,9 @@ After=network.target Type=oneshot RemainAfterExit=yes # Start: all demo services except load-generator (load-generator stays disabled by default) -ExecStart=/bin/sh -c 'systemctl start odigos-demo-membership.service odigos-demo-pricing.service odigos-demo-currency.service odigos-demo-frontend.service odigos-demo-geolocation.service odigos-demo-inventory.service odigos-demo-coupon.service 2>/dev/null || true' +ExecStart=/bin/sh -c 'systemctl start odigos-demo-membership.service odigos-demo-pricing.service odigos-demo-currency.service odigos-demo-frontend.service odigos-demo-geolocation.service odigos-demo-inventory.service odigos-demo-coupon.service odigos-demo-shipping.service 2>/dev/null || true' # Stop: all demo services including load-generator -ExecStop=/bin/sh -c 'systemctl stop odigos-demo-load-generator.service odigos-demo-membership.service odigos-demo-pricing.service odigos-demo-currency.service odigos-demo-frontend.service odigos-demo-geolocation.service odigos-demo-inventory.service odigos-demo-coupon.service 2>/dev/null || true' +ExecStop=/bin/sh -c 'systemctl stop odigos-demo-load-generator.service odigos-demo-membership.service odigos-demo-pricing.service odigos-demo-currency.service odigos-demo-frontend.service odigos-demo-geolocation.service odigos-demo-inventory.service odigos-demo-coupon.service odigos-demo-shipping.service 2>/dev/null || true' [Install] WantedBy=multi-user.target diff --git a/demo/nfpm.yaml b/demo/nfpm.yaml index 0261884..8165c0e 100644 --- a/demo/nfpm.yaml +++ b/demo/nfpm.yaml @@ -11,7 +11,7 @@ vendor: Odigos homepage: https://odigos.io description: | Meta-package that installs all Odigos demo applications (membership, pricing, - currency, frontend, geolocation, inventory, coupon, load-generator). + currency, frontend, geolocation, inventory, coupon, shipping, load-generator). systemctl start odigos-demo → start all services except load-generator systemctl stop odigos-demo → stop all services including load-generator depends: @@ -22,6 +22,7 @@ depends: - odigos-demo-geolocation (= ${VERSION}) - odigos-demo-inventory (= ${VERSION}) - odigos-demo-coupon (= ${VERSION}) + - odigos-demo-shipping (= ${VERSION}) - odigos-demo-load-generator (= ${VERSION}) contents: - src: distribution/systemd.service @@ -39,4 +40,5 @@ overrides: - "odigos-demo-geolocation = ${VERSION}" - "odigos-demo-inventory = ${VERSION}" - "odigos-demo-coupon = ${VERSION}" + - "odigos-demo-shipping = ${VERSION}" - "odigos-demo-load-generator = ${VERSION}" diff --git a/docker-compose.yaml b/docker-compose.yaml index c3ce08d..ef79eae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,7 @@ services: COUPON_SERVICE_HOST: coupon:8084 CURRENCY_SERVICE_HOST: nginx:8085 GEOLOCATION_SERVICE_HOST: geolocation:8086 + SHIPPING_SERVICE_HOST: shipping:8087 membership: build: context: ./membership @@ -87,3 +88,10 @@ services: environment: PORT: 8086 RAILS_MASTER_KEY: df0300c20246e24d331787f6540a72fa + shipping: + build: + context: ./shipping + ports: + - '8087:8087' + environment: + PORT: 8087 diff --git a/frontend/deployment/deployment.yaml b/frontend/deployment/deployment.yaml index 5185113..336feb0 100644 --- a/frontend/deployment/deployment.yaml +++ b/frontend/deployment/deployment.yaml @@ -27,5 +27,7 @@ spec: value: currency:8080 - name: GEOLOCATION_SERVICE_HOST value: geolocation:8080 + - name: SHIPPING_SERVICE_HOST + value: shipping:8080 ports: - containerPort: 8080 diff --git a/frontend/distribution/systemd.service b/frontend/distribution/systemd.service index 1047b37..14e0561 100644 --- a/frontend/distribution/systemd.service +++ b/frontend/distribution/systemd.service @@ -18,6 +18,7 @@ Environment="PRICING_SERVICE_HOST=localhost:8083" Environment="COUPON_SERVICE_HOST=localhost:8084" Environment="CURRENCY_SERVICE_HOST=localhost:8085" Environment="GEOLOCATION_SERVICE_HOST=localhost:8086" +Environment="SHIPPING_SERVICE_HOST=localhost:8087" [Install] WantedBy=multi-user.target diff --git a/frontend/src/main/java/dev/keyval/kvshop/frontend/Product.java b/frontend/src/main/java/dev/keyval/kvshop/frontend/Product.java index cabceef..3418286 100644 --- a/frontend/src/main/java/dev/keyval/kvshop/frontend/Product.java +++ b/frontend/src/main/java/dev/keyval/kvshop/frontend/Product.java @@ -5,18 +5,21 @@ public class Product { private String name; private double price; private String image; + private int shippingCents; public Product() { this.id = 0; this.name = ""; this.price = 0.0; this.image = ""; + this.shippingCents = 0; } public Product(int id, String name, double price, String image) { this.id = id; this.name = name; this.price = price; this.image = image; + this.shippingCents = 0; } public int getId() { @@ -50,4 +53,12 @@ public String getImage() { public void setImage(String image) { this.image = image; } + + public int getShippingCents() { + return shippingCents; + } + + public void setShippingCents(int shippingCents) { + this.shippingCents = shippingCents; + } } diff --git a/frontend/src/main/java/dev/keyval/kvshop/frontend/ProductController.java b/frontend/src/main/java/dev/keyval/kvshop/frontend/ProductController.java index 272def4..c537d84 100644 --- a/frontend/src/main/java/dev/keyval/kvshop/frontend/ProductController.java +++ b/frontend/src/main/java/dev/keyval/kvshop/frontend/ProductController.java @@ -13,6 +13,7 @@ public class ProductController { private final InventoryService inventoryService; private final PricingService pricingService; private final CouponService couponService; + private final ShippingService shippingService; @Autowired public ProductController( @@ -20,12 +21,14 @@ public ProductController( GeoService geoService, InventoryService inventoryService, PricingService pricingService, - CouponService couponService) { + CouponService couponService, + ShippingService shippingService) { this.currencyService = currencyService; this.geoService = geoService; this.inventoryService = inventoryService; this.pricingService = pricingService; this.couponService = couponService; + this.shippingService = shippingService; } @CrossOrigin(origins = "*") @@ -44,9 +47,13 @@ public String getConversionRate(@PathVariable String currencyPair) { public List getProducts() { List products = this.inventoryService.getInventory(); - // Add price to every product + // Add price and shipping quote to every product for (Product product : products) { product.setPrice(this.pricingService.getPrice(product.getId())); + ShippingQuoteResult quote = this.shippingService.getQuote(product.getId()); + if (quote != null) { + product.setShippingCents(quote.getShippingCents()); + } } // Get coupons @@ -66,6 +73,12 @@ public void buyProduct(@RequestParam(name = "id") int id) { // Validate price via pricing service double price = this.pricingService.getPrice(id); + ShippingQuoteResult shipping = this.shippingService.getQuote(id); + if (shipping != null) { + System.out.println("Shipping quote for buy id " + id + ": " + shipping.getShippingCents() + + " cents via " + shipping.getCarrier()); + } + String currencyPair = "usd-eur"; CurrencyResult currencyInfo = this.currencyService.getCurrencyInfo(currencyPair); int conversionRate = currencyInfo.getConversionRate(); diff --git a/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingQuoteResult.java b/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingQuoteResult.java new file mode 100644 index 0000000..a70e32b --- /dev/null +++ b/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingQuoteResult.java @@ -0,0 +1,40 @@ +package dev.keyval.kvshop.frontend; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ShippingQuoteResult { + @JsonProperty("productId") + private int productId; + + @JsonProperty("shippingCents") + private int shippingCents; + + @JsonProperty("carrier") + private String carrier; + + public ShippingQuoteResult() {} + + public int getProductId() { + return productId; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public int getShippingCents() { + return shippingCents; + } + + public void setShippingCents(int shippingCents) { + this.shippingCents = shippingCents; + } + + public String getCarrier() { + return carrier; + } + + public void setCarrier(String carrier) { + this.carrier = carrier; + } +} diff --git a/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingService.java b/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingService.java new file mode 100644 index 0000000..9f75d7d --- /dev/null +++ b/frontend/src/main/java/dev/keyval/kvshop/frontend/ShippingService.java @@ -0,0 +1,26 @@ +package dev.keyval.kvshop.frontend; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class ShippingService { + private final String shippingServiceHost; + private static final Logger log = LoggerFactory.getLogger(ShippingService.class); + + public ShippingService(@Value("${SHIPPING_SERVICE_HOST}") String shippingServiceHost) { + this.shippingServiceHost = shippingServiceHost; + } + + public ShippingQuoteResult getQuote(int productId) { + String url = "http://" + shippingServiceHost + "/quote?id=" + productId; + ShippingQuoteResult res = new RestTemplate().getForObject(url, ShippingQuoteResult.class); + log.info("Shipping quote for product {}: {} cents ({})", productId, + res != null ? res.getShippingCents() : -1, + res != null ? res.getCarrier() : "n/a"); + return res; + } +} diff --git a/frontend/webapp/components/ProductCard.js b/frontend/webapp/components/ProductCard.js index 0f51af0..d088889 100644 --- a/frontend/webapp/components/ProductCard.js +++ b/frontend/webapp/components/ProductCard.js @@ -7,6 +7,9 @@ const ProductCard = ({ product }) => { {product.name}

{product.name}

${product.price}

+ {product.shippingCents != null && product.shippingCents > 0 && ( +

Shipping: ${(product.shippingCents / 100).toFixed(2)}

+ )}