ballerina-guides/grpc-service

Name: grpc-service

Owner: ballerina-guides

Description: null

Created: 2018-04-19 03:10:02.0

Updated: 2018-04-30 13:55:25.0

Pushed: 2018-04-30 13:55:23.0

Homepage: null

Size: 338

Language: Ballerina

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status

gRPC Service

gRPC is a modern, open source remote procedure call (RPC) framework that is widely used in distributed computing. It enables client and server applications to communicate transparently. In gRPC, a client application can directly call methods of a server application that is on a different machine as if it was a local object. On the server side, the server implements and runs a gRPC server to handle client calls. On the client side, the client has a stub that provides the same methods as the server.

In this guide, you learn to build a comprehensive gRPC service using Ballerina.

This guide contains the following sections.

What you?ll build

You will build a real-world use case of an order management scenario in an online retail application. The order management scenario is modeled as a gRPC service; order_mgt_service, which accepts different proto requests as order management tasks, such as creating, retrieving, updating, and deleting orders. The following figure illustrates all the functionalities of the order_mgt gRPC service that we need to build.

gRPC Service

Prerequisites
Optional requirements
Implementation

If you want to skip the basics, you can download the git repo and directly move to the “Testing” section by skipping “Implementation” section.

Create the project structure

Ballerina is a complete programming language that supports custom project structures. Use the following package structure for this guide.

-service
? guide
   ??? grpc_service     
    ??? order_mgt_service.bal
    ??? tests
         ??? orderMgt_pb.bal
         ??? order_mgt_service_test.bal
Developing the gRPC service

Let's get started with the implementation of the order_mgt_service, which is a gRPC service that handles order management. This service can have dedicated procedures for each order management functionality.

The implementation of this gRPC service is shown below.

order_mgt_service.bal
rt ballerina/grpc;

RPC service endpoint definition.
oint grpc:Listener listener {
host:"localhost",
port:9090


rder management is done using an in memory map.
dd sample orders to the 'orderMap' at startup.
orderInfo> ordersMap;

ype definition for an order.
 orderInfo {
string id;
string name;
string description;


RPC service.
c:ServiceConfig
ice orderMgt bind listener {

// gRPC method to find an order.
findOrder(endpoint caller, string orderId) {
    string payload;
    // Find the requested order from the map.
    if (ordersMap.hasKey(orderId)) {
        json orderDetails = check <json>ordersMap[orderId];
        payload = orderDetails.toString();
    } else {
        payload = "Order : '" + orderId + "' cannot be found.";
    }

    // Send response to the caller.
    _ = caller->send(payload);
    _ = caller->complete();
}

// gRPC method to create a new Order.
addOrder(endpoint caller, orderInfo orderReq) {
    // Add the new order to the map.
    string orderId = orderReq.id;
    ordersMap[orderReq.id] = orderReq;
    // Create a response message.
    string payload = "Status : Order created; OrderID : " + orderId;

    // Send a response to the caller.
    _ = caller->send(payload);
    _ = caller->complete();
}

// gRPC method to update an existing Order.
updateOrder(endpoint caller, orderInfo updatedOrder) {
    string payload;
    // Find the order that needs to be updated.
    string orderId = updatedOrder.id;
    if (ordersMap.hasKey(orderId)) {
        // Update the existing order.
        ordersMap[orderId] = updatedOrder;
        payload = "Order : '" + orderId + "' updated.";
    } else {
        payload = "Order : '" + orderId + "' cannot be found.";
    }

    // Send a response to the caller.
    _ = caller->send(payload);
    _ = caller->complete();
}

// gRPC method to delete an existing Order.
cancelOrder(endpoint caller, string orderId) {
    string payload;
    if (ordersMap.hasKey(orderId)) {
        // Remove the requested order from the map.
        _ = ordersMap.remove(orderId);
        payload = "Order : '" + orderId + "' removed.";
    } else {
        payload = "Order : '" + orderId + "' cannot be found.";
    }

    // Send a response to the caller.
    _ = caller->send(payload);
    _ = caller->complete();
}

You can implement the business logic of each resource as per your requirements. For simplicity, we use an in-memory map to record all the order details. As shown in the above code, to create a gRPC service you need to import the ballerina/grpc and define a grpc:Listener endpoint.

Implement a gRPC client

You can also write a gRPC client in Ballerina to consume the methods implemented in the gRPC service. You can use the protobuf tool to automatically generate a client template and the client stub.

 ballerina build grpc_service/
orderMgt_sample_client.bal
rt ballerina/log;
rt ballerina/grpc;

his is client implementation for unary blocking scenario
tion main(string... args) {
// Client endpoint configuration
endpoint orderMgtBlockingClient orderMgtBlockingEp {
    url:"http://localhost:9090"
};

// Create an order
log:printInfo("-----------------------Create a new order-----------------------");
orderInfo orderReq = {id:"100500", name:"XYZ", description:"Sample order."};
var addResponse = orderMgtBlockingEp->addOrder(orderReq);
match addResponse {
    (string, grpc:Headers) payload => {
        string result;
        grpc:Headers resHeaders;
        (result, resHeaders) = payload;
        log:printInfo("Response - " + result + "\n");
    }
    error err => {
        log:printError("Error from Connector: " + err.message + "\n");
    }
}

// Update an order
log:printInfo("--------------------Update an existing order--------------------");
orderInfo updateReq = {id:"100500", name:"XYZ", description:"Updated."};
var updateResponse = orderMgtBlockingEp->updateOrder(updateReq);
match updateResponse {
    (string, grpc:Headers) payload => {
        string result;
        grpc:Headers resHeaders;
        (result, resHeaders) = payload;
        log:printInfo("Response - " + result + "\n");
    }
    error err => {
        log:printError("Error from Connector: " + err.message + "\n");
    }
}

// Find an order
log:printInfo("---------------------Find an existing order---------------------");
var findResponse = orderMgtBlockingEp->findOrder("100500");
match findResponse {
    (string, grpc:Headers) payload => {
        string result;
        grpc:Headers resHeaders;
        (result, resHeaders) = payload;
        log:printInfo("Response - " + result + "\n");
    }
    error err => {
        log:printError("Error from Connector: " + err.message + "\n");
    }
}

// Cancel an order
log:printInfo("-------------------------Cancel an order------------------------");
var cancelResponse = orderMgtBlockingEp->cancelOrder("100500");
match cancelResponse {
    (string, grpc:Headers) payload => {
        string result;
        grpc:Headers resHeaders;
        (result, resHeaders) = payload;
        log:printInfo("Response - " + result + "\n");
    }
    error err => {
        log:printError("Error from Connector: " + err.message + "\n");
    }
}

Testing
Invoking the gRPC service

You can run the gRPC service in your local environment. Open your terminal, navigate to grpc-service/guide and execute the following command.

llerina run grpc_service

Test the functionality of the 'orderMgt' gRPC service by running the gRPC client application that was implemented above. Use the command given below.

llerina run grpc_client

You will see log statements similar to what is printed below on your terminal as the response.

  [grpc_client] - -----------------------Create a new order----------------------- 
  [grpc_client] - Response - Status : Order created; OrderID : 100500

  [grpc_client] - --------------------Update an existing order-------------------- 
  [grpc_client] - Response - Order : '100500' updated.

  [grpc_client] - ---------------------Find an existing order--------------------- 
  [grpc_client] - Response - {"id":"100500","name":"XYZ","description":"Updated."}

  [grpc_client] - -------------------------Cancel an order------------------------ 
  [grpc_client] - Response - Order : '100500' removed.
Writing unit tests

In Ballerina, the unit test cases should be in the same package inside a folder named as 'tests'. When writing the test functions the below convention should be followed.

This guide contains unit test cases for each method available in the 'order_mgt_service'. The 'tests' folder also contains a copy of the client stub file, which was generated using the protobuf tool. Note that without this file you cannot run the tests in this guide.

To run the unit tests, navigate to grpc-service/guide and run the following command.

ballerina test grpc_service

To check the implementation of the test file, see order_mgt_service_test.bal.

Deployment

Once you are done with the development, you can deploy the gRPC service using any of the methods that we listed below.

Deploying locally
 ballerina build grpc_service
 ballerina run target/grpc_service.balx
Deploying on Docker

You can run the service that we developed above as a docker container. As Ballerina platform includes Ballerina_Docker_Extension, which offers native support for running ballerina programs on containers, you just need to put the corresponding docker annotations on your service code.

order_mgt_service.bal
rt ballerina/grpc;
rt ballerinax/docker;

ker:Config {
registry:"ballerina.guides.io",
name:"grpc_service",
tag:"v1.0"


ker:Expose{}
oint grpc:Listener listener {
host:"localhost",
port:9090


orderInfo> ordersMap;

 orderInfo {
string id;
string name;
string description;


RPC service.
c:ServiceConfig
ice orderMgt bind listener {
Deploying on Kubernetes
order_mgt_service.bal
rt ballerina/grpc;
rt ballerinax/kubernetes;

ernetes:Ingress {
hostname:"ballerina.guides.io",
name:"ballerina-guides-grpc-service",
path:"/"


ernetes:Service {
serviceType:"NodePort",
name:"ballerina-guides-grpc-service"


ernetes:Deployment {
image:"ballerina.guides.io/grpc_service:v1.0",
name:"ballerina-guides-grpc-service"


oint grpc:Listener listener {
host:"localhost",
port:9090


orderInfo> ordersMap;

 orderInfo {
string id;
string name;
string description;


RPC service.
c:ServiceConfig
ice orderMgt bind listener {
 ballerina build grpc_service

un following command to deploy kubernetes artifacts:  
ubectl apply -f ./target/grpc_service/kubernetes
 kubectl apply -f ./target/grpc_service/kubernetes 

eployment.extensions "ballerina-guides-grpc-service" created
ngress.extensions "ballerina-guides-grpc-service" created
ervice "ballerina-guides-grpc-service" created
 kubectl get service
 kubectl get deploy
 kubectl get pods
 kubectl get ingress

Node Port:

First, change the value of the url field of gRPC client endpoint to http://localhost:<Node_Port> in the orderMgt_sample_client.bal file, and then run it using the following command.

 ballerina run grpc_client     

Ingress:

Add /etc/hosts entry to match hostname.

27.0.0.1 ballerina.guides.io

First, change the value of the url field of gRPC client endpoint to http://ballerina.guides.io in the orderMgt_sample_client.bal file, and then run it using the following command.

 ballerina run grpc_client
Observability

Ballerina is by default observable. Meaning you can easily observe your services, resources, etc. However, observability is disabled by default via configuration. Observability can be enabled by adding following configurations to ballerina.conf file in grpc-service/guide/.

.observability]

.observability.metrics]
ag to enable Metrics
led=true

.observability.tracing]
ag to enable Tracing
led=true

NOTE: The above configuration is the minimum configuration needed to enable tracing and metrics. With these configurations default values are load as the other configuration parameters of metrics and tracing.

Tracing

You can monitor ballerina services using in built tracing capabilities of Ballerina. We'll use Jaeger as the distributed tracing system. Follow the following steps to use tracing with Ballerina.

Metrics

Metrics and alerts are built-in with ballerina. We will use Prometheus as the monitoring tool. Follow the below steps to set up Prometheus and view metrics for order_mgt_service.

b7a.observability.metrics]
nabled=true
rovider="micrometer"

b7a.observability.metrics.micrometer]
egistry.name="prometheus"

b7a.observability.metrics.prometheus]
ort=9700
ostname="0.0.0.0"
escriptions=false
tep="PT1M"

NOTE: Ballerina will by default have following metrics for HTTP server connector. You can enter following expression in Prometheus UI

Logging

Ballerina has a log package for logging to the console. You can import ballerina/log package and start logging. The following section will describe how to search, analyze, and visualize logs in real time using Elastic Stack.

i) Create a file named logstash.conf with the following content

t {  
ts{ 
 port => 5044 



er {  
k{  
 match => { 
 "message" => "%{TIMESTAMP_ISO8601:date}%{SPACE}%{WORD:logLevel}%{SPACE}
 \[%{GREEDYDATA:package}\]%{SPACE}\-%{SPACE}%{GREEDYDATA:logMessage}"
 }  



ut {  
sticsearch {  
 hosts => "elasticsearch:9200"  
 index => "store"  
 document_type => "store_logs"  


ii) Save the above logstash.conf inside a directory named as {SAMPLE_ROOT}\pipeline

iii) Start the logstash container, replace the {SAMPLE_ROOT} with your directory name

cker run -h logstash --name logstash --link elasticsearch:elasticsearch \
--rm -v ~/{SAMPLE_ROOT}/pipeline:/usr/share/logstash/pipeline/ \
044:5044 docker.elastic.co/logstash/logstash:6.2.2

i) Create a file named filebeat.yml with the following content

beat.prospectors:
pe: log
ths:
- /usr/share/filebeat/ballerina.log
ut.logstash:
sts: ["logstash:5044"]  

NOTE : Modify the ownership of filebeat.yml file using $chmod go-w filebeat.yml

ii) Save the above filebeat.yml inside a directory named as {SAMPLE_ROOT}\filebeat

iii) Start the logstash container, replace the {SAMPLE_ROOT} with your directory name

cker run -v {SAMPLE_ROOT}/filbeat/filebeat.yml:/usr/share/filebeat/filebeat.yml \
SAMPLE_ROOT}/guide/grpc_service/ballerina.log:/usr/share\
ebeat/ballerina.log --link logstash:logstash docker.elastic.co/beats/filebeat:6.2.2

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.