Skip to content

Instantly share code, notes, and snippets.

@amast09
Created February 17, 2023 17:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amast09/efa22c7ec935c9f73613e07d0be30e0e to your computer and use it in GitHub Desktop.
Save amast09/efa22c7ec935c9f73613e07d0be30e0e to your computer and use it in GitHub Desktop.
Foundations of Functional Programming in Scala: Redesigned Order
case class OrderId(value: String)
case class ItemId(value: String)
case class Item(id: ItemId, quantity: Int, unitPrice: Double)
sealed trait Order {
val id: OrderId
val createdAt: Instant
val orderStatus: OrderStatus
}
sealed trait OpenOrder extends Order {
def addItem(item: Order.Item): Order.DraftWithBasket =
this.addItems(NEL(item))
def addItems(items: NEL[Order.Item]): Order.DraftWithBasket =
Order.DraftWithBasket(this.id, this.createdAt, items)
}
sealed trait CheckoutOrder extends OpenOrder {
val basket: NEL[Order.Item]
def updateDeliveryAddress(deliveryAddress: Address): Order.CheckoutWithAddress =
Order.CheckoutWithAddress(this.id, this.createdAt, this.basket, deliveryAddress)
}
object Order {
case class DraftNoBasket(id: OrderId, createdAt: Instant) extends OpenOrder {
val orderStatus = OrderStatus.Draft
}
case class DraftWithBasket(id: OrderId, createdAt: Instant, basket: NEL[Order.Item]) extends OpenOrder {
val orderStatus = OrderStatus.Draft
def checkout(): CheckoutNoAddress =
CheckoutNoAddress(this.id, this.createdAt, this.basket)
}
case class CheckoutNoAddress(id: OrderId, createdAt: Instant, basket: NEL[Order.Item]) extends CheckoutOrder {
val orderStatus = OrderStatus.Checkout
}
case class CheckoutWithAddress(
id: OrderId,
createdAt: Instant,
basket: NEL[Order.Item],
deliveryAddress: Address
) extends CheckoutOrder {
val orderStatus = OrderStatus.Checkout
def submit(submittedAt: Instant): Submitted =
Submitted(this.id, this.createdAt, this.basket, this.deliveryAddress, submittedAt)
}
case class Submitted(
id: OrderId,
createdAt: Instant,
basket: NEL[Order.Item],
deliveryAddress: Address,
submittedAt: Instant
) extends Order {
val orderStatus = OrderStatus.Submitted
def deliver(deliveredAt: Instant): Delivered =
Delivered(this.id, this.createdAt, this.basket, this.deliveryAddress, this.submittedAt, deliveredAt)
}
case class Delivered(
id: OrderId,
createdAt: Instant,
basket: NEL[Item],
deliveryAddress: Address,
submittedAt: Instant,
deliveredAt: Instant
) extends Order {
val orderStatus = OrderStatus.Delivered
val shippingDuration: Duration = Duration.between(submittedAt, deliveredAt)
}
}
class OrderTest extends AnyFunSuite with ScalaCheckDrivenPropertyChecks {
test("Order happy path") {
forAll(arbitrary[String], instantGen, addressGen, nelOf(itemGen), durationGen, durationGen) {
(
orderId: String,
createdAt: Instant,
deliveryAddress: Address,
items: NEL[Item],
submittedAtDelay: Duration,
deliveredAtDelay: Duration
) =>
val submittedAt = createdAt.plus(submittedAtDelay)
val deliveredAt = submittedAt.plus(deliveredAtDelay)
val expectedOrder = Order.Delivered(
id = OrderId(orderId),
basket = items,
deliveryAddress = deliveryAddress,
createdAt = createdAt,
submittedAt = submittedAt,
deliveredAt = deliveredAt
)
val emptyOrder = Order.DraftNoBasket(OrderId(orderId), createdAt)
val orderWithBasket = emptyOrder.addItems(redefinedItems)
val orderAtCheckout = orderWithBasket.checkout()
val orderWithAddress = orderAtCheckout.updateDeliveryAddress(deliveryAddress)
val submittedOrder = orderWithAddress.submit(submittedAt)
val deliveredOrder = submittedOrder.deliver(deliveredAt)
assert(deliveredOrder == expectedOrder)
assert(expectedOrder.shippingDuration == Duration.between(submittedAt, deliveredAt))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment