I’d love if some of you would give me some feedbac...
# getting-started
d
I’d love if some of you would give me some feedback about this project I am doing just as a server for practicing Kotlin 🙂 Let me know if you spot any bad practice in the source code, happy to receive some constructive feedback!
k
??? The link is dead
😕 1
s
@Damiano Petrungaro is ketabs maybe a private project?
d
@Stephan Schroeder @Kirill Grouchnikov yes it was a private project, my bad! Now it's public 😅
s
• first impression: looks very advanced! (anybody who used data/value/sealed classes and Arrow is clearly advanced) •
InvalidEmail
could be a Singleton
Copy code
object InvalidEmailException: IllegalArgumentException(""Email must be a valid email address"")
• isn't
Email.isEqual
identical to the autogenerated equals-method!? 🤔 (is it meant to be
override fun equals
?? ) • you definitely have a very functional style, you avoid throwing exceptions by using Either all the time, but the code is definitely less concise because of it. E.g. this is what
Email
would look like in a non-functional style
Copy code
private val validEmailFormat =
            """(?:[a-z0-9!#${'$'}%&'*...\x0c\x0e-\x7f])+)\])""".toRegex()

@JvmInline
value class Email(val s: String) {
    init {
        require(!validEmailFormat.matches(s)) {"Email must be a valid email address"}
    }

    override fun toString(): String = s
}
• if you overwrite
toString()
only because you want to be able to access your private wrapped value, i'd suggest make the value public and drop your
toString()
• you could just use `runCatching{...}`to wrap the instanciation of value types (if you go my path so that instanciating them would throw an IllegalArgumentException) and getting a functional
Result
type So yeah, the code looks good, maybe a little much of ceremony, but that might just come with Arrow (which I haven't used yet).
d
Thanks for the feedback @Stephan Schroeder ! 3 days well spent learning Kotlin if you feel it's not that bad :) I agree that the code could have been more concise but the “functional” part was actually what made me like Kotlin :) so I was kinda forcing that :) I'll make a couple of commits to address what you suggested! Do you have any suggestions related to the routes and services I did? I am kinda trying to use a “pipe like” style but there's no pipe support in Kotlin so I have to handle “either” in a chain of
.let
which I feel can be simplified maybe
s
I come from Java, so even "base" Kotlin with it's first class functions, its nudge to immutability and it's easy to use collection api (it equivalent to Java's streaming api, but more concise and adapted to nullability) is more functional than I'm used to. I know of Arrow, but Arrow brings Kotlin on a whole other functional programming level, one that is too functional for me, because I prefer "Kotlin as a way better Java" over "Kotlin as a (close to) pure functional programming language". But from my not so functional standpoint chaining the collection api sounds (at least) similar to what you describe as pipe-like?!?
Copy code
val evenNumbers = listOf("2", "5", "13", "28", null).filterNotNull().map{it.toInt()}.filter{it%2==0}
If that's what you're interested, take a look at the functions on the Collection-Interface: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/#functions I haven't much experience with Ktor, so no tips there.
🙌 1
d
Thanks mate! Appreciate your feedback!
s
So let's see, what functional ways are used in "base" Kotlin: • there is a Result type in the api https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/ (but at my place of work we actually use a more functional version: https://github.com/michaelbull/kotlin-result/ ,so not completely "base" 😅, but not full Arrow ) • you don't have
Either
, but you do have
Pair
and
Triple
. I've never used
Triple
but
Pair
is used implicitly all the time thanks to the
to
-function which returns one
Copy code
val pair: Pair<String, Int> = "one" to 1
// or more common hidden in
val map = mapOf(
  "one" to 1,
  "two" to 2,
)
For other sum types you'd just construct them via sealed classes/interfaces. (Which comes probably from Java's nominal typing over structural typing aka "it's better to give structures a name") • plus the 1class-methods/lambdas and collection-api I already mentioned • nudge to immutability via
val
(next to
var
), immutable Collection-Interfaces and -one level up- completly immutable collections via https://github.com/Kotlin/kotlinx.collections.immutable
🙌 1
d
@Stephan Schroeder I refactored some of the code! Also the routes and the services now I think they are less verbose and more functional tho, let me know if you have any further feedback 😄
s
you seem to have a misconfiguration in your dependencies:
Copy code
dependencies {
    implementation("io.ktor:ktor-auth:1.6.8")
    implementation("io.ktor:ktor-auth-jwt:1.6.8")
    implementation("io.ktor:ktor-server-core:1.6.8")
    implementation("io.ktor:ktor-server-netty:1.6.8")
    implementation("io.ktor:ktor-serialization:1.6.8")
    ...
    testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.8")
    testImplementation("io.ktor:ktor-server-test-host:1.6.8")
}
you used the ktor-version so much, that you also used it for
kotlin-test
but it seems to use the same version as kotlin itself and there isn't an kotlin-version
1.6.8
, you most likely want
1.6.10
because that's the version you use for kotlin in the plugins block. There are several approaches to versioning variables (for versions that appear more than once), the easiest of which would be to define one within the dependency block:
Copy code
dependencies {
    val ktorVersion = "1.6.8"
    implementation("io.ktor:ktor-auth:$ktorVersion")
    implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
    implementation("io.ktor:ktor-server-core:$ktorVersion")
    implementation("io.ktor:ktor-server-netty:$ktorVersion")
    implementation("io.ktor:ktor-serialization:$ktorVersion")
    ...
    testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.10")
    testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
}
This works nicely for single module projects but you can't share these variables with the plugin block, which is a shame, because a shared
val kotlinVersion = "1.6.10"
surely would be useful here. For that you'd need a buildSrc-folder/approach, but it's more tricky to setup, but you can check out this demo project here: https://github.com/simon-void/ktor_serialization_demo Within the buildSrc-folder you define a singleton containing all the dependency info you need:
Copy code
object Deps {
    const val kotlinVersion = "1.6.10"
}
which you than can use throughout your gradle-config
Copy code
plugins {
    application
    kotlin("jvm") version Deps.kotlinVersion
    id("org.jetbrains.kotlin.plugin.serialization") version Deps.kotlinVersion
}

dependencies {
    ...
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:${Deps.kotlinVersion}")
}