Home

Awesome

Introduction

The <a href="https://github.com/GoogleCloudPlatform/microservices-demo" target="_blank">online boutique</a> is a cloud-native microservices demo application written by the Google cloud platform. It consists of a 10-tier microservices application. The application is a web-based e-commerce app using which users can browse items, add them to the cart, and purchase them. The microservices in the GCP demo are implemented using multiple programming languages such as Java, C#, Go, Javascript and Python.

Here, in this demo these microservices are written using Ballerina language to demonstrate the language features, showcase best practices when writing microservices, provide an in-depth understanding of how ballerina services can interact in a real-world scenario and highlight the support of Ballerina to deploy services natively in the cloud. In our implementation, communication between the microservices is handled using gRPC and the frontend is exposed via an HTTP service.

Architecture

image info

And also following is the automatically generated Ballerina design view of the demo application. image info

Microservices description

Service nameDescription
FrontendExposes an HTTP server to outside to serve data required for the React app. Acts as a frontend for all the backend microservices and abstracts the functionality.
CartServiceStores the product items added to the cart and retrieves them. In memory store and Redis is supported as storage options.
ProductCatalogServiceReads a list of products from a JSON file and provides the ability to search products and get them individually.
CurrencyServiceReads the exchange rates from a JSON and converts one currency value to another.
PaymentServiceValidates the card details (using the Luhn algorithm) against the supported card providers and returns a transaction ID. (Mock)
ShippingServiceGives the shipping cost estimates based on the shopping cart. Returns a tracking ID. (Mock)
EmailServiceSends the user an order confirmation email with the cart details using the Gmail connector. (mock).
CheckoutServiceRetrieves the user cart, prepares the order, and orchestrates the payment, shipping, and email notification.
RecommendationServiceRecommends other products based on the items added to the user’s cart.
AdServiceProvides text advertisements based on the context of the given words.

The same load generator service will be used for load testing. The original Go frontend service serves HTML directly using the HTTP server using Go template. In this sample, the backend is separated from the Ballerina HTTP service and React frontend.

Service Implementation

First, we will be covering general implementation-related stuff thats common to all the services and then we will be diving into specific implementations of each microservices.

As shown in the diagram, Frontend Service is the only service that is being exposed to the internet. All the microservices except the Frontend Service uses gRPC for service-to-service communication. You can see the following example from the Ad Service.

import ballerina/grpc;

@grpc:Descriptor {value: DEMO_DESC}
service "AdService" on new grpc:Listener(9099) {

    remote function GetAds(stubs:AdRequest request) returns stubs:AdResponse|error {
    }
}

Ballerina provides the capability to generate docker, and Kubernetes artifacts to deploy the code on the cloud with minimal configuration. To enable this you need to add the cloud="k8s" under build options into the Ballerina.toml file of the project.

[package]
org = "wso2"
name = "recommendationservice"
version = "0.1.0"

[build-options]
observabilityIncluded = true
cloud = "k8s"

Additionally, you could make a Cloud.toml file in the project directory and configure various things in the container and the deployment. For every microservice in this sample, we would be modifying the container org, name, and tag of the created kubernetes yaml. Additionally, we add the cloud.deployment.internal_domain_name property to define a name for the generated service name. This allows us to easily specify host name values for services that depend on this service. This will be explained in depth in the next section. You can find a sample from the recommendation service below. Service-specific features of the Cloud.toml will be covered in their own sections.

[container.image]
name="recommendation-service"
repository="wso2inc"
tag="v0.1.0"

[cloud.deployment]
internal_domain_name="recommendation-service"

Ballerina Language provides an in-built functionality to configure values at runtime through configurable module-level variables. This feature will be used in almost all the microservices we write in this sample. When we deploy the services in different platforms(local, docker-compose, k8s) the hostname of the service changes. Consider the following sample from the recommendation service. The recommendation service depends on the catalog service, therefore it needs to resolve the hostname of the catalog service. The value "localhost" is assigned as the default value but it will be changed depending on the value passed on to it in runtime. You can find more details about this on the configurable learn page.

configurable string catalogHost = "localhost";

@grpc:Descriptor {value: DEMO_DESC}
service "RecommendationService" on new grpc:Listener(9090) {
    private final stubs:ProductCatalogServiceClient catalogClient;

    function init() returns error? {
        self.catalogClient = check new ("http://" + catalogHost + ":9091");
    }

    remote function ListRecommendations(stubs:ListRecommendationsRequest request)
          returns stubs:ListRecommendationsResponse|error {
        ...
    }
}

You can override the value using Config.toml. Note that this "catalog-service" is the same value as cloud.deployment.internal_domain_name in the Cloud.toml of the Catalog Service.

catalogHost="catalog-service"

Then you could mount this Config.toml into Kubernetes using config maps by having the following entry in the Cloud.toml

[[cloud.config.files]]
file="./k8s/Config.toml"

Kustomize

Kustomize is a tool where you can add, remove or update Kubernetes yamls without modifying the original yaml. This tool can be used to apply more modifications to the generated yaml from code to cloud. In the kustomization.yaml in the root directory, you can find a sample kustomize definition. This simply takes all the generated yamls from each project and combines them into one. Then we need to add an environment variable to specify where the Config.toml is located for the email service. This is done by using kustomize patches. You can see the sample code below.

kustomization.yaml

resources:
  - adservice/target/kubernetes/adservice/adservice.yaml
  - cartservice/target/kubernetes/cartservice/cartservice.yaml
  - checkoutservice/target/kubernetes/checkoutservice/checkoutservice.yaml
  - currencyservice/target/kubernetes/currencyservice/currencyservice.yaml
  - emailservice/target/kubernetes/emailservice/emailservice.yaml
  - frontend/target/kubernetes/frontend/frontend.yaml
  - paymentservice/target/kubernetes/paymentservice/paymentservice.yaml
  - productcatalogservice/target/kubernetes/productcatalogservice/productcatalogservice.yaml
  - recommendationservice/target/kubernetes/recommendationservice/recommendationservice.yaml
  - shippingservice/target/kubernetes/shippingservice/shippingservice.yaml

patchesStrategicMerge:
- secret-env-patch.yaml

secret-env-patch.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: emailservice-deployment
spec:
  template:
    spec:
      containers:
        - name: emailservice-deployment
          env:
            - name: BAL_CONFIG_FILES
              value: "/home/ballerina/conf/Config.toml:"

Running the Microservices on the Cloud

Setting up Email Credentials

Docker-Compose

Then execute the build-all-docker.sh to build the Ballerina packages and Docker images, and then execute docker-compose up to run the containers.

./build-all-docker.sh
docker-compose up

For running the react application you require the following prerequisites.

Once all the prerequisites are installed. You can start the React application by executing following commands from the ui/ directory.

npm install
npm start

Kubernetes

You can use build-all-k8s.sh script to build the Kubernetes artifacts.

If you are using Minikube, you can execute the following command to configure your local environment to re-use the Docker daemon inside the Minikube instance.

build-all-k8s.sh minikube

If you are not using Minikube, you can execute the following command and push the docker images to your Docker registry manually.

build-all-k8s.sh

You can execute the following command to build the final YAML file. Kustomize is used for combining all the YAML files that have been generated into one.

kubectl kustomize ./ > final.yaml

You can deploy the artifacts into Kubernetes using the following command.

kubectl apply -f final.yaml

You can expose the frontend service via node port to access the backend services from the react app.

kubectl expose deployment frontend-deployment --type=NodePort --name=frontend-svc-local

Execute kubectl get svc and get the port of the frontend-svc-local service.

Execute kubectl port-forward svc/frontend-svc-local 27017:9098 to forward the frontend listening interface to localhost.

Change the value of the FRONTEND_SVC_URL variable in ui/src/lib/api.js to the frontend service (Example Value - http://localhost:27017')

Running the Microservices locally

bal pack
bal push --repository local
npm install
npm start

Observe services tracing information with Jaeger

After running the docker container, you can view the tracing information on Jaeger via http://localhost:16686/. You can select the service in the drop-down box as follows. image info

Then click on Find Traces and you will be able to view spans of services in the gcp-microservice. image info

For more information about observability in Ballerina, please visit Observe Ballerina Programs.

Key Highlights of Ballerina based implementation

Deviations from GCP Microservices Demo