ballerina-guides/restful-service

Name: restful-service

Owner: ballerina-guides

Description: null

Created: 2018-02-12 14:38:44.0

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

Pushed: 2018-04-30 14:39:14.0

Homepage: null

Size: 857

Language: Ballerina

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status

RESTful Service

REST (REpresentational State Transfer) is an architectural style for developing web services. It defines a set of constraints and properties based on HTTP.

In this guide you will learn about building a comprehensive RESTful Web Service using Ballerina.

The following are the sections available in this guide.

What you?ll build

To understanding how you can build a RESTful web service using Ballerina, let?s consider a real world use case of an order management scenario of an online retail application. We can model the order management scenario as a RESTful web service; 'order_mgt_service', which accepts different HTTP request for order management tasks such as order creation, retrieval, updating and deletion. The following figure illustrates all the required functionalities of the OrderMgt RESTful web service that we need to build.

RESTful 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.

ful-service
 guide
  ??? restful_service
       ??? order_mgt_service.bal
   ??? tests
        ??? order_mgt_service_test.bal
Developing the RESTful web service
Skeleton code for order_mgt_service.bal
rt ballerina/http;

oint http:Listener listener {
port:9090


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

ESTful service.
p:ServiceConfig { basePath: "/ordermgt" }
ice<http:Service> orderMgt bind listener {

// Resource that handles the HTTP GET requests that are directed to a specific
// order using path '/orders/<orderID>
@http:ResourceConfig {
    methods: ["GET"],
    path: "/order/{orderId}"
}
findOrder(endpoint client, http:Request req, string orderId) {
    // Implementation
}

// Resource that handles the HTTP POST requests that are directed to the path
// '/orders' to create a new Order.
@http:ResourceConfig {
    methods: ["POST"],
    path: "/order"
}
addOrder(endpoint client, http:Request req) {
    // Implementation
}

// Resource that handles the HTTP PUT requests that are directed to the path
// '/orders' to update an existing Order.
@http:ResourceConfig {
    methods: ["PUT"],
    path: "/order/{orderId}"
}
updateOrder(endpoint client, http:Request req, string orderId) {
    // Implementation
}

// Resource that handles the HTTP DELETE requests, which are directed to the path
// '/orders/<orderId>' to delete an existing Order.
@http:ResourceConfig {
    methods: ["DELETE"],
    path: "/order/{orderId}"
}
cancelOrder(endpoint client, http:Request req, string orderId) {
    // Implementation
}

order_mgt_service.bal
rt ballerina/http;

oint http:Listener listener {
port:9090


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

ESTful service.
p:ServiceConfig { basePath: "/ordermgt" }
ice<http:Service> orderMgt bind listener {

// Resource that handles the HTTP GET requests that are directed to a specific
// order using path '/orders/<orderID>
@http:ResourceConfig {
    methods: ["GET"],
    path: "/order/{orderId}"
}
findOrder(endpoint client, http:Request req, string orderId) {
    // Find the requested order from the map and retrieve it in JSON format.
    json? payload = ordersMap[orderId];
    http:Response response;
    if (payload == null) {
        payload = "Order : " + orderId + " cannot be found.";
    }

    // Set the JSON payload in the outgoing response message.
    response.setJsonPayload(payload);

    // Send response to the client.
    _ = client->respond(response);
}

// Resource that handles the HTTP POST requests that are directed to the path
// '/orders' to create a new Order.
@http:ResourceConfig {
    methods: ["POST"],
    path: "/order"
}
addOrder(endpoint client, http:Request req) {
    json orderReq = check req.getJsonPayload();
    string orderId = orderReq.Order.ID.toString();
    ordersMap[orderId] = orderReq;

    // Create response message.
    json payload = { status: "Order Created.", orderId: orderId };
    http:Response response;
    response.setJsonPayload(payload);

    // Set 201 Created status code in the response message.
    response.statusCode = 201;
    // Set 'Location' header in the response message.
    // This can be used by the client to locate the newly added order.
    response.setHeader("Location", "http://localhost:9090/ordermgt/order/" +
            orderId);

    // Send response to the client.
    _ = client->respond(response);
}

// Resource that handles the HTTP PUT requests that are directed to the path
// '/orders' to update an existing Order.
@http:ResourceConfig {
    methods: ["PUT"],
    path: "/order/{orderId}"
}
updateOrder(endpoint client, http:Request req, string orderId) {
    json updatedOrder = check req.getJsonPayload();

    // Find the order that needs to be updated and retrieve it in JSON format.
    json existingOrder = ordersMap[orderId];

    // Updating existing order with the attributes of the updated order.
    if (existingOrder != null) {
        existingOrder.Order.Name = updatedOrder.Order.Name;
        existingOrder.Order.Description = updatedOrder.Order.Description;
        ordersMap[orderId] = existingOrder;
    } else {
        existingOrder = "Order : " + orderId + " cannot be found.";
    }

    http:Response response;
    // Set the JSON payload to the outgoing response message to the client.
    response.setJsonPayload(existingOrder);
    // Send response to the client.
    _ = client->respond(response);
}

// Resource that handles the HTTP DELETE requests, which are directed to the path
// '/orders/<orderId>' to delete an existing Order.
@http:ResourceConfig {
    methods: ["DELETE"],
    path: "/order/{orderId}"
}
cancelOrder(endpoint client, http:Request req, string orderId) {
    http:Response response;
    // Remove the requested order from the map.
    _ = ordersMap.remove(orderId);

    json payload = "Order : " + orderId + " removed.";
    // Set a generated payload with order status.
    response.setJsonPayload(payload);

    // Send response to the client.
    _ = client->respond(response);
}

Testing
Invoking the RESTful service

You can run the RESTful service that you developed above, in your local environment. Open your terminal and navigate to restful-service/guide, and execute the following command.

llerina run restful_service

You can test the functionality of the OrderMgt RESTFul service by sending HTTP request for each order management operation. For example, we have used the curl commands to test each operation of order_mgt_service as follows.

Create Order

 -v -X POST -d \
Order": { "ID": "100500", "Name": "XYZ", "Description": "Sample order."}}' \
p://localhost:9090/ordermgt/order" -H "Content-Type:application/json"

ut :  
TP/1.1 201 Created
ntent-Type: application/json
cation: http://localhost:9090/ordermgt/order/100500
ansfer-Encoding: chunked
rver: wso2-http-transport

atus":"Order Created.","orderId":"100500"} 

Retrieve Order

 "http://localhost:9090/ordermgt/order/100500" 

ut : 
der":{"ID":"100500","Name":"XYZ","Description":"Sample order."}}

Update Order

 -X PUT -d '{ "Order": {"Name": "XYZ", "Description": "Updated order."}}' \
p://localhost:9090/ordermgt/order/100500" -H "Content-Type:application/json"

ut: 
der":{"ID":"100500","Name":"XYZ","Description":"Updated order."}}

Cancel Order

 -X DELETE "http://localhost:9090/ordermgt/order/100500"

ut:
er : 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 resource available in the 'order_mgt_service' implemented above.

To run the unit tests, open your terminal and navigate to restful-service/guide, and run the following command.

 ballerina test

To check the implementation of the test file, refer to the order_mgt_service_test.bal.

Deployment

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

Deploying locally
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/http;
rt ballerinax/docker;

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


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


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

ESTful service.
p:ServiceConfig { basePath: "/ordermgt" }
ice<http:Service> orderMgt bind listener {
Deploying on Kubernetes
order_mgt_service.bal
rt ballerina/http;
rt ballerinax/kubernetes;

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


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


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


oint http:Listener listener {
port:9090


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

ESTful service.
p:ServiceConfig { basePath: "/ordermgt" }
ice<http:Service> orderMgt bind listener {
 ballerina build restful_service

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

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

Node Port:

url -v -X POST -d \
{ "Order": { "ID": "100500", "Name": "XYZ", "Description": "Sample order."}}' \
http://localhost:<Node_Port>/ordermgt/order" -H "Content-Type:application/json"  

Ingress:

Add /etc/hosts entry to match hostname.

27.0.0.1 ballerina.guides.io

Access the service

url -v -X POST -d \
{ "Order": { "ID": "100500", "Name": "XYZ", "Description": "Sample order."}}' \
http://ballerina.guides.io/ordermgt/order" -H "Content-Type:application/json" 
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 restful-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 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}/filbeat/filebeat.yml:/usr/share/filebeat/filebeat.yml \
SAMPLE_ROOT}/guide/restful_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.