Back to integration tutorials
This tutorial helps you understand how Ballerina can be used to transform messages.
Overview
In this tutorial, you will develop a service via which you can reserve appointments at hospitals. The requests are transformed from one format to another and forwarded to the hospital service to reserve the appointment.
To implement this use case, you will develop a REST service with a single resource using Visual Studio Code with the Ballerina Swan Lake extension. The resource will receive the user request, transform the payload to the format expected by the hospital service, send a request with the transformed payload to the hospital service to make a reservation, and respond with the reservation details.
The flow is as follows.
-
Receive a request with a JSON payload similar to the following.
{ "firstName": "John", "lastName": "Doe", "dob": "1940-03-19", "ssn": [234, 23, 525], "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com", "doctor": "thomas collins", "hospitalId": "grandoak", "hospital": "grand oak community hospital", "cardNo": "7844481124110331", "appointmentDate": "2023-10-02" }
-
Transform the request payload to the following form.
{ "patient": { "name": "John Doe", "dob": "1940-03-19", "ssn": "234-23-525", "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com" }, "doctor": "thomas collins", "hospital": "grand oak community hospital", "appointment_date": "2023-10-02" }
-
Send a request to the hospital service and retrieve the response which will be similar to the following.
{ "appointmentNumber": 1, "doctor": { "name": "thomas collins", "hospital": "grand oak community hospital", "category": "surgery", "availability": "9.00 a.m - 11.00 a.m", "fee": 7000.0 }, "patient": { "name": "John Doe", "dob": "1940-03-19", "ssn": "234-23-525", "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com" }, "hospital": "grand oak community hospital", "confirmed": false, "appointmentDate": "2023-10-02" }
Concepts covered
- REST API
- HTTP client
- Data mapper
Develop the application
Step 1: Set up the workspace
Install Ballerina Swan Lake and the Ballerina Swan Lake VS Code extension on VS Code.
Step 2: Develop the service
Follow the instructions given in this section to develop the service.
-
Create a new Ballerina project using the
bal
command and open it in VS Code.$ bal new transforming-message-formats
-
Remove the generated content in the
main.bal
file and open the diagram view in VS Code. -
Generate record types corresponding to the response from the hospital service by providing a sample of the expected JSON payload.
{ "appointmentNumber": 1, "doctor": { "name": "thomas collins", "hospital": "grand oak community hospital", "category": "surgery", "availability": "9.00 a.m - 11.00 a.m", "fee": 7000.0 }, "patient": { "name": "John Doe", "dob": "1940-03-19", "ssn": "234-23-525", "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com" }, "hospital": "grand oak community hospital", "confirmed": false, "appointmentDate": "2023-10-02" }
The generated record types will be as follows.
type Doctor record { string name; string hospital; string category; string availability; decimal fee; }; type Patient record { string name; string dob; string ssn; string address; string phone; string email; }; type ReservationResponse record { int appointmentNumber; Doctor doctor; Patient patient; string hospital; boolean confirmed; string appointmentDate; };
Similarly, generate the record types corresponding to the request payloads to the healthcare service being developed (e.g.
HealthcareReservation
) and the request payload expected by the backend hospital service (e.g.,HospitalReservation
). The transformation will be done from the former type to the latter. Also remove the duplicatePatient
record if the same type has already been generated.type HospitalReservation record { Patient patient; string doctor; string hospital; string appointment_date; }; type HealthcareReservation record { string firstName; string lastName; string dob; int[3] ssn; string address; string phone; string email; string doctor; string hospitalId; string hospital; string cardNo; string appointmentDate; };
Note: Since the
ssn
field is required to be an array of three integers, update the type to a fixed-length array.Note: While it is possible to work with the JSON payload directly, using record types offers several advantages including enhanced type safety, data validation, and better tooling experience (e.g., completion).
Note: When the fields of the JSON objects are expected to be exactly those specified in the sample payload, the generated records can be updated to be closed records, which would indicate that no other fields are allowed or expected.
-
Define the HTTP service (REST API) that has the resource that accepts user requests, retrieves relevant details from the backend service, and responds to the request.
-
Open the Ballerina HTTP API Designer in VS Code.
-
Use
/healthcare
as the service path (or the context) for the service attached to the listener that is listening on port8290
. -
Define an HTTP resource that allows the
POST
operation on the resource path/categories/{category}/reserve
and accepts thecategory
path parameter (corresponding to the specialization). UseHealthcareReservation
as a parameter indicating that the resource expects a JSON object corresponding toHealthcareReservation
as the payload. UseReservationResponse
,http:NotFound
, andhttp:InternalServerError
as the response types.The generated service will be as follows.
service /healthcare on new http:Listener(8290) { resource function post categories/[string category]/reserve(HealthcareReservation reservation) returns ReservationResponse|http:NotFound|http:InternalServerError { } }
-
-
Use the data mapper to define the
transform
function which transforms aHealthcareReservation
record, representing the payload (reservation
), to aHospitalReservation
record.The data mapper view of the completed
transform
function is shown below.The source code of the
transform
function will be as follows.isolated function transform(HealthcareReservation reservation) returns HospitalReservation => let var ssn = reservation.ssn in { patient: { name: reservation.firstName + " " + reservation.lastName, dob: reservation.dob, ssn: string `${ssn[0]}-${ssn[1]}-${ssn[2]}`, address: reservation.address, phone: reservation.phone, email: reservation.email }, doctor: reservation.doctor, hospital: reservation.hospital, appointment_date: reservation.appointmentDate };
-
Define an
http:Client
object to send requests to the backend service.The generated code will be as follows.
final http:Client hospitalServiceEP = check new ("http://localhost:9090");
-
Implement the logic.
service /healthcare on new http:Listener(8290) { isolated resource function post categories/[string category]/reserve(HealthcareReservation reservation) returns ReservationResponse|http:NotFound|http:InternalServerError { HospitalReservation hospitalReservation = transform(reservation); ReservationResponse|http:ClientError resp = hospitalServiceEP->/[reservation.hospitalId]/categories/[category]/reserve.post(hospitalReservation); if resp is ReservationResponse { return resp; } log:printError("Reservation request failed", resp); if resp is http:ClientRequestError { return <http:NotFound> {body: "Unknown hospital, doctor or category"}; } return <http:InternalServerError> {body: resp.message()}; } }
-
Use the
transform
function to transform the payload from aHealthcareReservation
record to aHospitalReservation
record. -
Use the transformed payload in the request to the hospital service and get the response. Here, The
hospitalId
andcategory
values are used as path parameters. -
Use the
is
check to check whether the response isReservationResponse
and return it as is (i.e., reservation successful). -
If the response is not a
ReservationResponse
record, log the information atERROR
level. Return anhttp:NotFound
response if the response from the hospital service is anhttp:ClientRequestError
response or anhttp:InternalServerError
response otherwise.
-
You have successfully developed the required service.
Complete source code
import ballerina/http; import ballerina/log; final http:Client hospitalServiceEP = check new ("http://localhost:9090"); type HealthcareReservation record {| string firstName; string lastName; string dob; int[3] ssn; string address; string phone; string email; string doctor; string hospitalId; string hospital; string cardNo; string appointmentDate; |}; type Patient record {| string name; string dob; string ssn; string address; string phone; string email; |}; type HospitalReservation record {| Patient patient; string doctor; string hospital; string appointment_date; |}; type Doctor record {| string name; string hospital; string category; string availability; decimal fee; |}; type ReservationResponse record {| int appointmentNumber; Doctor doctor; Patient patient; string hospital; boolean confirmed; string appointmentDate; |}; service /healthcare on new http:Listener(8290) { isolated resource function post categories/[string category]/reserve(HealthcareReservation reservation) returns ReservationResponse|http:NotFound|http:InternalServerError { HospitalReservation hospitalReservation = transform(reservation); ReservationResponse|http:ClientError resp = hospitalServiceEP->/[reservation.hospitalId]/categories/[category]/reserve.post(hospitalReservation); if resp is ReservationResponse { return resp; } log:printError("Reservation request failed", resp); if resp is http:ClientRequestError { return <http:NotFound> {body: "Unknown hospital, doctor or category"}; } return <http:InternalServerError> {body: resp.message()}; } } isolated function transform(HealthcareReservation reservation) returns HospitalReservation => let var ssn = reservation.ssn in { patient: { name: reservation.firstName + " " + reservation.lastName, dob: reservation.dob, ssn: string `${ssn[0]}-${ssn[1]}-${ssn[2]}`, address: reservation.address, phone: reservation.phone, email: reservation.email }, doctor: reservation.doctor, hospital: reservation.hospital, appointment_date: reservation.appointmentDate };
Sequence Diagram
The sequence diagram view for the implemented resource method is the following.
Step 3: Build and run the service
Note: Alternatively, you can run this service by navigating to the project root and using the
bal run
command.transforming-message-formats$ bal run Compiling source integration_tutorials/transforming_message_formats:0.1.0 Running executable
Step 4: Try out the use case
Let's test the use case by sending a request to the service.
Start the backend service
Download the JAR file for the backend service and execute the following command to start the service.
$ bal run hospitalservice.jar
Send a request
Use the Try it feature to send a request to the service. Specify surgery
as the path parameter. Use the following as the request payload.
{ "firstName": "John", "lastName": "Doe", "dob": "1940-03-19", "ssn": [234, 23, 525], "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com", "doctor": "thomas collins", "hospitalId": "grandoak", "hospital": "grand oak community hospital", "cardNo": "7844481124110331", "appointmentDate": "2023-10-02" }
Verify the response
You will see a response similar to the following for a successful appointment reservation.
{ "appointmentNumber": 1, "doctor": { "name": "thomas collins", "hospital": "grand oak community hospital", "category": "surgery", "availability": "9.00 a.m - 11.00 a.m", "fee": 7000.0 }, "patient": { "name": "John Doe", "dob": "1940-03-19", "ssn": "234-23-525", "address": "California", "phone": "8770586755", "email": "johndoe@gmail.com" }, "hospital": "grand oak community hospital", "confirmed": false, "appointmentDate": "2023-10-02" }
Complete implementation
Check out the complete implementation on GitHub.