Micro-services the easy way with Cellery and Ballerina

Anuruddha Lanka Liyanarachchi
wso2-cellery
Published in
9 min readDec 9, 2019

In this article, we will be discussing how Cellery and Ballerina can be used to deploy integration micro-services.

Cellery can be installed by following these installation steps.

In this article, Cellery 0.5.0 complete setup is used with Ballerina 1.0.3.

Let’s look at what we are going to build. We will develop 3 micro-services as below. Source code can be found at GitHub repo.

1. Geo Service — This service is a wrapper to https://ip-api.com/. It accepts an IP address and gets the following response with latitude and longitude corresponding to the IP address.

2. Restaurant Service — The service fetches data from Zomato API. This service accepts a latitude and longitude and returns a list of restaurant details nearby. This API is secured via a token.

3. Aggregator Service — This service accepts an IP address and invokes the geolocation service. Then extract the latitude and longitude from the response and invoke the restaurant service. Finally responds the aggregated minimized result to the caller.

The services are written in Ballerina Language version 1.0.3. Let’s build and wrap micro-services to cells.

Geo Service to Cell

The service is pretty straight forward. The important thing is the docker annotation that is added to the service. When the source is built a Docker image containing the source will be generated with tag: anuruddhal/geo-data:v1.

$> ballerina build geo.bal
Compiling source
geo.bal
Generating executables
geo.jar
Generating docker artifacts...
@docker - complete 2/2
Run the following command to start a Docker container:
docker run -d -p 9090:9090 anuruddhal/geo-data:v1

Now let’s wrap the Docker image in a Cell file.

We have created a cell file wrapping docker image. The cell file contains HttpApiIngress with a container port and a context to identify the API. The API is exposed locally. Therefore it is not accessible via the global gateway. Let’s build the geo_cell.bal using Cellery CLI. The image tag is given as myorg/geo-cell:v1.

$> cellery build geo_cell.bal myorg/geo-cell:v1
✔ Creating temporary executable bal file
Compiling source
geo_cell_build.bal
Generating executables
Running executables
✔ Executing ballerina build
✔ Generating metadata
✔ Creating the cell image zip file
✔ Successfully built image: myorg/geo-cell:v1What's next?
--------------------------------------------------------
Execute the following command to run the image:
$ cellery run myorg/geo-cell:v1
--------------------------------------------------------

To view the cell in the browser following command can be used.

$> cellery view myorg/geo-cell:v1

Now let’s deploy the geo-cell to the Cellery cluster. We will use the -n option to provide the instance name geocell.

$> cellery run myorg/geo-cell:v1 -n geocell
✔ Extracting cell image
Compiling source
geo_cell_run.bal
Generating executables
Running executables
Info: Main Instance: geocell
Info: Validating dependencies
Info: Instances to be Used
------------------------------------------------------------------------------------------------------------------------
INSTANCE NAME CELL IMAGE USED INSTANCE KIND SHARED
------------------------------------------------------------------------------------------------------------------------
geocell myorg/geo-cell:v1 To be Created Cell -
------------------------------------------------------------------------------------------------------------------------
Info: Dependency Tree to be Used
No Dependencies
✔ Starting main instance geocell
✔ Successfully deployed cell image: myorg/geo-cell:v1What's next?
--------------------------------------------------------
Execute the following command to list running cells:
$ cellery list instances
--------------------------------------------------------

Now the geocell is deployed let’s move into restaurant service.

Restaurant Service to Cell

This service invokes the Zomato API with a token. The token is read via the environment variable USER_KEYusing the Ballerina config API. Let’s build the restaurant.bal to generate the Docker image with tag anuruddhal/restaurant:latest.

$> ballerina build restaurant.bal
Compiling source
restaurant.bal
Generating executables
restaurant.jar
Generating docker artifacts...
@docker - complete 2/2
Run the following command to start a Docker container:
docker run -d -p 9090:9090 anuruddhal/restaurant:latest

Now let’s wrap the Docker image with service in a Cell file.

The build method is the same as the previous geo_cell.balfile. The run method contains the logic to read the USER_KEY value from the environment and pass it to the cell instance. The cell will fail if the USER_KEY value is not set in the environment. Also, the API is exposed locally.

Let’s build a restaurant cell.

$> cellery build restaurant_cell.bal myorg/restaurant-cell:v1
✔ Creating temporary executable bal file
Compiling source
restaurant_cell_build.bal
Generating executables
Running executables
Warning: Value is empty for environment variable "USER_KEY"
✔ Executing ballerina build
✔ Generating metadata
✔ Creating the cell image zip file
✔ Successfully built image: myorg/restaurant-cell:v1What's next?
--------------------------------------------------------
Execute the following command to run the image:
$ cellery run myorg/restaurant-cell:v1
--------------------------------------------------------

Let’s view the restaurant cell.

$> cellery view myorg/restaurant-cell:v1

Let’s run the restaurant cell with an instance name restaurantcell. Before running we need to expose the USER_KEY token. An API token can be obtained from Zomato API authentication. Before running the cell expose the obtained key.

export USER_KEY=<your_api_token>

$> export USER_KEY=<api_key>
$> cellery run myorg/restaurant-cell:v1 -n restaurantcell
✔ Extracting cell image
Compiling source
restaurant_cell_run.bal
Generating executables
Running executables
Info: Main Instance: restaurantcell
Info: Validating dependencies
Info: Instances to be Used
------------------------------------------------------------------------------------------------------------------------
INSTANCE NAME CELL IMAGE USED INSTANCE KIND SHARED
------------------------------------------------------------------------------------------------------------------------
restaurantcell myorg/restaurant-cell:v1 To be Created Cell -
------------------------------------------------------------------------------------------------------------------------
Info: Dependency Tree to be Used
No Dependencies
✔ Starting main instance restaurantcell
✔ Successfully deployed cell image: myorg/restaurant-cell:v1What's next?
--------------------------------------------------------
Execute the following command to list running cells:
$ cellery list instances
--------------------------------------------------------

Two cells are deployed time to build and run Aggregator service/Service cell.

Aggregator Service to Suggestion Cell

Let’s look at the aggregator service source code.

As discussed earlier, this service accepts an IP address and invokes the geolocation service. Then extract the latitude and longitude from the response and invoke the restaurant service. Finally returns the aggregated result to the caller. The JSON manipulation is done in the getSuggesstions method to generate a custom response.

The service needs the URLs of the restaurant service and geo service to invoke them. The URLs are read from the environment by the means of two environment variables GEO_URL & RESTAURANT_URL. Let’s build the aggregate.bal to generate the Docker image anuruddhal/aggergator:latest .

$> ballerina build aggregate.bal
Compiling source
aggregate.bal
Generating executables
aggregate.jar
Generating docker artifacts…
@docker — complete 2/2
Run the following command to start a Docker container:
docker run -d -p 9090:9090 anuruddhal/aggergator:latest.

Now that we have a Docker image let’s wrap it in a cell.

Let’s go through the cell file. The suggestion component has 4 main parts.

Thesrcsection, provides the Docker image information.

Theingresses section defines the API definition. The API is exposed globally with a context and the port. Since aggregator service is accepting POST requests, definitions are added with aPOST resource.

The suggestion cell depends on two other cells. The dependent cell information is declared in the dependencies section with an alias.

The aggregator/suggestion service expects two environment variables. The environment variables are defined in the env section. The values will be populated later.

The cellery:getReference method is used to resolve the cell URLs at the runtime. This method accepts two parameters, component and the dependency alias. The dependency alias is the key to the dependency declared at dependencies section. In our case, it is geoCell and restaurantCell .

.....
dependencies: {
cells: { geoCell: {org: "myorg", name: "geo-cell", ver: "v1"}, restaurantCell:{org: "myorg", name: "restaurant-cell", ver: "v1"} }}
........

The cellery:getReferencemethod returns an open Ballerina record.

In order to determine the record key, the following Cellery CLI command can be used.

$> cellery list ingresses myorg/geo-cell:v1
COMPONENT INGRESS TYPE INGRESS CONTEXT INGRESS VERSION INGRESS PORT RESOURCE METHOD GLOBALLY EXPOSED VHOST INGRESS KEY
----------- -------------- ----------------- ----------------- -------------- ---------- -------- ------------------ ------- --------------------
geo Http geo 0.1 9090 N/A N/A local N/A geo_geoAPI_api_url
$> cellery list ingresses myorg/restaurant-cell:v1
COMPONENT INGRESS TYPE INGRESS CONTEXT INGRESS VERSION INGRESS PORT RESOURCE METHOD GLOBALLY EXPOSED VHOST INGRESS KEY
------------ -------------- ----------------- ----------------- -------------- ---------- -------- ------------------ ------- ----------------------------------
restaurant Http details 0.1 9090 N/A N/A local N/A restaurant_restaurantAPI_api_url

The ingress keys are generated as geo_geoAPI_api_url, restaurant_restaurantAPI_api_url thus, the following code set the values for environment variables GEO_URL & RESTAURANT_URL .

suggestionComp["envVars"]["GEO_URL"].value= <string>cellery:getReference(suggestionComp,"geoCell").get("geo_geoAPI_api_url");suggestionComp["envVars"]["RESTAURANT_URL"].value=<string>cellery:getReference(suggestionComp,"restaurantCell").get("restaurant_restaurantAPI_api_url");

Now let’s build the suggestion cell.

$> cellery build suggestion_cell.bal myorg/suggestion:v1
✔ Creating temporary executable bal file
Compiling source
suggestion_cell_build.bal
Generating executables
Running executables
✔ Executing ballerina build
✔ Generating metadata
✔ Creating the cell image zip file
✔ Successfully built image: myorg/suggestion:v1What's next?
--------------------------------------------------------
Execute the following command to run the image:
$ cellery run myorg/suggestion:v1
--------------------------------------------------------

Let’s view the cell. The cell will contain all the details about dependent cells as well.

Let’s run the cell. Since, the two dependect cells are running already we have to wire them to suggestion cell. Again, we will use the dependency alias to wire the running cells. The -l option is used to do this wiring. The format is like the following.

-l <dependencyAlias>:<instanceName>

$> cellery run myorg/suggestion:v1 -n suggestioncell -l geoCell:geocell -l restaurantCell:restaurantcell
✔ Extracting cell image
Compiling source
suggestion_cell_run.bal
Generating executables
Running executables
Info: Main Instance: suggestioncell
Info: Validating dependencies
Info: Instances to be Used
------------------------------------------------------------------------------------------------------------------------
INSTANCE NAME CELL IMAGE USED INSTANCE KIND SHARED
------------------------------------------------------------------------------------------------------------------------
geocell myorg/geo-cell:v1 Available in Runtime Cell -
restaurantcell myorg/restaurant-cell:v1 Available in Runtime Cell -
suggestioncell myorg/suggestion:v1 To be Created Cell -
------------------------------------------------------------------------------------------------------------------------
Info: Dependency Tree to be Used
suggestioncell
├── geoCell:geocell
└── restaurantCell:restaurantcell
✔ Starting main instance suggestioncell✔ Successfully deployed cell image: myorg/suggestion:v1What's next?
--------------------------------------------------------
Execute the following command to list running cells:
$ cellery list instances
--------------------------------------------------------

If the log is observed, we can see that the CLI has detected that the geocell instance and restaurant cell instance is already available in the runtime.

All the cells are deployed let’s access the suggestion service. (This is the only cell we exposed globally).

Invoking the Suggestion Service

Access the wso2 API manger store using URL https://wso2-apim/store. Sign in to the Store with the default username and password.

username:admin 
password:admin

The suggestion API will be published and available. Click on the API and click the subscribe button.

Go to application->default application->production keys and press Generate keys button.

At the bottom of the page under the heading Generate a Test Access Token . There will be an access token. Copy the access token.

Use the following curl command with the generated access token to invoke the suggestion API.

curl -v -X POST -d  203.94.95.4 -k -H "Authorization: Bearer <AccessToken>" https://wso2-apim-gateway/suggestioncell/suggest/0.1/suggest/restaurant/

The sample output will be as below. You can change the IP address 203.94.95.4 to get different suggestions.

{
"location": "Colombo Sri Lanka",
"restaurents": [
{
"name": "Ministry of Crab",
"address": "Old Colombo Dutch Hospital, Fort, Colombo 01",
"rating": "Excellent",
"average_cost_for_two": "7500LKR"
},
{
"name": "Noodles - Cinnamon Grand",
"address": "Cinnamon Grand, Kollupitiya, Colombo 03",
"rating": "Excellent",
"average_cost_for_two": "4000LKR"
},
{
"name": "Sky Lounge - The Kingsbury",
"address": "The Kingsbury, 48, Janadhipathi Mawatha, Fort, Colombo 01",
"rating": "Very Good",
"average_cost_for_two": "4000LKR"
},
{
"name": "The Bavarian",
"address": "11, Galle Face Court, Galle Road, Kollupitiya, Colombo 03",
"rating": "Excellent",
"average_cost_for_two": "4000LKR"
},
{
"name": "Coffee Stop - Cinnamon Grand",
"address": "Cinnamon Grand, Kollupitiya, Colombo 03",
"rating": "Excellent",
"average_cost_for_two": "1500LKR"
},
{
"name": "Taprobane - Cinnamon Grand",
"address": "Cinnamon Grand, Kollupitiya, Colombo 03",
"rating": "Excellent",
"average_cost_for_two": "5000LKR"
},
{
"name": "T.G.I. Friday's",
"address": "23, Canal Row, Fort, Colombo 01",
"rating": "Very Good",
"average_cost_for_two": "4000LKR"
},
{
"name": "Sugar Bistro and Wine Bar",
"address": "Crescat Boulevard, Lobby Level, Kollupitiya, Colombo 03",
"rating": "Very Good",
"average_cost_for_two": "3500LKR"
},
{
"name": "&Co Pub and Kitchen- The Steuart",
"address": "45 Janadhipathi Mawatha, Fort, Colombo 01",
"rating": "Very Good",
"average_cost_for_two": "3000LKR"
}
]
}

Pretty cool huh? 🆒

If you are feeling curious cellery dashboard can be accessed via URL http://cellery-dashboard/.

The dashboard has a lot of features in observing deployed cells.

Conclusion

  • It is very easy to develop Ballerina based micro-services due to in-built network data structure support and Docker annotations.
  • Cellery is a powerful code-first approach to build, run, observe and manage code-first composites on Kubernetes.

References

  1. https://developers.zomato.com/
  2. https://ip-api.com/
  3. https://ballerina.io/learn/by-example/
  4. https://cellery.io/

--

--