Skip to content

Instantly share code, notes, and snippets.

@colomboe
Created June 11, 2020 17:30
Show Gist options
  • Save colomboe/595313e2c346d58a6d8212b2debe20b7 to your computer and use it in GitHub Desktop.
Save colomboe/595313e2c346d58a6d8212b2debe20b7 to your computer and use it in GitHub Desktop.
Videostore Kata declarative rules
package videostore.declarative
import java.math.BigDecimal
import kotlin.reflect.KProperty
data class Days(val count: Int)
fun Int.days() = Days(this)
fun Int.day() = Days(this)
// ------------------------------------------------------------------------------------
// DSL for rental cost
data class Money(val value: BigDecimal)
fun Double.dollars(): Money = Money(BigDecimal.valueOf(this))
operator fun Money.plus(that: Money): Money = TODO()
typealias CostRule = (Days) -> Money
infix fun Money.`for the first`(days: Days): CostRule = { d -> TODO() }
infix fun Money.`daily, starting from`(days: Days): CostRule = { d -> TODO() }
@JvmName("andForDaysRule")
operator fun CostRule.plus(that: CostRule): CostRule = { TODO() }
// ------------------------------------------------------------------------------------
// DSL for frequent renter points
data class Points(val points: Int)
fun Int.point() = Points(this)
operator fun Points.plus(that: Points): Points = TODO()
typealias PointsRule = (Days) -> Points
infix fun Points.`for renting at least`(days: Days): PointsRule = { d -> TODO() }
@JvmName("andForPointsRule")
operator fun PointsRule.plus(that: PointsRule): PointsRule = { TODO() }
// ------------------------------------------------------------------------------------
// Domain implementation
sealed class MovieType
object Regular : MovieType()
object NewRelease : MovieType()
object Children : MovieType()
data class MovieTypeBusinessRules(val type: MovieType,
val rate: (Days) -> Money,
val frequentRenterPoints: (Days) -> Points)
private typealias movieType = MovieTypeBusinessRules
val businessRules = listOf(
movieType(
type = Regular,
rate = (2.00.dollars() `for the first` 2.days()) + (1.50.dollars() `daily, starting from` 3.days()),
frequentRenterPoints = 1.point() `for renting at least` 1.day()
),
movieType(
type = NewRelease,
rate = 3.00.dollars() `daily, starting from` 1.day(),
frequentRenterPoints = (1.point() `for renting at least` 1.day()) + (1.point() `for renting at least` 2.days())
),
movieType(
type = Children,
rate = (1.50.dollars() `for the first` 3.days()) + (1.50.dollars() `daily, starting from` 3.days()),
frequentRenterPoints = 1.point() `for renting at least` 1.day()
)
)
// ------------------------------------------------------------------------------------
// Usage
fun rulesForType(t: MovieType) = businessRules.first { it.type == t }
data class Rent(val movieType: MovieType, val days: Days)
fun <T> computeTotal(ruleProperty: KProperty<(Days) -> T>, sum: (T, T) -> T): (List<Rent>) -> T = { rents ->
rents.map { (type, days) -> ruleProperty.call(rulesForType(type))(days) }.reduce(sum)
}
val totalCost: (List<Rent>) -> Money = computeTotal(MovieTypeBusinessRules::rate, Money::plus)
val totalPoints: (List<Rent>) -> Points = computeTotal(MovieTypeBusinessRules::frequentRenterPoints, Points::plus)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment