ballerina-guides/asynchronous-invocation

Name: asynchronous-invocation

Owner: ballerina-guides

Description: null

Created: 2018-04-24 10:14:15.0

Updated: 2018-05-01 07:50:47.0

Pushed: 2018-05-01 07:50:46.0

Homepage: null

Size: 108

Language: Ballerina

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status

Asynchronous Invocations

Asynchronous invocations or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the code invoked to finish. Instead, the calling thread can use the result when the reply arrives.

In this guide you will learn about building a web service with asynchronous RESTful calls.

The following are the sections available in this guide.

What you?ll build

To understand how you can use asynchronous invocations with Ballerina, let?s consider a Stock Quote Summary service.

The following figure illustrates the scenario of the Stock Quote Summary service with asynchronous invocations.

       

async invocation

       

Prerequisites
Optional requirements
Implementation

If you want to skip the basics, you can download the git repo and directly move to theTestingsection by skipping theImplementationsection.

Create the project structure

Ballerina is a complete programming language that can have any custom project structure that you require. For this example, let's use the following package structure.

chronous-invocation
??? guide
    ??? stock_quote_data_backend
    ?   ??? stock_backend.bal
    ?   ??? tests
    ?       ??? stock_backend_test.bal
    ??? stock_quote_summary_service
    ?   ??? async_service.bal
    ?   ??? tests
    ?       ??? async_service_test.bal
    ??? tests
        ??? integration_test.bal
Implement the Stock Quote Summary service with asyncronous invocations
async_service.bal
rt ballerina/http;
rt ballerina/io;
rt ballerina/runtime;

cription {value:"Attributes associated with the service endpoint are defined here."}
oint http:Listener asyncServiceEP {
port: 9090


cription {value:"This service is to be exposed via HTTP/1.1."}
p:ServiceConfig {
basePath: "/quote-summary"

ice<http:Service> AsyncInvoker bind asyncServiceEP {

@Description {value:"The resource for the GET requests of the quote service."}

@http:ResourceConfig {
    methods: ["GET"],
    path: "/"
}
getQuote(endpoint caller, http:Request req) {
    // The endpoint for the Stock Quote Backend service.
    endpoint http:Client nasdaqServiceEP {
        url: "http://localhost:9095"
    };
    http:Request req = new;
    http:Response resp = new;
    string responseStr;

    // This initializes empty json to add results from the backend call.
    json  responseJson = {};

    io:println(" >> Invoking services asynchrnounsly...");

    // 'start' allows you to invoke a functions  asynchronously. Following three
    // remote invocation returns without waiting for response.

    // This calls the backend to get the stock quote for GOOG asynchronously.
    future <http:Response|http:HttpConnectorError> f1 = start nasdaqServiceEP
    -> get("/nasdaq/quote/GOOG", request = req);

    io:println(" >> Invocation completed for GOOG stock quote! Proceed without
    blocking for a response.");
    req = new;

    // This calls the backend to get the stock quote for APPL asynchronously.
    future <http:Response|http:HttpConnectorError> f2 = start nasdaqServiceEP
    -> get("/nasdaq/quote/APPL", request = req);

    io:println(" >> Invocation completed for APPL stock quote! Proceed without
    blocking for a response.");
    req = new;

    // This calls the backend to get the stock quote for MSFT asynchronously.
    future <http:Response|http:HttpConnectorError> f3 = start nasdaqServiceEP
    -> get("/nasdaq/quote/MSFT", request = req);

    io:println(" >> Invocation completed for MSFT stock quote! Proceed without
    blocking for a response.");

    // The ?await` keyword blocks until the previously started async function returns.
    // Append the results from all the responses of the stock data backend.
    var response1 = await f1;
    // Use `match` to check whether the responses are available.
// If a response is not available, an error is generated.
    match response1 {
        http:Response resp => {

            responseStr = check resp.getStringPayload();
            // Add the response from the `/GOOG` endpoint to the `responseJson` file.

            responseJson["GOOG"] = responseStr;
        }
        error err => {
            io:println(err.message);
            responseJson["GOOG"] = err.message;
        }
    }

    var response2 = await f2;
    match response2 {
        http:Response resp => {

            responseStr = check resp.getStringPayload();
            // Add the response from `/APPL` endpoint to `responseJson` file.
            responseJson["APPL"] = responseStr;
        }
        error err => {
            io:println(err.message);
            responseJson["APPL"] = err.message;
        }
    }

    var response3 = await f3;
    match response3 {
        http:Response resp => {
            responseStr = check resp.getStringPayload();
            // Add the response from the `/MSFT` endpoint to the `responseJson` file.
            responseJson["MSFT"] = responseStr;

        }
        error err => {
            io:println(err.message);
            responseJson["MSFT"] = err.message;
        }
    }

    // Send the response back to the client.
    resp.setJsonPayload(responseJson);
    io:println(" >> Response : " + responseJson.toString());
    _ = caller->respond(resp);
}

Mock remote service: stock_quote_data_backend

You can use any third-party remote service for the remote backend service. For ease of explanation, we have developed the mock stock quote remote backend with Ballerina. This mock stock data backend has the following resources and the respective responses.

NOTE: You can find the complete implementaion of the stock_quote_data_backend here

Testing
Invoking stock quote summary service
lerina run stock_quote_summary_service/

Get stock quote summary for GOOG, APPL and MSFT

 http://localhost:9090/quote-summary

ut :  

"GOOG": "GOOG, Alphabet Inc., 1013.41",
"APPL": "APPL, Apple Inc., 165.22",
"MSFT": "MSFT, Microsoft Corporation, 95.35"

Console output for stock_quote_summary_service(with asynchronous calls)

Invoking services asynchrnounsly...
Invocation completed for GOOG stock quote! Proceed without
    blocking for a response.
Invocation completed for APPL stock quote! Proceed without
    blocking for a response.
Invocation completed for MSFT stock quote! Proceed without
    blocking for a response.
Response : {
"GOOG": "GOOG, Alphabet Inc., 1013.41",
"APPL": "APPL, Apple Inc., 165.22",
"MSFT": "MSFT, Microsoft Corporation, 95.35"

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 every all the packages inside the asynchronous-invocation/guide directory.

To check the implementation of the test file, refer tests folder in the git repository.

Deployment

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

Deploying locally
lerina build stock_quote_summary_service

lerina build stock_quote_data_backend
lerina run target/stock_quote_summary_service.balx

lerina run target/stock_quote_data_backend.balx
lerina run target/stock_quote_data_backend.balx
erina: initiating service(s) in 'stock_backend.bal'
erina: started HTTP/WS endpoint 0.0.0.0:9095
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.

async_service.bal
rt ballerina/http;
rt ballerina/io;
rt ballerina/runtime;
rt ballerinax/docker;

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


ker:Expose{}
oint http:Listener listener {
port:9090


cription { value: "Service is to be exposed via HTTP/1.1." }
p:ServiceConfig {
basePath: "/quote-summary"

ice<http:Service> AsyncInvoker bind asyncServiceEP {
Deploying on Kubernetes
order_mgt_service.bal
rt ballerina/http;
rt ballerina/io;
rt ballerina/runtime;
rt ballerinax/kubernetes;

ernetes:Ingress {
hostname:"ballerina.guides.io",
name:"ballerina-guides-asynchronous-invocation",
path:"/"


ernetes:Service {
serviceType:"NodePort",
name:"ballerina-guides-asynchronous-invocation"


ernetes:Deployment {
image:"ballerina.guides.io/asynchronous-invocation:v1.0",
name:"ballerina-guides-asynchronous-invocation"


oint http:Listener listener {
port:9090


cription { value: "Service is to be exposed via HTTP/1.1." }
p:ServiceConfig {
basePath: "/quote-summary"

ice<http:Service> AsyncInvoker bind asyncServiceEP {
 ballerina build stock_quote_summary_service

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

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

Node Port:

rl http://localhost:9090/quote-summary

Ingress:

Add /etc/hosts entry to match hostname.

0.0.1 ballerina.guides.io

Access the service

rl http://localhost:9090/quote-summary
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 asynchronous-invocation/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 alarts 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 Ballerina restful 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}/filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml \
SAMPLE_ROOT}/guide/stock_quote_summary_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.