More Related Content Similar to Ladder of cqrs+es (20) Ladder of cqrs+es9. copyright Fringe81 Co.,Ltd.
It is not possible to create an optimal solution
for searching,reporting,and processing transactions
utilizing a single model.
-CQRS documents by Greg Young-
28. copyright Fringe81 Co.,Ltd.
C Q
別サービス
INSERT
&
UPDATE
集約用
レコード
Event
query1用
レコード
query2用
レコード
publish
EventBus
subscriber
Event:
発生した事象をあらわす
命名は過去形
29. copyright Fringe81 Co.,Ltd.
C Q
別サービス
INSERT
&
UPDATE
集約用
レコード
Event
query1用
レコード
query2用
レコード
publish
EventBus
subscriber
※ちなみにですが
これはEventSourcingでは
ありません(後ほど)
30. copyright Fringe81 Co.,Ltd.
val (updatedAggregate, event) =
aggregate.executeXxx(arg1, arg2)
repository.store(updatedAggregate)
eventBusPublisher.run(event)
38. copyright Fringe81 Co.,Ltd.
val (updatedAggregate, event) =
aggregate.executeXxx(arg1, arg2)
repository.store(updatedAggregate)
eventBusPublisher.run(event)
val updatedAggregate =
aggregate.executeXxx(arg1, arg2)
repository.store(updatedAggregate)
リポジトリ内からイベントパブリッシュ
(集約からイベントを取得)
39. copyright Fringe81 Co.,Ltd.
val (updatedAggregate, event) =
aggregate.executeXxx(arg1, arg2)
repository.store(updatedAggregate)
eventBusPublisher.run(event)
val updatedAggregate =
aggregate.executeXxx(arg1, arg2)
repository.store(updatedAggregate)
アトミックにしたいがDB保存とイベントpublishのTx
を同一にするのは難しい・・
48. copyright Fringe81 Co.,Ltd.
C Q
別サービス
INSERT
&
UPDATE
集約用
レコード
Event
query1用
レコード
query2用
レコード
publish
EventBus
subscriber
もしここの部分が
サービスの最初からあるわけではなくて
途中から必要になったとしたら・・・?
57. copyright Fringe81 Co.,Ltd.
C Q
別サービス
INSERT
や
UPDATE
集約用
レコード
Event
query1用
レコード
query2用
レコード
publish
EventBus
subscriber
集約の永続化
集約変更Eventのpublish
集約変更Eventの永続化
集約変更Eventのpublish
59. copyright Fringe81 Co.,Ltd.
メリット 要約
Performance ESにおいては永続化はイベントの append-only。
UPDATE処理のパフォーマンス改善が望める。
Simplification factとしてのイベントを保存するだけ。
O-R間のインピーダンスミスマッチが起きない。
Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし
て使える
Integration with
other
subsystems
疎結合を促す。
また、他のサブシステムへ publishした全てのイベントは eventStoreに保存
されている。
Deriving
additional
business value
from the event
history
将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を
持っていることで対処可能性が高まる。
例:ある時点での状態がどうであったかにこたえられるなど
Production
troubleshooting
本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に
できるためトラブルシューティングに役立つ
Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ
ラーを修正するだけで DB値を手動メンテせずにすむ場合がある
Testing テスト容易性が高まる
Flexibility イベント群があればいかなる構造のデータにも変換可能
参考:CQRS jornery(Introducing Event Sourcing)より
60. copyright Fringe81 Co.,Ltd.
メリット 要約
Performance ESにおいては永続化はイベントの append-only。
UPDATE処理のパフォーマンス改善が望める。
Simplification factとしてのイベントを保存するだけ。
O-R間のインピーダンスミスマッチが起きない。
Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし
て使える
Integration with
other
subsystems
疎結合を促す。
また、他のサブシステムへ publishした全てのイベントは eventStoreに保存
されている。
Deriving
additional
business value
from the event
history
将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を
持っていることで対処可能性が高まる。
例:ある時点での状態がどうであったかにこたえられるなど
Production
troubleshooting
本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に
できるためトラブルシューティングに役立つ
Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ
ラーを修正するだけで DB値を手動メンテせずにすむ場合がある
Testing テスト容易性が高まる
Flexibility イベント群があればいかなる構造のデータにも変換可能
参考:CQRS jornery(Introducing Event Sourcing)より
イベント群があれば
いかなる構造のデータにも
変換可能
61. copyright Fringe81 Co.,Ltd.
メリット 要約
Performance ESにおいては永続化はイベントの append-only。
UPDATE処理のパフォーマンス改善が望める。
Simplification factとしてのイベントを保存するだけ。
O-R間のインピーダンスミスマッチが起きない。
Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし
て使える
Integration with
other
subsystems
疎結合を促す。
また、他のサブシステムへ publishした全てのイベントは eventStoreに保存
されている。
Deriving
additional
business value
from the event
history
将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を
持っていることで対処可能性が高まる。
例:ある時点での状態がどうであったかにこたえられるなど
Production
troubleshooting
本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に
できるためトラブルシューティングに役立つ
Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ
ラーを修正するだけで DB値を手動メンテせずにすむ場合がある
Testing テスト容易性が高まる
Flexibility イベント群があればいかなる構造のデータにも変換可能
参考:CQRS jornery(Introducing Event Sourcing)より
将来起こるビジネス要件を
予想することは困難だが
全てのイベント履歴を持っていることで
対処可能性が高まる
62. copyright Fringe81 Co.,Ltd.
メリット 要約
Performance ESにおいては永続化はイベントの append-only。
UPDATE処理のパフォーマンス改善が望める。
Simplification factとしてのイベントを保存するだけ。
O-R間のインピーダンスミスマッチが起きない。
Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし
て使える
Integration with
other
subsystems
疎結合を促す。
また、他のサブシステムへ publishした全てのイベントは eventStoreに保存
されている。
Deriving
additional
business value
from the event
history
将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を
持っていることで対処可能性が高まる。
例:ある時点での状態がどうであったかにこたえられるなど
Production
troubleshooting
本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に
できるためトラブルシューティングに役立つ
Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ
ラーを修正するだけで DB値を手動メンテせずにすむ場合がある
Testing テスト容易性が高まる
Flexibility イベント群があればいかなる構造のデータにも変換可能
参考:CQRS jornery(Introducing Event Sourcing)より
本番環境のデータを
テスト環境にコピーするなどして
状態の再現を簡単にできるため
トラブルシューティングに役立つ
82. copyright Fringe81 Co.,Ltd.
class ExamplePersistentActor(id: String) extends PersistentActor {
override def persistenceId = id
var state = ExampleState()
def updateState(event: Evt): Unit = {
state = state.updated(event)
}
override val receiveRecover: Receive = {
case evt: Evt => updateState(evt)
case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot
}
override val receiveCommand: Receive = {
case Cmd(data) =>
persist(Evt(data)) { event =>
updateState(event)
if (lastSequenceNr % 10 == 0 && lastSequenceNr != 0) {
saveSnapshot(state.compress)
}
}
}
83. copyright Fringe81 Co.,Ltd.
class ExamplePersistentActor(id: String) extends PersistentActor {
override def persistenceId = id
var state = ExampleState()
def updateState(event: Evt): Unit = {
state = state.updated(event)
}
override val receiveRecover: Receive = {
case evt: Evt => updateState(evt)
case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot
}
override val receiveCommand: Receive = {
case Cmd(data) =>
persist(Evt(data)) { event =>
updateState(event)
if (lastSequenceNr % 10 == 0 && lastSequenceNr != 0) {
saveSnapshot(state.compress)
}
}
}
10回毎にsnapshot
94. copyright Fringe81 Co.,Ltd.
sealed trait Lottery {
def id: LotteryId
}
case class EmptyLottery(id: LotteryId) extends Lottery
case class NonEmptyLottery(
participants: List[String], id: LotteryId) extends Lottery
case class FinishedLottery(
winner: String, id: LotteryId) extends Lottery
<C側のモデル>
95. copyright Fringe81 Co.,Ltd.
case class LotteryView(
participants: List[String] = List(),
winner: Option[String] = None,
runDate: Option[OffsetDateTime] = None,
id: LotteryId
)
<Q側のモデル>
96. copyright Fringe81 Co.,Ltd.
sealed trait LotteryCommand
case object CreateLottery extends LotteryCommand
case class AddParticipant(name:String) extends LotteryCommand
case class RemoveParticipant(name:String) extends LotteryCommand
case object Run extends LotteryCommand
sealed trait LotteryEvent {
def lotteryId: LotteryId
}
case class LotteryCreated(lotteryId: LotteryId) extends LotteryEvent
sealed trait LotteryUpdateEvent extends LotteryEvent
case class ParticipantAdded(name: String, raffleId: LotteryId) extends LotteryUpdateEvent
case class ParticipantRemoved(name: String, raffleId: LotteryId) extends LotteryUpdateEvent
case class WinnerSelected(winner:String,date:OffsetDateTime,lotteryId:LotteryId) extends LotteryUpdateEvent
<CommandとEvent>
99. copyright Fringe81 Co.,Ltd.
case class NonEmptyLottery(...) extends Lottery {
def acceptParticipants = ...
def removeParticipants = …
def runTheLottery =
Lottery.actions
.commandHandler {
OneEvent {
case Run =>
val winner = participants(Random.nextInt(participants.size))
WinnerSelected(winner, OffsetDateTime.now, id)
}
}
.eventHandler {
case evt: WinnerSelected => FinishedLottery(evt.winner, id)
}
Command => Event
100. copyright Fringe81 Co.,Ltd.
case class NonEmptyLottery(...) extends Lottery {
def acceptParticipants = ...
def removeParticipants = …
def runTheLottery =
Lottery.actions
.commandHandler {
OneEvent {
case Run =>
val winner = participants(Random.nextInt(participants.size))
WinnerSelected(winner, OffsetDateTime.now, id)
}
}
.eventHandler {
case evt: WinnerSelected => FinishedLottery(evt.winner, id)
}
Event => Aggregate
101. copyright Fringe81 Co.,Ltd.
object Lottery extends Types[Lottery] {
type Id = LotteryId
type Command = LotteryCommand
type Event = LotteryEvent
def create(lotteryId: LotteryId) = ...
def behavior(lotteryId: LotteryId): Behavior[Lottery, LotteryCommand, LotteryEvent] =
Behavior
.first {
create(lotteryId)
}
.andThen {
case r: EmptyLottery =>
r.acceptParticipants
case r: NonEmptyLottery =>
r.acceptParticipants ++ r.removeParticipants ++ r.runTheLottery
case r: FinishedLottery =>
r.rejectAllCommands
}
103. copyright Fringe81 Co.,Ltd.
class LotteryViewProjection(dao: LotteryDAO) extends Projection[LotteryEvent] {
def handleEvent = attempt.HandleEvent {
case evt: LotteryCreated =>
dao.save(LotteryView(id = evt.raffleId))
case evt: LotteryUpdateEvent =>
dao.updateById(evt.lotteryId) { r =>
evt match {
case e: ParticipantAdded =>
r.copy(participants = r.participants :+ newParticipant(e))
case e: ParticipantRemoved =>
r.copy(participants = r.participants.filter(_.name != e.name))
case e: WinnerSelected =>
r.copy(winner = Some(e.winner), runDate = Some(e.date))
}
}
}
Eventをもとに
Q側DBを更新
105. copyright Fringe81 Co.,Ltd.
object AppContext {
val backend = {
val inMemoryBackend = new InMemoryBackend
inMemoryBackend
.configure { // “C” in CQRS
aggregate(Lottery.behavior)
}
.configure { // “Q” in CQRS
projection(
projection = new LotteryViewProjection(new LotteryDAO()),
publisherFactory =
inMemoryBackend.inMemoryPublisherFactory[LotteryEvent]
)
}
}
}
106. copyright Fringe81 Co.,Ltd.
object AppContext {
val backend = {
val inMemoryBackend = new InMemoryBackend
inMemoryBackend
.configure { // “C” in CQRS
aggregate(Lottery.behavior)
}
.configure { // “Q” in CQRS
projection(
projection = new LotteryViewProjection(new LotteryDAO()),
publisherFactory =
inMemoryBackend.inMemoryPublisherFactory[LotteryEvent]
)
}
}
}
108. copyright Fringe81 Co.,Ltd.
val lotteryRef =
AppContext.backend.aggregateRef[Lottery].forId(lotteryId)
lotteryRef ! CreateLottery
lotteryRef ! AddParticipant("Ichiro")
lotteryRef ! AddParticipant("Jiro")
lotteryRef ! AddParticipant("Saburo")
lotteryRef ! Run
dao.find(lotteryId) match {
case Success(r) => println(r.winner)
case Failure(t) => println(t.getMessage)
}
Command
を送る
109. copyright Fringe81 Co.,Ltd.
val lotteryRef =
AppContext.backend.aggregateRef[Lottery].forId(lotteryId)
lotteryRef ! CreateLottery
lotteryRef ! AddParticipant("Ichiro")
lotteryRef ! AddParticipant("Jiro")
lotteryRef ! AddParticipant("Saburo")
lotteryRef ! Run
dao.find(lotteryId) match {
case Success(r) => println(r.winner)
case Failure(t) => println(t.getMessage)
}
Q側を
確認