NEXT Insurance is a US based insurtech startup, revolutionizing the small business insurance industry. NEXT was founded 6 years ago and ever since we have been building our microservices in Kotlin. During this period we grew from a small startup with one backend developer(myself) to a $4B company with 150 backend developers. We have written over 1.2M lines of code in Kotlin and aquired long mileage with this programming language. In this talk I am going to share our experiences, insights and pains.
This talk was given in JFokus 2022
14. NEXT
14
● Working flawlessly in production
● Smooth Onboarding of Java
developers
● Good IDE support
● Community Adoption is growing
● Selling point for hiring
24. NEXT
Main Tech Stack Components
AWS EC2
K8s
JVM
Kotlin
Dropwizard
Jetty Jersey Logback JDBI
Guice
JUNIT
Flyway
RDS
Kotlin
Script
24
Jackson
25. NEXT
25
Data classes - The Prince that was Promised
data class AddressInfo(
val telephone: String,
val street: String,
val num: Int,
val city: String,
val zipCode: String,
val state: State?
)
● Widely used >6100 data classes
● Used for DTO, DAO
● Rest API Serialization / deserialization with jackson
● hashcode/equals/(se|ge)tters saves a lot of boilerplate
● copy() helps working with immutable objects
26. NEXT
26
Data Classes - Jackson Serialization
fun ObjectMapper.applyNiSettings(): ObjectMapper {
27. NEXT
27
Data Classes - Jackson Serialization
fun ObjectMapper.applyNiSettings(): ObjectMapper {
return this
.registerModule(KotlinModule())
28. NEXT
28
Data Classes - Jackson Serialization
fun ObjectMapper.applyNiSettings(): ObjectMapper {
return this
.registerModule(KotlinModule())
.configure(
DeserializationFeature
.FAIL_ON_UNKNOWN_PROPERTIES, false)
29. NEXT
29
Data Classes - Jackson DeSerialization
fun ObjectMapper.applyNiSettings(): ObjectMapper {
return this
.registerModule(KotlinModule())
.configure(
DeserializationFeature
.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(
DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,
true) }
30. NEXT
30
The Promise of Null Safety
val productVersion:String = versions[versionId]
Map<String,Sting>
31. NEXT
31
The Promise of Null Safety
val productVersion:String? = versions[versionId]
Map<String,Sting>
32. NEXT
32
The Promise of Null Safety
val productVersion:String = versions[versionId]!!
NullPointerException
33. NEXT
33
The Promise of Null Safety
val productVersion:String =
versions.require(versionId)
A better
NullPointerException
34. NEXT
34
The Promise of Null Safety
val productVersion:String = versions[versionId]!!
require(productVersion.status == Staged) {
"Version $versionId: Can't unstage status…" }
versions[versionId] = productVersion.copy(
status = ProductVersionStatus.Draft,
updatedBy = updatedBy)
35. NEXT
35
The Promise of Null Safety
● Null Safety Mechanisms baked in the type system
● No more NPEs ?
● ( not really) >11052 invocations of !!
● Where do they come from
○ interacting with Java based third party libraries
○ Getting values from maps
37. NEXT
37
Operator Overloading
● 155 overloads (plus,minus,times,get,invoke)
● Most usages makes sense
operator fun times(factor: BigDecimal) = …
operator fun plus(toAdd: AmountWithAttribution) = …
operator fun minus(toSubtract: AmountWithAttribution) =
operator fun compareTo(other: AmountWithAttribution) =
42. NEXT
42
Extension Functions - my preferance
private fun isVersionLowerThan(
thisVersion:String , thatVersion: String) =
…
43. NEXT
43
Extension Functions
data class WhateverDTO(val state: State,
@field:NotBlank val zipCode: String,
@field:NotBlank val city: String,
@field:NotBlank val streetAddress: String
)
44. NEXT
44
Extension Functions
data class WhateverDTO(val state: State,
@field:NotBlank val zipCode: String,
@field:NotBlank val city: String,
@field:NotBlank val streetAddress: String
)
fun WhateverDTO.getAsParamValue() =
"${streetAddress}$EOL${city}, ${state.dbName} $zipCode"
49. NEXT
49
Coroutines : launch
fun CoroutineScope.launchWithContext(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job =
launch(MDCContext() + context, start, block)
also asyncWithContext,runBlockingWithContext, flowOnWithContext
50. NEXT
50
Type Aliases
From Kotlin documentation:
Type aliases provide alternative names for existing types. If the type name is
too long you can introduce a different shorter name and use the new one
instead.
It's useful to shorten long generic types. For instance, it's often tempting to
shrink collection types:
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K,
MutableList<File>>
51. NEXT
51
Type Aliases - The feature we use too much
typealias AffiliateId = Int
typealias AgencyId = Int
typealias AgencyAggregatorId = Int
typealias AgentId = String
typealias BundleId = Int
typealias BusinessId = String
.
.
.
// additional 50 type aliases
Controversial in the community as
well
○ https://medium.com/@amlcurran/a
voiding-primitive-obsession-in-swift
-5325b65d521e
○ https://medium.com/@jerzy.chalup
ski/kotlin-the-missing-parts-67645d
9a02f4
52. NEXT
52
typealias ZipCode = String
typealias Address = String
fun main() {
var zip: ZipCode = "56934"
val addr: Address = "bb"
zip = addr
}
Type Aliases - Are not Type Safe
53. NEXT
53
abstract class SomeClassResponse(
open val success: Success,
open val responseMessage: ResponseMessage)
Type Aliases - What is behind the magic
typealias Success = Boolean
typealias ResponseMessage = String
55. NEXT
55
@Deprecated("Stop using me")
data class DeprecateMe(val a: String, val b: String)
typealias IamNotDeprecatedAndILikeIt = DeprecateMe
fun main() {
val a = DeprecateMe("a", "b")
val c = IamNotDeprecatedAndILikeIt("a", "b")
Type Aliases - Hides Deprecation
56. NEXT
56
private typealias ValidationResults =
List<Pair<OTPValidationResponse, DigitsPasswordData>>
Put Type Aliases to Good Use
typealias Row = Array<String>
typealias StringMatrix = List<Array<String>>
57. NEXT
57
@JvmInline
value class NiJexlExpression(
val expressionString: String)
Inline Classes to the rescue
● Prefer inline classes instead of typealias
● Type safety at compilation
● Migration is not so easy.
● There is a problem with inline classes and our current Jackson Version
● Type erased at runtime ….(Heap Dumps ?)
59. NEXT
59
Migrations along the Years
● Migration to Kotlin 1.1.0 ( Java8 support ) was smooth
● Migration to (almost) every version of kotlin immediately
● Migration to Java 11 – only a year ago )
● Migration to Java 17 – 5 months ago
61. NEXT
Proprietary
&
Confidential
NEXT
Kotlin - Java Bytecode Compatibility
● Kotlin 1.4.10 supports Java 14 Bytecode (class version)
● Kotlin 1.4.21 supports Java 15 Bytecode (class version)
● Kotlin 1.5.21 supports Java 16 Bytecode (class version)
● By the time java 17 was released 1.7 bytecode was not
supported
61
Kotlin
62. NEXT
NEXT
Gradle - Java bytecode Compatibility
Gradle
https://docs.gradle.org/current/userguide/compatibility.html 62
63. NEXT
Kotlin API deprecation
@Deprecated("Use minOrNull instead."
,
ReplaceWith("this.minOrNull()"))
@DeprecatedSinceKotlin
(warningSince = "1.4", errorSince =
"1.5", hiddenSince = "1.6")
@SinceKotlin("1.1")
public fun Iterable<Double>.min(): Double? {
return minOrNull()
}
min()
max()
minBy()
maxBy()
minOrNull()
maxOrNull()
minByOrNull()
maxByOrNull()
63
64. NEXT
Detaching gradle compiler version
64
val kotlinVersion = libs.findVersion("kotlin").get()
// 1.6.10 opposed to 1.5.31 from gradle
implementation("org.jetbrains.kotlin:kotlin-gradle-plug
in:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-script-runt
ime:$kotlinVersion")