Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
CQRS+EventSourcingをAkka Persistenceを使って実装してみる
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
satoshi-m8a
March 16, 2016
Programming
120
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
CQRS+EventSourcingをAkka Persistenceを使って実装してみる
CQRS+EventSourcingをAkka Persistenceを使って実装してみる
satoshi-m8a
March 16, 2016
More Decks by satoshi-m8a
See All by satoshi-m8a
Akka Persistent FSM を使ってみる。
satoshim8a
0
120
Other Decks in Programming
See All in Programming
エージェンティックRAGにAWSで入門しよう!
har1101
7
1.1k
JavaDoc 再入門
nagise
0
280
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
290
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
1.1k
GitHub Copilot CLIのいいところ
htkym
2
1.3k
さぁV100、メモリをお食べ・・・
nilpe
0
130
Oxlintのカスタムルールの現況
syumai
5
1k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
2.4k
タクシーアプリ『GO』の バックエンド開発のおける AI利活用と若者のすべて
pyama86
3
1.9k
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
12k
ふつうのFeature Flag実践入門
irof
7
3.6k
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
450
Featured
See All Featured
[SF Ruby Conf 2025] Rails X
palkan
2
1.1k
sira's awesome portfolio website redesign presentation
elsirapls
0
270
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.8k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
0
1.6k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
310
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
250
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
250
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
820
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
Transcript
CQRS+EventSourcingΛAkka Persistence Λ࣮ͬͯͯ͠ΈΔɻʙίπͱϋϚΓϙΠϯτʙ 2016/03/16 Reactive Messaging PatternsϓϨಡॻձ - CQRSɺESͷجຊΛֶͿ -
Satoshi Matsushita
ࣗݾհ • Satoshi Matsushita @satoshi_m8a • Scala, Akka, DDD, ϑϩϯτΤϯυ,
ίϯϐϡʔλϏδϣϯ, ػցֶश • ήώϧϯגࣜձࣾ ɹ Python, Go, Erlang, Scala, OCaml, TypeScript ʮGehirn Infrastructure Servicesʯ ηΩϡϦςΟஅ • ECͷγεςϜΛAkka PersistenceΛͬͯ։ൃ͍ͯͨ͠ɻ
Akka + DDD ؾӡͷߴ·Γ(1) • Scala Matsuri 2016 ೋΞϯΧϯϑΝϨϯε
DDD+CQRS+EventSourcing࣮͢Δձ (AkkaύϑΥʔϚϯενϡʔχϯάʹ͍ͭͯͯ͠ΈΑ͏ձ) by ͔ͱ͡ΎΜ͞Μ(@j5ik2o)
Akka + DDD ؾӡͷߴ·Γ(2) • Vaughn Vernon ࢯͷॻ੶
Akka + DDD ؾӡͷߴ·Γ(3) • LightbendͷҰ؏ͨ͠πʔϧΩοτ • DDDΛҙࣝͨ͠ͷ Akka Persistence
Akka Persistence Query
Akka + DDD ؾӡͷߴ·Γ(4) • Lagom ɹϚΠΫϩαʔϏεΛߏங͢ΔͨΊͷϑϨʔϜϫʔΫ ɹCQRS+ES͕ϕʔεʹͳ͍ͬͯΔ
Akka + DDD ؾӡͷߴ·Γ(5) • ϚΠΫϩαʔϏεԽͷྲྀΕ • ϦΞΫςΟϒͱ͍͏ߟ͑ํͷ·Γ
࣍ • CQRS • Πϕϯτιʔγϯά • ίϚϯυαΠυ • ΫΤϦαΠυ •
ࢀߟ
CQRS Command Query Responsibility Segregation ίϚϯυɾΫΤϦ
Α͋͘Δ֊Խύλʔϯ ϓϨθϯςʔγϣϯ ΞϓϦέʔγϣϯ υϝΠϯ ΠϯϑϥετϥΫνϟ
CQRS֓೦ਤ ϓϨθϯςʔγϣϯ ΞϓϦέʔγϣϯ υϝΠϯ ΠϯϑϥετϥΫνϟ σʔλΞΫηε ίϚϯυαΠυ ΫΤϦαΠυ
υϝΠϯϞσϧ • ྫɿTwitterͷϑΥϩϫʔ / ϑΥϩΠʔ Ϣʔβʔ ϑΥϩϫʔͷϦετ ϑΥϩΠʔͷϦετ ϒϩοΫϦετ
υϝΠϯϞσϧ • ϑΥϩʔ͢Δͱ͍͏ৼΔ͍ʹண͢Δͱ Ϣʔβʔ ϑΥϩʔ͢Δ(userId) ϒϩοΫ͞ΕΔ(userId) ϑΥϩΠʔͷϦετ ϒϩοΫ͞Ε͍ͯΔ ϢʔβʔͷϦετ
CQRS Ϣʔβʔ ϑΥϩΠʔͷϦετ ϒϩοΫ͞Ε͍ͯΔ ϢʔβʔͷϦετ ίϚϯυαΠυ ΫΤϦαΠυ ϑΥϩϫʔͷϦετ ϑΥϩΠʔͷϦετ ϒϩοΫ͞Ε͍ͯΔ
ϢʔβʔͷϦετ ϒϩοΫ͍ͯ͠Δ ϢʔβʔͷϦετ … ϢʔβʔͷϦετ
ෳࡶ͞ʹཱ͔ͪ͏ • ෳࡶͳυϝΠϯΛɺͦͷ··ෳࡶͳυϝΠϯϞσϧ ʹམͱͯ͠ຬ͕ͪ͠ • ·ͣɺίϯςΩετׂΛݕ౼ • υϝΠϯΛΑ͘؍͠ɺৼΔ͍ʹϑΥʔΧε͢Δ • CQRSESͷݕ౼ͦͷ͋ͱ
Event Sourcing
Event Sourcing ΧʔτID ɿ “cart1” ɿ “A”->0, “B”->1 Χʔτ࡞ AΛՃ
BΛՃ AΛআ ΧʔτID ɿ “cart1” ɿ “A”->1, “B”->0
Snapshot 1 2 Snapshot 101 100 • શͯͷΠϕϯτΛॳΊ͔Β෮ݩ͍ͯͯ͠ ͕͔͔࣌ؒΔ •
εφοϓγϣοτΛͱ్ͬͯத͔Β෮ݩ
CQRS+ES ίϚϯυαΠυ ΫΤϦαΠυ Journal Aggregate Root Command Service Projection DAO
Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
σʔλϕʔεબͷϙΠϯτ • ίϚϯυαΠυ ɹɾCassandra, DynamoDB, Riak ɹɾॻ͖ࠐΈΛεέʔϧͰ͖ΔͷɺՄ༻ੑͷߴ͍ͷ͕ྑ͍ • ΫΤϦαΠυ ɹɾ֤छRDB,
NoSQL(υΩϡϝϯτࢦɾάϥϑࢦ) ɹɾΫΤϦʹڧ͍ͷ͕ྑ͍ ɹɾΈ߹ΘͤOK
Materialized View Pattern • ίϚϯυαΠυͷDB͕ਖ਼ͷσʔλΛอ࣋͢Δɺ ΫΤϦαΠυͦΕͷView • ϦʔυϨϓϦΧͷߏஙʢಡΈࠐΈΛεέʔϧʣ https://msdn.microsoft.com/ja-jp/library/dn589782.aspxɹ͔ΒҾ༻
αʔϏε౷߹༰қ • ৽͍͠αʔϏεΛՃͨ͠ΒɺυϝΠϯΠϕϯτΛྲྀ ͠ࠐΉɻ • ͔͋ͨɺͦͷ৽͍͠αʔϏε͕࠷ॳ͔Β౷߹͞Εͯ Δ͔ͷΑ͏ʹৼΔ͏ɻ • ݱࡏͷΠϕϯτ·Ͱ͍͍ͭͨΒɺγεςϜʹೃછΜ Ͱ͍Δɻ
݁Ռ߹ੑ ίϚϯυαΠυ ΫΤϦαΠυ Journal Aggregate Root Command Service Projection DAO
Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
Over Kill • ྫɿIDɺ໊લɺύεϫʔυɺE-MailΞυϨεΛ࣋ͭɺ ձһAR • ύεϫʔυE-MailΞυϨεͷมߋཤྺΛ͏͜ͱ ͰɺϏδωεͷՁΛੜΉͷ͔ʁ • CQRS͚ͩɺ͘͠୯७ͳCRUD͕Ͱ͖Δ͚ͩͰΑ
͍ͷͰʁ
༨ஊɿ७ਮͳREST APIDDDʹ͔ͳ͍ • REST APIͰҰ୴গͳ͘ͳͬͨใΛ෮ݩ͢Δͷࠔ • ७ਮͳRESTʹͩ͜ΘΒͳ͍ɻ CQRSͰ࡞ͬͨં֯ͷϦονͳίϚϯυϞσϧ͕ҙຯΛͳ͞ͳ͘ͳΔɻ ۀͰൃੜ͢Δૢ࡞ ใྔɿେ
REST API ใྔɿখ ϦονͳίϚϯυϞσϧ ใྔɿେ ʼ ʻ ×
ESͷϝϦοτɾσϝϦοτ • ɹϝϦοτ ΠϯϐʔμϯεϛεϚον͕ͳ͍ɻ ཤྺཧ͕ෆཁɺσʔλղੳσόοάʹ͑Δɻ ΠϕϯτهͷΈͳͷͰύϑΥʔϚϯε͕ྑ͍ɻ ػೳՃ༰қɻ • ɹσϝϦοτ Πϕϯτͷमਖ਼͕ࡶ(ޙड़)
σʔλαΠζͷ
CQRS+ESͷϝϦοτɾσϝϦοτ • ɹϝϦοτ υϝΠϯͷৼΔ͍͕໌֬ʹͳΔ ViewΛॊೈʹͭ͘ΕΔ εέʔϧॊೈʹ • ɹσϝϦοτ ݁Ռ߹ੑ
AkkaͰ࡞Δ CQRS+ES ίϚϯυαΠυ ΫΤϦαΠυ Journal Aggregate Root Command Service Projection
DAO Query Service DB DB Command Domain Event Domain Event DTO DTO Polling Akka Persistence Akka Persistence Plugin Akka Persistence Query Slick3 Akka Cluster Sharding
ίϚϯυαΠυ
Akka Persistence • Actorͷ෦ঢ়ଶΛӬଓԽ͢Δ͜ͱ͕Ͱ͖Δ • AkkaͷCQRSͱΠϕϯτιʔγϯάʹΘΕΔ • ϝοηʔδͷ࠶ૹͷΈఏڙʢAt least once
deliveryʣ
ྫɿΧϯτ͢ΔActor • CounUpίϚϯυΛड͚औΓɺ෦ͷΧϯτΛ૿ Ճ͍ͤͯ͘͞ɻ
PersistentActor Persistent Actor Journal persistenceId = “c100” count = 0
CountUp CountIncreased Ack(ӬଓԽྃ) • ίϚϯυΛड͚͚ɺυϝΠϯΠϕϯτΛൃߦ͢Δɻ ᶃ ᶄ ᶅ
PersistentActor Persistent Actor Journal persistenceId = “c100” count = 1
Ack ᶆ Ack ᶇ • Journal͔ΒͷAckΛͪɺ෦ঢ়ଶΛߋ৽͢Δ
ϙΠϯτ • ෦ঢ়ଶ(count)ͷߋ৽υϝΠϯΠϕϯτͷӬଓԽ ྃΛ͔ͬͯΒߦ͏ • ӬଓԽ͞Ε͍ͯͳ͍Πϕϯτى͍ͬͯ͜ͳ͍Πϕ ϯτͱಉٛ
PersistentActorͷ෮ݩ • ΫϥογϡɺλΠϜΞτ࣌ͷఀࢭɺγϟʔυͷҠ ಈͳͲ༷ʑͳཧ༝ͰActor࠶ىಈ͢Δɻ • ࠶ىಈͨ͠ActorΛݩͷঢ়ଶʹ͠ɺίϚϯυΛड ͚͚͍ͨɻ
PersistentActorͷ෮ݩ Persistent Actor Journal persistenceId = “c100” count = 3
CountIncreased ᶃ ᶄ CountIncreased CountIncreased Select Events where persistenceId = “c100” ᶅ
Akka Persistence class CountUpActor extends PersistentActor { override def persistenceId:
String = self.path.name context.setReceiveTimeout(120.seconds) var count: Int = 0 def updateState(event: Increased) = { this.count = this.count + event.amount } override def receiveRecover: Receive = { case e: Increased => updateState(e) } override def receiveCommand: Receive = { case c: CountUp => persist(Increased(c.amount)) { event => updateState(event) sender() ! event } case ReceiveTimeout => context.parent ! Passivate(stopMessage = Stop) case Stop => context.stop(self) } } <- ͜͜ͰӬଓԽ <- ӬଓԽ͕ऴΘͬͨޙʹঢ়ଶΛߋ৽ <- ෮ݩͨ͠ΠϕϯτΛݩʹঢ়ଶΛߋ৽
ϙΠϯτ • Recovery͕ྃ͢Δ·ͰɺίϚϯυΛॲཧ͠ͳ͍Α ͏ʹͳ͍ͬͯΔɻ • Recovery࣌෦ঢ়ଶͷߋ৽͚ͩΛߦ͏ɺ ֎෦ίϚϯυϝοηʔδΛൃߦͯ͠ͳΒͳ ͍ɻ
Aggregate Root • ࣮ࡍPersistentActorΛܧঝͯ͠ɺAggregateRoot ΞΫλʔΛ࡞Δͱྑ͍ɻ(c.f. akka-ddd) https://github.com/pawelkaczor/akka-ddd/blob/master/akka-ddd-core/src/main/scala/pl/newicom/dddd/ aggregate/AggregateRoot.scala • εφοϓγϣοτૢ࡞,
GracefulPassivation, ϦΧόϦΛӅṭ
υϝΠϯΠϕϯτͷઃܭ • υϝΠϯΠϕϯτىͬͨ͜ࣄ࣮Λද͢ɻ Πϕϯτ໊աڈܗ (Increased, Decresed, Created) • ʮॅॴΛมߋ͠·ͨ͠ʯ vs
ʮҾͬӽ͠·ͨ͠ʯ • ʮچγεςϜ͔ΒσʔλΛҠߦ͠·ͨ͠ʯΠϕϯτ • ͖͔͚ͬͱͳͬͨίϚϯυΛΠϕϯτͷϝλσʔλͱͯ͠อ࣋͢Δ͜ͱ • ཻࡉ͔͗ͯ͢ྑ͘ͳ͍ɻ e.g.ʮ༣ศ൪߸Λมߋ͠·ͨ͠ʯ
υϝΠϯΠϕϯτͷγϦΞϥΠζ • υϝΠϯΠϕϯτγϦΞϥΠζ͞Εͯɺ ίϚϯυαΠυͷDBʹอଘ͞ΕΔɻ • σϑΥϧτͰJavaͷγϦΞϥΠβ͕ΘΕΔ • JavaͷγϦΞϥΠβ໘Ͱɺ֦ு໘Ͱ͕͋Δ • ࣮ӡ༻͢ΔͷͰ͋Εɺ
Google Protocol Buffers ͕ແ
υϝΠϯΠϕϯτͷεΩʔϚมߋ • ϑΟʔϧυΛՃͨ͠ΓɺҰͭͷΠϕϯτΛׂͳͲ • EventAdapterΛͬͨΓɺҰԠͷղܾํ๏͋Δ͕ࡶ • Stamina ɹhttps://github.com/scalapenos/stamina
Persistence Plugin • Cassandra, JDBC, DynamoDB, Riak ͚ͷPlugin • ςετ༻ͷInMemory
Plugin LevelDB Plugin • ReadJournal API(ޙड़)ͷ࣮͍͢͠DB͕͓͢͢Ί • Cassandra PluginAkkaެࣜ
ΫΤϦαΠυ
Akka Persistence Query • CQRSͷΫΤϦαΠυͷ࣮ʹΘΕΔ • ΫΤϦαΠυશମͰͳ͘ɺ Journal͔ΒΫΤϦଆͷDBͷӨʹΘΕΔ • experimental
(Akka 2.4.2) Pluginग़ἧ͍ͬͯͳ͍
Journal Projection DAO DB Domain Event DTO Polling ΫΤϦαΠυ DTO
• Read Journal APIΛ࣮ͨ͠Persistence Plugin Λ͏ • JournalΛPollingͯ͠ɺυϝΠϯΠϕϯτΛͪड͚Δ
ReadJournal API • EventsByTagQuery ɹλάΛݩʹΠϕϯτΛऔಘ • EventsByPersistenceIdQuery ɹPersistenceIdΛݩʹΠϕϯτΛऔಘɹ • AllPersistenceIdsQuery
ɹͯ͢ͷPersistenceIdΛऔಘ • CurrentPersistenceIdsQuery ɹݱࡏଘࡏ͢ΔશͯͷPersistenceIdΛऔಘʢϙʔϦϯάͳ͠ʣ • ͯ͢ͷJournal Plugin͕͜ΕΒ࣮͍ͯ͠ΔΘ͚Ͱͳ͍ ɹ࣮͕ࠔͳͷ͋ΔͷͰɺJournal༻ͷDBબͼ৻ॏʹ
ΠϕϯτʹλάΛ༩͢Δ class ThreadEventAdapter extends WriteEventAdapter { override def manifest(event:
Any): String = "" val tags = Set("Thread") override def toJournal(event: Any): Any = event match { case e: ThreadEvent => Tagged(event, tags) case _ => event } }
Projection • Read Model Projection / Read Model Updaterͱ͍͏ •
υϝΠϯΠϕϯτΛݩʹɺViewΛߏங͢Δ
Projection val readJournal = PersistenceQuery(system) .readJournalFor[LeveldbReadJournal](LeveldbReadJournal.Identifier) implicit val mat
= ActorMaterializer()(system) val dao = new ThreadsDao(dbConfig) val projection = new ThreadProjection(dao) readJournal .eventsByTag("Thread", projection.lastOffset) .mapAsync(1) { envelope => projection.update(envelope.event).map(_ => envelope.offset) } .mapAsync(1) { offset => projection.saveProgress(offset) } .runWith(Sink.ignore)
ΫΤϦ • Slick3ͳͲΛͬͯΫΤϦ͢Δɻ
ͦͷଞ • Process Manager ෳͷAggregate Rootʹ·͕ͨͬͨॲཧΛॱং ྑ࣮͘ߦ͢Δ ɹPersistentFSMΛ͏ɻ • Cluster
Sharding ɹAggregate RootΛࢄͤ͞Δɻ ɹCluster Singleton
·ͱΊ • CQRS+ESͷίϚϯυαΠυͱΫΤϦαΠυΛ Akka Persistenceͱ Akka Persistence QueryͰ࣮ ͨ͠ •
Lagom
ࢀߟ • CQRS Journey https://msdn.microsoft.com/ja-jp/library/jj554200.aspx • .NETͷΤϯλʔϓϥΠζΞϓϦέʔγϣϯΞʔΩςΫνϟ • ࣮ફυϝΠϯۦಈઃܭ
Reactive Messaging Patterns with the Actor Model ಡॻձ ڵຯͷ͋Δํ͓͕͚͍ͩ͘͞ɻ
͋Γ͕ͱ͏͍͟͝·ͨ͠