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

Akka Persistence Typedにおけるドメインオブジェクトの実装パターン / P...

Akka Persistence Typedにおけるドメインオブジェクトの実装パターン / Pattern of Implement Domain Object with Akka Persistence Typed

ScalaMatsuri2022 発表スライド。

Akka Persistence Typedはイベントソーシングを実現する強力なツールですが、コマンドハンドラとイベントハンドラを分けて実装しなければならない制約があり、ドメインオブジェクトの実装にも影響を与えます。

このセッションでは、その制約の中でできるだけスマートにドメインオブジェクトを実装するために試した実装パターンをご紹介します。

-----
ScalaMatsuri 2022 Talk.

Akka Persistence Typed is a powerful tool for Event Sourcing, but it has a restriction that CommandHandlers and EventHandlers must be implemented separately, which affects the implementation of Domain Objects.

In this session, I will introduce an implementation pattern within this constraint.

Yohei TSUJI

March 19, 2022
Tweet

More Decks by Yohei TSUJI

Other Decks in Programming

Transcript

  1. Pattern of Implement Domain Object with Akka Persistence Typed 2022.3.19

    ScalaMatsuri 2022 TSUJI Yohei "LLB 1FSTJTUFODF5ZQFEʹ͓͚ΔυϝΠϯΦϒδΣΫτ ͷ࣮૷ύλʔϯ
  2. 2 TSUJI Yohei Scala Engineer at Chatwork Co., Ltd. Scala

    DDD(Domain Driven Design) Microservice Architecture and Reactive Architecture(Akka) We are working re-architecting our service with Akka. Twitter / GitHub @crossroad0201 "LLBΛ࢖ͬͨαʔϏεͷٕज़࡮৽ʹऔΓ૊ΜͰ͍·͢ɻ 2
  3. Contents • The Akka Persistence Typed • The Domain Object

    • Integrate the Akka Persistence Typed and the Domain Object • Conclusion 3 ·ͣ"LLB 1FSTJTUFODF5ZQFEͱυϝΠϯΦϒδΣΫτͷ ཁ݅ʹ৮Ε͔ͯΒɺͦΕΒͷ౷߹ํ๏Λ঺հ͠·͢ɻ
  4. Summary of Akka Persistence Typed • Akka sub-module for persisting

    Actors. • Event Sourcing. Not persists state of Actor, persists EVENTs that created by domain logic. • Insert EVENTs only. No update, No locks, high performance and scalability. • Can implement CQRS and Event-driven architecture by streaming events. ΠϕϯτιʔγϯάͰΞΫλʔΛӬଓԽ͠·͢ɻ ӬଓԽ͞ΕΔͷ͸ঢ়ଶͦͷ΋ͷͰ͸ͳ͘ɺΠϕϯτͰ͢ɻ 5 Persistent Actor Journal Insert EVENT Insert EVENT
  5. Implementation constraints • Needs to be implement 2 callback functions

    for processing a message, called the Command Handler and Event Handler. ̍ͭͷϝοηʔδͷॲཧΛɺίϚϯυϋϯυϥͱΠϕϯτ ϋϯυϥʹ෼͚ͯॲཧΛ࣮૷͢Δඞཁ͕͋Γ·͢ɻ 6 object ExamplePersistentBehavior { def apply(…): Behavior[…] = EventSourcedBehavior( commandHandler, eventHandler ) val commandHandler: (State, Command) => Effect[Event, State] = ??? val eventHandler: (State, Event) => State = ??? }
  6. What to do at the callbacks • The Command Handler

    • Do domain logic. (may fail) • Create EVENTs with result of domain logic and persists it. • (If necessary) Reply to caller. • The Event Handler • Update the state of Actor using created EVENTs. • NO fail, NO side effects. 7 ίϚϯυϋϯυϥͰ͸υϝΠϯϩδοΫΛ࣮ߦͯ͠Πϕϯ τΛੜ੒͠ɺΠϕϯτϋϯυϥͰঢ়ଶΛߋ৽͠·͢ɻ
  7. What to do at the Domain Object 1. Check invariants

    with latest state. 2. Do domain logic. 3. (If Event-driven architecture) Create an EVENTs. 4. Update the state to latest. 9 ෆม৚݅ΛνΣοΫͯ͠υϝΠϯϩδοΫΛ࣮ߦ͠ɺΠϕ ϯτΛੜ੒͢Δͱڞʹঢ়ଶΛ࠷৽ʹߋ৽͠·͢ɻ
  8. What is the ideal Domain Object • Encapsulate knowledge about

    the Business domain. • Test it easily. • NOT depends specific technology. (just like Akka Persistence Typed) • (If Event-driven architecture) Define and create EVENTs in the domain layer. 10 υϝΠϯΦϒδΣΫτ͸ɺಛఆٕज़ͷӨڹΛड͚ͣʹ७ਮ ʹϏδωευϝΠϯͷ஌ࣝΛΧϓηϧԽ͢Δͷ͕ཧ૝Ͱ͢
  9. Where to do, where to implement 1. Check invariants with

    latest state. 2. Do domain logic. 3. (If Event-driven architecture) Create an EVENTs. 4. Update the state to latest. 12 υϝΠϯΦϒδΣΫτͰߦ͏खଓ͖΋ɺίϚϯυϋϯυϥ ͱΠϕϯτϋϯυϥʹରԠͤ͞ͳ͚Ε͹ͳΓ·ͤΜɻ in Command Handler In Event Handler
  10. Implementation styles Introduce 3 styles. See the example code for

    more details. https://github.com/crossroad0201/ddd-with-akka-persistence-typed • Style A -> example1 • Style B -> example3 • Style C -> example5 13 ͜͜Ͱ͸̏ͭͷ࣮૷ελΠϧΛ঺հ͠·͢ɻ (JU)VCͷαϯϓϧίʔυ΋͋Θͤͯࢀর͍ͩ͘͞ɻ
  11. Implementation style A γϯϓϧͳ࣮૷Ͱ͕͢ɺυϝΠϯϩδοΫͱΠϕϯτͷఆ ٛੜ੒͕ΞΫλʔଆʹ࣮૷͞Ε·͢ɻ 14 package example1.domain case class

    Task(…, subject: Subject, status: Status) { def canEditSubject: Boolean = status != Status.Done def editSubject(newSubject: Subject): Task = { require(canEditSubject) copy(subject = newSubject) } package example1.interfaceadapter object TaskPersistenceBehavior { def commandHandler(…) = case (Just(entity), EditSubject(subject, replyTo)) => if (entity.canEditSubject) Effect.persist(SubjectEdited(subject)) .thenReply(replyTo)(_ => EditSubjectSucceeded) def eventHandler(...) = case (Just(entity), SubjectEdited(newSubject)) => Just(entity.editSubject(newSubject)) case class SubjectEdited(newSubject: Subject) extends TaskEvent Domain Actor (1) Check invariants Domain (2) Do domain logic Actor (3) Define EVENTs Actor (4) Create EVENT Actor (5) Update state Domain (3) (4) (1) (2) (5)
  12. Implementation style B υϝΠϯϩδοΫͷ࣮ߦͱΠϕϯτͷੜ੒ΛɺυϝΠϯΦ ϒδΣΫτʹΧϓηϧԽͰ͖·ͨ͠ɻ 15 package example3.domain case class

    Task(…, subject: Subject, status: Status) { def editSubject(newSubject: Subject) : Either[…] = if (status == Status.Todo) Right(SubjectEdited(newSubject)) def applyEvent(event: TaskMutationEvent) = event match { case SubjectEdited(newSubject) => copy(subject = newSubject) case class SubjectEdited(newSubject: Subject) extends TaskMutationEvent package example3.interfaceadapter object TaskPersistenceBehavior { def commandHandler(…) = case (Just(entity), EditSubject(newSubject, replyTo)) => entity.editSubject(newSubject) match { case Right(event) => Effect.persist(event) .thenReply(replyTo)(_ => EditSubjectSucceeded) def eventHandler(...) = case (Just(entity), event: TaskMutationEvent) => Just(entity.applyEvent(event)) Domain Actor (1) Check invariants Domain (2) Do domain logic Domain (3) Define EVENTs Domain (4) Create EVENT Domain (5) Update state Domain (1), (2) (3) (4) (5)
  13. Implementation style C ͞Βʹൃల࣮ͤͨ͞૷ྫɻυϝΠϯϩδοΫ͕ฦ݁͢Ռܕ ͔Β࠷৽ͷঢ়ଶΛऔಘͰ͖ɺςετ༰қੑΛߴΊ·͢ɻ 16 package example5.domain trait Result[EVENT,

    ENTITY] { val event: EVENT def entity: ENTITY } case class Task(…, subject: Subject, state: State) { def editSubject(newSubject: Subject): Either[…] = if (status == Status.Todo) { Right(Result(SubjectEdited(newSubject), Task.this)) case class SubjectEdited(newSubject: Subject) extends TaskMutationEvent { override def playTo(entity: Task): Task = entity.copy(subject = newSubject) package example5.interfaceadapter object TaskPersistenceBehavior { def commandHandler(…) = case (Just(entity), EditSubject(newSubject, replyTo)) => entity.editSubject(newSubject) match { case Right(result) => Effect.persist(result.event) .thenReply(replyTo)(_ => EditSubjectSucceeded) def eventHandler(...) = case (Just(entity), event: TaskMutationEvent) => Just(event.playTo(entity)) (1) Check invariants Domain (2) Do domain logic Domain (3) Define EVENTs Domain (4) Create EVENT Domain (5) Update state Domain ❗'enEty' will not used by Actor. No costs because lazy evaluaEon. Domain Actor (1), (2) (3) (4) (5)
  14. Comparing test code for the Domain Object 17 val sut:

    Task = Task.create( …, Subject("Test"), … ) val actualChecked = sut.canEditSubject assert(actualChecked) // Can not test domain logic // and event creation in this test. val actualEntity = sut.editSubject(Subject("Edited")) assert(actualEntity == Task(…, Subject("Edited"), …) ) val sut: Task = Task.applyEvent( Task.create( …, Subject("Test"), … ) ) val actualEvent = sut.editSubject(Subject("Edited")) assert(actualEvent == Right(SubjectEdited(Subject("Edited"))) ) val actualEntity = sut.applyEvent(actualEvent) assert(actualEntity == Task(…, Subject("Edited"), …) ) val sut: Task = Task.create( …, Subject("Test"), … ) .entity val actual = sut.editSubject(Subject("Edited")) assert(actual.event == SubjectEdited(Subject("Edited")) ) assert(actual.entity == Task( …, Subject("Edited"), …) ) To create test target, needs create and apply an EVENT…🤔 Create test target easily!!👍 Style A Style B Style C
  15. Conclusion • Encapsulate as many as possible to the Domain

    Objects. (Style B or C) • Testability for the Domain Objects is important for growth it. • If your business domain is very simple, there is also a way to implement everything to the Actor sate without separated domain layer. Ͱ͖Δ͚ͩଟ͘ΛυϝΠϯΦϒδΣΫτʹΧϓηϧԽ͠ɺ ୯ಠͰςετͰ͖ΔΑ͏ʹ͓ͯ͘͜͠ͱΛ͓͢͢Ί͠·͢ 18
  16. 19 19 Thank you for listening. Thank you to my

    team. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ