Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds remote-procedure-call via gRPC #3189

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
<module>money</module>
<module>table-inheritance</module>
<module>bloc</module>
<module>remote-procedure-call</module>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested: microservices-rpc

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for the folder name as well)

</modules>
<repositories>
<repository>
Expand Down
147 changes: 147 additions & 0 deletions remote-procedure-call/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README.md needs to follow the specified format. See https://github.com/iluwatar/java-design-patterns/wiki/01.-How-to-contribute

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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions remote-procedure-call/cart-service/etc/cart-service.urm.puml
Original file line number Diff line number Diff line change
@@ -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<ProductInCart>
- 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<ProductResponse>)
- reduceProductQuantity(request: ReduceProductRequest, responseStreamObserver : StreamObserver<ReduceProductResponse>)
}

}
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
42 changes: 42 additions & 0 deletions remote-procedure-call/cart-service/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

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.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<artifactId>remote-procedure-call</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>cart-service</artifactId>
<packaging>jar</packaging>

</project>
Original file line number Diff line number Diff line change
@@ -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
*/
Comment on lines +36 to +42
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, above the class, please explain the pattern briefly and describe how the example implements it

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");
Comment on lines +54 to +72
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comments. Remember, this is material for studying the design pattern.

}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading