The online boutique 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.
And also following is the automatically generated Ballerina design view of the demo application.
Service name | Description |
---|---|
Frontend | Exposes 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. |
CartService | Stores the product items added to the cart and retrieves them. In memory store and Redis is supported as storage options. |
ProductCatalogService | Reads a list of products from a JSON file and provides the ability to search products and get them individually. |
CurrencyService | Reads the exchange rates from a JSON and converts one currency value to another. |
PaymentService | Validates the card details (using the Luhn algorithm) against the supported card providers and returns a transaction ID. (Mock) |
ShippingService | Gives the shipping cost estimates based on the shopping cart. Returns a tracking ID. (Mock) |
EmailService | Sends the user an order confirmation email with the cart details using the Gmail connector. (mock). |
CheckoutService | Retrieves the user cart, prepares the order, and orchestrates the payment, shipping, and email notification. |
RecommendationService | Recommends other products based on the items added to the user’s cart. |
AdService | Provides 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.
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 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:"
A Gmail Account with access
https://support.google.com/mail/answer/56256?hl=en
New project with Gmail API
enabled on the API Console.
OAuth Credentials
User Type
as Internal
and click Create. Add an App name
, User support email
and Developer email address
click Save.OAuth 2.0 Configuration
icon in the top right corner and click on Use your own OAuth credentials
and
provide your OAuth Client ID
and OAuth Client Secret
.auth.gmail.send
).Create the GmailConfig.toml
file in emailservice/
and paste the following code after replacing the values.
[gmailConfig]
refreshToken = "<your-refresh-token>"
clientId = "<your-client-id>"
clientSecret = "<your-client-secret>"
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
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')
client_stubs
and money
modules to the local central as follows. Execute below commmands within each module.
bal pack
bal push --repository local
bal run
to start the service.ui/
directory.
npm install
npm start
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.
Then click on Find Traces
and you will be able to view spans of services in the gcp-microservice.
For more information about observability in Ballerina, please visit Observe Ballerina Programs.