Blogg

Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på Twitter

Callista medarbetare Jesper Holmberg Callista medarbetare Marcus Cedergren Callista medarbetare Niclas Bentley Callista medarbetare Peter Larsson

KotlinConf 24

// Jesper HolmbergMarcus CedergrenNiclas BentleyPeter Larsson

KotlinConf 2024 ägde rum i Köpenhamn den 22-24 maj och var en stor succé. Med över 2 000 deltagare från hela världen, 102 talare och 77 sessioner, var konferensen en guldgruva av information och inspiration för alla som är intresserade av Kotlin. Från Callista var vi ett gäng som hade några inspirerande dagar i Köpenhamn - här kommer en första presentation av några intressanta föredrag.

kotlinconf24.jpg
Mingel på KotlinConf 2024 Köpenhamn

Exposed

Chantal Loncle från Jetbrains presenterade Exposed som är ett bibliotek för databasåtkomst byggt med Kotlin. Det fungerar som ett abstraktionslager och syftar till att förenkla interaktionen med databaser med bibehållen typkontroll. Projektet startades redan 2017 men har fram till 2023 drivits som ett sidoprojekt mer eller mindre av en ensam utvecklare. Ramverket har nu prioriterats upp rejält av Jetbrains som har ett team som arbetar aktivt med det. Exposed kan användas i två stilar - DSL (Domänspecifikt språk) och DAO (Data Access Object). Enkelt uttryckt innebär DSL-varianten att man använder en typsäker syntax som liknar SQL medan DAO-varianten ger möjlighet att utföra CRUD-operationer på entiteter mer i stil med andra ORM-ramverk som t ex Hibernate.

Lite kodexempel för att få en känsla för hur användandet av Exposed kan se ut. Först behöver man definera hur en tabell ser ut:

object StarWarsFilms : Table() {
    val id: Column<Int> = integer("id").autoIncrement()
    val sequelId: Column<Int> = integer("sequel_id").uniqueIndex()
    val name: Column<String> = varchar("name", 50)
    val director: Column<String> = varchar("director", 50)
    override val primaryKey = PrimaryKey(id)
}

Med DSL stilen kan man då tex hämta ut filmer enligt:

StarWarsFilms.selectAll().where { StarWarsFilms.sequelId eq 8 }.forEach {
    println(it[StarWarsFilms.name])
}

Skall man använda DAO mönstret behöver man också definera en entitetsklass:

class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
 companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
 var sequelId by StarWarsFilms.sequelId
 var name     by StarWarsFilms.name
 var director by StarWarsFilms.director
}

Med hjälp av denna kan man sedan t ex lägga till en ny post enligt:

val nextMovie = StarWarsFilm.new {
  name = "The Last Jedi"
  sequelId = 8
  director = "Rian Johnson"
}

Både DAO- och DSL-stilarna har sina egna fördelar och nackdelar, och valet beror på personliga preferenser och projektets specifika behov. Ett mer etablerat alternativ till Exposed (som nu är i version 0.5) är JOOQ som har ett betydligt rikare och moget api, men det skall bli spännande att se hur Exposed utvecklas nu när Jetbrains satsar på det. Läs mer om exposed på jetbrains.github.io/Exposed

Power-assert compiler plugin

Kort presentation av Brian Norman, Kotlin Compiler Core, Jetbrains.

Power-assert förenklar felsökning genom att skapa tydliga felmeddelanden (diagram) för “assert” funktioner. Detta är mycket användbart vid enhetstestning eller för att debugga utan krav på externa beroenden, och är dessutom kraftfullare än de vanliga testramverken för kotlin. Pluginen finns tillgänglig från och med Kotliin 2.0 och är för närvarande experimentell. Förutom kotlin.assert() kan fler funktioner konfigureras och det går även att konfigurera vilken del av kodbasen som ska omfattas (test, main, etc).

För varje assert anrop genereras ett diagram med variabler dess värden och logik, dvs. väsentligt kraftfullare än att bara rapportera om förväntat utfall.

Exempel:

// sample test from kotlin docs
class SampleTest {
    @Test
    fun testFunction() {
        val hello = "Hello"
        val world = "world!"
        assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
    }
}

// output without power-assert
Incorrect length

// output with power-assert
Incorrect length
assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
       |     |      |  |     |               |
       |     |      |  |     |               3
       |     |      |  |     orl
       |     |      |  world!
       |     |      false
       |     5
       Hello

// typical test framwork output (assertEquals)
Incorrect length
Expected :5
Actual   :3

Context parameters

Den stora nyheten på konferensen var naturligtvis att Kotlin 2.0 äntligen släppts i skarp version. Vi har väntat länge på att releasen skulle bli klar, men Kotlin 2.0 innebär i sig ganska få nyheter för oss utvecklare. Den stora betydelsen är istället att i och med den omfattande refaktorisering av kompilatorn som har skett, så har en grund lagts för många intressanta språknyheter som har inväntat 2.0-releasen för att kunna slutföras och inlemmas i kommande Kotlin-releaser.

En av de största av dessa språk-features är context parameters (som tidigare kallats context receivers). Dessa släptes i en tidig version redan i Kotlin 1.6, men p.g.a. arbetet med 2.0-kompilatorn har funktionen inte blivit slutgiltigt fastställd. På konferensen slog man fast att planen är att denna feature ska vara klar i och med 2.2-releasen, som vi realistiskt kan förvänta oss till våren nästa år.

Context parameters innebär i korthet att man i sin kod specificerar att man måste ha en instans av en viss klass som en kontext i den körbara koden. Det kan liknas vid implicits i Scala, eller kanske AOP i Java: det är ett lättarbetat sätt att specificera att funktionalitet som oftast är ortogonal mot vad affärskoden egentligen utför ska kunna användas i den specificerade kontexten. Typiska exempel på när det här kan komma att vara användbart är t.ex. att ange att loggnings-funktionalitet måste vara tillgänglig, eller att kod måste utföras inom ramen för en transaktion. Den stora fördelen med en sådan här kodkonstruktion är att utvecklaren får hjälp av kompilatorn att garantera att koden är korrekt.

En grupp som kanske mer ivrigt än andra väntar på att context parameters ska bli slutfört är användarna av det välkända Arrow-biblioteket, som möjliggör många intressanta funktionella lösningar i Kotlin-kod.

Här är ett exempel (hämtat från Functional Error Handling in Kotlin).

Anta att vi har följande interface definierat (från Arrow-biblioteket):

public interface Raise<in Error> {
    public fun raise(r: Error): Nothing
}

Om vi har en implementation av detta interface tillgängligt, kan vi göra det möjligt anropa raise-metoden med ett Error, vilket fyller ungefär samma funktion som att kasta en Exception i traditionell kod. Vi måste ha ett sådant objekt tillgängligt i den kontext vi arbetar, vilket vi kan ange genom en context parameter som nedan:

interface Jobs {
    context (Raise<JobError>)
    fun findById(id: JobId): Job
}

Ovanstående kod kan tolkas som att “vi kan bara använda metoden findById om vi har ett objekt av typen Raise i vår runtime-kontext”. Med andra ord specificerar vi med hjälp av kompilatorn att när findById anropas så har vi möjlighet att lyfta ett Error i vår Raise-kontext.

Den efterfrågade kontexten kan man t.ex. skapa med standard-konstruktionen with(…):

with(Raise<DbError>()) {
    return jobsImpl.findById(id)
}

Med användningen av context parameters som ovan får vi ett mycket smidigt sätt att med hjälp av kompilatorn indikera att Errors av en viss typ kan skapas i den anropade koden, och se till att dessa Errors hanteras korrekt i den yttre, anropande koden.

Building libraries for the next 25 years

Presentation av Martin Bonnin, maintainer av Apollo Kotlin

Som utvecklare av ett funktionsbibliotek är det viktigt att noggrant överväga namnet på biblioteket. Ett bra namn är avgörande och bör:

  • Berätta en historia
  • Vara unikt
  • Lätt att komma ihåg
  • Lätt att uttala

Att utveckla ett funktionsbibliotek kan innebära andra överväganden än den dagliga utvecklingen. Du bör till exempel vara noga med att överväga eventuella förändringar i API:t, eftersom dessa kan påverka många användare om biblioteket är brett använt. Source breaking kan vara bra att tänka på.

Source breaking changes

Exempel:

fun greet (name: String) : String {
    return "Hello $name"
}

Om funktionen ändras till:

fun greet (nickName: String) : String {
    return "Hello $nickName"
}

En sådan ändring kommer att orsaka kompileringsfel för användare som använder “named arguments”:

fun main() {
    greet(name =  "kotlinConf")
}

Som utvecklare av biblioteksfunktioner ställs du inför många frågeställningar. Här får du bra vägledning:

https://kotl.in/api-guide

Publicering

Sedan JCenter avvecklades 2021, är bästa stället att publicera ditt bibliotek på Maven Central. Maven Central erbjuder:

  • Kostnadsfri publicering
  • Oföränderligt (immutable) innehåll
  • Verifiering av ägandeskap
  • Kontroll av licenser och signering

Gradle Maven Publish Plugin https://github.com/vanniktech/gradle-maven-publish-plugin är ett användbart verktyg och automatiserar publicering till Maven Central. Den hjälper även till med signering och varnar om API:t för ditt bibliotek kommer att brytas.

Tack för att du läser Callistas blogg.
Hjälp oss att nå ut med information genom att dela nyheter och artiklar i ditt nätverk.

Kommentarer