How I wrote a coursework on gRPC+REST in conjunction with a client on Android+Jetpack Compose

Link to article - https://habr.com/ru/companies/dododev/articles/764540/

Link to article – https://habr.com/ru/companies/dododev/articles/764540/

After several days of thinking about a course topic, I came across the application Radio Arzamas. I really liked the muted, dark-colored interface. I uploaded a couple of screenshots from the application into Figma. I turned them back and forth and an idea came to me. I decided to develop a mobile application where you can read biographies and articles about authors of Russian literature of the 18th and 19th centuries. But in order to add at least some feature, and not just throw a wall of text, I came up with the idea of ​​putting this information in the format of vertical clips.

Literally in one evening I found all the necessary additional references for my work and added them to the figma.

I really liked this idea. It combined all my favorite things at that time: novels by Russian classics and films, which I often watched before starting work on my coursework.

Untitled

After prototyping in figma, I sketched out the technical specifications, clarifying that it could change during development. The teacher approved the idea and said that this would be more than enough for the coursework.

Untitled

I worked on my coursework from morning to evening every day for a month. In order to get as deep into my coursework as possible and not be too distracted by other work at the university, I either froze them or outsourced them to my classmates.

Let me remind you that the topic of the course work is to develop a client-server application. But since I’m an Android developer, there’s no point in talking about any cool experience with the server side. I just happened to write a couple of simple projects with CRUD methods on Ktor in pet projects.

For the server in the course work, I decided to choose Go. I really like it for its syntax and its brevity. I learned Go a year ago, during the winter holidays. But writing simple REST CRUD things again wasn’t very interesting to me. I wanted some variety, so I decided to try gRPC for a small portion of my coursework. The authorization service was perfect for this.

What is gRPC

Google Remote Procedure Call or gRPC is an open source RPC framework. It is great for creating scalable and fast APIs. The protocol is widely used, for example by Google, IBM, Netflix.

Untitled

gRPC uses HTTP/2 as its transport protocol. HTTP/2 provides more efficient use of network resources than HTTP/1.1 by allowing multiple requests and responses to be sent in parallel within a single TCP connection. This reduces latency and improves overall performance.

HHTP 2 is a new network data transfer protocol (released in 2015). Innovations:

  • binary message transmission format (as opposed to text HTTP 1.1);

  • more advanced compression of HTTP messages – smaller size, higher speed;

  • data streams;

  • multiplexing, stream prioritization, etc.

When to use

  • monolithic application that must be accessed externally or through a browser – REST API;

  • microservices that communicate with each other – gRPC;

  • many different languages ​​- gRPC (or REST API);

  • data streaming required – gRPC;

  • The data transfer speed is critical (huge number of requests or narrow channel) – gRPC.

ProtoBuf

ProtoBuf is an interface description language and data serialization system developed by Google. They are used to serialize structured data, similar to XML, but are more efficient, faster, and smaller in size. The data structure in ProtoBuf is described in files with the extension .proto.

ProtoBuf supports various data types, for example: int32, float, bool, string, bytes. ProtoBuf serializes data into a binary format, making it very efficient in both size and serialization/deserialization speed. After defining the data structure in .proto files, the compiler is used protoc to generate code in various programming languages.

In my course work, I only wrote an authorization service on gRPC for a “test of the pen”. So .proto the file for the service looks like the code below:

service Auth {
	rpc Register (RegisterRequest) returns (RegisterResponse);
	rpc Login (LoginRequest) returns (LoginResponse);
}

message RegisterRequest {
	string phone = 1;
	string password = 2;
}

message RegisterResponse {
	int64 user_id = 1;
}

message LoginRequest {
	string phone = 1;
	string password = 2;
}

message LoginResponse {
	string token = 1
}

Difference between gRPC and REST

When a client and server communicate via REST, they pass JSON messages to each other.

Untitled

JSON is a text-based data exchange format based on JavaScript. It is used to serialize and transfer data between the server and web applications.

Untitled

gRPC uses ProtoBuf by default due to its high performance and efficiency. However, gRPC also supports JSON and other formats, making it flexible for various use cases. Further in the table, we compare ProtoBuf and JSON.

Criterion

Protocol Buffers (ProtoBuf)

JSON

Data Format

Binary

Text

Message Size

Typically smaller, more compact

Usually more due to text format

Processing speed

Faster due to smaller size and binary nature

Slower, requires text parsing

Readability

Requires special tools for reading and debugging

Easy to read and debug by humans

Interoperability

Good support between different languages

Excellent support on all platforms

Compatibility

Strict compatibility, requires exact data schema matching

Flexible, easily adapts to changes

Data typing

Strongly typed, requires all fields to be defined

Dynamically typed

Usage

Preferred for high performance and optimized systems

Widely used for web API and easy integration

If you need performance and compactness, then ProtoBuf will be the preferred choice. If ease of debugging and broad compatibility are more important, then it may be smarter to use JSON.

JSON

{ 
  "timestamp": 15030534477, 
  "url": "http://example.com/" 
}

Pros:

Minuses:

  • not compressed during transmission, transmitted as text (large message size);

  • redundant (keys are repeated);

  • no strong typing.

ProtoBuf

message RegisterRequest { 
  int64 timestamp = 1; 
  string url = 2; 
}

Pros:

Minuses:

gRPC supports several types of interactions:

Unary RPC: This is the most basic and simple model in gRPC. The client sends one request to the server and receives one message in response. This is similar to a traditional function call in programming. Suitable for simple queries and operations where one-time interaction is required;

Server streaming RPC: in this model, the client sends one request to the server, after which the server starts sending a stream of responses. Suitable for scenarios where the server needs to send a large amount of data or constantly updated information;

Client streaming RPC: the client sends a data stream to the server. After the stream has finished sending, the client waits for a response from the server. This type is suitable for scenarios where the client needs to send a large amount of data or a series of messages;

Bidirectional streaming RPC: In bidirectional streaming RPC, the client and server exchange data streams in both directions. The client can start sending a series of messages without waiting for the server's responses, and vice versa.

For the project I chose the simplest model – Unary RPC. Since the project currently does not have logic that would require complex architecture or interaction techniques.

Compiler protoc, which comes with gRPC, is compatible with a wide range of programming languages. This makes gRPC great for multilingual environments where you connect many different microservices written in different languages ​​and running on different platforms.

In contrast, the REST API does not offer native code generation functionality. You should use a third party tool like Swagger to generate code for API calls in different languages. This isn't an inconvenience, but it's worth noting that gRPC doesn't rely on any external tools to generate code.

According to widely cited benchmarks, gRPC API connections are significantly faster than REST API connections. GRPC is about 7 times faster than REST at receiving data and about 10 times faster than REST at sending data for this particular payload. This is mainly due to the dense packing of protocol buffers and the use of HTTP/2 in gRPC.

Untitled

Despite the advantages in message transfer speed, the gRPC implementation is slightly slower than the REST implementation. Implementing a simple gRPC service took me about 30% longer than REST.

All other logic on the server side will be implemented in REST. Receiving articles, biographies, user data. All this and more runs on REST in conjunction with a SQLite database.

Practical example of network architecture

First, I wrote a gRPC server in Go. But then I realized that Retrofit doesn’t know how to do gRPC. I looked for alternatives and saw that there were options with a library Wire from the developers of Retrofit. But it seemed unnecessary to me to introduce such a library for two gRPC methods into my course project. Therefore, I continued to search for simpler alternatives. As a result, I found a solution in the form of a standard library from Google.

If you use gRPC on the client, then you no longer need to write your own models for the server. Let's just add .proto files. They themselves generate the necessary models. These files are located in the module in which you placed .proto files in folder build.

Untitled

As a result, we have two classes. One serves as a data model, the other as a contract between the server and client. But these two files will not be generated just like that. You need to do a few things for this to work:

  1. add dependencies for gRPC and proto;

  2. add proto generation to gradle.

First, let's add dependencies to the version catalog.

[versions]
protobuf = "3.25.2"
protobufPlugin = "0.9.4"
kotlinx-serialization = "1.6.3"
grpc = "1.62.2"

[libraries]
# gRPC
grpc-okhttp = { module = "io.grpc:grpc-okhttp", version.ref = "grpc" }
annotations-api = { module = "org.apache.tomcat:annotations-api", version.ref = "annotationApi" }
grpc-protoc-gen-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" }
grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" }
protobuf-lite = { module = "io.grpc:grpc-protobuf-lite", version.ref = "grpc" }

# Protobuf
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }

[bundles]
grpc = [
    "grpc-stub",
    "grpc-okhttp",
    "grpc-protoc-gen-java",
    "annotations-api",
    "protobuf-lite",
]

[plugins]
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
protobuf-classpath = { id = "com.google.protobuf", version.ref = "protobufGradlePlugin" }

Next, let's enable code generation in gradle. Please note that you need to connect where it is located .proto file.

import com.google.protobuf.gradle.id

plugins {
    alias(libs.plugins.app.feature.domain)
    alias(libs.plugins.protobuf)
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.19.2"
    }
    plugins {
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:1.47.0"
        }
    }
    generateProtoTasks {
        all().forEach { task ->
            task.builtins {
                create("java") {
                    option("lite")
                }
            }
            task.plugins {
                create("grpc") {
                    option("lite")
                }
            }
        }
    }
}

android {
    namespace = "ru.popkov.android.core.feature.domain"
}

dependencies {
    implementation(libs.bundles.grpc)
    api(libs.protobuf.kotlin.lite)
}

Well, perhaps the most important thing is the repository in which the implementation of our gRPC methods is stored.

@AutoBind
@Singleton
class AuthRepository @Inject constructor(
    private val dataStore: Token,
) : AuthRepository {

    override suspend fun registerUser(registerRequest: AuthOuterClass.RegisterRequest): RegisterResponse {
        val channel =
            ManagedChannelBuilder.forAddress("10.0.2.2", 4040).usePlaintext().build()
        val client = AuthGrpc.newBlockingStub(channel)
        return client.register(registerRequest)
    }
}

The function accepts a request from the proto file generated by the class and returns proto model-response. In the method itself in the variable channel you need to connect to the server address, in my case it’s localhost. And then everything is quite simple, we establish a connection and “pull” the desired “handle”.

The request to the repository in the ViewModel itself might look something like this:

private suspend fun registerNewUser(
    phoneNumber: String,
    password: String
): Deferred<AuthOuterClass.RegisterResponse> {
    val request = AuthOuterClass.RegisterRequest.newBuilder()
        .setPhone(phoneNumber)
        .setPassword(password)
        .build()

    val handler = CoroutineExceptionHandler { _, throwable ->
        Timber.tag("Auth").d("exception: %s", throwable)
    }

    return viewModelScope.async(handler) {
        authRepository.registerUser(request)
    }
}

This is all the logic that needs to be written to work with gRPC in an Android application.

Conclusion

The result was a good MVP project. It has some sub-optimal moments and a couple of bugs. In the future, no longer as part of the coursework, I want to finish the project and possibly expand its functionality.

What I want to add:

  • support for animated giggles of authors;

  • short videos about literature on the screen of vertical clips

You can see how the application works here shorts. You can view the full source code of the server and client using the links below:

  1. mobile application source code;

  2. gRPC+REST server source code.

You can also look at the text explanatory note for course work.

Thank you for attention!

Denis Popkov

Middle Android developer at Live Typing

If you find inaccuracies/errors in the article or simply want to add your opinion to it, please comment! Or you can write to me on Telegram – t.me/MolodoyDenis.

Similar Posts

Leave a Reply

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