Scilim

blog

About

Beyond REST: gRPC

2024-06-23

Table of contents

Introduction

The objective of the project was the implementation of a concurrent server/client system that interacts through gRPC. Furthermore, emphasis was placed on Thread Safety and proper exception handling on the server side to make good use of the capabilities of the RPC protocol. Server-side logging was also implemented, as well as an interesting abstraction of classes for client management. Additionally, various bash scripts that we used to test the system are included as extra material.

System's Objectives

Hours before a passenger flight takes off from an airport, the airline's check-in counter staff are responsible for receiving passengers with tickets for that flight. They verify the passengers' information, check their baggage, and perform the check-in process, allowing them to board and begin their journey. This practical work covers the fundamental aspects of the check-in counter assignment process from the perspective of the passenger, the airlines, and the airport administration through the implementation of five remote services.

A passenger arrives at the airport with their reservation code. If there are already counters available for that flight, the passenger is informed of the corresponding counter range and proceeds to a single queue for the entire range. Airlines have several counters grouped together, called a range, to attend to more passengers simultaneously. Once in the queue, the passenger waits for their turn, and when it comes, they complete the check-in and can board.

Airlines must request a range of counters from the airport administration to begin the check-in process. Since the number of counters at the airport is limited, it is common for an airline to request a certain number of counters that may not be available because they are being used for other flights. In such cases, the airline's request is registered, and when the counters are freed up or new ones are added, the pending request can be fulfilled. Once assigned, when the airline completes the process, it is responsible for releasing the counters so that they can be used by other flights/airlines.

The airport administration divides the airport into sectors and manages the creation of counters, placing them in a specific sector. Additionally, the administration receives the passenger manifest, listing all passengers expected to board based on tickets sold by the airlines. Finally, the administration must be able to review everything that has occurred at each of the counters.

Overall Project Structure

The project encompasses three Maven projects

  • api: where the .proto files for gRPC are defined
  • client: where the different clients are defined
  • server: where the server executable is defined. A servant is created for encapsulating each of the services that the server exposes through gRPC.
.
├── README.md
├── api
│   ├── pom.xml
│   └── src
│       └── main
│           └── proto
│               ├── airport_service.proto
│               ├── checkin_service.proto
│               ├── counter_reservation_service.proto
│               ├── counter_service.proto
│               ├── notifications_service.proto
│               └── service.proto
├── client
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── assembly
│       │   │   ├── assembly.xml
│       │   │   └── overlay
│       │   │       ├── adminClient.sh
│       │   │       ├── counterClient.sh
│       │   │       ├── eventsClient.sh
│       │   │       ├── passengerClient.sh
│       │   │       └── queryClient.sh
│       │   ├── java
│       │   │   └── ar
│       │   │       └── edu
│       │   │           └── itba
│       │   │               └── pod
│       │   │                   └── tpe1
│       │   │                       └── client
│       │   │                           ├── Action.java
│       │   │                           ├── Arguments.java
│       │   │                           ├── Client.java
│       │   │                           ├── Util.java
│       │   │                           ├── admin
│       │   │                           │   ├── AirportAdminAction.java
│       │   │                           │   ├── AirportAdminActions.java
│       │   │                           │   ├── AirportAdminClient.java
│       │   │                           │   └── actions
│       │   │                           │       ├── AddCounters.java
│       │   │                           │       ├── AddSector.java
│       │   │                           │       └── Manifest.java
│       │   │                           ├── checkin
│       │   │                           │   ├── CheckInAction.java
│       │   │                           │   ├── CheckInActions.java
│       │   │                           │   ├── CheckInClient.java
│       │   │                           │   └── actions
│       │   │                           │       ├── FetchCounter.java
│       │   │                           │       ├── PassengerCheckin.java
│       │   │                           │       └── PassengerStatus.java
│       │   │                           ├── counter
│       │   │                           │   ├── CounterReservationAction.java
│       │   │                           │   ├── CounterReservationActions.java
│       │   │                           │   ├── CounterReservationClient.java
│       │   │                           │   └── actions
│       │   │                           │       ├── AssignCounters.java
│       │   │                           │       ├── CheckInCounters.java
│       │   │                           │       ├── FreeCounters.java
│       │   │                           │       ├── ListCounters.java
│       │   │                           │       ├── ListPendingAssignments.java
│       │   │                           │       └── ListSectors.java
│       │   │                           ├── exceptions
│       │   │                           │   └── ServerUnavailableException.java
│       │   │                           ├── notifications
│       │   │                           │   ├── NotificationsAction.java
│       │   │                           │   ├── NotificationsActions.java
│       │   │                           │   ├── NotificationsClient.java
│       │   │                           │   └── actions
│       │   │                           │       ├── RegisterNotifications.java
│       │   │                           │       └── RemoveNotifications.java
│       │   │                           └── query
│       │   │                               ├── CounterQueryAction.java
│       │   │                               ├── CounterQueryActions.java
│       │   │                               ├── CounterQueryClient.java
│       │   │                               └── actions
│       │   │                                   ├── CheckIns.java
│       │   │                                   └── QueryCounters.java
│       │   └── resources
│       │       ├── log4j.xml
│       │       └── manifest.csv
│       └── test
│           └── java
│               └── ar
│                   └── edu
│                       └── itba
│                           └── pod
│                               └── tpe1
│                                   └── client
│                                       ├── passengersErrorCase1.csv
│                                       ├── passengersErrorCase2.csv
│                                       └── passengersOk.csv
├── compile.sh
├── pom.xml
├── run_server.sh
├── scriptTests
│   ├── README.md
│   ├── basicSetUp.sh
│   ├── checkInStatus.sh
│   ├── manifest.csv
│   ├── notificationsTest.sh
│   ├── notificationsTest_parallelActions.sh
│   ├── notificationsTest_parallelActions_multiplePendings.sh
│   ├── pendingTests.sh
│   ├── queryCounters.sh
│   └── run-all.sh
├── server
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── assembly
│       │   │   ├── assembly.xml
│       │   │   └── overlay
│       │   │       └── run-server.sh
│       │   ├── java
│       │   │   └── ar
│       │   │       └── edu
│       │   │           └── itba
│       │   │               └── pod
│       │   │                   └── tpe1
│       │   │                       ├── data
│       │   │                       │   ├── Airport.java
│       │   │                       │   ├── Notifications.java
│       │   │                       │   ├── exceptions
│       │   │                       │   │   └── CounterReleaseException.java
│       │   │                       │   └── utils
│       │   │                       │       ├── Airline.java
│       │   │                       │       ├── Booking.java
│       │   │                       │       ├── CheckIn.java
│       │   │                       │       ├── CheckInStatus.java
│       │   │                       │       ├── Flight.java
│       │   │                       │       ├── FreeCounterResult.java
│       │   │                       │       ├── Notification.java
│       │   │                       │       ├── RangeCounter.java
│       │   │                       │       ├── RequestedRangeCounter.java
│       │   │                       │       └── Sector.java
│       │   │                       ├── servant
│       │   │                       │   ├── AirportAdminServant.java
│       │   │                       │   ├── CounterQueryServant.java
│       │   │                       │   ├── CounterReservationService.java
│       │   │                       │   ├── NotificationsServant.java
│       │   │                       │   └── PassengerCheckInServant.java
│       │   │                       └── server
│       │   │                           └── Server.java
│       │   └── resources
│       │       └── log4j.xml
│       └── test
│           └── java
│               └── ar
│                   └── edu
│                       └── itba
│                           └── pod
│                               └── tpe1
│                                   └── data
│                                       └── AirportTest.java
└── shell.nix

Problems Encountered

Server's Internal State and Separation of Business Logic

Initially, we did not consider creating a separation between the Servants and the business logic (as can be seen in the first commits). That is, there was coupling between the handling of gRPC request/responses and the specific logic of the service being exposed. The problem became apparent when we noticed how the services had to interact with each other, completely breaking the logic we had initially planned.

After some time, we realized that the solution was simply "encapsulation." By adding a business logic layer, not only did we reduce the coupling between the gRPC-handling layer, but we also grouped the state independently of the servant. This way, interaction between servants was possible.

Additionally, to maintain a correct interaction between servants, particularly consistency in the instantiation of the state, the classes handling the business logic were made Singleton. Thus, maintaining a single source of truth.

Finally, we believe there is significant room for improving the encapsulation/interaction between the components of the business logic.

Handling Maven and Java Versions

Some group members encountered issues with managing dependencies. In particular, Ubuntu ships with Maven 3.6.3 by default, and JDK 17 is incompatible with this version of Maven.

The solution was simply to manually update Maven on the machines running Ubuntu.

Utilization of the RPC Capabilities Spectrum

The development methodology was more of an iterative nature, with improvements being made commit by commit. One such improvement was making better use of the RPC protocol.

Initially, we made the mistake of creating a status field in the .proto files, exactly as we were told not to do in the theoretical classes. After working a bit more on the servants and clients, we realized that by using status, we were not correctly utilizing the RPC protocol.

In the same way that HTTP status codes are used in a REST API to determine the success or failure of requests (information that should not go in the message body), in RPC, the status code should not be defined within the object sent as a response.

Possible Improvements

Design of Data Structures for Ensuring Thread Safety

While working to guarantee Thread Safety near the deadline, we realized that the way we had designed the data structures, in a rather on-the-fly manner, made it very difficult to ensure Thread Safety.

We now understand that for this type of system, it is essential to have a good understanding of the service requirements and to carefully plan what structures to propose before writing the first line of code.

Nevertheless, we were able to resolve many parts without using large blocking locks. For example, since the requirements do not consider the deletion of flights or counters, in cases where a counter exists, we can say that this invariant will always hold. This allows us to release the lock immediately after validating this and not worry about the sector/flight in question ceasing to exist.

Organization of .proto Files

The files defining the API messages have ample room for improvement by simplifying repeated messages and eliminating Status messages that were partially or completely replaced by the gRPC protocol itself.

Conclusions

This project presented a collection of challenging tasks. It taught us the complexity of managing concurrency in a server and some considerations for doing so. It also allowed us to move beyond the classic "REST" API (wrongly referred to as REST), which exposes services through HTTP endpoints.

The implementation of a server/client architecture based on RPC definitely opened the door to the diversity of ways to carry out service/client interactions over the internet, and even beyond the "server/client" scheme.

On the other hand, deepening our understanding of concurrency management was fascinating. We've been approaching this topic from different angles since Operating Systems, and we consider this project to be an important pillar in our understanding of it.

Contributors

Marco Scilipoti

Martin Ippolito

Martin E. Zahnd