Improving Kotlin's internal with ArchUnit
Tags: kotlin, architecture, java
Published: 2020-05-26 16:06:57

Kotlin is an awesome language that enables developers to write more concise and safer code. However, it has some weak spots [[1]] [[2]]

Another of its pain points is that Kotlin lacks real package-private scope and `internal` visibility modifier in Kotlin is weaker than the package-private access level in Java. [[3]]

In Java, class marked with package-private access level is visible only within the same package. On the other hand, Kotlin's internal modifier makes class visible within the same source set of files compiled together. This makes an internal modifier much broader than its Java counterpart.

Using an internal modifier and creating a separate module is cumbersome compared to using package scope back in Java.

So maybe we can make an internal modifier more strict? 

It turns out we can use the [ArchUnit][archunit] library to achieve that goal. ArchUnit checks the architecture of code during unit tests and can be easily extended using custom rules.

We can create a rule that finds all internal Kotlin classes in our application and check if they're accessing other packages.

Let's start with checking if we deal with Kotlin class and whether this class is internal. Let's define two [extension functions][extension] on `Class`:

```kotlin
private fun Class<*>.isKotlinClass() = this.declaredAnnotations.any {
    it.annotationClass.qualifiedName == "kotlin.Metadata"
}

private fun Class<*>.isInternal() = this.kotlin.visibility == KVisibility.INTERNAL
```

Then, we need to perform two checks: 

a) [no public classes are accessing internal classes residing in subpackages][lower]
b) [no internal classes are accessing classes residing in outer packages][upper]

Having Kotlin internal detection present and both rules we can wire everything together using ArchUnit powered unit tests:

```kotlin
    @Test
    fun `limit kotlin-internal scope`(){

        val nonInternalKotlinClasses = ClassFileImporter()
            .importPackages("com.acme")
            .that(isKotlinNotInternal())

        noClasses()
            .should(accessClassesThatResideInASubpackage())
            .check(nonInternalKotlinClasses)

        val internalKotlinClasses = ClassFileImporter()
            .importPackages("com.acme")
            .that(isKotlinInternal())

        noClasses()
            .should(accessClassesThatResideInAnOuterPackage())
            .check(internalKotlinClasses)
    }
```
The downside of using ArchUnit is that you know that something went wrong during the tests instead of compile time. Although by applying those checks you are sure that internal classes are subject to more strict rules than offered by Kotlin.

Using ArchUnit to perform internal visibility checks together with [improved IDE support][4] will greatly help in building modularized Kotlin applications.

Source code for checks and example project can be found here: https://github.com/kkocel/archunit-kotlin-internal


[1]: https://youtrack.jetbrains.com/issue/KT-6653
[2]: https://youtrack.jetbrains.com/issue/KT-20781
[3]: https://youtrack.jetbrains.com/issue/KT-29227
[4]: https://youtrack.jetbrains.com/issue/KT-32886
[archunit]: https://www.archunit.org/
[lower]: https://github.com/kkocel/archunit-kotlin-internal/blob/master/src/test/java/tech/kocel/kotlin/archunit/internal/KotlinInternalDependencyRules.java#L45
[upper]: https://github.com/kkocel/archunit-kotlin-internal/blob/master/src/test/java/tech/kocel/kotlin/archunit/internal/KotlinInternalDependencyRules.java#L25
[extension]: https://kotlinlang.org/docs/reference/extensions.html#extension-functions