Search

Pandemic Preparedness with Akka Serverless

The COVID-19 pandemic highlighted the need for a scalable, easy-to-build testing and vaccine appointment booking system. The high demand showed manual booking wasn’t feasible or sustainable. As a result, many countries faced severe public health consequences as hospitals struggled to keep up with the cases in part because of vaccine inaccessibility.

There were two main problems:

  • Existing booking systems couldn’t keep up with high demand.
  • Building new, scalable booking system back ends was difficult, slow, and required expertise, to which not all governments and public health agencies have easy access.

So how do we improve our response time in the future?

We can employ modern technological solutions like Akka Serverless. Akka Serverless can help mitigate those issues by offering development teams the ability to quickly build scalable backends using their existing skills. This makes it a perfect fit for launching booking systems that can handle millions of users immediately.

In this tutorial, we’ll build a serverless back end for a booking service system and explain how Akka Serverless can be a game-changer for development teams in the future.

Getting Started

Akka Serverless is a platform-as-a-service to build scalable backends using the language of your choice. It supports Java, Scala, JavaScript, TypeScript, and Python. Additionally, it removes the unnecessary burden of database management, message brokers, service meshes, security, caching, service discovery, and Kubernetes clusters.

Before we start building out a back end, here are the necessary prerequisites:

Building the Back End

Let’s start with making a directory for our hands-on tutorial. Type this command into your integrated development environment (IDE):

				
					mkdir BookingSystem
				
			

Navigate to that directory or open that directory using your favorite editor.

				
					cd BookingSystem
				
			

You can use the Maven archetype and a Maven plugin, which can help you generate the code according to your needs, or else download the prebuilt pom.xml file to follow the tutorial. You can download or copy the pom.xml file from GitHub.

We’ll use protocol buffers to define our API and the domain. Protocol buffers are an open-source data format used to serialize structured data, which Akka Serverless uses. For the API and domain, we need to create two directories first:

				
					mkdir -p ./src/main/proto/booking/service/api
mkdir -p ./src/main/proto/booking/service/domain

				
			

Defining the API

Inside the src/main/proto/booking/service/api subdirectory, create a booking_service_api.proto file. Then, inside the file, add some declarations:

				
					syntax = "proto3"; //protobuf syntax version

package booking.service.api; //package name

option java_outer_classname = "BookingServiceApi"; //outer classname
import "akkaserverless/annotations.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";

				
			

Now we must create a service endpoint annotated to akkaserverless.codegen.

Akka Serverless supports various types of the state model. In this case, we’ll use the Event Sourced Entity State model to define the events because it tracks changes to data, which is important for a booking system. Another benefit of using the Event Sources Entity model is that every entity is scalable and long-lived. Also, it stores the changes into the journal, which is useful for auditing.

Event sourcing is an often misunderstood and hard to implement application architectural pattern. Fortunately, Akka Serverless makes it simple.

Add this code to the booking_service_api.proto file:

				
					service BookingService {
  option (akkaserverless.codegen) = {
    event_sourced_entity: {
      name: "booking.service.domain.BookingServiceEntity"
      entity_type: "eventsourced-booking-service"
      state: "booking.service.domain.PatientState"
      events: [
        "booking.service.domain.BookingAdded", 
        "booking.service.domain.BookingRemoved",
        "booking.service.domain.PatientAdded"]
    }
  };

				
			
Inside the event_sourced_entity object, we define the name, entity_type, state, and events. This tutorial creates three events, one to add a booking, one to remove the booking, and one to create a patient. Before defining the methods, we must define messages to define the fields to use. We’ll start with creating messages associated with the patients, which we want to fetch and create.
				
					message Patient {
  repeated Bookings bookings = 1;
  PatientDetails patientDetails = 2;
}

message GetPatient {
  string patient_id = 1 [(akkaserverless.field).entity_key = true];
}

message PatientDetails {
  string patient_id = 1 [(akkaserverless.field).entity_key = true];
  string patientName = 2;
}

				
			

Then, for every patient, we need to enable multiple bookings and cancellations. So, we’ll define a message to add a booking, delete a booking, and get the bookings.

				
					message Bookings {
  string booking_id = 1;
  string type = 2;
  string date = 3;
}

message AddBooking {
  string patient_id = 1 [(akkaserverless.field).entity_key = true];
  string booking_id = 2;
  string type = 3;
  string date = 4;
}

message DeleteBooking {
  string patient_id = 1 [(akkaserverless.field).entity_key = true];
  string booking_id = 2;
}

				
			

For the fields with the value (akkaserverless.field).entity_key = true, Akka Serverless uses the respective field’s value in order to correctly route the messages.

After creating the required messages, it’s time to develop some remote procedure calls (RPC), or APIs. The google.api.http annotation exposes the service to the HTTP endpoint alongside the already exposed gRPC endpoint.

				
					rpc CreateBooking(AddBooking) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      post: "/patient/{patient_id}/bookings/add"
      body: "*"
    };
  }

rpc CreatePatient(PatientDetails) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      post: "/patient/{patient_id}/add"
      body: "*"
    };
  }

rpc RemoveBooking(DeleteBooking) returns (google.protobuf.Empty) {
    option (google.api.http).post = 
        "/patient/{patient_id}/bookings/{booking_id}/remove";
  }
  
rpc RetrievePatient(GetPatient) returns (Patient) {
    option (google.api.http) = {
      get: "/patients/{patient_id}"
      additional_bindings: {
        get: "/patients/{patient_id}/bookings"
        response_body: "bookings"
      }
    };
  }

				
			

After these steps, the final booking_service_api.proto file should match this file. The next step is to define the domain.

Defining the Domain

Like the previous step, inside the src/main/proto/booking/service/domain subdirectory, create a booking_service_domain.proto file, then add these declarations:

				
					syntax = "proto3";
package booking.service.domain;
option java_outer_classname = "BookingServiceDomain";

				
			

After that, we must add messages for entity data and event data. Messages for entity data are as follows:

				
					message PatientState {
  repeated Bookings bookings = 1;
  PatientDetails patientDetails = 2;
}

message PatientDetails {
  string patientId = 1;
  string patientName = 2;
}

message Bookings {
   string bookingId = 1;
   string type = 2;
   string date = 3;
}

				
			

The messages for event data are below:

				
					message PatientAdded {
  PatientDetails patientDetails = 1;
}

message BookingAdded {
  Bookings booking = 1;
}

message BookingRemoved {
  string bookingId = 1;
}

				
			

After following these steps, the final file should look like this.

Compiling the Project

The next step is to run a Maven command to generate the classes and the get and set methods. It will throw build errors if anything is wrong in the .proto files.

				
					mvn compile
				
			

You should get a result like this:

Writing Business Logic

The compile command creates various files and methods to support the development process. We’ll be writing the logic inside the src/main/java/booking/service/domain/BookingServiceEntity.java file, which the compile command has already created.

Now we must write some business logic. We’ll only discuss one method and associated event handlers in this tutorial, but the complete BookingServiceEntity.java file is here, including all the methods, event handlers, and utility functions.

These are the imports to use in the BookingServiceEntity.java file:

				
					package booking.service.domain;
import booking.service.api.BookingServiceApi;
import com.akkaserverless.javasdk.eventsourcedentity.EventSourcedEntity;
import com.akkaserverless.javasdk.eventsourcedentity.EventSourcedEntity.Effect;
import com.akkaserverless.javasdk.eventsourcedentity.EventSourcedEntityContext;
import com.google.protobuf.Empty;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

				
			

Here’s the complete method of creating the booking for a given patient:

				
					@Override
public Effect<Empty> createBooking(
    BookingServiceDomain.PatientState currentState,
    BookingServiceApi.AddBooking addBooking) {
    BookingServiceDomain.BookingAdded bookingAddedEvent =   
        BookingServiceDomain.BookingAdded.newBuilder()
            .setBooking(BookingServiceDomain.Bookings.newBuilder()
                .setBookingId(addBooking.getBookingId())
                .setType(addBooking.getType())
                .setDate(addBooking.getDate())
                .build())
            .build();
    return effects()
        .emitEvent(bookingAddedEvent)
        .thenReply(__ -> Empty.getDefaultInstance());
}
				
			
This method gets the new booking details from the API and then creates a new booking by firing the bookingAddedEvent. Once the new booking is created, the bookingAdded event handler applies the changes to the global state.
				
					@Override
public BookingServiceDomain.PatientState bookingAdded(BookingServiceDomain.PatientState currentState,
    BookingServiceDomain.BookingAdded bookingAdded) {
    Map < String, BookingServiceApi.Bookings > patient = domainPatientToMap(currentState);
    BookingServiceApi.PatientDetails currentPatientDetails = domainPatientDetailsToMap(currentState);
    BookingServiceApi.Bookings item = patient.get(bookingAdded.getBooking().getBookingId());
    if (item == null) {
        item = domainBookingToApi(bookingAdded.getBooking());
    } else {
        item = item.toBuilder()
            .setDate(bookingAdded.getBooking().getDate())
            .build();
    }
    patient.put(item.getBookingId(), item);
    return mapToDomainPatient(patient, currentPatientDetails);
}
				
			

This event handler uses several utility functions to improve overall code readability, which is shown in bold. In the final code, we similarly have method handlers for removing the booking, creating a patient, and getting the bookings associated with a patient. We also have two other event handlers to delete the booking and create patient events.

Deploy and Test

Remember, one of the requirements of this project was to install the Akka Serverless CLI. It will help to deploy the application easily. Like this:

				
					akkasls auth login
				
			
Now, follow these instructions to create a new project and set that project as default. In the root of the project, inside the pom.xml file, replace the dockerImage property with your container image.

Run the command below:

				
					mvn deploy
				
			

You can either log in to the developer console or use the CLI to verify the deployed service with this command:

				
					akkasls service list
				
			

It’s normal for the service to be in “unavailable” status for some time, but reach out to the support team if it persists.

Now is the time to use the grpcurl package to proxy the akkasls service, which opens the gRPC web UI in the localhost port 8080.

				
					akkasls service proxy booking-service --grpcui
				
			
Now, let’s add a patient (named Jill Cipher) by passing the patient_id and patientName.
To add a COVID-19 vaccine booking for Jill, we must pass the patient_id, booking_id, booking type, and date.
We must pass the patient_id, which is 10, to retrieve the created booking.

After clicking Invoke, we successfully fetched the patient details and the bookings.

To delete the booking, we must pass the patient_id 10 and booking_id 11.

After deleting the booking, if we try to fetch the same patient, we get the following output, which suggests that we successfully deleted the booking.

Patients can now quickly book or cancel appointments.

Next Steps

This tutorial demonstrated how to leverage Akka Serverless to build out scalable back ends in no time. We can use these same techniques in future emergencies to quickly build vaccine booking portals, tools to help match refugees with resources, and more. Now, system backends can scale rapidly to match emergency responses.

You can follow this tutorial to kickstart your serverless application with a more sophisticated architecture with the necessary fields and validations. Try out Akka Serverless for free and check out their official web page to learn more.

If you’re interested in developing expert technical content that performs, let’s have a conversation today.

Facebook
Twitter
LinkedIn
Reddit
Email

POST INFORMATION

If you work in a tech space and aren’t sure if we cover you, hit the button below to get in touch with us. Tell us a little about your content goals or your project, and we’ll reach back within 2 business days. 

Share via
Copy link
Powered by Social Snap