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
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
satoshi-m8a
March 16, 2016
Programming
1
110
CQRS+EventSourcingをAkka Persistenceを使って実装してみる
CQRS+EventSourcingをAkka Persistenceを使って実装してみる
satoshi-m8a
March 16, 2016
Tweet
Share
More Decks by satoshi-m8a
See All by satoshi-m8a
Akka Persistent FSM を使ってみる。
satoshim8a
0
120
Other Decks in Programming
See All in Programming
AIと一緒にレガシーに向き合ってみた
nyafunta9858
0
260
humanlayerのブログから学ぶ、良いCLAUDE.mdの書き方
tsukamoto1783
0
200
Fluid Templating in TYPO3 14
s2b
0
130
AIによる開発の民主化を支える コンテキスト管理のこれまでとこれから
mulyu
3
500
OCaml 5でモダンな並列プログラミングを Enjoyしよう!
haochenx
0
150
インターン生でもAuth0で認証基盤刷新が出来るのか
taku271
0
190
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
4k
Package Management Learnings from Homebrew
mikemcquaid
0
230
ノイジーネイバー問題を解決する 公平なキューイング
occhi
0
110
SourceGeneratorのススメ
htkym
0
200
開発者から情シスまで - 多様なユーザー層に届けるAPI提供戦略 / Postman API Night Okinawa 2026 Winter
tasshi
0
210
AI時代の認知負荷との向き合い方
optfit
0
170
Featured
See All Featured
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
130
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Deep Space Network (abreviated)
tonyrice
0
66
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
0
1.1k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
61
52k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.3k
The Curious Case for Waylosing
cassininazir
0
240
Typedesign – Prime Four
hannesfritz
42
3k
GraphQLの誤解/rethinking-graphql
sonatard
74
11k
jQuery: Nuts, Bolts and Bling
dougneiner
65
8.4k
How GitHub (no longer) Works
holman
316
140k
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 ಡॻձ ڵຯͷ͋Δํ͓͕͚͍ͩ͘͞ɻ
͋Γ͕ͱ͏͍͟͝·ͨ͠