Ballerina supports manipulating data of a database quite easily. There are in-built as well as external clients available for interacting with various databases.
This guide walks you through exposing data from a MongoDB Atlas cloud database hosted on Google Cloud, as a RESTFul service.
The following are the sections available in this guide.
You'll build a RESTful service that provides an API to perform keyword serching on a MongoDB Atlas cloud database cluster. The service will be depoyed on Google Kubernetes Engine(GKE) of GCP.
Ballerina Language Version |
---|
0.990.0 |
- Ballerina Distribution
- A Text Editor or an IDE
- MongoDB Atlas account - free tier would suffice
- Google Cloud Platform account
- Ballerina IDE plugins (IntelliJ IDEA, VSCode)
Ballerina is a complete programming language that can have any custom project structure that you wish. Although the language allows you to have any module structure, use the following module structure for this project to follow this guide.
ballerina-gke-deployment
└── guide
└── mongodb_atlas_data_service
└──mongodb-service.bal
-
Create the above directories in your local machine and also create empty
.bal
file. -
Then open the terminal and navigate to
ballerina-gke-deployment/guide
and run Ballerina project initializing toolkit.
$ ballerina init
MongoDB Atlas is a cloud-hosted service for provisioning, running, monitoring, and maintaining MongoDB deployments. You can easily get an MongoDB Atlas account and a free-tier MongoDB cluster created by following this tutorial from MongoDB documentation.
Once you create the cluster, you can obtain the connection URI of the cluster. There will be two types of connection strings, Short SRV connection string and Standard connection string. You can use either of them in this guide.
Once you obtain the connection string, import the data in data.txt in to the cluster using
mongoimport
utility similar to below. data.txt
contains JSON documents which contain information on a set of
books and authors of them. This will create a collection named Books
and insert the documents to that collection.
mongoimport --uri "mongodb://<user>:<password>@cluster0-shard-00-00-munbd.gcp.mongodb.net:27017,cluster0-shard-00-01-munbd.gcp.mongodb.net:27017,cluster0-shard-00-02-munbd.gcp.mongodb.net:27017/BallerinaDemoDB?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin" --collection Books --file data.json
Since we are creating a service that performs a keyword search, we need to create an index for the Books
collection.
You can do that by connecting the cluster through mongo shell similar to below,
mongo "mongodb://cluster0-shard-00-00-munbd.gcp.mongodb.net:27017,cluster0-shard-00-01-munbd.gcp.mongodb.net:27017,cluster0-shard-00-02-munbd.gcp.mongodb.net:27017/test?replicaSet=Cluster0-shard-0" --ssl --authenticationDatabase admin --username <username> --password <password>
and running the following command. This allows text search over the name and author fields of the Books
collection.
MongoDB Enterprise Cluster0-shard-0:PRIMARY> db.Books.createIndex( { name: "text", author: "text" } );
Now we are implemeting the Ballerina HTTP service that exposes the MongoDB database we just created and provides an API to perform search based on keywords.
import ballerina/http;
import ballerina/log;
import ballerinax/kubernetes;
import wso2/mongodb;
// The configuration fields can be obtained from the MongoDB Atlas URI components
mongodb:Client conn = new({
host: "cluster0-shard-00-00-munbd.gcp.mongodb.net:27017,cluster0-shard-00-01-munbd.gcp.mongodb.net:27017,cluster0-shard-00-02-munbd.gcp.mongodb.net:27017",
dbName: "BallerinaDemoDB",
username: "<db_username>",
password: "<db_password>",
options: { authSource: "admin", sslEnabled: true, retryWrites: true, replicaSet: "Cluster0-shard-0" }
});
// It is possible to use the direct connection string in the client configuration, as well
//mongodb:Client conn = new({
// dbName: "BallerinaDemoDB",
// options : { url: "mongodb://<db_username>:<db_password>@cluster0-shard-00-00-munbd.gcp.mongodb.net:27017,cluster0-shard-00-01-munbd.gcp.mongodb.net:27017,cluster0-shard-00-02-munbd.gcp.mongodb.net:27017/BallerinaDemoDB?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true"}
//});
// Also, the short SRV connection string can be used if the driver version is compatible with it
//mongodb:Client conn = new({
// dbName: "BallerinaDemoDB",
// options : { url: "mongodb+srv://testuser:test@cluster0-munbd.gcp.mongodb.net/test?retryWrites=true"}
//});
@kubernetes:Service {
name:"search-service",
serviceType:"LoadBalancer",
port:80
}
listener http:Listener httpListener = new(9090);
@kubernetes:Deployment {
enableLiveness:true,
image:"<user>/ballerina_mongodbaltas_service:latest",
push:true,
username:"<user>",
password:"<password>",
baseImage: "mongodb-ballerina:1.0",
imagePullPolicy: "Always"
}
// By default, Ballerina exposes a service via HTTP/1.1.
service searchService on httpListener {
@http:ResourceConfig {
path:"/search/{keyword}"
}
resource function search(http:Caller caller, http:Request req, string keyword) {
http:Response res = new;
json searchQuery = { "$text": { "$search": keyword } };
var jsonRet = conn->find("Books", searchQuery);
if (jsonRet is json) {
json[] dataArray = <json[]>jsonRet;
if (dataArray.length() == 0) {
res.setPayload({ "Status": "No data found for the keyword: `" + untaint keyword + "`"});
} else {
res.setPayload({ "Status": "Data found", "Results": jsonRet });
}
} else {
log:printError("Error", err = jsonRet);
res.statusCode = 500;
res.setPayload({ "Status": "Error occured during the search" });
}
// Send the response back to the caller.
var result = caller->respond(res);
if (result is error) {
log:printError("Error sending response", err = result);
}
}
}
We will be building a Docker image here and publishing it to Docker Hub. This is required, since we cannot simply have the Docker image in the local registry, and run the Kubernetes applicates in GKE, where it needs to have access to the Docker image in a globally accessible location. For this, an image name should be given in the format $username/$image_name in the "image" property, and "username" and "password" properties needs to contain the Docker Hub account username and password respectively. The property "push" is set to "true" to signal the build process to push the build Docker image to Docker Hub.
You can build the Ballerina service using $ ballerina build hello_world_service.bal
. You should be able to see the following output.
ballerina build mongodb_atlas_data_service
Compiling source
manurip/mongodb_atlas_data_service:0.0.1
Running tests
manurip/mongodb_atlas_data_service:0.0.1
No tests found
Generating executable
./target/mongodb_atlas_data_service.balx
@kubernetes:Service - complete 1/1
@kubernetes:Deployment - complete 1/1
@kubernetes:Docker - complete 3/3
@kubernetes:Helm - complete 1/1
Run the following command to deploy the Kubernetes artifacts:
kubectl apply -f /home/manurip/Documents/Work/Repositories/mongodb-atlas-data-service/guide/target/kubernetes/mongodb_atlas_data_service
Run the following command to install the application using Helm:
helm install --name mongodb-atlas-data-service-deployment /home/manurip/Documents/Work/Repositories/mongodb-atlas-data-service/guide/target/kubernetes/mongodb_atlas_data_service/mongodb-atlas-data-service-deployment
After the build is complete, the Docker image is created and pushed to Docker Hub. The Kubernetes deployment artifacts are generated as well.
- Configuring GKE environment
Before deploying the service on GKE, you will need to setup the GKE environment to create the Kubernetes cluster and deploy an application.
Let's start by installing the Google Cloud SDK in our local machine. Please refer to Google Cloud SDK Installation in finding the steps for the installation.
Next step is gcloud configuration and creating a Google Cloud Platform project.
You can begin with gcloud init
command. Detailed information on initialization and creating a GCP project can be found on this<TODO: add link!!!> guide.
Create a project named "BallerinaDemo".
With the following command you can list the projects.
$ gcloud projects list
PROJECT_ID NAME PROJECT_NUMBER
ballerinademo-225007 BallerinaDemo 1036334079773
- Create the Kubernetes cluster
Next step is creating a kubernetes cluster in the project we just created. With a command similar to below, you can create a cluster with minimal resources.
gcloud container clusters create ballerina_demo_cluster --zone us-central1 --machine-type g1-small --disk-size 30GB --max-nodes-per-pool 1
With the following command you can verify the cluster is running.
$ gcloud container clusters list
ballerina-demo-cluster us-central1 1.9.7-gke.11 35.239.235.173 g1-small 1.9.7-gke.11 3 RUNNING
Also, with `kubectl get nodes` commands you can verify the connection to the cluster. In next steps we'll be using
`kubectl` in order to create our kubernetes deployment.
Please note that when you create a cluster using gcloud container clusters create, an entry is automatically added to the kubeconfig in your environment, and the current context changes to that cluster. Therefore, we don't have to do any manual
configuration to make it possible for `kubectl` to talk to the cluster.
```bash
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-ballerina-demo-clust-default-pool-70ca2fd4-jv97 Ready <none> 4h v1.9.7-gke.11
gke-ballerina-demo-clust-default-pool-77c556be-x42n Ready <none> 4h v1.9.7-gke.11
gke-ballerina-demo-clust-default-pool-8a9f3889-l6ks Ready <none> 4h v1.9.7-gke.11
- Deploying the Ballerina service in GKE
Since the Kubernetes artifacts were automatically built in the earlier Ballerina application build, we simply have to run the following command to deploy the Ballerina service in GKE:
kubectl apply -f /home/manurip/Documents/Work/Repositories/mongodb-atlas-data-service/guide/target/kubernetes/mongodb_atlas_data_service
service "search-service" created
deployment.extensions "mongodb-atlas-data-service-deployment" created
When you list the pods in Kubernetes, it shows that the current application was deployed successfully.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mongodb-atlas-data-service-deployment-6c696bcf9f-9xgs6 1/1 Running 0 28m
After verifying that the pod is alive, we can list the services to see the status of the Kubernetes service created to represent our Ballerina service:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.19.240.1 <none> 443/TCP 9h
search-service LoadBalancer 10.19.252.7 35.239.75.169 80:32014/TCP 29m
You've just deployed your first Ballerina service in GKE!. You can test out the service using a web browser with the URL http://$EXTERNAL-IP/searchService/search/$keyword, or by running the following cURL command: Here we are searching for results which contain the keyword "Engineering".
$ curl http://$EXTERNAL-IP/searchService/search/Engineering
{"Status":"Data found", "Results":[{"_id":1, "name":"Engineering Mathematics", "author":"H K Daas"}, {"_id":10, "name":"Higher engineering mathematics", "author":"J. Bird"}]}