Skip to content

Instantly share code, notes, and snippets.

@NikitaMelnikov
Created July 6, 2016 10:34
Show Gist options
  • Save NikitaMelnikov/d4e20f274175ca479e0577ecf6798674 to your computer and use it in GitHub Desktop.
Save NikitaMelnikov/d4e20f274175ca479e0577ecf6798674 to your computer and use it in GitHub Desktop.
case class Persist(data: Any)
case class PaymentError(data: Any)
object Database {
def props() = Props(classOf[Database])
}
class Database extends Actor with ActorLogging {
def receive = {
case Persist(what) =>
log.info(s"[Persist] $what")
case PaymentError(why) =>
log.error(s"[Persist] $why")
}
}
object PaymentProtocol {
type Timestamp = Long
sealed trait Command
object Command {
case class Create(user: String, provider: String, amount: Long) extends Command
case object Pay extends Command
case class Finish(status: Boolean, message: Option[String] = None)
}
sealed trait State
object State {
case object New extends State
case object Created extends State
case object InProgress extends State
trait Ready extends State
case object Successful extends Ready
case object Failed extends Ready
}
sealed trait Data
object Data {
case object Empty extends Data
case class Initialized(user: String, provider: String, amount: Long, createdAt: Timestamp) extends Data
case class PayedPayment(user: String, provider: String, amount: Long, createdAt: Timestamp, updatedAt: Timestamp, result: Boolean) extends Data
}
}
object Payment {
def props(database: ActorRef) = Props(classOf[Payment], database)
}
class Payment(database: ActorRef) extends LoggingFSM[PaymentProtocol.State, PaymentProtocol.Data] {
import PaymentProtocol._
startWith(State.New, Data.Empty)
when(State.New) {
case Event(command: Command.Create, Data.Empty) => stay using Data.Initialized(command.user, command.provider, command.amount, System.currentTimeMillis())
case Event(Command.Pay, data: Data.Initialized) => goto(State.InProgress) using data
}
when(State.InProgress, stateTimeout = 2 seconds) {
case Event(Command.Finish(status, message), data: Data.Initialized) =>
goto(if(status) State.Successful else State.Failed) using Data.PayedPayment(data.user, data.provider, data.amount, data.createdAt, System.currentTimeMillis(), status)
}
// TODO:
when(State.Successful)(FSM.NullFunction)
when(State.Failed)(FSM.NullFunction)
onTransition {
case State.InProgress -> State.Successful => nextStateData match {
case payment: Data.PayedPayment => database ! Persist(payment)
case _ =>
}
case _ -> State.Failed =>
database ! PaymentError((stateData, nextStateData))
}
whenUnhandled {
case e =>
log.error(e.toString)
goto(Failed)
}
}
object Processing extends App {
import PaymentProtocol._
implicit val system = ActorSystem("Processing")
val db = system.actorOf(Database.props(), "Database")
runSuccessfulPayment()
runPaymentWithTimeout()
def runSuccessfulPayment(): Unit = {
val payment = system.actorOf(Payment.props(db), "Payment-1")
// Init payment
payment ! Command.Create("Some user", "WebMoney", 1000)
// User payment command
payment ! Command.Pay
// Provider response
payment ! Command.Finish(status = true)
}
def runPaymentWithTimeout(): Unit = {
val payment = system.actorOf(Payment.props(db), "Payment-2")
// Init payment
payment ! Command.Create("Another user", "Yandex", 1000)
// User payment command
payment ! Command.Pay
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment