Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin multiplatform + Domain models = ❤️

Nico Krijnen
January 19, 2023

Kotlin multiplatform + Domain models = ❤️

In most of the applications we write, the domain model with all the crucial business logic only lives in the backend to ensure consistent behavior of a system. But with Kotlin multi-platform, that same domain model can suddenly also be used in your user-facing applications.

In this talk, we will look at what that means for the applications you build. What does your code look like when you share your domain model? How do you ensure that that domain model is re-usable? What kind of trouble can you run into and what techniques can you use to avoid that trouble? And how does the ability to use the full domain model benefit the user experience? And last but not least, how can your organizational team structure make this way of working a huge success or a road towards disaster?!

Nico Krijnen

January 19, 2023
Tweet

More Decks by Nico Krijnen

Other Decks in Business

Transcript

  1. 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
  2. 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
  3. Routes Services Data Access UI HTML / CSS Application Layer

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

    Data Access Layer Navigation Offline Storage Where is the business logic?
  5. 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?
  6. 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?
  7. 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?
  8. 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?
  9. 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?
  10. 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?
  11. 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
  12. Domain model? Domain model = only 20% of your code

    Routes Services Data Access UI HTML / CSS Application Layer Data Access Layer Navigation Offline Storage
  13. ✅ Pure Kotlin + Stdlib ❌ JVM apis or libraries

    ❌ JS apis or libraries ❌ Native apis or libraries Domain model = Free of dependencies
  14. Hexagonal / Union architecture ✅ Isolated, pure-code domain model ✅

    No framework code mixed in ✅ Easy to read and understand ✅ Easy to test ✅ Always-valid Immutable
  15. @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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. Kotlin Isolated ❤ ❤ ❤ Kotlin multiplatform 🤔 Not for

    everyone 👬👫 Team organization matters Domain models Key Takeaways