SlideShare a Scribd company logo
1 of 28
Akka Persistent FSMを使ってみる
2016/03/04 「Akkaを語る会」
@satoshi_m8a
課題
• 複数の集約ををまたがる処理を順序良く実行したい。
• 境界づけられたコンテキストをまたがる処理を順序良く実
行したい。
• それらの処理がどこまで進行しているのかを記録したい。
• クラッシュしたら途中からやり直したい。もしくは最初の
状態に戻したい。
• 状態によっては別の処理を実行したい。
目次
• Sagaについて
• PersistentFSM
• 実装
Sagaについて
Saga…
• Saga(サーガ)と呼ぶと混乱を招く
サーガ?佐賀?Sa・Ga?
• Long-Lived Transaction (H Garcaa-Molrna 著 “Sagas” )
• Long-Running Process
• Process Manager
Long-lived Transaction
• Sagaの最初期の説明
• DBMSの用語
• 複数のDBトランザクションを一つのトランザクシ
ョンにまとめる
Long-Running Process (長期プロセス)
• 複数の処理を同時に開始しそれらの処理を追跡する。
• 条件分岐はない
• AllPhoneNumbersCounted、MatchedPhoneNumbersCountedなどのクエリに応答するよう
なイベントが発行されている。
実践ドメイン駆動設計 ヴァーン・ヴァーノン (著), 高木 正弘 (翻訳) から引用
Process Manager
• Aggregateや境界づけられたコンテキストをまたいでメッセージを仲介しながら、ひとつのビジネスプ
ロセスを遂行するための仕組み
• コンテキスト内のAggregateだけでなく、コンテキスト外のAggregateを束ねた処理も作れる。
• 集約からのメッセージによって、条件分岐することもある。(動的なルーティング)
• 順序立てても良いし、同時に実行するプロセスがあってもよい。
• 汎用性が高い
http://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html
参考:CQRS Journey
https://msdn.microsoft.com/en-us/library/jj554200.aspx
例:カンファレンス予約システム
• Process Manager自体はビジネスロジックをもたず、状態に応じてメッセージをルーティングするだけ
。
https://msdn.microsoft.com/en-us/library/jj554200.aspx
例:カンファレンス予約システム
• CreateOrder → MakeReservation → MakePayment
の順に実行する。
• Reservationが出来なければ、MakePayment は実行
せずに、WaitListに登録する。
• CreateOrder, MakeReservation, MakePayment が失
敗やタイムアウトしたら全ての処理を取り消す。
PersistentFSM
Akka Persistence
• Actorの内部状態を永続化できるようになる。
• CQRSとイベントソーシングに使える。
• at least once delivery
PersistentFSM
• experimental(2016/03/04時点)
• FSM(有限状態機械)のように振る舞うPersistentActor
PersistentFSM
• whenで状態ごとのハンドラとデフォルトタイムアウト
を定義する
• gotoによって状態遷移する
• forMaxで次の状態のタイムアウトをセットする
• applyingでイベントを元に内部データを更新
• イベントのシーケンスが保存される。
実装
例:カンファレンス予約システム
https://msdn.microsoft.com/en-us/library/jj554200.aspx
実装(1)
object OrderProcessManager {
def props(receiver: ActorRef, order: ActorRef, reservation: ActorRef, payment: ActorRef, waitList: ActorRef) =
Props(new OrderProcessManager(receiver,order, reservation, payment, waitList))
object States {
sealed trait OrderProcessState extends FSMState
case object Untouched extends OrderProcessState {
override def identifier: String = "untouched"
}
case object OrderProcessing extends OrderProcessState {
override def identifier: String = "order-processing"
}
case object ReservationProcessing extends OrderProcessState {
override def identifier: String = "reservation-processing"
}
case object PaymentProcessing extends OrderProcessState {
override def identifier: String = "payment-processing"
}
case object AddToWaitListProcessing extends OrderProcessState {
override def identifier: String = "add-to-wait-list-processing"
}
case object OrderCompleted extends OrderProcessState {
override def identifier: String = "order-completed"
}
case object CancellationProcessing extends OrderProcessState {
override def identifier: String = "cancellation-processing"
}
}
}
実装(2)
object Commands {
sealed trait OrderProcessCommand extends Command[OrderProcessId]
case class PlaceOrder(id: OrderProcessId) extends OrderProcessCommand
case class CancelOrderProcess(id: OrderProcessId) extends OrderProcessCommand
}
object Events {
sealed trait OrderProcessEvent extends DomainEvent
case class OrderStarted(orderId: OrderId) extends OrderProcessEvent
case class OrderAdded(orderId: OrderId) extends OrderProcessEvent
case class ReservationAdded(reservationId: ReservationId, seatId: SeatId) extends OrderProcessEvent
case class WaitListAdded(waitListId: WaitListId) extends OrderProcessEvent
case class PaymentAdded(paymentId: PaymentId) extends OrderProcessEvent
case class OrderConfirmed(orderId: OrderId) extends OrderProcessEvent
case class PaymentRemoved(paymentId: PaymentId) extends OrderProcessEvent
case class ReservationRemoved(reservationId: ReservationId) extends OrderProcessEvent
case class WaitListRemoved(waitListId: WaitListId) extends OrderProcessEvent
case class OrderRemoved(orderId: OrderId) extends OrderProcessEvent
}
object Data {
case class OrderProcessData(orderId: Option[OrderId],
reservationId: Option[ReservationId],
waitListId: Option[WaitListId],
paymentId: Option[PaymentId]) {
def isAllCanceled = orderId.isEmpty && reservationId.isEmpty && waitListId.isEmpty &&
paymentId.isEmpty
}
}
実装(3)
class OrderProcessManager(receiver: ActorRef, order: ActorRef, reservation: ActorRef, payment: ActorRef, waitList: ActorRef)
extends PersistentFSM[OrderProcessState, OrderProcessData, OrderProcessEvent] {
override def domainEventClassTag: ClassTag[OrderProcessEvent] = scala.reflect.classTag[OrderProcessEvent]
override def persistenceId: String = self.path.parent.name + "-" + self.path.name
override def applyEvent(domainEvent: OrderProcessEvent, currentData: OrderProcessData): OrderProcessData = {
domainEvent match {
case OrderStarted(orderId) => currentData.copy(orderId = Some(orderId))
case OrderAdded(orderId) => currentData.copy(orderId = Some(orderId))
case ReservationAdded(reservationId, seatId) => currentData.copy(reservationId = Some(reservationId))
case WaitListAdded(waitListId) => currentData.copy(waitListId = Some(waitListId))
case PaymentAdded(paymentId) => currentData.copy(paymentId = Some(paymentId))
case PaymentRemoved(_) => currentData.copy(paymentId = None)
case ReservationRemoved(_) => currentData.copy(reservationId = None)
case WaitListRemoved(_) => currentData.copy(waitListId = None)
case OrderRemoved(_) => currentData.copy(orderId = None)
case OrderConfirmed(_) => currentData
}
}
実装(4)
startWith(Untouched, OrderProcessData(None, None, None, None))
when(Untouched, 10 seconds) {
case Event(PlaceOrder(id), _) =>
val orderId = OrderId(UUID.randomUUID().toString)
goto(OrderProcessing) applying OrderStarted(orderId) forMax (5 seconds) andThen {
case OrderProcessData(Some(oid), _, _, _) =>
order ! CreateOrder(orderId)
}
}
when(OrderProcessing, 10 seconds) {
case Event(OrderCreated(orderId), _) =>
val reservationId = ReservationId(UUID.randomUUID().toString)
goto(ReservationProcessing) applying OrderAdded(orderId) forMax (5 seconds) andThen {
case OrderProcessData(Some(oid), _, _, _) =>
reservation ! MakeReservation(reservationId)
}
case Event(StateTimeout, _) =>
goto(CancellationProcessing) andThen {
case d: OrderProcessData =>
cancelAll(d)
}
}
実装(5)
when(ReservationProcessing, 10 seconds) {
case Event(SeatReserved(reservationId, seatId), _) =>
val paymentId = PaymentId(UUID.randomUUID().toString)
goto(PaymentProcessing) applying ReservationAdded(reservationId, seatId) forMax (5 seconds) andThen {
case OrderProcessData(Some(_), Some(rid), _, _) =>
payment ! MakePayment(paymentId)
}
case Event(SeatNotReserved(reservationId, waitListId), _) =>
goto(AddToWaitListProcessing) applying WaitListAdded(waitListId) forMax (5 seconds) andThen {
case OrderProcessData(Some(_), None, Some(wid), _) =>
waitList ! AddSeatToWaitList(waitListId)
}
case Event(StateTimeout, _) =>
goto(CancellationProcessing) andThen {
case d: OrderProcessData =>
cancelAll(d)
}
}
when(AddToWaitListProcessing, 10 seconds) {
case Event(SeatAdded(waitListId), OrderProcessData(Some(oid), _, _, _)) =>
receiver ! OrderConfirmed(oid)
goto(OrderCompleted)
}
実装(6)
when(PaymentProcessing, 10 seconds) {
case Event(PaymentAccepted(paymentId), OrderProcessData(Some(oid), _, _, _)) =>
receiver ! OrderConfirmed(oid)
goto(OrderCompleted) applying PaymentAdded(paymentId)
case Event(StateTimeout, _) =>
goto(CancellationProcessing) andThen {
case d: OrderProcessData =>
cancelAll(d)
}
}
when(OrderCompleted, 10 seconds) {
case Event(CancelOrderProcess(id), OrderProcessData(Some(oid), Some(rid), wid, pid)) =>
goto(CancellationProcessing) andThen {
case d: OrderProcessData =>
cancelAll(d)
}
}
実装(7)
when(CancellationProcessing, 10 seconds) {
case Event(PaymentCanceled(paymentId), _) =>
stay applying PaymentRemoved(paymentId) andThen {
case d: OrderProcessData if d.isAllCanceled =>
goto(Untouched)
}
case Event(WaitListCanceled(waitListId), _) =>
stay applying WaitListRemoved(waitListId) andThen {
case d: OrderProcessData if d.isAllCanceled =>
goto(Untouched)
}
case Event(ReservationCanceled(reservationId), _) =>
stay applying ReservationRemoved(reservationId) andThen {
case d: OrderProcessData if d.isAllCanceled =>
goto(Untouched)
}
case Event(OrderCanceled(orderId), _) =>
stay applying OrderRemoved(orderId) andThen {
case d: OrderProcessData if d.isAllCanceled =>
goto(Untouched)
}
case Event(StateTimeout, _) =>
stay andThen {
case d: OrderProcessData =>
cancelAll(d)
}
}
実装(8)
private def cancelAll(d: OrderProcessData) = {
d.paymentId.foreach {
id =>
payment ! CancelPayment(id)
}
d.waitListId.foreach {
id =>
waitList ! CancelWaitList(id)
}
d.reservationId.foreach {
id =>
reservation ! CancelReservation(id)
}
d.orderId.foreach {
id =>
order ! CancelOrder(id)
}
}
まとめと考察
• PersistentFSMでProcess Managerを実装した
• become/unbecomeとの違い
• 集約に実装するか、プロセスマネージャーを起動するか、集約がプ
ロセスマネージャーを起動するか。
• 各集約にはAtLeastOnceDeliveryでコマンドを送るか、Timeoutで何
度もリトライするか。
• 各集約からイベントはEventBusやDistributed Pub-Subでトピック
を購読するか。
参考
• http://doc.akka.io/docs/akka/snapshot/scala/persiste
nce.html
• http://www.enterpriseintegrationpatterns.com/pattern
s/messaging/ProcessManager.html
• https://msdn.microsoft.com/en-
us/library/jj554200.aspx

More Related Content

Featured

PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Applitools
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at WorkGetSmarter
 
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...DevGAMM Conference
 
Barbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationErica Santiago
 

Featured (20)

PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
 
ChatGPT webinar slides
ChatGPT webinar slidesChatGPT webinar slides
ChatGPT webinar slides
 
More than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike RoutesMore than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike Routes
 
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
 
Barbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy Presentation
 

Akka Persistent FSMを使ってみる

  • 1. Akka Persistent FSMを使ってみる 2016/03/04 「Akkaを語る会」 @satoshi_m8a
  • 2. 課題 • 複数の集約ををまたがる処理を順序良く実行したい。 • 境界づけられたコンテキストをまたがる処理を順序良く実 行したい。 • それらの処理がどこまで進行しているのかを記録したい。 • クラッシュしたら途中からやり直したい。もしくは最初の 状態に戻したい。 • 状態によっては別の処理を実行したい。
  • 6. サーガ?佐賀?Sa・Ga? • Long-Lived Transaction (H Garcaa-Molrna 著 “Sagas” ) • Long-Running Process • Process Manager
  • 7. Long-lived Transaction • Sagaの最初期の説明 • DBMSの用語 • 複数のDBトランザクションを一つのトランザクシ ョンにまとめる
  • 8. Long-Running Process (長期プロセス) • 複数の処理を同時に開始しそれらの処理を追跡する。 • 条件分岐はない • AllPhoneNumbersCounted、MatchedPhoneNumbersCountedなどのクエリに応答するよう なイベントが発行されている。 実践ドメイン駆動設計 ヴァーン・ヴァーノン (著), 高木 正弘 (翻訳) から引用
  • 9. Process Manager • Aggregateや境界づけられたコンテキストをまたいでメッセージを仲介しながら、ひとつのビジネスプ ロセスを遂行するための仕組み • コンテキスト内のAggregateだけでなく、コンテキスト外のAggregateを束ねた処理も作れる。 • 集約からのメッセージによって、条件分岐することもある。(動的なルーティング) • 順序立てても良いし、同時に実行するプロセスがあってもよい。 • 汎用性が高い http://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html
  • 12. 例:カンファレンス予約システム • CreateOrder → MakeReservation → MakePayment の順に実行する。 • Reservationが出来なければ、MakePayment は実行 せずに、WaitListに登録する。 • CreateOrder, MakeReservation, MakePayment が失 敗やタイムアウトしたら全ての処理を取り消す。
  • 14. Akka Persistence • Actorの内部状態を永続化できるようになる。 • CQRSとイベントソーシングに使える。 • at least once delivery
  • 16. PersistentFSM • whenで状態ごとのハンドラとデフォルトタイムアウト を定義する • gotoによって状態遷移する • forMaxで次の状態のタイムアウトをセットする • applyingでイベントを元に内部データを更新 • イベントのシーケンスが保存される。
  • 19. 実装(1) object OrderProcessManager { def props(receiver: ActorRef, order: ActorRef, reservation: ActorRef, payment: ActorRef, waitList: ActorRef) = Props(new OrderProcessManager(receiver,order, reservation, payment, waitList)) object States { sealed trait OrderProcessState extends FSMState case object Untouched extends OrderProcessState { override def identifier: String = "untouched" } case object OrderProcessing extends OrderProcessState { override def identifier: String = "order-processing" } case object ReservationProcessing extends OrderProcessState { override def identifier: String = "reservation-processing" } case object PaymentProcessing extends OrderProcessState { override def identifier: String = "payment-processing" } case object AddToWaitListProcessing extends OrderProcessState { override def identifier: String = "add-to-wait-list-processing" } case object OrderCompleted extends OrderProcessState { override def identifier: String = "order-completed" } case object CancellationProcessing extends OrderProcessState { override def identifier: String = "cancellation-processing" } } }
  • 20. 実装(2) object Commands { sealed trait OrderProcessCommand extends Command[OrderProcessId] case class PlaceOrder(id: OrderProcessId) extends OrderProcessCommand case class CancelOrderProcess(id: OrderProcessId) extends OrderProcessCommand } object Events { sealed trait OrderProcessEvent extends DomainEvent case class OrderStarted(orderId: OrderId) extends OrderProcessEvent case class OrderAdded(orderId: OrderId) extends OrderProcessEvent case class ReservationAdded(reservationId: ReservationId, seatId: SeatId) extends OrderProcessEvent case class WaitListAdded(waitListId: WaitListId) extends OrderProcessEvent case class PaymentAdded(paymentId: PaymentId) extends OrderProcessEvent case class OrderConfirmed(orderId: OrderId) extends OrderProcessEvent case class PaymentRemoved(paymentId: PaymentId) extends OrderProcessEvent case class ReservationRemoved(reservationId: ReservationId) extends OrderProcessEvent case class WaitListRemoved(waitListId: WaitListId) extends OrderProcessEvent case class OrderRemoved(orderId: OrderId) extends OrderProcessEvent } object Data { case class OrderProcessData(orderId: Option[OrderId], reservationId: Option[ReservationId], waitListId: Option[WaitListId], paymentId: Option[PaymentId]) { def isAllCanceled = orderId.isEmpty && reservationId.isEmpty && waitListId.isEmpty && paymentId.isEmpty } }
  • 21. 実装(3) class OrderProcessManager(receiver: ActorRef, order: ActorRef, reservation: ActorRef, payment: ActorRef, waitList: ActorRef) extends PersistentFSM[OrderProcessState, OrderProcessData, OrderProcessEvent] { override def domainEventClassTag: ClassTag[OrderProcessEvent] = scala.reflect.classTag[OrderProcessEvent] override def persistenceId: String = self.path.parent.name + "-" + self.path.name override def applyEvent(domainEvent: OrderProcessEvent, currentData: OrderProcessData): OrderProcessData = { domainEvent match { case OrderStarted(orderId) => currentData.copy(orderId = Some(orderId)) case OrderAdded(orderId) => currentData.copy(orderId = Some(orderId)) case ReservationAdded(reservationId, seatId) => currentData.copy(reservationId = Some(reservationId)) case WaitListAdded(waitListId) => currentData.copy(waitListId = Some(waitListId)) case PaymentAdded(paymentId) => currentData.copy(paymentId = Some(paymentId)) case PaymentRemoved(_) => currentData.copy(paymentId = None) case ReservationRemoved(_) => currentData.copy(reservationId = None) case WaitListRemoved(_) => currentData.copy(waitListId = None) case OrderRemoved(_) => currentData.copy(orderId = None) case OrderConfirmed(_) => currentData } }
  • 22. 実装(4) startWith(Untouched, OrderProcessData(None, None, None, None)) when(Untouched, 10 seconds) { case Event(PlaceOrder(id), _) => val orderId = OrderId(UUID.randomUUID().toString) goto(OrderProcessing) applying OrderStarted(orderId) forMax (5 seconds) andThen { case OrderProcessData(Some(oid), _, _, _) => order ! CreateOrder(orderId) } } when(OrderProcessing, 10 seconds) { case Event(OrderCreated(orderId), _) => val reservationId = ReservationId(UUID.randomUUID().toString) goto(ReservationProcessing) applying OrderAdded(orderId) forMax (5 seconds) andThen { case OrderProcessData(Some(oid), _, _, _) => reservation ! MakeReservation(reservationId) } case Event(StateTimeout, _) => goto(CancellationProcessing) andThen { case d: OrderProcessData => cancelAll(d) } }
  • 23. 実装(5) when(ReservationProcessing, 10 seconds) { case Event(SeatReserved(reservationId, seatId), _) => val paymentId = PaymentId(UUID.randomUUID().toString) goto(PaymentProcessing) applying ReservationAdded(reservationId, seatId) forMax (5 seconds) andThen { case OrderProcessData(Some(_), Some(rid), _, _) => payment ! MakePayment(paymentId) } case Event(SeatNotReserved(reservationId, waitListId), _) => goto(AddToWaitListProcessing) applying WaitListAdded(waitListId) forMax (5 seconds) andThen { case OrderProcessData(Some(_), None, Some(wid), _) => waitList ! AddSeatToWaitList(waitListId) } case Event(StateTimeout, _) => goto(CancellationProcessing) andThen { case d: OrderProcessData => cancelAll(d) } } when(AddToWaitListProcessing, 10 seconds) { case Event(SeatAdded(waitListId), OrderProcessData(Some(oid), _, _, _)) => receiver ! OrderConfirmed(oid) goto(OrderCompleted) }
  • 24. 実装(6) when(PaymentProcessing, 10 seconds) { case Event(PaymentAccepted(paymentId), OrderProcessData(Some(oid), _, _, _)) => receiver ! OrderConfirmed(oid) goto(OrderCompleted) applying PaymentAdded(paymentId) case Event(StateTimeout, _) => goto(CancellationProcessing) andThen { case d: OrderProcessData => cancelAll(d) } } when(OrderCompleted, 10 seconds) { case Event(CancelOrderProcess(id), OrderProcessData(Some(oid), Some(rid), wid, pid)) => goto(CancellationProcessing) andThen { case d: OrderProcessData => cancelAll(d) } }
  • 25. 実装(7) when(CancellationProcessing, 10 seconds) { case Event(PaymentCanceled(paymentId), _) => stay applying PaymentRemoved(paymentId) andThen { case d: OrderProcessData if d.isAllCanceled => goto(Untouched) } case Event(WaitListCanceled(waitListId), _) => stay applying WaitListRemoved(waitListId) andThen { case d: OrderProcessData if d.isAllCanceled => goto(Untouched) } case Event(ReservationCanceled(reservationId), _) => stay applying ReservationRemoved(reservationId) andThen { case d: OrderProcessData if d.isAllCanceled => goto(Untouched) } case Event(OrderCanceled(orderId), _) => stay applying OrderRemoved(orderId) andThen { case d: OrderProcessData if d.isAllCanceled => goto(Untouched) } case Event(StateTimeout, _) => stay andThen { case d: OrderProcessData => cancelAll(d) } }
  • 26. 実装(8) private def cancelAll(d: OrderProcessData) = { d.paymentId.foreach { id => payment ! CancelPayment(id) } d.waitListId.foreach { id => waitList ! CancelWaitList(id) } d.reservationId.foreach { id => reservation ! CancelReservation(id) } d.orderId.foreach { id => order ! CancelOrder(id) } }
  • 27. まとめと考察 • PersistentFSMでProcess Managerを実装した • become/unbecomeとの違い • 集約に実装するか、プロセスマネージャーを起動するか、集約がプ ロセスマネージャーを起動するか。 • 各集約にはAtLeastOnceDeliveryでコマンドを送るか、Timeoutで何 度もリトライするか。 • 各集約からイベントはEventBusやDistributed Pub-Subでトピック を購読するか。