24. Domain & Feature Requests
Domain:
● Users place Orders in the auction system
Order
25. Domain & Feature Requests
Domain:
● Users place Orders in the auction system
● Orders can be:
○ General - contain list of Products
○ Complex - combined from two or more
other Orders
○ Cancelled - used to be fully fledged
Orders, but now are cancelled
Order
General Complex Cancelled
26. Domain & Feature Requests
Domain:
● Users place Orders in the auction system
● Orders can be:
○ General - contain list of Products
○ Complex - combined from two or more
other Orders
○ Cancelled - used to be fully fledged
Orders, but now are cancelled
● Products can be:
○ Basic - id & price
○ Discounted - wrapped Product &
discount
○ OutOfStock - used to be in warehouse, but
now gone (sorry)
Order
General Complex Cancelled
Product
Basic Discounter
Out of
Stock
27. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
28. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
29. sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
30. sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
31. sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
32. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
33. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
34. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
35. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
36. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
37. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
38. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
39. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[error] OrderTest.scala evaluate is not a member of jw.ComplexOrder
[error] order.evaluate should equal (BigDecimal("11.0"))
[error] ^
[error] one error found
47. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[error] OrderTest.scala evaluate is not a member of jw.ComplexOrder
[error] order.evaluate should equal (BigDecimal("11.0"))
[error] ^
[error] one error found
48. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
49. sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
50. trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
51. trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
52. trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
53. trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
54. trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
55. trait Evaluatable[T] { def evaluate: T }
sealed trait Product extends Evaluatable[BigDecimal]
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = price
}
case class DiscountedProduct(product: Product, discount: Double) extends Product {
def evaluate: BigDecimal = product.evaluate * (1 - discount)
}
case object OutOfStock extends Product {
def evaluate: BigDecimal = BigDecimal("0.0")
}
56. trait Evaluatable[T] { def evaluate: T }
sealed trait Product extends Evaluatable[BigDecimal]
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = price
}
case class DiscountedProduct(product: Product, discount: Double) extends Product {
def evaluate: BigDecimal = product.evaluate * (1 - discount)
}
case object OutOfStock extends Product {
def evaluate: BigDecimal = BigDecimal("0.0")
}
57. trait Evaluatable[T] { def evaluate: T }
sealed trait Product extends Evaluatable[BigDecimal]
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = price
}
case class DiscountedProduct(product: Product, discount: Double) extends Product {
def evaluate: BigDecimal = product.evaluate * (1 - discount)
}
case object OutOfStock extends Product {
def evaluate: BigDecimal = BigDecimal("0.0")
}
58. trait Evaluatable[T] { def evaluate: T }
sealed trait Product extends Evaluatable[BigDecimal]
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = price
}
case class DiscountedProduct(product: Product, discount: Double) extends Product {
def evaluate: BigDecimal = product.evaluate * (1 - discount)
}
case object OutOfStock extends Product {
def evaluate: BigDecimal = BigDecimal("0.0")
}
59. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
60. test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[info] OrderTest:
[info] - should evaluate order
61.
62. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
63. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
64. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
72. test("should calculate average") {
val o1 = GeneralOrder(DiscountedProduct(product =
BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil)
val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil)
val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0"))
}
[error] OrderTest.scala average is not a member of jw.Order
[error] Order.average should equal (BigDecimal("5.0"))
[error] ^
[error] one error found
100. test("should calculate average") {
val o1 = GeneralOrder(DiscountedProduct(product =
BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil)
val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil)
val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0"))
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
101.
102. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
103. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
104. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
105. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
106. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
107. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
108. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
109. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
110. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
111. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
[error] OrderTest.scala not found: value JsonSerializer
[error] JsonSerializer.write(order) should equal(expectedJson)
[error] ^
123. sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
124. sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String =
125. sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = json match {
case JsonObject(elems) =>
val entries = for {
(key, value) <- elems
} yield s""""$key: ${write(value)}""""
"{" + entries.mkString(", ") + "}"
case JsonArray(elems) => "[" + elems.map(write).mkString(", ") + "]"
case JsonString(value) => s""""$value""""
case JsonNumber(value) => value.toString
}
}
126. sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = ...
}
127. sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = ...
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
128. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
129. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
130. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
131. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = ...
def toJson: JsonValue = JsonObject(Map(
"type" -> JsonString("general"),
"products" -> JsonArray(products.map(_.toJson))
))
}
132. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
case object CancelledOrder extends Order {
def evaluate: BigDecimal = ...
def toJson: JsonValue = JsonString("cancelled order")
}
133. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = ...
def toJson: JsonValue = JsonObject(Map(
"type" -> JsonString("complex"),
"orders" -> JsonArray(orders.map(_.toJson)))
)
}
134. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = ...
def toJson: JsonValue = JsonObject(Map(
"type" -> JsonString("basic"),
"id" -> JsonNumber(BigDecimal(id)),
"price" -> JsonNumber(price)
))
}
136. sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable
case object OutOfStock extends Product {
def evaluate: BigDecimal = ...
def toJson: JsonValue = JsonString("out of stock")
}
137. test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
[info] - should serialize to json
138. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
139. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
140.
141.
142.
143.
144. Our goal:
● DRY principle
● Separation of concerns
● Epic decoupling
● Clean API
● Minimal Boilerplate
202. sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
203. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders
204. Domain & Feature Requests
Feature Requests:
● Present current state of orders (JSON)
● Calculate final checkout
● Calculate average Order price
● “Simplify” Orders