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
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
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.
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.
http://xyz.retail.com/order
.The response from the service contains an HTTP 201 Created message with the location header pointing to the newly created resource http://xyz.retail.com/order/123456
.http://xyz.retail.com/order/<orderId>
http://xyz.retail.com/order/<orderId>
.If you want to skip the basics, you can download the git repo and directly move to the “Testing” section by skipping “Implementation” section.
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
Create the above directories in your local machine and also create empty .bal
files.
Then open the terminal and navigate to restful-service/guide
and run Ballerina project initializing toolkit.
llerina init
We can get started with a Ballerina service; 'order_mgt_service', which is the RESTful service that serves the order management request. order_mgt_service can have multiple resources and each resource is dedicated for a specific order management functionality.
You can add the content to your Ballerina service as shown below. In that code segment you can find the implementation of the service and resource skeletons of 'order_mgt_service'. For each order management operation, there is a dedicated resource and inside each resource we can implement the order management operation logic.
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
}
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);
}
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."
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.
@test:Config
. See the below example.t:Config
tion testResourceAddOrder() {
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.
Once you are done with the development, you can deploy the service using any of the methods that we listed below.
As the first step, you can build a Ballerina executable archive (.balx) of the service that we developed above. Navigate to restful-service/guide
and run the following command.
llerina build restful_service
Once the restful_service.balx is created inside the target folder, you can run that with the following command.
llerina run target/restful_service.balx
The successful execution of the service will show us the following output.
erina: initiating service(s) in 'target/restful_service.balx'
erina: started HTTP/WS endpoint 0.0.0.0:9090
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.
ballerinax/docker
and use the annotation @docker:Config
as shown below to enable docker image generation during the build time.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 {
@docker:Config
annotation is used to provide the basic docker image configurations for the sample. @docker:Expose {}
is used to expose the port.
Now you can build a Ballerina executable archive (.balx) of the service that we developed above, using the following command. This will also create the corresponding docker image using the docker annotations that you have configured above. Navigate to restful-service/guide
and run the following command.
llerina build restful_service
following command to start docker container:
er run -d -p 9090:9090 ballerina.guides.io/restful_service:v1.0
Once you successfully build the docker image, you can run it with the docker run
command that is shown in the previous step.
cker run -d -p 9090:9090 ballerina.guides.io/restful_service:v1.0
Here we run the docker image with flag -p <host_port>:<container_port>
so that we use the host port 9090 and the container port 9090. Therefore you can access the service through the host port.
Verify docker container is running with the use of $ docker ps
. The status of the docker container should be shown as 'Up'.
You can access the service using the same curl commands that we've used above.
-v -X POST -d \
Order": { "ID": "100500", "Name": "XYZ", "Description": "Sample order."}}' \
p://localhost:9090/ordermgt/order" -H "Content-Type:application/json"
You can run the service that we developed above, on Kubernetes. The Ballerina language offers native support for running a ballerina programs on Kubernetes, with the use of Kubernetes annotations that you can include as part of your service code. Also, it will take care of the creation of the docker images. So you don't need to explicitly create docker images prior to deploying it on Kubernetes. Refer to Ballerina_Kubernetes_Extension for more details and samples on Kubernetes deployment with Ballerina. You can also find details on using Minikube to deploy Ballerina programs.
Let's now see how we can deploy our order_mgt_service
on Kubernetes.
First we need to import ballerinax/kubernetes
and use @kubernetes
annotations as shown below to enable kubernetes deployment for the service we developed above.
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 {
Here we have used @kubernetes:Deployment
to specify the docker image name which will be created as part of building this service.
We have also specified @kubernetes:Service
so that it will create a Kubernetes service which will expose the Ballerina service that is running on a Pod.
In addition we have used @kubernetes:Ingress
which is the external interface to access your service (with path /
and host name ballerina.guides.io
)
Now you can build a Ballerina executable archive (.balx) of the service that we developed above, using the following command. This will also create the corresponding docker image and the Kubernetes artifacts using the Kubernetes annotations that you have configured above.
ballerina build restful_service
un following command to deploy kubernetes artifacts:
ubectl apply -f ./target/restful_service/kubernetes
@kubernetes:Deployment
is created, by using $ docker images
../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"
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.
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.
You can add the following configurations for tracing. Note that these configurations are optional if you already have the basic configuration in ballerina.conf
as described above.
.observability]
.observability.tracing]
led=true
="jaeger"
.observability.tracing.jaeger]
rter.hostname="localhost"
rter.port=5775
ler.param=1.0
ler.type="const"
rter.flush.interval.ms=2000
rter.log.spans=true
rter.max.buffer.spans=1000
Run Jaeger docker image using the following command
cker run -d -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 \
686:16686 p14268:14268 jaegertracing/all-in-one:latest
Navigate to restful-service/guide
and run the restful-service using following command
llerina run restful_service/
Observe the tracing using Jaeger UI using following URL
://localhost:16686
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.
ballerina.conf
as described under Observability
section.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"
Create a file prometheus.yml
inside /tmp/
location. Add the below configurations to the prometheus.yml
file.
al:
ape_interval: 15s
luation_interval: 15s
pe_configs:
ob_name: prometheus
tatic_configs:
- targets: ['172.17.0.1:9797']
NOTE : Replace 172.17.0.1
if your local docker IP differs from 172.17.0.1
Run the Prometheus docker image using the following command
cker run -p 19090:9090 -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
/prometheus
You can access Prometheus at the following URL
://localhost:19090/
NOTE: Ballerina will by default have following metrics for HTTP server connector. You can enter following expression in Prometheus UI
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.
Start the Ballerina Service with the following command from restful-service/guide
hup ballerina run restful_service/ &>> ballerina.log&
NOTE: This will write the console log to the ballerina.log
file in the restful-service/guide
directory
Start Elasticsearch using the following command
Start Elasticsearch using the following command
cker run -p 9200:9200 -p 9300:9300 -it -h elasticsearch --name \
ticsearch docker.elastic.co/elasticsearch/elasticsearch:6.2.2
NOTE: Linux users might need to run sudo sysctl -w vm.max_map_count=262144
to increase vm.max_map_count
Start Kibana plugin for data visualization with Elasticsearch
cker run -p 5601:5601 -h kibana --name kibana --link \
ticsearch:elasticsearch docker.elastic.co/kibana/kibana:6.2.2
Configure logstash to format the ballerina logs
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
://localhost:5601