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:
- A free Akka Serverless Account
- Akka Serverless CLI
- Docker 20.10.8 or higher
- A Docker repository to push the Docker image
- Java 11 or higher
- Maven 3.x or higher
- grpcurl
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"]
}
};
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 import
s 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 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());
}
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
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

patient_id
and patientName
. 
patient_id
, booking_id
, booking type
, and date
. 
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.