Slide 1

Slide 1 text

Kotlin multiplatform + Domain models = ❤ Nico Krijnen Niels Marsman

Slide 2

Slide 2 text

Nico Krijnen & Niels Marsman Nico Krijnen Niels Marsman

Slide 3

Slide 3 text

Moving fast & Focus on innovation It's all about

Slide 4

Slide 4 text

What is a domain model? Need to look at business logic Decisions made by code that are specific to the domain of the app Cargo shipping Airline booking Education Insurance Medical research

Slide 5

Slide 5 text

What is a domain model? Need to look at business logic Decisions made by code that are specific to the domain of the app Cargo shipping Airline booking Education Insurance Medical research

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage

Slide 9

Slide 9 text

Routes Services Data Access

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Routes Services Data Access UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage Where is the business logic?

Slide 13

Slide 13 text

Routes Services Data Access UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage Where is the business logic?

Slide 14

Slide 14 text

class LegoSetRentalService(private val legoSetRentalDAO: LegoSetRentalDAO) { // . .. fun rent(set: LegoSet, duration: Duration) { var price = set.basePrice price *= when (set.condition) { SetCondition.MINT -> 1.0 SetCondition.GOOD -> 0.9 SetCondition.FAIR -> 0.75 SetCondition.POOR -> 0.5 } price *= when { duration.days >= 30 - > 0.8 duration.days >= 7 -> 0.9 else -> 1.0 } legoSetRentalDAO.insert(LegoSetRental(set, duration, price)) } } Where is the business logic?

Slide 15

Slide 15 text

class LegoSetRentalService(private val legoSetRentalDAO: LegoSetRentalDAO) { // . .. fun rent(set: LegoSet, duration: Duration) { var price = set.basePrice price *= when (set.condition) { SetCondition.MINT -> 1.0 SetCondition.GOOD -> 0.9 SetCondition.FAIR -> 0.75 SetCondition.POOR -> 0.5 } price *= when { duration.days >= 30 - > 0.8 duration.days >= 7 -> 0.9 else -> 1.0 } legoSetRentalDAO.insert(LegoSetRental(set, duration, price)) } } Where is the business logic?

Slide 16

Slide 16 text

class LegoSetReturnService( ... ) { fun returnSet(set: LegoSet, damageReport: DamageReport) { requireNotNull(set.currentRental) { "Set is not currently rented" } val condition = when { damageReport.missingPieces > set.totalPieces.value * 0.05 -> SetCondition.POOR damageReport.missingPieces > 0 - > SetCondition.FAIR else - > set.condition } val status = when { condition = = SetCondition.POOR - > RentalStatus.MAINTENANCE else - > RentalStatus.AVAILABLE } legoSetRentalDAO.insert(LegoSetReturn(set, condition, status)) } } Where is the business logic?

Slide 17

Slide 17 text

class LegoSetReturnService( ... ) { fun returnSet(set: LegoSet, damageReport: DamageReport) { requireNotNull(set.currentRental) { "Set is not currently rented" } val condition = when { damageReport.missingPieces > set.totalPieces.value * 0.05 -> SetCondition.POOR damageReport.missingPieces > 0 - > SetCondition.FAIR else - > set.condition } val status = when { condition = = SetCondition.POOR - > RentalStatus.MAINTENANCE else - > RentalStatus.AVAILABLE } legoSetRentalDAO.insert(LegoSetReturn(set, condition, status)) } } Where is the business logic?

Slide 18

Slide 18 text

class LegoSetReturnService( ... ) { fun returnSet(set: LegoSet, damageReport: DamageReport) { val condition = SetCondition.by(set, damageReport) val status = RentalStatus.by(condition) legoSetRentalDAO.insert(LegoSetReturnRequest(set, condition, status)) } } sealed interface SetCondition { data object Mint : SetCondition data object Good : SetCondition data object Fair : SetCondition data object Poor : SetCondition companion object { fun by(set: LegoSet, damageReport: DamageReport): SetCondition = when { damageReport.missingPieces > set.totalPieces.value * 0.05 -> Poor damageReport.missingPieces > 0 -> Fair else -> set.condition } } } Where is the business logic?

Slide 19

Slide 19 text

class LegoSetReturnService( ... ) { fun returnSet(set: LegoSet, damageReport: DamageReport) { val condition = SetCondition.by(set, damageReport) val status = RentalStatus.by(condition) legoSetRentalDAO.insert(LegoSetReturnRequest(set, condition, status)) } } sealed interface SetCondition { data object Mint : SetCondition data object Good : SetCondition data object Fair : SetCondition data object Poor : SetCondition companion object { fun by(set: LegoSet, damageReport: DamageReport): SetCondition = when { damageReport.missingPieces > set.totalPieces.value * 0.05 -> Poor damageReport.missingPieces > 0 -> Fair else -> set.condition } } } class LegoSetReturnService( ... ) { fun returnSet(set: LegoSet, damageRe requireNotNull(set.currentRental val condition = when { damageReport.missingPieces > -> damageReport.missingPieces > - > else - > set.condition } val status = when { condition = = SetCondition.PO - > else - > RentalStatus.AVAILAB } legoSetRentalDAO.insert(LegoSetR } } Where is the business logic?

Slide 20

Slide 20 text

Domain model? Code that makes your app unique and deals with your domain Routes Services Data Access UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage

Slide 21

Slide 21 text

Domain model? Domain model = only 20% of your code Routes Services Data Access UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage

Slide 22

Slide 22 text

Model = Simplification Each useful to solve a specific problem

Slide 23

Slide 23 text

Domain Model Stub Stub Stub Domain Model Domain Model Domain Model

Slide 24

Slide 24 text

Domain Model Domain Model Domain Model Domain Model Stub Stub Stub

Slide 25

Slide 25 text

Kotlin Multiplatform!

Slide 26

Slide 26 text

Kotlin Multiplatform!

Slide 27

Slide 27 text

Kotlin Multiplatform! Kotlin Compiler JVM JavaScript Android TypeScript (preview) Android NDK iOS watchOS tvOS macOS Windows Linux WebAssembly

Slide 28

Slide 28 text

Domain Model Domain Model Domain Model Domain Model Domain Model

Slide 29

Slide 29 text

Ingredients of succes

Slide 30

Slide 30 text

Domain Model = Encapsulated

Slide 31

Slide 31 text

Hexagonal / Union Architecture

Slide 32

Slide 32 text

Dependencies only point inward

Slide 33

Slide 33 text

✅ Pure Kotlin + Stdlib ❌ JVM apis or libraries ❌ JS apis or libraries ❌ Native apis or libraries Domain model = Free of dependencies

Slide 34

Slide 34 text

Hexagonal / Union architecture ✅ Isolated, pure-code domain model ✅ No framework code mixed in ✅ Easy to read and understand ✅ Easy to test ✅ Always-valid Immutable

Slide 35

Slide 35 text

In action

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

@Composable private fun LegoSetDetailScreen( legoSet: LegoSet, onBackClick: () -> Unit, onRentClick: (Duration) -> Unit, modifier: Modifier = Modifier ) { Scaffold( topBar = { TopAppBar( title = { Text("Set Details") }, navigationIcon = { IconButton(onClick = onBackClick) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") } }, actions = { IconButton(onClick = { legoSet.toggleFavorite() }) { Icon( if (legoSet.isFavorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder, "Favorite" ) } } ) } ) .. . Kotlin / UI

Slide 38

Slide 38 text

Column( modifier = Modifier .padding(16.dp) .fillMaxWidth() ) { Box( modifier = Modifier .fillMaxWidth() .height(300.dp) ) { HorizontalPager( state = pagerState, contentPadding = PaddingValues(bottom = 0.dp), modifier = Modifier.fillMaxSize() ) { page -> // Image Carousel } } } Text( text = legoSet.name, style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically ) { Text( text = "Set #${legoSet.id} • Ages ${legoSet.ageRecommendation}", style = MaterialTheme.typography.body1 ) } Kotlin / UI

Slide 39

Slide 39 text

class LegoSet( .. . ) { fun calculateRentalPrice(duration: Duration): Money { . .. } fun returnSet(missingPieces: Int, damageReport: String? = null) { . .. } fun canBeRentedTo(age: Int, duration: Duration): RentalEligibility { . .. } fun performMaintenance(replacedPieces: Int) { . .. } } Kotlin / Domain Model

Slide 40

Slide 40 text

fun calculateRentalPrice(duration: Duration): Money { var price = basePrice / 7.0 * duration.days.toDouble() // Condition affects price val conditionMultiplier = when (condition) { SetCondition.MINT - > 1.0 SetCondition.GOOD - > 0.9 SetCondition.FAIR - > 0.75 SetCondition.POOR - > 0.5 } price = price * conditionMultiplier // Duration discounts price = when { duration.days >= 30 - > price * 0.8 // 20% discount for monthly rentals duration.days >= 7 -> price * 0.9 // 10% discount for weekly rentals else -> price } // Premium for high-value sets if (replacementValue > USD(200.0)) { price = price * 1.2 } return price } Kotlin / Domain Model

Slide 41

Slide 41 text

fun canBeRentedTo(age: Int, duration: Duration): RentalEligibility { if (status != RentalStatus.AVAILABLE) { return RentalEligibility.NotEligible( "Set is currently ${status.name.lowercase()}") } if (AgeRecommendation(age) < recommendedAge) { return RentalEligibility.NotEligible( "Set requires minimum age of ${recommendedAge}") } if (condition == SetCondition.POOR && duration.days > 7) { return RentalEligibility.NotEligible( "Sets in poor condition can only be rented for up to 7 days") } return RentalEligibility.Eligible() } Kotlin / Domain Model

Slide 42

Slide 42 text

data class Duration private constructor(val days: Int) { init { require(days >= 0) { "Duration cannot be negative" } } companion object { fun days(days: Int) = Duration(days) fun weeks(weeks: Int) = Duration(weeks * 7) } val weeks: Int get() = days / 7 } enum class SetCondition { MINT, GOOD, FAIR, POOR } // Result type for rental eligibility check sealed class RentalEligibility { data class Eligible(val message: String = "Rental is eligible") : RentalEligibility() data class NotEligible(val reason: String) : RentalEligibility() } Kotlin / Domain Model

Slide 43

Slide 43 text

Let's do some live coding!

Slide 44

Slide 44 text

Can we re-use more?

Slide 45

Slide 45 text

as reached Beta! o becoming Stable now! Single codebase for the application NativeCode SharedCode Businesslogic andcore View iOS specificAPIs Android specificAPIs View Learn more about beta → Case studies Support Get started Kotlin Multiplatform Mobile is an SDK for iOS and Android app development. It offers all the combined benefits of creating cross-platform and native apps. It is trusted in production by many of the world’s leading companies, including Philips, Netflix, Leroy Merlin, and VMWare. Singl logic Maintain a other logic Implement devices. Busines andcor iOS spec We loved the “shared business, native UI” idea that Kotlin Multiplatform promoted, and it m our teams did not have to give up using their preferred toolchains.” “ Get started Case studies Kotlin Multiplatform

Slide 46

Slide 46 text

Kotlin Multiplatform Mobile is an SDK for iOS and Android app development. It offers all the combined benefits of creating cross-platform and native apps. It is trusted in production by many of the world’s leading companies, including Philips, Netflix, Leroy Merlin, and VMWare. Singl logic Maintain a other logic Implement devices. Busines andcor iOS spec We loved the “shared business, native UI” idea that Kotlin Multiplatform promoted, and it m our teams did not have to give up using their preferred toolchains.” “ Get started Case studies kmp.jetbrains.com

Slide 47

Slide 47 text

Organize for success!

Slide 48

Slide 48 text

Organize for success!

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

🚨

Slide 51

Slide 51 text

🚨 🚨 🚨 🚨 🚨 🚨

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Kotlin Isolated ❤ ❤ ❤ Kotlin multiplatform 🤔 Not for everyone 👬👫 Team organization matters Domain models Key Takeaways

Slide 56

Slide 56 text

Kotlin multiplatform + Domain models = ❤

Slide 57

Slide 57 text

Kotlin multiplatform + Domain models = ❤ Nico Krijnen Niels Marsman