Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CQRS+EventSourcingをAkka Persistenceを使って実装してみる

CQRS+EventSourcingをAkka Persistenceを使って実装してみる

CQRS+EventSourcingをAkka Persistenceを使って実装してみる

Avatar for satoshi-m8a

satoshi-m8a

March 16, 2016
Tweet

More Decks by satoshi-m8a

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ • Satoshi Matsushita @satoshi_m8a • Scala, Akka, DDD, ϑϩϯτΤϯυ,

    ίϯϐϡʔλϏδϣϯ, ػցֶश • ήώϧϯגࣜձࣾ
 ɹ Python, Go, Erlang, Scala, OCaml, TypeScript 
 ʮGehirn Infrastructure Servicesʯ
 ηΩϡϦςΟ਍அ • ECͷγεςϜΛAkka PersistenceΛ࢖ͬͯ։ൃ͍ͯͨ͠ɻ
  2. Akka + DDD ؾӡͷߴ·Γ(1) • Scala Matsuri 2016 ೋ೔໨ΞϯΧϯϑΝϨϯε
 


    DDD+CQRS+EventSourcing࣮૷͢Δձ
 (AkkaύϑΥʔϚϯενϡʔχϯάʹ͍ͭͯ࿩ͯ͠ΈΑ͏ձ) by ͔ͱ͡ΎΜ͞Μ(@j5ik2o)
  3. Event Sourcing ΧʔτID ɿ “cart1” ঎඼ɿ “A”->0, “B”->1 Χʔτ࡞੒ ঎඼AΛ௥Ճ

    ঎඼BΛ௥Ճ ঎඼AΛ࡟আ ΧʔτID ɿ “cart1” ঎඼ɿ “A”->1, “B”->0
  4. CQRS+ES ίϚϯυαΠυ ΫΤϦαΠυ Journal Aggregate Root Command Service Projection DAO

    Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
  5. ݁Ռ੔߹ੑ ίϚϯυαΠυ ΫΤϦαΠυ Journal Aggregate Root Command Service Projection DAO

    Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
  6. 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
  7. PersistentActor Persistent Actor Journal persistenceId = “c100” count = 0

    CountUp CountIncreased Ack(ӬଓԽ׬ྃ) • ίϚϯυΛड͚෇͚ɺυϝΠϯΠϕϯτΛൃߦ͢Δɻ ᶃ ᶄ ᶅ
  8. PersistentActor Persistent Actor Journal persistenceId = “c100” count = 1

    Ack ᶆ Ack ᶇ • Journal͔ΒͷAckΛ଴ͪɺ಺෦ঢ়ଶΛߋ৽͢Δ
  9. PersistentActorͷ෮ݩ Persistent Actor Journal persistenceId = “c100” count = 3

    CountIncreased ᶃ ᶄ CountIncreased CountIncreased Select Events where persistenceId = “c100” ᶅ
  10. 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)
 }
 } <- ͜͜ͰӬଓԽ <- ӬଓԽ͕ऴΘͬͨޙʹঢ়ଶΛߋ৽ <- ෮ݩͨ͠ΠϕϯτΛݩʹঢ়ଶΛߋ৽
  11. υϝΠϯΠϕϯτͷઃܭ • υϝΠϯΠϕϯτ͸ىͬͨ͜ࣄ࣮Λද͢ɻ
 Πϕϯτ໊͸աڈܗ (Increased, Decresed, Created) • ʮॅॴΛมߋ͠·ͨ͠ʯ vs

    ʮҾͬӽ͠·ͨ͠ʯ • ʮچγεςϜ͔ΒσʔλΛҠߦ͠·ͨ͠ʯΠϕϯτ • ͖͔͚ͬͱͳͬͨίϚϯυΛΠϕϯτͷϝλσʔλͱͯ͠อ࣋͢Δ͜ͱ΋ • ཻ౓͸ࡉ͔͗ͯ͢΋ྑ͘ͳ͍ɻ
 e.g.ʮ༣ศ൪߸Λมߋ͠·ͨ͠ʯ
  12. Persistence Plugin • Cassandra, JDBC, DynamoDB, Riak ޲͚ͷPlugin • ςετ༻ͷInMemory

    Plugin΍ LevelDB Plugin • ReadJournal API(ޙड़)ͷ࣮૷͠΍͍͢DB͕͓͢͢Ί • Cassandra Plugin͸Akkaެࣜ
  13. Journal Projection DAO DB Domain Event DTO Polling ΫΤϦαΠυ DTO

    • Read Journal APIΛ࣮૷ͨ͠Persistence Plugin Λ࢖͏ • JournalΛPollingͯ͠ɺυϝΠϯΠϕϯτΛ଴ͪड͚Δ
  14. ReadJournal API • EventsByTagQuery
 ɹλάΛݩʹΠϕϯτΛऔಘ • EventsByPersistenceIdQuery
 ɹPersistenceIdΛݩʹΠϕϯτΛऔಘɹ • AllPersistenceIdsQuery


    ɹ͢΂ͯͷPersistenceIdΛऔಘ • CurrentPersistenceIdsQuery
 ɹݱࡏଘࡏ͢ΔશͯͷPersistenceIdΛऔಘʢϙʔϦϯάͳ͠ʣ • ͢΂ͯͷJournal Plugin͕͜ΕΒ࣮૷͍ͯ͠ΔΘ͚Ͱ͸ͳ͍
 ɹ࣮૷͕ࠔ೉ͳ΋ͷ΋͋ΔͷͰɺJournal༻ͷDBબͼ͸৻ॏʹ
  15. ΠϕϯτʹλάΛ෇༩͢Δ 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
 }
 }

  16. 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)