Created
September 18, 2016 12:07
-
-
Save NikitaMelnikov/74f21342a925d883578f7c36fa24c687 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.bitbucket.fsm | |
import akka.actor._ | |
import akka.persistence.fsm.PersistentFSM | |
import akka.persistence.fsm.PersistentFSM._ | |
import akka.stream.ActorMaterializer | |
import com.bitbucket.fsm.Order._ | |
import scala.concurrent.Future | |
import scala.reflect._ | |
import scala.util.Success | |
object Order { | |
type Timestamp = Long | |
sealed trait Command | |
case class AddItem(item: Item) extends Command | |
case class AddDiscount(discount: Discount) extends Command | |
case class AcceptDiscount(discount: Discount) extends Command | |
case class RejectDiscount(cause: Throwable) extends Command | |
case object Pay extends Command | |
case class RejectPayment(cause: Throwable) extends Command | |
case object CompletePayment extends Command | |
case class Item(name: String, cost: Int) | |
case class Discount(code: String, amount: Int) | |
sealed trait State extends FSMState | |
case object Empty extends State { | |
def identifier: String = "created" | |
} | |
case object Buying extends State { | |
def identifier: String = "buying" | |
} | |
case object WaitingPayment extends State { | |
def identifier: String = "payment" | |
} | |
case object Payed extends State { | |
def identifier: String = "payed" | |
} | |
case object Rejected extends State { | |
def identifier: String = "rejected" | |
} | |
sealed trait Data { | |
def items: Set[Item] | |
def discount: Option[Discount] | |
def payed: Boolean | |
def cost: Int = items.map(_.cost).sum | |
def amount: Int = cost - discount.map(_.amount).getOrElse(0) | |
} | |
case class EmptyOrder() extends Data { | |
def items: Set[Item] = Set.empty[Item] | |
def discount: Option[Discount] = None | |
def payed: Boolean = false | |
} | |
case class OrderData(items: Set[Item], discount: Option[Discount]) extends Data { | |
def payed: Boolean = false | |
} | |
case class PayedOrder(items: Set[Item], discount: Option[Discount], completedAt: Timestamp) extends Data { | |
def payed: Boolean = true | |
} | |
case class RejectedOrder(items: Set[Item], discount: Option[Discount], cause: Exception, payed: Boolean = false) extends Data | |
sealed trait DomainEvent | |
case class ItemAdded(item: Item) extends DomainEvent | |
case class DiscountAdded(discount: Discount) extends DomainEvent | |
case class DiscountRejected(discount: Discount) extends DomainEvent | |
case class DiscountApplied(discount: Discount) extends DomainEvent | |
case class PaymentStarted(amount: Int, at: Timestamp) extends DomainEvent | |
case class PaymentRejected(cause: Throwable) extends DomainEvent | |
case object PaymentComplete extends DomainEvent | |
} | |
class Order(id: String) extends PersistentFSM[Order.State, Data, DomainEvent] { | |
implicit def domainEventClassTag: ClassTag[DomainEvent] = ClassTag(classOf[DomainEvent]) | |
def persistenceId: String = s"user-$id" | |
startWith(Empty, EmptyOrder()) | |
when(Empty) { | |
case Event(AddItem(item), _) => goto(Buying) applying ItemAdded(item) | |
} | |
when(Buying) { | |
case Event(AddItem(item), _) => stay applying ItemAdded(item) | |
case Event(AddDiscount(discount), _) => stay andThen { _ => | |
applyDiscount(discount) onComplete { | |
case Success(_) => self ! AcceptDiscount(discount) | |
case scala.util.Failure(cause) => self ! RejectDiscount(cause) | |
} | |
} | |
case Event(AcceptDiscount(discount), _) => stay applying DiscountAdded(discount) | |
case Event(RejectDiscount(cause), _ ) => stay // TODO: Notify | |
case Event(Pay, data: OrderData) => goto(WaitingPayment) applying PaymentStarted(data.cost, time) andThen { _ => | |
pay(id, data.cost) onComplete { | |
case Success(_) => self ! PaymentComplete | |
case scala.util.Failure(cause) => self ! PaymentRejected(cause) | |
} | |
} | |
} | |
when(WaitingPayment) { | |
case Event(PaymentComplete, _) => goto(Payed) applying PaymentComplete | |
case Event(PaymentRejected, _) => ??? | |
} | |
when(Payed) { | |
case Event(CompletePayment, _) => goto(Payed) applying PaymentComplete | |
case Event(RejectPayment(cause), _) => goto(Rejected) applying PaymentRejected(cause) | |
} | |
def applyEvent(domainEvent: DomainEvent, currentData: Data): Data = { | |
(domainEvent, currentData) match { | |
case (ItemAdded(item), data: EmptyOrder) => OrderData(items = Set(item), discount = None) | |
case (ItemAdded(item), data: OrderData) => data.copy(items = data.items + item) | |
??? | |
} | |
} | |
// TODO: | |
def pay(id: String, amount: Int): Future[Unit] = ??? | |
// TODO: | |
def applyDiscount(discount: Discount): Future[Unit] = ??? | |
def time = System.currentTimeMillis() | |
} | |
object Boot extends App { | |
implicit val system = ActorSystem() | |
implicit val ec = system.dispatcher | |
implicit val materializer = ActorMaterializer() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment