diff --git a/pom.xml b/pom.xml
index 5ac889f7f24f..989d5d436f90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -225,6 +225,7 @@
money
table-inheritance
bloc
+ remote-procedure-call
diff --git a/remote-procedure-call/README.md b/remote-procedure-call/README.md
new file mode 100644
index 000000000000..58c2b7eb3a9d
--- /dev/null
+++ b/remote-procedure-call/README.md
@@ -0,0 +1,147 @@
+---
+title: Remote Procedure Call in Java using gRPC
+shortTitle: gRPC in Java
+description: Remote Procedure Call in Java using gRPC communicating between 2 microservices product and cart application
+category: Data Transfer
+language: en
+tag:
+- Behavioral
+- Integration
+- Messaging
+- Client-Server
+- Data Transfer
+- Microservices
+- Remote Procedure Call
+- Remote Method Invocation
+---
+# Remote Method Invocation / Remote Procedure Call
+Remote Method Invocation has various different aliases, Remote Procedure Call / Remote Method Invocation or RPC for short. Is a protocol
+that one program can use to request a service from a different program located in same or another computer in a network without having to understand network details.
+
+RMI can be implemented in various different languages and frameworks. Technologies like REST, gRPC and Thrift can be used for RMI. In this example we
+will be using gRPC to implement Remote Procedure Call in Java.
+
+## Terminologies
+- Client: The client is the application that sends a request to the server.
+- Server: The server is the application that receives the request from the client and sends a response back to the client.
+- Stub: The client-side proxy for the server.
+- Skeleton: The server-side proxy for the client.
+- Protocol: The set of rules that the client and server follow to communicate with each other.
+- Stream: The sequence of messages that are sent between the client and server, understand it as list of objects.
+
+## What is gRPC?
+[gRPC](https://grpc.io/docs/what-is-grpc/introduction/) is a high-performance, open-source and universal RPC framework. gRPC was developed by Google but is now open-source
+and is based on the HTTP/2 protocol.
+
+A gRPC client can call a method on a gRPC server similar to if it was calling a method on a local object.
+This allows client and server to communicate with each other by just using method calls. gRPC internally uses [protobuf](https://protobuf.dev/) to serialize the data for communication.
+
+## When to use gRPC?
+gRPC should be used when you need high performance communication between client and server. It is mostly used in micro-service architecture when one
+service needs to communicate with another service.
+
+For communication you need to define contract / interfaces denoting the method signature, data types and parameters along with return type.
+These methods can be called by client service just like a method call, when using gRPC, a gRPC service is created which will in-turn call the
+implementation of the RPC method in the server and return the response (if any).
+
+Start by creating a .proto file which should have all the methods and data types you need for communication between the services.
+When you compile your code `maven/gradle gRPC` plugin will in return create objects and interfaces which you need to implement / extend in your
+server side. The client will then call the method defined in .proto file using the generated stubs by gPRC. In return inside the server the
+implementation of the method will be called and the response will be sent back to the client.
+
+### In this example
+We will be using 2 different micro-services
+- product-service
+- cart-service
+
+Along with a shopping.proto file to define the contract between the services.
+- ShoppingService
+
+This is a basic e-commerce simulation.
+
+In this simple example the `product-service` has data related to products and is used a source of truth. The `cart-service`
+needs the product data that is available in `product-service`. Certain number of products in turn may be bought by a customer,
+inside the cart service at which point the product quantity needs to be decreased in `product-service`, hence the need for bidirectional
+communication from `product-service` -> `cart-service` and vice versa they both communicate via gRPC.
+
+- getAllProducts() - gets all the product from state in `product-service` and stores it in `cart-service`
+- reduceProductQuantity() - reduces the quantity of a product by `id` fetched by `getAllProducts` and stored in `cart-service`
+ when the method is hit, it reduces the quantity of product with same `id` in `product-service`
+
+## How to implement gRPC in Java?
+### .proto file
+- Create a [.proto](https://protobuf.dev/programming-guides/proto2/) file [example](./proto/shopping.proto) defining the service and message contracts
+- Define service interfaces, method signatures and data types in your .proto file
+### At the server end
+- Add gRPC and protobuf dependencies in your `pom.xml`
+- Include gRPC and protobuf plugins in `mvn build plugins`, for it to generate interfaces from your `.proto` during compilation
+- Include the .proto file directory in mvn build plugins to generate the interfaces
+- Build the project via `mvn clean install`
+- gRPC will generate the stubs and skeletons for you
+- Implement the service logic for the generated methods of skeleton in your service classes
+- Start the gRPC server at server's side on a specific port and attach the gRPC Implementation service to it
+### At the client end
+- Add gRPC and protobuf dependencies in your `pom.xml`
+- Include gRPC and protobuf plugins in `mvn build plugins`, for it to generate interfaces from your `.proto` during compilation
+- Include the .proto file directory in mvn build plugins to generate the interfaces
+- Build the project via `mvn clean install`
+- gRPC will generate the stubs and skeletons for you
+- A stub will expose the available methods to be called by the client, call the methods you need on server via the stub
+- Create Channel with server's host and port at client's end to communicate between server and client
+- Start client, and you are good to go
+
+## gRPC in action
+### Product Service
+#### Service
+- ProductService - API Interface for Internal logic in `product-service`
+- ProductServiceImpl - Implementation of ProductService, saves product data in memory for simplicity, exposes getter(s) for the same.
+ Houses Composition of ProductService to store state.
+- ProductServiceGrpcImpl - gRPC contract implementation, methods to retrieve all products and reduce quantity of a product.
+This file implements the logic that should be executed when gRPC methods are called
+
+#### Model
+- Product - Product POJO Model
+#### Mocks
+- ProductMocks - Mock data of Product for testing and state initialization.
+
+### Cart Service
+#### Service
+- CartService - API Interface for Internal logic in `cart-service`,
+- CartServiceImpl - Implementation of CartService, methods to call the stub to populate data in cart and reduce quantities.
+ This file calls the gRPC method to communicate with `product-service`.
+#### Model
+- ProductInCart - Cut from Product POJO in `product-service`
+
+### proto
+Proto folder contains all the proto files which define contract for the services.
+proto files end with .proto and contain the types, methods and services that are to be used in gRPC communication.
+
+### Good practise
+- Keep types / method names in PascalCase in .proto file
+
+### How to run this project
+- Clone the project
+- navigate to 'remote-procedure-call' directory via
+```shell
+cd java-design-patterns/remote-procedure-call
+```
+- build the project with, this will download dependencies, compile .proto to java interface and classes and create final jar
+```shell
+mvn clean install
+```
+- Start the `product-service` before `cart-service` as `cart-service` depends on product-service
+```shell
+mvn exec:java -Dexec.mainClass="com.iluwatar.rpc.product.Main" -pl product-service
+```
+- Start a new terminal session
+- navigate to 'remote-procedure-call' directory
+- Start the `cart-service`
+```shell
+mvn exec:java -Dexec.mainClass="com.iluwatar.rpc.cart.Main" -pl cart-service
+```
+- `cart-service` on startup will hit a gRPC call to `product-service` to get all products and populate the cart.
+- `cart-service` will then reduce the quantity of a product in `product-service` when a product is bought.
+- `cart-service` will then shut-down gracefully.
+- all the operations will be logged in the console.
+- `product-service` will continue to run and can be used for further operations by running `cart-service` again.
+- To stop the services, press `ctrl+c` in the terminal.
diff --git a/remote-procedure-call/cart-service/etc/cart-service.urm.png b/remote-procedure-call/cart-service/etc/cart-service.urm.png
new file mode 100644
index 000000000000..ab374fa9732b
Binary files /dev/null and b/remote-procedure-call/cart-service/etc/cart-service.urm.png differ
diff --git a/remote-procedure-call/cart-service/etc/cart-service.urm.puml b/remote-procedure-call/cart-service/etc/cart-service.urm.puml
new file mode 100644
index 000000000000..b49796603e31
--- /dev/null
+++ b/remote-procedure-call/cart-service/etc/cart-service.urm.puml
@@ -0,0 +1,70 @@
+@startuml
+skinparam dpi 300
+scale 0.3
+
+package com.iluwatar.rpc.cart.model {
+ class ProductInCart {
+ - id : Long
+ - name : String
+ - price : double
+ - quantityToReduce : int
+ - type : String
+ }
+}
+
+package com.iluwatar.rpc.cart.service {
+ interface CartService {
+ + getAllProducts() {abstract}
+ + getRandomProductFromCart() : ProductInCart {abstract}
+ + reduceCartQuantityFromProduct(ProductInCart) {abstract}
+ }
+
+ class CartServiceImpl {
+ - log : Logger {static}
+ - productsInCart : List
+ - shoppingServiceBlockingStub : ShoppingServiceBlockingStub
+ + CartServiceImpl(shoppingStub : ShoppingServiceBlockingStub)
+ + getAllProducts()
+ + getRandomProductFromCart() : ProductInCart
+ + reduceCartQuantityFromProduct(product : ProductInCart)
+ }
+}
+
+package com.iluwatar.rpc.proto {
+ class Empty {}
+
+ class ProductResponse {
+ - id : long
+ - name : String
+ - price : double
+ - type : String
+ }
+
+ class ReduceProductRequest {
+ - productId : long
+ - quantity : int
+ }
+
+ class ReduceProductResponse {
+ - message : String
+ - status : boolean
+ }
+
+ class ShoppingServiceImplBase {
+ - getAllProducts(request: Empty, responseStreamObserver: StreamObserver)
+ - reduceProductQuantity(request: ReduceProductRequest, responseStreamObserver : StreamObserver)
+ }
+
+}
+package com.iluwatar.rpc.cart {
+ class Main {
+ - HOST : String {static}
+ - SERVER_PORT : int {static}
+ - log : Logger {static}
+ + main(args : String[]) {static}
+ }
+}
+
+CartServiceImpl --> "-productsInCart" ProductInCart
+CartServiceImpl ..|> CartService
+@enduml
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/pom.xml b/remote-procedure-call/cart-service/pom.xml
new file mode 100644
index 000000000000..7fad34a3edb6
--- /dev/null
+++ b/remote-procedure-call/cart-service/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ remote-procedure-call
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+
+ 4.0.0
+ cart-service
+ jar
+
+
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/Main.java b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/Main.java
new file mode 100644
index 000000000000..db8bd1738ee5
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/Main.java
@@ -0,0 +1,74 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart;
+
+import com.iluwatar.rpc.cart.service.CartServiceImpl;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceBlockingStub;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Main class for the cart service.
+ * Initializes the shopping channel and the cart service.
+ *
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class Main {
+ private static final int SERVER_PORT = 8080;
+ private static final String HOST = "localhost";
+ private static final Logger log = LoggerFactory.getLogger(Main.class);
+ /**
+ * Main method to initialize the cart service and channel for RPC connection
+ * initializes blocking stub and passes it to CartServiceImpl for constructor initialization.
+ * Initializes data fetching from product-service.
+ * shuts down after 1 request
+ */
+ public static void main(String[] args) {
+ ManagedChannel productChannel = ManagedChannelBuilder
+ .forAddress(HOST, SERVER_PORT)
+ .usePlaintext()
+ .enableRetry()
+ .keepAliveTime(10, TimeUnit.SECONDS)
+ .build();
+
+ ShoppingServiceBlockingStub blockingStub = ShoppingServiceGrpc.newBlockingStub(productChannel);
+ log.info("cart-service started");
+
+ var cartService = new CartServiceImpl(blockingStub);
+ cartService.getAllProducts();
+
+ var productInCart = cartService.getRandomProductFromCart();
+ productInCart.setQuantityToReduce(10);
+
+ cartService.reduceCartQuantityFromProduct(productInCart);
+ productChannel.shutdown();
+ log.info("cart-service execution successful, shutting down");
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/model/ProductInCart.java b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/model/ProductInCart.java
new file mode 100644
index 000000000000..522dce357f5d
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/model/ProductInCart.java
@@ -0,0 +1,46 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart.model;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * ProductInCart is a POJO model class for product in cart.
+ * ProductInCart is a cut from Product class in product-service
+ *
+ * @link com.iluwatar.rpc.product.model.Product
+ * @author CoderSleek
+ * @version 1.0
+ */
+@Data
+@Builder(toBuilder = true)
+public class ProductInCart {
+ private Long id;
+ private String name;
+ private String type;
+ private double price;
+ private int quantityToReduce;
+}
diff --git a/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartService.java b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartService.java
new file mode 100644
index 000000000000..e6720f439cd4
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartService.java
@@ -0,0 +1,61 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart.service;
+
+import com.iluwatar.rpc.cart.model.ProductInCart;
+
+/**
+ * API Contract for Cart service, which can be exposed.
+ * Includes contract method for grpc stub as well.
+ * Thread safety is not guaranteed.
+ * @link com.iluwatar.rpc.cart.model.ProductInCart
+ * @author CoderSleek
+ * @version 1.0
+ */
+public interface CartService {
+ /**
+ * returns a random product from cart, if cart is empty throws IllegalStateException.
+ * randomized just for demonstration purposes.
+ *
+ * @return randomly chosen ProductInCart from List of ProductInCart
+ * @throws IllegalStateException if cart is empty
+ */
+ ProductInCart getRandomProductFromCart() throws IllegalStateException;
+
+ /**
+ * reduces the quantity of a product from cart by doing a RPC call to product service.
+ *
+ * @param product product whose quantity needs to be reduced in product-service
+ * @throws IllegalArgumentException if product is null or id invalid
+ * @throws IllegalStateException if product is not found in cart or cart is null
+ */
+ void reduceCartQuantityFromProduct(ProductInCart product)
+ throws IllegalStateException, IllegalArgumentException;
+
+ /**
+ * RPC call to get all the products from product-service and add them to cart.
+ */
+ void getAllProducts();
+}
diff --git a/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartServiceImpl.java b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartServiceImpl.java
new file mode 100644
index 000000000000..c647bde3110d
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/main/java/com/iluwatar/rpc/cart/service/CartServiceImpl.java
@@ -0,0 +1,132 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart.service;
+
+import com.iluwatar.rpc.cart.model.ProductInCart;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceBlockingStub;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of cart-service Contract exposed by gRPC proto.
+ * Thread safety is not guaranteed.
+ * @link com.iluwatar.rpc.cart.model.ProductInCart
+ * @link com.iluwatar.rpc.cart.service.CartService
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class CartServiceImpl implements CartService {
+ private final ShoppingServiceBlockingStub shoppingServiceBlockingStub;
+ private final List productsInCart = new ArrayList<>();
+ private static final Logger log = LoggerFactory.getLogger(CartServiceImpl.class);
+
+ /**
+ * Constructor to initialize CartServiceImpl with ProductServiceBlockingStub.
+ * blocking-stub is initialized in Main
+ * @param shoppingStub ProductServiceBlockingStub
+ * @throws IllegalArgumentException if ProductServiceBlockingStub is null
+ */
+ public CartServiceImpl(ShoppingServiceBlockingStub shoppingStub) {
+ if (shoppingStub == null) {
+ throw new IllegalArgumentException("ShoppingServiceBlockingStub is null");
+ }
+ this.shoppingServiceBlockingStub = shoppingStub;
+ }
+
+ @Override
+ public void reduceCartQuantityFromProduct(ProductInCart product)
+ throws IllegalArgumentException, IllegalStateException {
+ log.info("Started Request for reducing product quantity from cart in product-service");
+ if (product == null) {
+ throw new IllegalArgumentException("Product state is null");
+ }
+ if (product.getId() <= 0) {
+ throw new IllegalArgumentException("Invalid Product id");
+ }
+ if (productsInCart.isEmpty()) {
+ throw new IllegalStateException("Products In cart array empty, populate cart first");
+ }
+
+ // type exposed and maintained in gRPC proto
+ ReduceProductRequest request = ReduceProductRequest
+ .newBuilder()
+ .setProductId(product.getId())
+ .setQuantity(product.getQuantityToReduce())
+ .build();
+
+ log.info("Initiating RPC call to reduce quantity of product with id: {}", product.getId());
+ ReduceProductResponse response = shoppingServiceBlockingStub.reduceProductQuantity(request);
+ log.info("Completed RPC call reduce quantity with status: {}", response.getStatus());
+ log.info("RPC call response message: {}", response.getMessage());
+ log.info("Completed Request for reducing product quantity from cart in product-service");
+ }
+
+ @Override
+ public void getAllProducts() {
+ log.info("Started request to fetch all products from product-service");
+ // stream of products / multiple products
+ try {
+ shoppingServiceBlockingStub.getAllProducts(null)
+ .forEachRemaining(product ->
+ productsInCart.add(ProductInCart.builder()
+ .name(product.getName())
+ .id(product.getId())
+ .price(product.getPrice())
+ .type(product.getType())
+ .quantityToReduce(0)
+ .build())
+ );
+ } catch (Exception e) {
+ log.error("Error occurred while fetching products: ", e);
+ throw new IllegalStateException("Failed to fetch products from ProductService", e);
+ }
+ log.info("Fetching of products completed");
+ }
+
+ /**
+ * returns a random product from cart, if cart is empty throws IllegalStateException.
+ * randomized just for demonstration purposes.
+ * returns a new instance of ProductInCart to avoid mutation of quantityToReduce in original object.
+ *
+ * @return randomly chosen ProductInCart from List of ProductInCart
+ * @throws IllegalStateException if cart is empty
+ */
+ @Override
+ public ProductInCart getRandomProductFromCart() throws IllegalStateException {
+ if (productsInCart.isEmpty()) {
+ throw new IllegalStateException("Products In cart array empty");
+ }
+
+ // randomly choose a product for dynamic results
+ int randInt = new Random().nextInt(productsInCart.size());
+ // to builder is used to return a copy of the object to avoid mutation of original object
+ return productsInCart.get(randInt).toBuilder().build();
+ }
+}
diff --git a/remote-procedure-call/cart-service/src/main/resources/logback.xml b/remote-procedure-call/cart-service/src/main/resources/logback.xml
new file mode 100644
index 000000000000..ed930c512e7e
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/main/resources/logback.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplIntegrationTest.java b/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplIntegrationTest.java
new file mode 100644
index 000000000000..64bdbe46eb20
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplIntegrationTest.java
@@ -0,0 +1,96 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart.service;
+
+import com.iluwatar.rpc.cart.model.ProductInCart;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceBlockingStub;
+import com.iluwatar.rpc.proto.Shopping.ProductResponse;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import java.util.ArrayList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class CartServiceImplIntegrationTest {
+
+ @Mock
+ private ShoppingServiceBlockingStub productServiceBlockingStub;
+
+ @InjectMocks
+ private CartServiceImpl cartService;
+
+ private ProductResponse product;
+
+ @BeforeEach
+ public void setUp() {
+ product = ProductResponse.newBuilder()
+ .setId(1)
+ .setName("Test")
+ .setPrice(100)
+ .setType("Test")
+ .build();
+
+ ArrayList productList = new ArrayList<>();
+
+ productList.add(product);
+ when(productServiceBlockingStub.getAllProducts(null)).thenReturn(productList.iterator());
+ cartService.getAllProducts();
+ }
+
+ @Test
+ void testGetRandomProductFromCartAndReduceQuantity_Success() {
+ ProductInCart randomProduct = cartService.getRandomProductFromCart();
+
+ assertNotNull(randomProduct);
+ assertEquals(product.getId(), randomProduct.getId());
+ assertEquals(product.getName(), randomProduct.getName());
+ randomProduct.setQuantityToReduce(100);
+
+ ReduceProductResponse response = ReduceProductResponse.newBuilder()
+ .setStatus(true)
+ .setMessage("Success")
+ .build();
+
+ when(productServiceBlockingStub.reduceProductQuantity(
+ any(ReduceProductRequest.class)
+ )).thenReturn(response);
+ cartService.reduceCartQuantityFromProduct(randomProduct);
+
+ verify(productServiceBlockingStub, times(1)).reduceProductQuantity(
+ any(ReduceProductRequest.class));
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplTest.java b/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplTest.java
new file mode 100644
index 000000000000..6cd2ccdd627c
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/test/java/com/iluwatar/rpc/cart/service/CartServiceImplTest.java
@@ -0,0 +1,168 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.cart.service;
+
+import com.iluwatar.rpc.cart.model.ProductInCart;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceBlockingStub;
+import com.iluwatar.rpc.proto.Shopping;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CartServiceImplTest {
+ @Mock
+ private ShoppingServiceBlockingStub productServiceBlockingStub;
+ @InjectMocks
+ private CartServiceImpl cartService;
+
+ private static List products;
+
+ @BeforeEach
+ public void setUp() {
+ products = new ArrayList<>();
+ var product = productInCartProvider();
+ products.add(Shopping.ProductResponse.newBuilder()
+ .setId(product.getId())
+ .setPrice(product.getPrice())
+ .setType(product.getType())
+ .setName(product.getName())
+ .build());
+ }
+
+ @Test
+ void testReduceCartQuantityFromProduct_NullProduct() {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
+ cartService.reduceCartQuantityFromProduct(null);
+ });
+ assertEquals("Product state is null", exception.getMessage());
+ }
+
+ @Test
+ void testReduceCartQuantityFromProduct_InvalidProductProperties() {
+ ProductInCart invalidProduct = productInCartProvider();
+ invalidProduct.setId(-1L);
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
+ cartService.reduceCartQuantityFromProduct(invalidProduct);
+ });
+ assertEquals("Invalid Product id", exception.getMessage());
+ }
+
+ @Test
+ void testReduceCartQuantityFromProduct_EmptyCart() {
+ ProductInCart product = productInCartProvider();
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ cartService.reduceCartQuantityFromProduct(product);
+ });
+ assertEquals("Products In cart array empty, populate cart first", exception.getMessage());
+ }
+
+ @Test
+ void testReduceCartQuantityFromProduct_Success() {
+ when(productServiceBlockingStub.getAllProducts(null)).thenReturn(products.iterator());
+
+ ProductInCart product = productInCartProvider();
+ product.setId(3L);
+
+ ReduceProductResponse response =
+ ReduceProductResponse.newBuilder().setStatus(true).setMessage("Success").build();
+ when(productServiceBlockingStub.reduceProductQuantity(
+ any(ReduceProductRequest.class))).thenReturn(response);
+
+ cartService.getAllProducts();
+ cartService.reduceCartQuantityFromProduct(product);
+ verify(productServiceBlockingStub, times(1)).reduceProductQuantity(
+ any(ReduceProductRequest.class));
+ }
+
+ @Test
+ void testGetAllProducts_Success() {
+ when(productServiceBlockingStub.getAllProducts(null)).thenReturn(products.iterator());
+
+ List products = new ArrayList<>();
+ var productInCart = productInCartProvider();
+ products.add(Shopping.ProductResponse.newBuilder()
+ .setId(productInCart.getId())
+ .setName(productInCart.getName())
+ .setType(productInCart.getType())
+ .setPrice(productInCart.getPrice() + 10)
+ .build());
+
+ when(productServiceBlockingStub.getAllProducts(null)).thenReturn(products.iterator());
+
+ cartService.getAllProducts();
+ assertDoesNotThrow(() -> cartService.getRandomProductFromCart());
+ }
+
+ @Test
+ void testGetAllProducts_Exception() {
+ when(productServiceBlockingStub.getAllProducts(null)).thenThrow(
+ new RuntimeException("Service error"));
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ cartService.getAllProducts();
+ });
+ assertEquals("Failed to fetch products from ProductService", exception.getMessage());
+ }
+
+ @Test
+ void testGetRandomProductFromCart_EmptyCart() {
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ cartService.getRandomProductFromCart();
+ });
+ assertEquals("Products In cart array empty", exception.getMessage());
+ }
+
+ @Test
+ void testGetRandomProductFromCart_Success() {
+ when(productServiceBlockingStub.getAllProducts(null)).thenReturn(products.iterator());
+
+ cartService.getAllProducts();
+ ProductInCart randomProduct = cartService.getRandomProductFromCart();
+ assertNotNull(randomProduct);
+ }
+
+ private ProductInCart productInCartProvider() {
+ return ProductInCart.builder()
+ .id(1L)
+ .name("Test")
+ .type("Test type")
+ .price(0.0)
+ .quantityToReduce(10)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/cart-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/remote-procedure-call/cart-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 000000000000..ca6ee9cea8ec
--- /dev/null
+++ b/remote-procedure-call/cart-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/remote-procedure-call/etc/remote-procedure-call.urm.puml b/remote-procedure-call/etc/remote-procedure-call.urm.puml
new file mode 100644
index 000000000000..02af47ddf261
--- /dev/null
+++ b/remote-procedure-call/etc/remote-procedure-call.urm.puml
@@ -0,0 +1,2 @@
+@startuml
+@enduml
\ No newline at end of file
diff --git a/remote-procedure-call/pom.xml b/remote-procedure-call/pom.xml
new file mode 100644
index 000000000000..a9c8761191a9
--- /dev/null
+++ b/remote-procedure-call/pom.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+
+
+ 1.68.0
+ 4.28.2
+ 3.0.0
+ 5.14.2
+ 3.11.0
+ 0.6.1
+
+
+ 4.0.0
+ remote-procedure-call
+ 1.26.0-SNAPSHOT
+ pom
+
+
+ cart-service
+ product-service
+
+
+
+
+ io.grpc
+ grpc-netty-shaded
+ ${grpc-version}
+ runtime
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc-version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc-version}
+
+
+ com.google.protobuf
+ protobuf-java
+ ${protobuf-version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito-version}
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.1
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin-version}
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin-version}
+
+ ${project.basedir}/../proto
+ com.google.protobuf:protoc:4.28.2:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:1.68.0:exe:${os.detected.classifier}
+
+
+
+
+ @generated=omit
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/etc/product-service.urm.png b/remote-procedure-call/product-service/etc/product-service.urm.png
new file mode 100644
index 000000000000..948b3d5273be
Binary files /dev/null and b/remote-procedure-call/product-service/etc/product-service.urm.png differ
diff --git a/remote-procedure-call/product-service/etc/product-service.urm.puml b/remote-procedure-call/product-service/etc/product-service.urm.puml
new file mode 100644
index 000000000000..799192b93d2e
--- /dev/null
+++ b/remote-procedure-call/product-service/etc/product-service.urm.puml
@@ -0,0 +1,80 @@
+@startuml
+skinparam dpi 300
+scale 0.3
+
+package com.iluwatar.rpc.product {
+ class Main {
+ - SERVER_PORT : int {static}
+ - log : Logger {static}
+ + main(args : String[]) {static}
+ }
+}
+
+package com.iluwatar.rpc.product.model {
+ class Product {
+ - id : Long
+ - name : String
+ - price : double
+ - quantity : int
+ - type : String
+ }
+}
+
+package com.iluwatar.rpc.product.mocks {
+ class ProductMocks {
+ + ProductMocks()
+ + getMockProducts() : List {static}
+ }
+}
+
+package com.iluwatar.rpc.product.service {
+ interface ProductService {
+ + getAllProducts() : List {abstract}
+ + getProduct(Long) : Optional {abstract}
+ }
+ class ProductServiceGrpcImpl {
+ - log : Logger {static}
+ - productService : ProductService
+ + ProductServiceGrpcImpl(productService : ProductService)
+ + getAllProducts(request : Empty, responseStreamObserver : StreamObserver)
+ + reduceProductQuantity(request : ReduceProductRequest, responseStreamObserver : StreamObserver)
+ }
+ class ProductServiceImpl {
+ - products : List
+ + ProductServiceImpl(products : List)
+ + getAllProducts() : List
+ + getProduct(id : Long) : Optional
+ }
+}
+
+package com.iluwatar.rpc.proto {
+ class Empty {}
+
+ class ProductResponse {
+ - id : long
+ - name : String
+ - price : double
+ - type : String
+ }
+
+ class ReduceProductRequest {
+ - productId : long
+ - quantity : int
+ }
+
+ class ReduceProductResponse {
+ - message : String
+ - status : boolean
+ }
+
+ class ShoppingServiceImplBase {
+ - getAllProducts(request: Empty, responseStreamObserver: StreamObserver)
+ - reduceProductQuantity(request: ReduceProductRequest, responseStreamObserver : StreamObserver)
+ }
+}
+
+ProductServiceImpl --> "-products" Product
+ProductServiceGrpcImpl --> "-productService" ProductService
+ProductServiceGrpcImpl --|> ShoppingServiceImplBase
+ProductServiceImpl ..|> ProductService
+@enduml
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/pom.xml b/remote-procedure-call/product-service/pom.xml
new file mode 100644
index 000000000000..bf54b0e75413
--- /dev/null
+++ b/remote-procedure-call/product-service/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ remote-procedure-call
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+
+ 4.0.0
+ product-service
+ jar
+
+
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/Main.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/Main.java
new file mode 100644
index 000000000000..bd1c958cbb20
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/Main.java
@@ -0,0 +1,72 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product;
+
+import com.iluwatar.rpc.product.mocks.ProductMocks;
+import com.iluwatar.rpc.product.model.Product;
+import com.iluwatar.rpc.product.service.ProductServiceGrpcImpl;
+import com.iluwatar.rpc.product.service.ProductServiceImpl;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import java.io.IOException;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Main class to start the product service.
+ *
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class Main {
+ private static final int SERVER_PORT = 8080;
+ private static final Logger log = LoggerFactory.getLogger(Main.class);
+ /**
+ * Main method to start the product service.
+ * instantiates product mock data and starts the gRPC server.
+ * constructor injects ProductService into ProductServiceGrpcImpl
+ * listens on default server port 8080
+ *
+ * @param args the input arguments
+ * @throws IOException the io exception
+ * @throws InterruptedException the interrupted exception
+ */
+ public static void main(String[] args) throws IOException, InterruptedException {
+ List products = ProductMocks.getMockProducts();
+ var productServiceImpl = new ProductServiceImpl(products);
+ var productServiceGrpcImpl = new ProductServiceGrpcImpl(productServiceImpl);
+
+ Server server = ServerBuilder
+ .forPort(SERVER_PORT)
+ .addService(productServiceGrpcImpl)
+ .build();
+
+ log.info("Starting server on port: " + SERVER_PORT);
+ log.info("Waiting for request---------");
+ server.start();
+ server.awaitTermination();
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/mocks/ProductMocks.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/mocks/ProductMocks.java
new file mode 100644
index 000000000000..68b5cd036fa2
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/mocks/ProductMocks.java
@@ -0,0 +1,54 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.mocks;
+
+import com.iluwatar.rpc.product.model.Product;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mocks data for product POJO, used for testing and in memory store.
+ * Thread safety is not guaranteed.
+ * @link com.iluwatar.rpc.product.model.Product
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class ProductMocks {
+ /**
+ * Returns new Mock product ArrayList on each call, to reset state.
+ *
+ * @return the mock products
+ */
+ public static List getMockProducts() {
+ return new ArrayList<>() {{
+ add(new Product(1L, "Product 1", "Type 1", 50.0, 10));
+ add(new Product(2L, "Product 2", "Type 2", 40.0, 20));
+ add(new Product(3L, "Product 3", "Type 3", 30.0, 30));
+ add(new Product(4L, "Product 4", "Type 4", 20.0, 40));
+ add(new Product(5L, "Product 5", "Type 5", 10.0, 50));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/model/Product.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/model/Product.java
new file mode 100644
index 000000000000..f0975877b35b
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/model/Product.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Product POJO class.
+ *
+ * @author CoderSleek
+ * @version 1.0
+ */
+@Data
+@AllArgsConstructor
+public class Product {
+ private Long id;
+ private String name;
+ private String type;
+ private double price;
+ private int quantity;
+}
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductService.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductService.java
new file mode 100644
index 000000000000..01ef6f2dbb5b
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductService.java
@@ -0,0 +1,55 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.model.Product;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * API Contract for Product service, which can be exposed.
+ * Includes getter methods for product by id and all products.
+ * Thread safety is not guaranteed.
+ *
+ * @link com.iluwatar.rpc.product.model.Product
+ * @author CoderSleek
+ * @version 1.0
+ */
+public interface ProductService {
+ /**
+ * Get optional of product by id.
+ *
+ * @param id id of product
+ * @return product
+ */
+ Optional getProduct(Long id);
+
+ /**
+ * Get all products.
+ * @return ArrayList of all products
+ * @throws IllegalStateException thrown when products array is null or empty, denotes improper initialization
+ */
+ List getAllProducts() throws IllegalStateException;
+}
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImpl.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImpl.java
new file mode 100644
index 000000000000..2dd5d58e3f2f
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImpl.java
@@ -0,0 +1,147 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.model.Product;
+import com.iluwatar.rpc.proto.Shopping.Empty;
+import com.iluwatar.rpc.proto.Shopping.ProductResponse;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceImplBase;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of product-service Contract exposed by gRPC proto.
+ * Thread safety is not guaranteed.
+ *
+ * @link com.iluwatar.rpc.product.service.ProductService
+ * @link com.iluwatar.rpc.product.model.Product
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class ProductServiceGrpcImpl extends ShoppingServiceImplBase {
+ private final ProductService productService;
+ private static final Logger log = LoggerFactory.getLogger(ProductServiceGrpcImpl.class);
+
+ /**
+ * Constructor for initializing Implementation of ProductService Contract internally.
+ * ProductService Implementation stores Product data in internal state for simplicity.
+ *
+ * @param productService ProductService
+ */
+ public ProductServiceGrpcImpl(ProductService productService) {
+ this.productService = productService;
+ }
+
+ /**
+ * Reduces the quantity of a specified product via product id by the requested quantity.
+ * To simulate a customer buying an item, once bought the quantity of the product is reduced from stock.
+ * @param request contains product id and quantity to reduce
+ * @param responseStreamObserver for iterating over response
+ */
+ @Override
+ public void reduceProductQuantity(ReduceProductRequest request,
+ StreamObserver responseStreamObserver) {
+ log.info("Received request to reduce product quantity");
+ boolean status;
+ String message;
+ var product = this.productService.getProduct(request.getProductId());
+
+ if (product.isEmpty()) {
+ status = false;
+ message = "Product with ID does not exist";
+ } else {
+ int productQuantity = product.get().getQuantity();
+ int requestedQuantity = request.getQuantity();
+
+ if (requestedQuantity <= 0) {
+ status = false;
+ message = "Invalid Quantity";
+ } else if (requestedQuantity > productQuantity) {
+ status = false;
+ message = "Product has less quantity in stock than requested";
+ } else {
+ log.info("Before reducing quantity: {}", productQuantity);
+ product.get().setQuantity(productQuantity - requestedQuantity);
+ log.info("After reducing quantity: {}", product.get().getQuantity());
+ status = true;
+ message = "Success";
+ }
+ }
+
+ var response = ReduceProductResponse
+ .newBuilder()
+ .setMessage(message)
+ .setStatus(status)
+ .build();
+
+ responseStreamObserver.onNext(response);
+ responseStreamObserver.onCompleted();
+ log.info("Request to Reduce Product Quantity Execution Completed");
+ }
+
+ /**
+ * Fetches all products from ProductService and streams them to the client.
+ * Throws NOT_FOUND status exception if products are not found.
+ * @param request empty placeholder request
+ * @param responseStreamObserver for iterating over responses
+ */
+ @Override
+ public void getAllProducts(Empty request,
+ StreamObserver responseStreamObserver) {
+ log.info("Received request to fetch all products");
+ List products;
+
+ try {
+ products = this.productService.getAllProducts();
+ } catch (IllegalStateException e) {
+ log.error("Failed to fetch products from ProductService", e);
+ responseStreamObserver.onError(
+ Status.NOT_FOUND
+ .withDescription(e.getMessage())
+ .asException());
+ responseStreamObserver.onCompleted();
+ return;
+ }
+
+ for (var product : products) {
+ responseStreamObserver.onNext(
+ ProductResponse.newBuilder()
+ .setId(product.getId())
+ .setName(product.getName())
+ .setPrice(product.getPrice())
+ .setType(product.getType())
+ .build()
+ );
+ }
+
+ responseStreamObserver.onCompleted();
+ log.info("Request to fetch all products Execution Completed");
+ }
+}
diff --git a/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceImpl.java b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceImpl.java
new file mode 100644
index 000000000000..2ce1749b1927
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/java/com/iluwatar/rpc/product/service/ProductServiceImpl.java
@@ -0,0 +1,78 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.model.Product;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of ProductService Contract, maintains Product state for simplicity.
+ * Provides getter methods for product by id and all products.
+ * Thread safety is not guaranteed.
+ *
+ * @link com.iluwatar.rpc.product.service.ProductService
+ * @link com.iluwatar.rpc.product.model.Product
+ * @author CoderSleek
+ * @version 1.0
+ */
+public class ProductServiceImpl implements ProductService {
+ private final List products;
+
+ /**
+ * Constructor to set internal state of Product array.
+ *
+ * @param products ArrayList of products
+ */
+ public ProductServiceImpl(List products) {
+ this.products = products;
+ }
+
+ /**
+ * Get optional of product by id.
+ * @param id id of product
+ * @return Product first item in the list with matching id, if not found returns empty optional
+ */
+ @Override
+ public Optional getProduct(Long id) {
+ return products
+ .stream()
+ .filter(product -> product.getId().equals(id))
+ .findFirst();
+ }
+
+ @Override
+ public List getAllProducts() throws IllegalStateException {
+ if (products == null) {
+ throw new IllegalStateException("Products array is not initialized properly");
+ }
+
+ if (products.isEmpty()) {
+ throw new IllegalStateException("Products array does not have any data");
+ }
+
+ return products;
+ }
+}
diff --git a/remote-procedure-call/product-service/src/main/resources/logback.xml b/remote-procedure-call/product-service/src/main/resources/logback.xml
new file mode 100644
index 000000000000..ed930c512e7e
--- /dev/null
+++ b/remote-procedure-call/product-service/src/main/resources/logback.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImplTest.java b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImplTest.java
new file mode 100644
index 000000000000..4a390b9123d1
--- /dev/null
+++ b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceGrpcImplTest.java
@@ -0,0 +1,192 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.mocks.ProductMocks;
+import com.iluwatar.rpc.product.model.Product;
+import com.iluwatar.rpc.proto.Shopping.Empty;
+import com.iluwatar.rpc.proto.Shopping.ProductResponse;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import io.grpc.StatusException;
+import io.grpc.stub.StreamObserver;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.any;
+
+@ExtendWith(MockitoExtension.class)
+class ProductServiceGrpcImplTest {
+ @Mock
+ private ProductService productService;
+
+ @InjectMocks
+ private ProductServiceGrpcImpl productServiceGrpcImpl;
+
+ private List productsMockData;
+
+ @BeforeEach
+ public void setUp() {
+ productsMockData = ProductMocks.getMockProducts();
+ }
+
+ @Test
+ void testReduceProductQuantity_ProductNotFound() {
+ var request = ReduceProductRequest.newBuilder()
+ .setProductId(6L)
+ .setQuantity(5)
+ .build();
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getProduct(request.getProductId())).thenReturn(Optional.empty());
+
+ productServiceGrpcImpl.reduceProductQuantity(request, responseObserver);
+
+ ArgumentCaptor responseCaptor =
+ ArgumentCaptor.forClass(ReduceProductResponse.class);
+ verify(responseObserver).onNext(responseCaptor.capture());
+ verify(responseObserver).onCompleted();
+
+ ReduceProductResponse response = responseCaptor.getValue();
+ assertFalse(response.getStatus());
+ assertEquals("Product with ID does not exist", response.getMessage());
+ }
+
+ @Test
+ void testReduceProductQuantity_InvalidQuantity() {
+ Product product = productsMockData.get(0);
+ ReduceProductRequest request = ReduceProductRequest.newBuilder()
+ .setProductId(product.getId())
+ .setQuantity(-5)
+ .build();
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getProduct(product.getId())).thenReturn(Optional.of(product));
+
+ productServiceGrpcImpl.reduceProductQuantity(request, responseObserver);
+
+ ArgumentCaptor responseCaptor =
+ ArgumentCaptor.forClass(ReduceProductResponse.class);
+ verify(responseObserver).onNext(responseCaptor.capture());
+ verify(responseObserver).onCompleted();
+
+ ReduceProductResponse response = responseCaptor.getValue();
+ assertFalse(response.getStatus());
+ assertEquals("Invalid Quantity", response.getMessage());
+ }
+
+ @Test
+ void testReduceProductQuantity_InsufficientQuantity() {
+ Product product = productsMockData.get(0);
+
+ ReduceProductRequest request = ReduceProductRequest.newBuilder()
+ .setProductId(product.getId())
+ .setQuantity(1000)
+ .build();
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getProduct(product.getId())).thenReturn(Optional.of(product));
+
+ productServiceGrpcImpl.reduceProductQuantity(request, responseObserver);
+
+ ArgumentCaptor responseCaptor =
+ ArgumentCaptor.forClass(ReduceProductResponse.class);
+ verify(responseObserver).onNext(responseCaptor.capture());
+ verify(responseObserver).onCompleted();
+
+ ReduceProductResponse response = responseCaptor.getValue();
+ assertFalse(response.getStatus());
+ assertEquals("Product has less quantity in stock than requested", response.getMessage());
+ }
+
+ @Test
+ void testReduceProductQuantity_Success() {
+ Product product = productsMockData.get(0);
+ ReduceProductRequest request = ReduceProductRequest.newBuilder()
+ .setProductId(product.getId())
+ .setQuantity(5)
+ .build();
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getProduct(product.getId())).thenReturn(Optional.of(product));
+
+ productServiceGrpcImpl.reduceProductQuantity(request, responseObserver);
+
+ ArgumentCaptor responseCaptor =
+ ArgumentCaptor.forClass(ReduceProductResponse.class);
+ verify(responseObserver).onNext(responseCaptor.capture());
+ verify(responseObserver).onCompleted();
+
+ ReduceProductResponse response = responseCaptor.getValue();
+ assertTrue(response.getStatus());
+ assertEquals("Success", response.getMessage());
+ assertEquals(5, product.getQuantity());
+ }
+
+ @Test
+ void testGetAllProducts_Success() {
+ List productsLocal = ProductMocks.getMockProducts();
+ var product = productsLocal.get(0);
+
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getAllProducts()).thenReturn(productsMockData);
+ productServiceGrpcImpl.getAllProducts(null, responseObserver);
+
+ ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ProductResponse.class);
+ verify(responseObserver, times(5)).onNext(responseCaptor.capture());
+ verify(responseObserver).onCompleted();
+
+ assertEquals(productsLocal.size(), responseCaptor.getAllValues().size());
+ assertEquals(Long.compare(product.getId(), responseCaptor.getAllValues().get(0).getId()), 0);
+ assertEquals(product.getName(), responseCaptor.getAllValues().get(0).getName());
+ assertEquals(product.getType(), responseCaptor.getAllValues().get(0).getType());
+ assertEquals(
+ Double.compare(product.getPrice(), responseCaptor.getAllValues().get(0).getPrice()), 0);
+ }
+
+ @Test
+ void testGetAllProducts_Failure() {
+ StreamObserver responseObserver = mock(StreamObserver.class);
+
+ when(productService.getAllProducts()).thenThrow(new IllegalStateException("Database error"));
+
+ productServiceGrpcImpl.getAllProducts(Empty.newBuilder().build(), responseObserver);
+
+ verify(responseObserver).onError(any(StatusException.class));
+ verify(responseObserver).onCompleted();
+ }
+}
diff --git a/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceImplTest.java b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceImplTest.java
new file mode 100644
index 000000000000..9c82c86605d3
--- /dev/null
+++ b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceImplTest.java
@@ -0,0 +1,92 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.mocks.ProductMocks;
+import com.iluwatar.rpc.product.model.Product;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class ProductServiceImplTest {
+ private static List products;
+ private ProductServiceImpl productService;
+
+ @BeforeEach
+ void setUp() {
+ products = ProductMocks.getMockProducts();
+ productService = new ProductServiceImpl(products);
+ }
+
+ @Test
+ void testGetProduct() {
+ Optional product = productService.getProduct(1L);
+ assertTrue(product.isPresent());
+ assertEquals(1L, product.get().getId());
+ assertEquals("Product 1", product.get().getName());
+ assertEquals(50.0, product.get().getPrice());
+ }
+
+ @Test
+ void testGetProductNotFound() {
+ Optional product = productService.getProduct(6L);
+ assertFalse(product.isPresent());
+ }
+
+ @Test
+ void testGetAllProducts() {
+ List allProducts = productService.getAllProducts();
+ assertNotNull(allProducts);
+ assertEquals(5, allProducts.size());
+ }
+
+ @Test
+ void testGetAllProductsEmpty() {
+ products.clear();
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ productService.getAllProducts();
+ });
+ assertEquals("Products array does not have any data", exception.getMessage());
+ }
+
+ @Test
+ void testGetAllProductNull() {
+ productService = new ProductServiceImpl(null);
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ productService.getAllProducts();
+ });
+ assertEquals("Products array is not initialized properly", exception.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceIntegrationTest.java b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceIntegrationTest.java
new file mode 100644
index 000000000000..a9a87463b43b
--- /dev/null
+++ b/remote-procedure-call/product-service/src/test/java/com/iluwatar/rpc/product/service/ProductServiceIntegrationTest.java
@@ -0,0 +1,102 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.rpc.product.service;
+
+import com.iluwatar.rpc.product.mocks.ProductMocks;
+import com.iluwatar.rpc.product.model.Product;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc;
+import com.iluwatar.rpc.proto.ShoppingServiceGrpc.ShoppingServiceBlockingStub;
+import com.iluwatar.rpc.proto.Shopping.ProductResponse;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductRequest;
+import com.iluwatar.rpc.proto.Shopping.ReduceProductResponse;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class ProductServiceIntegrationTest {
+ private static Server server;
+ private static ManagedChannel channel;
+ private static ShoppingServiceBlockingStub blockingStub;
+
+ @BeforeAll
+ public static void setup() throws IOException {
+ List products = ProductMocks.getMockProducts();
+ var productServiceMain = new ProductServiceImpl(products);
+
+ server = ServerBuilder
+ .forPort(8080)
+ .addService(new ProductServiceGrpcImpl(productServiceMain))
+ .build()
+ .start();
+
+ channel = ManagedChannelBuilder.forAddress("localhost", 8080)
+ .usePlaintext()
+ .build();
+
+ blockingStub = ShoppingServiceGrpc.newBlockingStub(channel);
+ }
+
+ @AfterAll
+ public static void teardown() {
+ channel.shutdownNow();
+ server.shutdownNow();
+ }
+
+ @Test
+ void testReduceProductQuantity() {
+ ReduceProductRequest request = ReduceProductRequest.newBuilder()
+ .setProductId(1L)
+ .setQuantity(5)
+ .build();
+
+ ReduceProductResponse response = blockingStub.reduceProductQuantity(request);
+
+ assertTrue(response.getStatus());
+ assertEquals("Success", response.getMessage());
+ }
+
+ @Test
+ void testGetAllProducts() {
+ var responseIterator = blockingStub.getAllProducts(null);
+
+ ArrayList responses = new ArrayList<>();
+ responseIterator.forEachRemaining(responses::add);
+
+ assertEquals(ProductMocks.getMockProducts().size(), responses.size());
+ }
+}
diff --git a/remote-procedure-call/product-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/remote-procedure-call/product-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 000000000000..ca6ee9cea8ec
--- /dev/null
+++ b/remote-procedure-call/product-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/remote-procedure-call/proto/shopping.proto b/remote-procedure-call/proto/shopping.proto
new file mode 100644
index 000000000000..47515f054fda
--- /dev/null
+++ b/remote-procedure-call/proto/shopping.proto
@@ -0,0 +1,52 @@
+//
+// This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+//
+// The MIT License
+// Copyright © 2014-2022 Ilkka Seppälä
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+syntax = "proto3";
+
+package com.iluwatar.rpc.proto;
+
+service ShoppingService {
+ rpc ReduceProductQuantity (ReduceProductRequest) returns (ReduceProductResponse);
+ rpc GetAllProducts (Empty) returns (stream ProductResponse);
+}
+
+message ReduceProductRequest {
+ uint64 product_id = 1;
+ uint32 quantity = 2;
+}
+
+message ReduceProductResponse {
+ bool status = 1;
+ string message = 2;
+}
+
+message ProductResponse {
+ uint64 id = 1;
+ string name = 2;
+ string type = 3;
+ double price = 4;
+}
+
+message Empty {}