Skip to content

Backend for Frontend

Backend for frontend(BFF) is a service design pattern that provides the core idea to create separate back-end services for specific front-end applications. This pattern allows you to have separate back-end service layers(shim) depending on the user experience you expect to have in the front-end application. The BFF design pattern has its own advantages and disadvantages. The usage of the pattern depends on your use case and requirements. For more information on the BFF design pattern, see Pattern: Backends For Frontends by Sam Newman and The Back-end for Front-end Pattern (BFF) by Phil Calçado.

What you’ll build

Let’s take a real world use case of an online healthcare management system to understand how BFF works. Assume that the healthcare provider needs to have a desktop application as well as a mobile application to provide users a quality online experience. The manner in which information is displayed to a user can vary depending on whether the user signs in to the desktop application or mobile application. This is because the resource limitations such as screen size, battery life, and data usage in mobile devices can cause the mobile application to show minimal viable information to an end user, whereas when it comes to desktop applications, it is possible to display more information and allow multiple network calls to get the required information. The difference in requirements when it comes to each application leads to the need to have a separate BFF for each application. Here, the BFF layer consumes existing downstream services and acts as a shim to translate the required information depending on the user experience.

The following diagram illustrates the scenario:

BFF Design

For this scenario, lets assume we have two applications called desktop application and mobile application. For each application, there is a specific back-end service (BFF) called desktop BFF and mobile BFF respectively. These BFFs consume a set of downstream services called appointment management service, medical record management service, notification management service, and message management service. In this guide, Ballerina is used to build both the BFF layer and the downstream service layer.

Prerequisites

Implementation

  • Create a new Ballerina project named backend-for-frontend.
$ ballerina new backend-for-frontend
  • Navigate to the backend-for-frontend directory.

  • Add a new module named healthcare_management_service to the project.

$ ballerina add healthcare_management_service
  • Open the project with VS Code. The project structure will be similar to the following.

.
├── Ballerina.toml
└── src
   └── healthcare_management_service
      ├── main.bal
      ├── Module.md
      ├── resources
      └── tests
            ├── main_test.bal
            └── resources
We can remove the file main_test.bal for the moment, since we are not writing any tests for our service. Also let's remove the main.bal file and add two directories under healthcare_management_service named downstream_services and backends_for_frontends.

The final directory structure should be similar to the following.

.
├── Ballerina.toml
└── src
   └── healthcare_management_service
      ├── backends_for_frontends
      ├── downstream_services
      ├── Module.md
      ├── resources
      └── tests
            └── resources
  • Now that we have created the project structure, the next step is to develop the services.

First let’s implement the set of downstream services under downstream_services directory. Here we're going to create four services appointment_mgt_service, medical_record_mgt_service, message_mgt_service, and notification_mgt_service, each responding with pre-defined JSON payloads for demonstration purpose.

appointment_mgt_service.bal

import ballerina/http;

@http:ServiceConfig {
    basePath: "/appointmentMgt"
}
service appointment_mgt_service on new http:Listener(9092) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/list"
    }
    resource function getAppointments(http:Caller caller, http:Request req) {
        http:Response response = new;
        json appointmentsResponse = [
        {
            "id": "APT01",
            "name": "Family Medicine",
            "location": "Main Hospital",
            "time": "2018-08-23, 08.30AM",
            "description": "Doctor visit for family medicine"
        },
        {
            "id": "APT02",
            "name": "Lab Test Appointment",
            "location": "Main Lab",
            "time": "2018-08-20, 07.30AM",
            "description": "Blood test"
        }
        ];
        response.setJsonPayload(appointmentsResponse);
        checkpanic caller->respond(response);
    }

}

medical_record_mgt_service.bal

import ballerina/http;

@http:ServiceConfig {
    basePath: "/recordMgt"
}
service medical_record_mgt_service on new http:Listener(9093) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/list"
    }
    resource function getMedicalRecords(http:Caller caller, http:Request req) {
        http:Response response = new;
        json medicalRecordsResponse = [{
            "id": "MED01",
            "name": "Fasting Glucose Test",
            "description": "Test Result for Fasting Glucose test is normal"
        },
        {
            "id": "MED02",
            "name": "Allergies",
            "description":"Allergy condition recorded due to Summer allergies"
        }];
        response.setJsonPayload(medicalRecordsResponse);
        checkpanic caller->respond(response);
    }
}

message_mgt_service.bal

import ballerina/http;

@http:ServiceConfig {
    basePath: "/messageMgt"
}
service message_mgt_service on new http:Listener(9095) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/list"
    }
    resource function getMessages(http:Caller caller, http:Request req) {
        http:Response response = new;
        json messageResponse = [
        {
            "id": "NOT01",
            "name": "Lab Test Result Notification",
            "description": "Test Result of Glucose test is ready"
        },
        {
            "id": "NOT02",
            "name": "Flu Vaccine Status",
            "description": "Flu vaccines due for this year"
        }
        ];
        response.setJsonPayload(messageResponse);
        checkpanic caller->respond(response);
    }
}

notification_mgt_service.bal

import ballerina/http;

@http:ServiceConfig {
    basePath: "/notificationMgt"
}
service notification_mgt_service on new http:Listener(9094) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/list"
    }
    resource function getNotifications(http:Caller caller, http:Request req) {
        http:Response response = new;
        json notificationsResponse = [
        {
            "id": "NOT01",
            "name": "Lab Test Result Notification",
            "description": "Test Result of Glucose test is ready"
        },
        {
            "id": "NOT02",
            "name": "Flu Vaccine Status",
            "description": "Flu vaccines due for this year"
        }
        ];
        response.setJsonPayload(notificationsResponse);
        checkpanic caller->respond(response);
    }
}

  • Now let’s move to the key implementation of BFF services under backends_for_frontends directory.

The first BFF is the mobile_bff_service, which is a shim used to support Mobile user experience in this use case. When loading mobile application's home page, it calls a single resource in Mobile BFF and retrieves appointments, medical records, and messages. This will reduce the number of backend calls and will help to load the home pages in a more efficient way. Since different mobile apps have different methods of sending notifications, we are not using the notification management service.

mobile_bff_service.bal

import ballerina/http;

http:Client mobileAppointmentEP = new ("http://localhost:9092/appointmentMgt");
http:Client mobileMedicalRecordEP = new ("http://localhost:9093/recordMgt");
http:Client mobileMessageEP = new ("http://localhost:9095/messageMgt");

// RESTful service.
@http:ServiceConfig {
    basePath: "/mobile"
}
service mobile_bff_service on new http:Listener(9090) {

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/data"
    }
    resource function getAppData(http:Caller caller, http:Request req) {

        http:Response appointmentResponse = checkpanic mobileAppointmentEP->get("/list");
        json appointmentList = checkpanic appointmentResponse.getJsonPayload();

        http:Response reportResponse = checkpanic mobileMedicalRecordEP->get("/list");
        json medicalRecordList = checkpanic reportResponse.getJsonPayload();

        http:Response messageResponse = checkpanic mobileMessageEP->get("/list");
        json messageList = checkpanic messageResponse.getJsonPayload();

        map<json> profileJsonMap = {};
        profileJsonMap["Appointments"] = appointmentList;
        profileJsonMap["Messages"] = medicalRecordList;
        profileJsonMap["MedicalRecords"] = messageList;
        json profileJson = <json>map<json>.constructFrom(profileJsonMap);
        http:Response response = new ();
        response.setJsonPayload(<@untainted> profileJson);
        checkpanic caller->respond(response);
    }
}

The second BFF is the desktop_bff_service, which is a shim used to support desktop application user experience. When a user loads the desktop application home page, there can be multiple calls to the desktop_bff_service to retrieve comparatively large amounts of data based on the desktop application requirement. The desktop application can call the desktop BFF separately to retrieve appointments and medical records. The desktop application can also call desktop BFF to retrieve messages and notifications in a single call.

desktop_bff_service.bal

import ballerina/http;

http:Client desktopAppointmentEP = new("http://localhost:9092/appointmentMgt");
http:Client desktopMedicalRecordEP = new("http://localhost:9093/recordMgt");
http:Client desktopNotificationEP = new("http://localhost:9094/notificationMgt");
http:Client desktopMessageEP = new("http://localhost:9095/messageMgt");

@http:ServiceConfig {
    basePath: "/desktop"
}
service desktop_bff_service on new http:Listener(9091) {

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/alerts"
    }
    resource function getAlerts(http:Caller caller, http:Request req) {

        http:Response notificationResponse = checkpanic desktopNotificationEP->get("/list");        
        http:Response messageResponse = checkpanic desktopMessageEP->get("/list");
        json notificationList = checkpanic notificationResponse.getJsonPayload();
        json messageList = checkpanic messageResponse.getJsonPayload();
        map<json> alertJsonMap = {};       
        alertJsonMap["Notifications"] = notificationList; 
        alertJsonMap["Messages"] = messageList;        
        json alertJson = <json> map<json>.constructFrom(alertJsonMap);
        http:Response response = new;
        response.setJsonPayload(<@untainted> alertJson);
        checkpanic caller->respond(response);
    }

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/appointments"
    }
    resource function getAppointments(http:Caller caller, http:Request req) {
        http:Response appointmentResponse = checkpanic desktopAppointmentEP->get("/list");
        json appointmentList = checkpanic appointmentResponse.getJsonPayload();
        map<json>|error appointmentListMap = map<json>.constructFrom(appointmentList);
        http:Response response = new();
        response.setJsonPayload(<@untainted> appointmentList);
        checkpanic caller->respond(response);
    }

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/medicalRecords"
    }
    resource function getMedicalRecords(http:Caller caller, http:Request req) {
        http:Response recordResponse = checkpanic desktopMedicalRecordEP->get("/list");
        json medicalRecordList = checkpanic recordResponse.getJsonPayload();        
        http:Response response = new();
        response.setJsonPayload(<@untainted> medicalRecordList);
        checkpanic caller->respond(response);
    }
}

Run the Integration

  • First let’s build the module. While being in the backend-for-frontend directory, execute the following command.

    $ ballerina build healthcare_management_service

This creates the executables.

  • Now run the .jar file created in the above step.

    $ java -jar target/bin/healthcare_management_service.jar

Now we can see that six services have started on ports 9090, 9091, 9092, 9093, 9094, and 9095.

  • Let's access the Mobile BFF with the following curl command.
$ curl http://localhost:9090/mobile/data

We receive a JSON payload similar to the following

{  
   "Appointments":[  
      {  
         "id":"APT01",
         "name":"Family Medicine",
         "location":"Main Hospital",
         "time":"2018-08-23, 08.30AM",
         "description":"Doctor visit for family medicine"
      },
      {  
         "id":"APT02",
         "name":"Lab Test Appointment",
         "location":"Main Lab",
         "time":"2018-08-20, 07.30AM",
         "description":"Blood test"
      }
   ],
   "Messages":[  
      {  
         "id":"MED01",
         "name":"Fasting Glucose Test",
         "description":"Test Result for Fasting Glucose test is normal"
      },
      {  
         "id":"MED02",
         "name":"Allergies",
         "description":"Allergy condition recorded due to Summer allergies"
      }
   ],
   "MedicalRecords":[  
      {  
         "id":"NOT01",
         "name":"Lab Test Result Notification",
         "description":"Test Result of Glucose test is ready"
      },
      {  
         "id":"NOT02",
         "name":"Flu Vaccine Status",
         "description":"Flu vaccines due for this year"
      }
   ]
}
  • Now let's access each resource of the Desktop BFF. The response of each call is given below the Curl request.
$ curl http://localhost:9091/desktop/alerts
{  
   "Notifications":[  
      {  
         "id":"NOT01",
         "name":"Lab Test Result Notification",
         "description":"Test Result of Glucose test is ready"
      },
      {  
         "id":"NOT02",
         "name":"Flu Vaccine Status",
         "description":"Flu vaccines due for this year"
      }
   ],
   "Messages":[  
      {  
         "id":"NOT01",
         "name":"Lab Test Result Notification",
         "description":"Test Result of Glucose test is ready"
      },
      {  
         "id":"NOT02",
         "name":"Flu Vaccine Status",
         "description":"Flu vaccines due for this year"
      }
   ]
}
$ curl http://localhost:9091/desktop/appointments
[  
   {  
      "id":"APT01",
      "name":"Family Medicine",
      "location":"Main Hospital",
      "time":"2018-08-23, 08.30AM",
      "description":"Doctor visit for family medicine"
   },
   {  
      "id":"APT02",
      "name":"Lab Test Appointment",
      "location":"Main Lab",
      "time":"2018-08-20, 07.30AM",
      "description":"Blood test"
   }
]
$ curl http://localhost:9091/desktop/medicalRecords
[  
   {  
      "id":"MED01",
      "name":"Fasting Glucose Test",
      "description":"Test Result for Fasting Glucose test is normal"
   },
   {  
      "id":"MED02",
      "name":"Allergies",
      "description":"Allergy condition recorded due to Summer allergies"
   }
]
Top