Monday, July 24, 2023

Going one step further with Kotlin & gRPC

Recently, I tried using Quarkus with Kotlin for grpc.

I have worked with grpc for communication between microservices in Java & Golang.

But with Kotlin, I don't recall having set it up myself or tweaked a thing or two besides adding a few fields to the protobuf messages as part of syncing with dependent systems.

Most importantly, I had never streamed data using the stream keyword in protobuf file.

Initially it seemed very overwhelming to set up things and get running because originally Quarkus was probably made to work with Java and then ported for Kotlin.

There are 2 things one would ever want to do with gRPC:

1. Implement a new microservice that others can call & get data from

2. Call an existing microservice to get data from it, also called consuming a grpc service.

Quarkus Docs has samples for both at :

1. https://quarkus.io/guides/grpc-service-implementation

2. https://quarkus.io/guides/grpc-service-consumption

But the former only contains a blocking implementation, a non-blocking implementation is missing and not so easy to find elsewhere.

As i read more about streaming, I realized that bidirectional streaming is also possible such as a chat app.

import "google/protobuf/timestamp.proto";
service VenueService {
rpc listVenues(Location) returns (stream Venue) { }
rpc RecordTrip(stream Location) returns (TripSummary) {}
rpc chat(stream Comment) returns (stream Comment) {}
}
message Comment {
   string text = 1;
   google.protobuf.Timestamp sentTime = 3;
}

There's more to add to this, with a different example, perhaps a hotel reservation. This post shall be updated soon with more details.

References:

  1. https://genekuo.medium.com/developing-a-rest-service-with-quarkus-and-deploying-to-minikube-c956b9ac900f
  2. https://medium.com/@georgeleung_7777/seamless-backpressure-handling-with-grpc-kotlin-a6f99cab4b95
  3. https://codingwithmohit.com/grpc/grpc-kotlin-coroutines/
  4. https://techdozo.dev/grpc-synchronous-and-asynchronous-unary-rpc-in-java/
  5. https://techdozo.dev/grpc-interceptor-unary-interceptor-with-code-example/
  6. https://techdozo.dev/grpc-synchronous-and-asynchronous-server-streaming-rpc/
  7. https://techdozo.dev/grpc-synchronous-and-asynchronous-unary-rpc-in-java/
  8. https://techdozo.dev/grpc-bidirectional-streaming-with-code-example/
  9. https://www.baeldung.com/java-grpc-streaming

Sunday, July 31, 2022

Learn the same old thing again - Kotlin

 As you might have guessed, I got very busy between March 2021 & March 2022. In fact I have been very busy at work & play between March 2020 & January 2022. Remote work has its own challenges. But work from office is even more hassle.

The last time I learned Kotlin was in July 2019. 3 years later, I am ready to learn it again with all the new features but for backend software development this time. For me, Kotlin is a combination of 4 languages now - Java, Python, Golang & Swift.

Kotlin is so much fun, because we get to write so many "fun"s & the capability to write extensions on top of them.


One feature I stumbled across and got surprised at is the ability to use data types as variable names as you can see in the example above.

Code:
fun main() {
    var MSG = "Hello"
    val Int = 5
    val String = "World"
    val Boolean = false
    println(Int)
    println(String)
    println(!Boolean)
    MSG+=','
    var greeting = "$MSG world!"
    println(greeting)
}

Output:
5
World
true
Hello, world!

Functions are fun

A function with two Int parameters and Int return type.

A function body can be an expression. Its return type is inferred.

A function that returns no meaningful value.

Unit return type can be omitted.

In Kotlin, if can also be used as an expression.

There is a new thing worth learning now. It is called the "when" expression. Seems pretty similar to "switch" case.
fun sum(a: Int, b: Int): Int = a + b
fun sayHello(): Unit = println("Hello")
fun isPositive(number: Int): Boolean = number > 0

Return type can be inferred and therefore omitted when feasible

fun sum(a: Int, b: Int) = a + b // Int
fun sayHello() = println("Hello"// Unit
fun isPositive(number: Int) = number > 0 // Boolean

If you don't like for, while & do-while, or find it all too confusing, here is a new way to use loops - repeat

fun main() {
    repeat(3) {
        println("Hello")
    }
}
fun main() {
repeat(5) {
println("Kotlin")
}
}

If you don't like writing too much to make code modular, put functions aside, make way for lambda expressions.

fun(a: Int, b: Int): Int {
    return a * b
}

{ a: Int, b: Int -> a * b }
To invoke it inline,
println({ a: Int, b: Int -> a * b } (5,3))
We can assign the function to a variable and then invoke it by invoking the variable.

val mul1 = fun(a: Int, b: Int): Int {
    return a * b
}

val mul2 = { a: Int, b: Int -> a * b }

println(mul1(23))  // prints "6"
println(mul2(23))  // prints "6" too

Lambda can help save lines of code as well as impact code readability.

fun isNotDot(c: Char): Boolean = c != '.'
val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter(::isNotDot)


val originalText = "I don't know... what to say..."val textWithoutDots = originalText.filter({ c: Char -> c != '.' })
val textWithoutDots = originalText.filter(){ c -> c != '.' }val textWithoutDots = originalText.filter { c -> c != '.' }val textWithoutDots = originalText.filter { it != '.' }

fun sum(a: Int, b: Int): Int = a + b
val mul2 = { a: Int, b: Int -> a * b }

Functions can be returned as objects

fun identity(x: Int) = x
fun half(x:Int) = x/2
fun zero(x:Int) = 0

fun generate(functionName: String): (Int) -> Int {
if (functionName == "identity") return ::identity
else if (functionName == "half") return ::half
else return ::zero//if (functionName == "zero")
}

fun composition(value: Int, y: (Int) -> Int, g: (Int) -> Int): Int {
return y(g(value))
}

Double Dots & IN for Ranges
Kotlin allows generating ranges and checking inclusion in ranges using .. & in.

If we need to check that a value is not within a range, we can just add ! (not) before in.

val within = 10 !in 5..50 // true
val notWithin = 100 !in 10..99 // true

We may combine ranges using standard logical operators.

val within = c in 5..10 || c in 20..30 || c in 40..50

You can assign a range to a variable and use it later.

val range = 100..200
println(300 in range) // false

In addition to integer ranges, we can also use ranges of characters and even strings (according to dictionary order).

println('b' in 'a'..'c'// true
println('k' in 'a'..'e'// false

println("hello" in "he".."hi"// true
println("abc" in "aab".."aac"// false

That's gonna be some fun for permutations & combinations.

When in dots, have fun!

val result = when (op) {
    "+" -> a + b
    "-" -> a - b
    "*" -> a * b
    else -> "Unknown operator"
}
println(result)
when (op) {
    "+""plus" -> println(a + b)
    "-""minus", -> println(a - b) // trailing comma
    "*""times" -> println(a * b)
    else -> println("Unknown operator")
}
when (op) {
    "+""plus" -> {
        val sum = a + b
        println(sum)
    }
    "-""minus" -> {
        val diff = a - b
        println(diff)
    }
    "*""times" -> {
        val product = a * b
        println(product)
    }
    else -> println("Unknown operator")
}
println(when(op) {
    "+" -> a + b
    "-" -> a - b "*" -> a * b
    else -> "Unknown operator"
})
when (n) {
    0 -> println("n is zero")
    in 1..10 -> println("n is between 1 and 10 (inclusive)")
    in 25..30 -> println("n is between 25 and 30 (inclusive)")
    else -> println("n is outside a range")
}
when {
        n == 0 -> println("n is zero")
        n in 100..200 -> println("n is between 100 and 200")
        n > 300 -> println("n is greater than 300")
        n < 0 -> println("n is negative")
}
    when(readln().toInt()) {
in 0..9 -> println(1)
in 10..99 -> println(2)
in 100..999 -> println(3)
in 1000..9999 -> println(4)
}

fun Request.getBody() = when (val response = executeRequest()) { is Success -> response.body is HttpError -> throw HttpException(response.status) }

Functions can be passed as parameters to other functions
    fun odd(x: Int): Boolean = x % 2 == 1
    println(odd(6)) // => false
    println(odd(7)) // => true

// If the return type can be inferred then we don't need to specify it.
    fun even(x: Int) = x % 2 == 0
    println(even(6)) // => true
    println(even(7)) // => false

// Functions can take functions as arguments and return functions.
    fun not(f: (Int) -> Boolean): (Int) -> Boolean {
        return {n -> !f.invoke(n)}
    }
// Named functions can be specified as arguments using the :: operator.
    val notOdd = not(::odd)
    val notEven = not(::even)
    // Lambda expressions can be specified as arguments.
    val notZero = not {n -> n == 0}
Extension functions
fun String.remove(c: Char): String {
        return this.filter {it != c}
    }
println("Hello, world!".remove('l')) // => Heo, word!

Going one step further with Kotlin & gRPC

Recently, I tried using Quarkus with Kotlin for grpc. I have worked with grpc for communication between microservices in Java & Golang. ...