NashTech Insights

Building Efficient API with gRPC and Kotlin

Harsh Vardhan
Harsh Vardhan
Table of Contents
photo of headphone and amplifier

Introduction to gRPC

gRPC (gRPC Remote Procedure Call) is an open-source framework developed by Google that facilitates communication between different software applications using remote procedure calls (RPCs). It’s designed to enable efficient and high-performance communication between distributed systems, making it particularly useful for microservices architectures and other scenarios where applications need to interact over a network.

Why we need gRPC

While REST APIs have been widely used for communication between software systems, it offers a compelling alternative. The need for gRPC arises from its ability to provide faster and more efficient communication compared to REST. gRPC uses a binary protocol and multiplexing, leading to reduced overhead and improved performance. It also offers automatic code generation for various programming languages, making it easier to create client and server code.

Additionally, gRPC supports advanced features like bidirectional streaming and strong data typing. So, when speed, efficiency, and seamless cross-language compatibility are essential, gRPC presents a valuable choice alongside traditional REST APIs.

Proto file and its role

A proto file, short for Protocol Buffers file, serves as a language-agnostic blueprint for defining structured data and gRPC service interfaces. It enables efficient serialization, supports versioning, and allows for code generation in various programming languages. In the context of gRPC, proto files play a pivotal role by specifying the request and response formats for service methods, facilitating cross-language communication, and serving as the foundation for automatic code generation, ensuring consistency and efficiency in distributed systems.

Now let’s start by defining a todo.proto for ToDO service,

Defining a proto

todo.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.nashtech.protoBuf";

message Todo {
  int32 id = 1;
  string title = 2;
  string description = 3;
  bool done = 4;
}

message TodoId {
  int32 id = 1;
}

message TodoAll {
}

message ListTodoResponse {
  // List of ToDos
  repeated Todo todo = 1;
}

message CreateTodoRequest {
  string title = 1;
  string description = 2;
}

message UpdateTodoRequest {
  int32 id = 1;
  string title = 2;
  string description = 3;
  bool done = 4;
}

message DeleteTodoResponse {
  enum Status {
    DELETED_SUCCESSFULLY = 0;
    ID_NOT_FOUND = 1;
  }

  Status status = 1;
  string message = 2;
}

message UpdateTodoResponse {
  string message = 1;
  Todo updatedTodo = 2;
}

service TodoService {
  rpc CreateTodo (CreateTodoRequest) returns (Todo);
  rpc GetTodo (TodoId) returns (Todo);
  rpc UpdateTodo (UpdateTodoRequest) returns (UpdateTodoResponse);
  rpc DeleteTodo (TodoId) returns (DeleteTodoResponse);
  rpc ListAllTodo (TodoAll) returns (ListTodoResponse);
}

Required maven dependencies

<dependencies>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${maven.protobuf.java.version}</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-services</artifactId>
            <version>${maven.grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>${maven.grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${maven.grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${maven.grpc.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>${maven.protobuf.java.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>${maven.javax.annotation.api.version}</version>
        </dependency>
    </dependencies>

For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin

protobuf-maven-plugin

<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.7.1</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Now we are ready to generate the Jar file of the projects by running the maven command mvn clean install. It will generate stubs or we can say code for our proto class

Implementing the gRPC Service

With our .proto file in place, we can now implement the server-side logic in Kotlin. Here’s a breakdown of the key components in our TodoService class:

Create a kotlin class TodoService and annotate it with @GrpcService. This annotation serves as a marker annotation that Spring Boot’s gRPC support scans for when searching for gRPC service classes to automatically configure and expose as gRPC services.

@GrpcService
class TodoService : TodoServiceGrpc.TodoServiceImplBase() {
    private val todos = mutableMapOf<Int, Todo>()

    // Implement service methods here...
}

Implementing createTodo

override fun createTodo(
        request: CreateTodoRequest?,
        responseObserver: StreamObserver<Todo>?
    ) {
        if (request != null) {
            val id = todos.size + 1
            val newTodo = Todo.newBuilder()
                .setId(id)
                .setTitle(request.title)
                .setDescription(request.description)
                .setDone(false)
                .build()

            todos[id] = newTodo
            responseObserver?.onNext(newTodo)
            responseObserver?.onCompleted()
        } else {
            responseObserver?.onError(Status.INVALID_ARGUMENT
                .withDescription("Invalid request: Todo data is missing.")
                .asRuntimeException())
        }
    }

Let’s go through each part of the code:

  1. Method Signature:
    • override fun createTodo: This line indicates that you are overriding the createTodo method, as defined in the gRPC service interface, with your custom implementation.
    • request: CreateTodoRequest?,: This parameter represents the incoming gRPC request, specifically a CreateTodoRequest object. The ? indicates that it can be nullable (i.e., it might not be present in the request).
    • responseObserver: StreamObserver<Todo>?: This parameter is a response observer that allows you to send the response back to the client. It observes the responses as they are generated.
  2. Request Handling:
    • if (request != null) {: This line checks if the request parameter is not null, ensuring that valid data has been provided in the request.
  3. Creating a New TODO:
    • val id = todos.size + 1: Here, you calculate the ID for the new TODO item by incrementing the size of an existing list of TODO items (todos) by one.
    • val newTodo = Todo.newBuilder()...: This code creates a new Todo object using the builder pattern. You set the ID, title, description, and done properties for the new TODO item based on the data provided in the request.
    • todos[id] = newTodo: You add the newly created TODO item to your data store (e.g., a map where the key is the ID).
  4. Sending a Response to the Client:
    • responseObserver?.onNext(newTodo): You use the responseObserver to send the newly created TODO item (newTodo) as a response to the client. This informs the client that the TODO item has been successfully created.
    • responseObserver?.onCompleted(): You call onCompleted to signal that you have completed the response and that the RPC (Remote Procedure Call) is finished.
  5. Error Handling:
    • If the request is null (indicating an invalid request), you use responseObserver?.onError(...) to send an error response to the client. In this case, you send a status of INVALID_ARGUMENT along with an error message indicating that the Todo data is missing.

Implementing getTodo

override fun getTodo(
        request: TodoId?,
        responseObserver: StreamObserver<Todo>?
    ) {
        if (request != null && todos.containsKey(request.id)) {
            val todo = todos[request.id]
            responseObserver?.onNext(todo)
            responseObserver?.onCompleted()
        } else {
            responseObserver?.onError(Status.NOT_FOUND
                .withDescription("Todo with ID ${request?.id} not found.")
                .asRuntimeException())
        }
    }

Similarly we can implement all other remaining methods with our logic.

Test grpc server on BloomRPC

Now you are ready to test your grpc server on BloomRPC.

Conclusion

In this tutorial, we’ve covered the implementation of a gRPC server using Kotlin. We’ve defined our gRPC service using a .proto file and implemented the server-side logic for creating, retrieving, updating, deleting, and listing TODO items. You can now use this as a foundation to build more complex gRPC services with Kotlin.

Harsh Vardhan

Harsh Vardhan

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

%d