Part 3: Creating BFF microservice architecture using Kubernetes and NodeJs

vansh Kapoorvansh Kapoor
6 min read

We are done with our Product and Order Svc and now its time to create our star of the show, The 180 lbs Heavy Weight Champion BFF Svc.

You can clone the code for this blog from here https://github.com/vanshkapoor/Bff-Microservice

For the folks learning, they can try creating user svc by themselves. The deployments would be very similar to Order and Product Svc.

Development Time

Am I The Only One Around Here Meme - Imgflip

BFF Svc will not be having any datastore as it will act as an API Gate way for all the other services.
That means, we will be calling all the routes of other services inside our BFF.

For this, our folder structure will be in this way

Our BFF will be making REST API calls to our other services. This way we can have an interservice communication and the calls will be fast since our services will be residing within the same cluster.

Our Service Directory having service for each microservice will not have any logic. This is like our Repo Layer which will be only used to have CRUD Operations.

💡
We will keep our business logic in the Route Layer. Ideally you should have 3 Layers: Route, Service and Repo Layer, for keeping the blog short and easy to understand I went with just 2 Layers.

Let's see our Product service

const { default: axios } = require("axios");
const { ProductSvcBaseUrl } = require("../config/apiconfig");

class ProductSvc {
    constructor(){
        this.url = ProductSvcBaseUrl; // Attached our product svc to url
        console.log(this.url);
    }

    async getAll(){
        console.log("URL: ", this.url);
        const response = await axios.get(`${this.url}/`); // Rest Call
        return response.data;
    }

    async findOne(id){
        const response = await axios.get(`${this.url}/${id}`);
        return response.data;
    }

    async createProduct(ProductName, ProductQuantity){
        const response = await axios.post(`${this.url}/`, {
            ProductName, ProductQuantity
        });
        return response.data;
    }

// This route is for updating our product store quantity when order is placed
    async updateQuantity(ProductId, ProductQuantity){
        try {
            await axios.post(`${this.url}/${ProductId}/quantity`, {
                ProductQuantity
            });
            return;
        } catch(e) {
            throw new Error("Product quantity update failed")
        }
    }
}

module.exports = new ProductSvc();

Order Service

const { default: axios } = require("axios");
const { OrderSvcBaseUrl } = require("../config/apiconfig");

class OrderSvc {
    constructor(){
        this.url = OrderSvcBaseUrl
        console.log(this.url);
    }

    async getAll(){
        console.log(this.url);
        const response = await axios.get(`${this.url}/`);
        return response.data;
    }

    async findOne(id){
        const response = await axios.get(`${this.url}/${id}`);
        return response.data;
    }

    async createOrder(ProductId, UserId, Quantity, Cost){
        try{
            const response = await axios.post(`${this.url}/`, {
                ProductId, UserId, Quantity, Cost
            });
            return response.data;
        } catch (e) {
            throw new Error("Unable to create order")
        }
    }

    async deleteOrder(){
        const response = await axios.delete(`${this.url}/`);
        return response;
    }
}

module.exports = new OrderSvc();

Placing an Order

It's now time to write the business logic for placing an order.

For placing an order we need to make sure that our API is consistent. It has to be safe of Race conditions.

We will design the place order API in this flow :

The user req goes to BFF.

Step 1:

The BFF validates if the product is present and we have sufficient quantity.

If Not, we throw back an Error.

    const { ProductId, UserId, Quantity, Cost } = req.body;

    let product = null;
    try {
        product = await productSvc.findOne(ProductId); // get product
        if(product.data == null) // error if not present
        {
            throw new Error("Product doesn't exist");
        }
        if(product.data.ProductQuantity < Quantity) // error if less quantity
        {
            throw new Error("Product not available");
        }
    } catch(error){
        res.status(400).send({message: error.message});
    }

Step 2:
Next we call the Order Svc and place the Order

newOrder = await orderSvc.createOrder(ProductId, UserId, Quantity, Cost);

Step 3:
Once, order is success we will update the product quanitity

await productSvc.updateQuantity(ProductId, product.data.ProductQuantity - Quantity);

Step 4:
If the update quantity fails, we want the order to be cancelled and return an error. This is to keep our database consistent and avoid issues.

    try{
        newOrder = await orderSvc.createOrder(ProductId, UserId, Quantity, Cost);
        await productSvc.updateQuantity(ProductId, product.data.ProductQuantity - Quantity);
        res.json({ data: newOrder });
    } catch(error){
        if(error && error.message == "Product quantity update failed")
        {
            await orderSvc.deleteOrder(newOrder.data.id); // we revert our order created as quantity update failed
        }
        res.status(400).send({message: "Unable to place order"});
    }

Our final Order Placement route will be this:

router.post("/", async ( req, res ) => {
    const { ProductId, UserId, Quantity, Cost } = req.body;

    let product = null;
    try {
        product = await productSvc.findOne(ProductId);
        if(product.data == null)
        {
            throw new Error("Product doesn't exist");
        }
        if(product.data.ProductQuantity < Quantity)
        {
            throw new Error("Product not available");
        }
    } catch(error){
        res.status(400).send({message: error.message});
    }

    let newOrder = null;
    try{
        newOrder = await orderSvc.createOrder(ProductId, UserId, Quantity, Cost);
        await productSvc.updateQuantity(ProductId, product.data.ProductQuantity - Quantity);
        res.json({ data: newOrder });
    } catch(error){
        if(error && error.message == "Product quantity update failed")
        {
            await orderSvc.deleteOrder(newOrder.data.id); // we revert our order created as quantity update failed
        }
        res.status(400).send({message: "Unable to place order"});
    }
})

You can go through the codebase from this github link.

One Last Dance

Time to deploy the BFF service in kubernetes and make appropriate connection to the other services using their service name and not the IP address.

our BFF Deployment.yml file:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bffsvc-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: bffsvc
  template:
    metadata:
      labels:
        app: bffsvc
    spec:
      containers:
      - name: usercontainer
        image: vanshk7/bffsvc
        ports:
        - containerPort: 3003
        env:
        - name: PRODUCT_SVC_BASE_URL 
          value: 'http://productsvc:3001'
        - name: ORDER_SVC_BASE_URL
          value: 'http://ordersvc:3002'

Now this is important step, if you notice in env variable the values are set to http and pointing to our service names.

and our BFF service.yml file will be

apiVersion: v1
kind: Service
metadata:
  name: bffsvc
spec:
  selector:
    app: bffsvc
  ports:
    - protocol: TCP
      port: 3003
      targetPort: 3003
  type: NodePort

Its time to test our final big gun BFF service.

Port forwarding BFF service and no other service is port forward now.

Seeing our Product API's

Seeing our Order API's

Placing an Order

  1. Hitting with quantity more than order quantity

  2. Hitting with product Id not present

  3. Placing correct order and seeing product quantity getting updated.

Awesome and kudos to actually making till here! We have successfully created an end to end microservice BFF architecture with consistent API designs for an E commerce application.

This design is super scalable and efficient for handling good amount of requests and maybe in some other blog we will see how much!

The Office on Peacock on X: "Happy National High Five Day from Jim and Pam!  #AirFive #theoffice http://t.co/PfnJUSBc" / X

You can fork the code from this github link and read about it more.

Feel free connect with me on my linked and always open to talk more on designing and building scalable backend.

Untill next time, take care and happy coding👻

Linkedin

explore more such blogs here

You can read our previous parts of this series

https://vanshkapoor.hashnode.dev/part-2-creating-bff-architecture-using-kubernetes-and-nodejs

https://vanshkapoor.hashnode.dev/part-1-creating-bff-microservice-architecture-using-nodejs-and-kubernetes

0
Subscribe to my newsletter

Read articles from vansh Kapoor directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

vansh Kapoor
vansh Kapoor

Developing large scale application for multiple clients in Thoughtworks. Love to share my knowledge in React, Js and clean coding paractices and travel.