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

最新DDDアーキテクチャとAkkaでの実装ヒントについて

 最新DDDアーキテクチャとAkkaでの実装ヒントについて

DDD+CQRS+Event SourcingとAkkaでの実装ヒントを解説。

933291444e456bfb511a66a2fa9c6929?s=128

かとじゅん

March 26, 2016
Tweet

Transcript

  1. ࠷৽DDDΞʔΩςΫνϟ ͱAkkaͰͷ࣮૷ώϯτ ʹ͍ͭͯ Ճ౻५Ұ ͔ͱ͡ΎΜ  $IBU8PSL 

  2. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/3/26 © ChatWork All rights reserved. ࣗݾ঺հ w!KJLP w$IBU8PSL͔Βདྷ·ͨ͠ɻ

    wࡽ͍ͯ͠Δίʔυ wIUUQTHJUIVCDPNKJLP wIUUQTHJUIVCDPNTJTJPI wIUUQTHJUIVCDPNDIBUXPSLTCUBXT wIUUQTHJUIVCDPNDIBUXPSLTCUEPDLFS w࠷ۙ͸$234 &4 "LLBͳͲʹ஫໨͍ͯ͠·͢ɻ w%%%Ͱ໎ࢠʹͳͬͯͨΒ੠͔͚͍ͯͩ͘͞ɻΞυό Πε͠·͢ɻ
  3. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/3/26 © ChatWork All rights reserved. ΞδΣϯμ w4DBMBͰͷ࠷৽%%%ΞʔΩςΫνϟͱ"LLBͰͷ࣮૷ ώϯτʹ͍ͭͯ࿩͠·͢ɻ

    wΞδΣϯμ w%%% $234 &WFOU4PVSDJOHʹ͍ͭͯ w%%%ͱ͸Կ͔ʁ w$234ͱ͸Կ͔ʁ w&WFOU4PVSDJOHͱ͸Կ͔ʁ w"LLBͰͲͷΑ͏ʹ࣮૷͢Δͷ͔ʁ
  4. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 4 %%% $234

    &WFOU4PVSDJOHʹ͍ͭͯ
  5. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 5 ':*%%%ؔ࿈͓͢͢Ίਤॻ w

    %%% w ʹൃץɻ w ೔ຊޠ൛ w %&"" w ʹൃץɻ w ೔ຊޠ൛ɻ w *%%% w ʹൃץɻ w ೔ຊޠ൛ɻ w 3.1 w ʹൃץɻ w ಡॻձ͋Γ·͢
  6. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 6 %%%ͱ͸Կ͔ʁ w

    Ϗδωεͷ஌ࣝΛ൓өͨ͠ιϑτ΢ΣΞϞσϧ ϢϏΩλεݴޠ ڥք͚ͮΒΕͨ ίϯςΩετ αϒυϝΠϯ ϨΠϠʔԽ ΞʔΩςΫνϟ ϏϧσΟϯά ϒϩοΫ υϝΠϯϞσϧ ϥΠϑαΠΫϧ؅ཧ ઓུతϞσϦϯά ઓज़తϞσϦϯά ରԠ෇͘ υϝΠϯͷִ཭ •υϝΠϯର͢ΔιϦϡʔγϣϯ •υϝΠϯϞσϧͷ෼཭/౷߹ઓུ ໰୊ͱͦͷ༏ઌॱҐͷఆٛ ઐ໳Ոͱ։ൃऀͷڞ௨ݴޠ
  7. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 7 ϨΠϠʔԽΞʔΩςΫνϟ w

    υϝΠϯΛִ཭͢Δ͜ͱ͕໨తɻ Client Application Database Domain Layer Application Layer Records Infrastructure Layer Form Dto Aggregate Record Record Aggregate
  8. case class User(identifier: UserId, statusType: StatusType.Value, name: UserName, profile: UserProfile,

    config: UserConfig) case class UserProfile(name: String, address: ContactAddress) case class UserConfig(hashedPassword: String) ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 8 ϨΠϠʔԽΞʔΩςΫνϟͷ࣮૷্ͷ໰୊ wϨΠϠʔΛލ͙Ϟσϧม׵ wॻ͖ࠐΈ3FRVFTU+40/ˠू໿ˠ3FDPSE wಡΈࠐΈ3FTQPOTF40/ˡू໿ˡ3FDPSE wू໿ͱςʔϒϧϞσϧͷΠϯϐʔμϯεϛεϚον id status name zip_code pref_code city_name address … hashed_password 1 1 xxx 123-4567 yyy zzz aaa … xyeogkdeid … … … … … … … … … n … … … … … … … … ΠϯϐʔμϯεϛεϚον
  9. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 9 ଞͷ%%%ΞʔΩςΫνϟྫ

  10. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 10 ϔΩαΰφϧcΫϦʔϯcΦχΦϯ ΞʔΩςΫνϟ

    w ґଘؔ܎ͷٯసͱ֎෦ɾ಺෦ͷ֓೦Λߟྀͨ͠ύλʔϯ
  11. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 11 ϔΩαΰφϧܥͰ͸ґଘؔ܎͕%*1ʹͳΔ w

    ґଘؔ܎ٯసͷݪଇ %*1%FQFOEFODZ*OWFSTJPO1SJODJQMF ΛϨΠϠʔԽ ΞʔΩςΫνϟʹద༻͠ԁঢ়ʹల։ͨ͠΋ͷɻυϝΠϯ૚͕Πϯϑϥετ ϥΫνϟ૚ʹʢ௚઀ʣґଘ͠ͳ͘ͳΔ
  12. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 12 %*11PSU"EBQUPSͷ۩ମྫ w

    ಺ଆ͔Β֎ଆʹ͸௚઀ґଘ͕Ͱ͖ͳ͍ɻ
  13. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 13 ࠓ·ͰͱԿ͕ҧ͏ͷ͔ʁ wυϝΠϯ૚͕֎ʹ࿙Εग़͞ͳ͍Α͏ʹ͢Δͱ͍͏

    ఺͕ڧௐ͞Ε͍ͯΔɻ֎ଆ͔Β಺ଆʹ͔͠௚઀త ʹґଘͰ͖ͳ͍ɻ಺ଆ͔Β֎ଆʹґଘ͢Δ৔߹͸* 'Λ௨ͯؒ͠઀తʹґଘ͢Δɻ ࣮͸ɺ͜Ε͸ϨΠ ϠʔԽΞʔΩςΫνϟͰ΋ݴٴ͞Ε͍ͯΔ͜ͱ  wݸਓతʹ͸ɺυϝΠϯ૚ͷՄൖੑΛҙࣝͯ͠ଟछ ଟ༷ͳΞμϓλʹରԠ͢Δߟ͑ํʹݟ͑Δɻ wͰ͸ɺՄൖɾඇՄൖͷ൑அج४ͱ͸Կ͔ʁ
  14. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 14 υϝΠϯ૚ͷՄൖੑͱ͸ w

    ೖग़ྗσόΠε͸ɺཁ݅ʹΑͬͯมΘΔͷ Ͱɺཁ݅ʹΑͬͯมΘΒͳ͍ϙʔτʹґଘ ͠ɺΞμϓλ૚͸੾ସՄೳʹɻ w υϝΠϯ૚ͷՄൖੑ͸ґଘઌʹىҼ͢Δ໰ ୊ɻΠϯϑϥετϥΫνϟ૚ʹ4DBMB͚ͩ Ͱͳ͘"LLB΋ؚΊΔͱ͍͏ߟ͑ํ΋͋Δ͕ɺ *0σόΠεʹ͍ۙ෦෼Ͱ͸ϙʔτͷΑ͏ ͳந৅ʹґଘґଘ͢΂͖ͩͱߟ͑Δɻ ΠϯϑϥετϥΫνϟ૚
 ཁ݅ʹΑͬͯ มΘΒͳ͍෦෼ υϝΠϯ૚ (Ϣʔεέʔε૚΋ྫ֎Ͱ͸ͳ͍) Ξμϓλ ཁ݅ʹΑͬͯ มΘΔ෦෼ ϙʔτ(I/F)
 ཁ݅ʹΑͬͯ มΘΒͳ͍෦෼ w ΠϯϑϥετϥΫνϟ૚͸ɺ্ҐͷϨΠϠʔΛࢧ͑ΔҰൠతͳٕज़తͳػೳΛఏڙ͢ Δɻ͜Εʹ͸ɺΞϓϦέʔγϣϯͷͨΊͷϝοηʔδૹ৴ɺυϝΠϯͷͨΊͷӬଓ ԽɺϢʔβΠϯλʔϑΣΠεͷͨΊͷ΢ΟδοτඳըͳͲ͕͋Δɻ &WBOT  w ϔΩαΰφϧܥ͸υϝΠϯ૚ͷՄൖੑΛҙࣝ͢ΔɻΠϯϑϥετϥΫνϟ૚͸ཁ݅ʹ ΑͬͯมΘΒͳ͍෦෼Ͱ͋ΔͨΊɺυϝΠϯ૚͸ΠϯϑϥετϥΫνϟ૚ʹґଘͯ͠ ΋υϝΠϯ૚ͷՄൖੑ͸௿Լ͠ͳ͍ͱߟ͑ΒΕΔɻྫ͑͹ɺTDBMBMBOH ͳͲɻ
  15. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 15 $234ͱ͸ w

    $PNNBOEBOE2VFSZ3FTQPOTJCJMJUZ4FHSFHBUJPO wίϚϯυɾΫΤϦ੹຿෼཭ w ೥(SFH:PVOHࢯ͕ߟҊͨ͠ύλʔϯɻ w ೥ʹ#FSUSBOE.FZFSࢯ͕ߟҊͨ͠ίϚϯυΫΤϦ෼ ཭ݪଇ $PNNBOE2VFSZ4FQBSBUJPO$24 ΛΞʔΩς Ϋνϟʹద༻ͨ͠΋ͷ͕$234ɻ w ʮ͋ΒΏΔϝιου͸ɺΞΫγϣϯΛ࣮ߦ͢ΔίϚϯ υ͔ɺݺͼग़͠ݩʹσʔλΛฦ͢ΫΤϦ͔ͷ͍ͣΕ͔ Ͱ͋ͬͯɺ྆ํΛߦͬͯ͸ͳΒͳ͍ɻ͜Ε͸ɺ࣭໰Λ ͢Δ͜ͱͰճ౴ΛมԽͤͯ͞͸ͳΒͳ͍ͱ͍͏͜ͱ ͩɻʯ
  16. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 16 ୯७ͳ$234ΞʔΩςΫνϟ w

    $24ʹج͍ͮͨ࠷΋୯७ͳߏ੒͸ɺυϝΠϯ૚ΛϥΠτͱϦʔυͷܥ Λ෼͚Δɻ Client Write Stack Read Stack Database Domain Layer Application Layer Infrastructure Layer Domain Layer Application Layer Infrastructure Layer
  17. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 17 ඪ४తͳ$234ΞʔΩςΫνϟ w

    ϥΠτͱϦʔυܥΛ͞Βʹ෼ੳ͍ͯ͘͠ͱɺϦʔυଆ͸ΫΤϦཁ݅ʹ ରԠ͢Δͷ͕໨తͳͷͰυϝΠϯϞσϧ͕ෆཁʹͳΔɻ Client Write Stack Read Stack Database Domain Layer Application Layer DAO + DTO Read Records Infrastructure Layer Write Records ReadModel Updater ܗࣜม׵Λߦ͏
  18. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 18 ͳͥ$234ͳͷ͔ʁ wυϝΠϯϞσϧͷ୯७Խ

    wϦʔυଆʹ͸υϝΠϯϞσϧ͕ෆཁʹͳ ΔɻΫΤϦʹඞཁͳ%50͕͋Ε͹Α͍ɻ wϦʔυ͞ΕΔ͜ͱΛલఏʹू໿Λߟ͑Δ ඞཁ͕ͳ͍ɻ wϥΠτଆͷϞσϧ͸ৼΔ෣͍͚ͩʹ஫ྗ Ͱ͖ΔͷͰɺ୯७ͳ΋ͷʹͳΔɻ
  19. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 19 %%% $234ΞʔΩςΫνϟ

     w ϦϙδτϦ͸TUPSF FOUJUZ SFTPMWF#Z JE ఔ౓ͷ࣮૷ͰΑ͍ɻ w ΑΓෳࡶͳΫΤϦཁ݅͸%"0 %50Ͱ࣮ݱ͢Δɻ Domain Layer Repository(Id => Aggregate) Aggregate RootEntity Value Object DAO + DTO DAO findById findByName findByDeptId EmpDto { id, name, deptName }
  20. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 20 38෼཭͚ͩͳΒ΄͔ʹ΋ํ๏͕͋Δ w

    υϝΠϯϞσϧͷࣸ૾Λ4MBWFʹ࡞Δ͚ͩɻͨͩ͠4MBWF%#͸ϨεϙϯεϞσϧͰ͸ ͳ͍ͷͰɺ$MJFOUʹฦ͢લʹϨεϙϯεϞσϧͰ͋Δ%50ʹม׵͢Δඞཁ͕͋Δɻ Client Write Stack Read Stack Database Domain Layer Application Layer DAO + Converter + DTO Slave DB Infrastructure Layer Master DB
  21. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 21 3ܥʹ͸4NBSU6*"OUJ1BUUFSO͕࢖͑Δ w

    υϝΠϯϞσϧ͸8ܥ͚ͩ ʹଘࡏ͢ΔͨΊɺ3ܥͰ͸ 4NBSU6*"OUJ1BUUFSO͕ ར༻Ͱ͖Δɻ w %50͸Ϗϡʔ΍Ϩεϙϯ εΛҙࣝͨ͠ܗࣜΛ࠾༻ Ͱ͖Δɻ w Ξϯνύλʔϯ΋෭࡞༻ Λ੍ޚ͢Ε͹ޮྗΛൃش Client Read Stack Database DAO Read DB DTO as View or Response
  22. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 22 $234ΞʔΩςΫνϟΛ࠾༻͢ΔϝϦοτ w

    ઃܭ͕γϯϓϧʹͳΔ w 38ʹ͓͍ͯ૬ޓʹແବͳૢ࡞Λ࣮ߦ͠ͳ͍Α͏ʹ͢Δ͜ͱ͕೉͍͕͠ɺίϚϯυ ͱΫΤϦΛ෼཭͢Δ͜ͱͰ͜ͷෳࡶ͞ΛܰݮͰ͖Δɻ w ϓϩδΣΫγϣϯ͕͋Ε͹ϦʔυଆͷΠϯϐʔμϯεϛεϚονͷղফ͕ෆཁͱͳ Δɻ w υϝΠϯϞσϧΛλεΫॏࢹͰߟ͑ΒΕΔΑ͏ʹͳΔ w ৼΔ·͍ λεΫ Λத৺ʹ͓͍ͨυϝΠϯϞσϦϯά͕Ͱ͖ΔΑ͏ʹͳΔɻ w ίʔυ͕ཧղ͠΍͘͢ͳΔ w ϥΠτͱϦʔυͦΕͧΕʹγεςϜͷܥ͕෼͔ΕΔͷͰίʔυ͕ಡΈ΍͘͢ͳΔɻ w ϦάϨογϣϯ͠ʹ͍͘ w ยํͷܥͷมߋ͕΋͏ҰํͷܥʹӨڹΛ༩͑ʹ͍͘ɻϦάϨογϣϯͷϦεΫΛܰ ݮͰ͖Δɻ w εέʔϥϏϦςΟ΁ͷد༩ w Ϣʔβ਺͕૿͑ͯ΋ಉ͡ϨϕϧͰύϑΥʔϚϯεΛҡ࣋͢ΔͨΊʹɺ38ͦΕͧΕ ʹಛੑͷҧ͏࠷దԽ͕͠΍͘͢ͳΔɻ ྫඇಉظॻ͖ࠐΈ΍ϦʔυΩϟογϡͳͲ
  23. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 23 &WFOU4PVSDJOHͱ͸Կ͔ʁ w

    (SFH:PVOHࢯߟҊ w &WFOU4PVSDJOH ҎԼ&4 ͱ͸ɺσʔλͰ͸ͳ͘Կ͔͠Βͷग़དྷࣄʹυϝ ΠϯΠϕϯτΛϞσϦϯάͷओ໾ͱ͢Δ͜ͱɻ w υϝΠϯϞσϧΛσʔλͱͯ֨͠ೲ͢ΔͷͰ͸ͳ͘ɺൃੜ͢ΔυϝΠϯΠ ϕϯτΛ͢΂ͯӬଓԽ͢Δɻ جຊతʹ௥Ճ͔͠͠ͳ͍  w &4͸$234Λܶతʹվྑ͢Δ ҎԼ$234 &4 ɻ CartItemAdded { id, cartId, itemId, itemCount } CartItemCountUpdated { id, cartId, itemId, itemCount } CartCreated { id, cartId, userId } CartItemRemoved { id, cartId, itemId } …
  24. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 24 4UBUF4PVSDJOH͔Β&WFOU4PVSDJOH΁ w

    4UBUF4PVSDJOH ҎԼ44  w σʔλϕʔεΛεφοϓγϣοτͱͯ͠දݱ $36%ͳͲ ɻৗʹ.VUBUJPOΛൃੜͤ͞ɺ࠷ޙ ʹ֬ೝ͞Εͨਖ਼ৗͳঢ়ଶΛ֨ೲ͢Δ ʮ࠷ޙʹ֬ೝ͞Εͨਖ਼ৗͳঢ়ଶʯΞϓϩʔν ɻ w &WFOU4PVSDJOH w Ϗδωεϧʔϧ్͕தͰมߋ͞ΕΕΔͱաڈ͸ࣦΘΕΔɻΞʔΩςΫνϟΛޙ͔Βमਖ਼͢ Δ৔߹ɺগྔͷෆ଍৘ใͰ΋େ͖ͳมߋίετΛ෷͏ඞཁ͕͋Δɻͦ΋ͦ΋εφοϓ γϣοτϕʔεͷσʔλදݱ͸ɺΠϕϯτϕʔεͷσʔλදݱͰ͋ΔණࢁͷҰ֯ʹա͗ͳ ͍ɻ w &4Ͱ͸ʮൃੜͨ͠ΠϕϯτʯΞϓϩʔνͰ͋Γɺओཁͳσʔλιʔε͸υϝΠϯΠϕϯτ ʹɻυϝΠϯΠϕϯτ͸ෆมͰ͋ΔͨΊΠϕϯτσʔλϕʔεΛϦϓϦέʔγϣϯ͢Δͷ͸ ൺֱత؆୯ɻ͜Ε͸εέʔϥϏϦςΟʹେ͖ͳӨڹΛ༩͑Δɻ w υϝΠϯΠϕϯτ͕͋Ε͹͢΂͕ͯखʹೖΔɻϦʔυϞσϧ͸&WFOUΛϦϓϨΠ͠ߏங͢Δ ͜ͱ͕Ͱ͖ΔɻυϝΠϯΠϕϯτ͔ΒεφοϓγϣοτΛߏஙͰ͖Δɻ Event Sourcing State Sourcing εφοϓγϣοτϕʔε ͷσʔλදݱ(DB) Πϕϯτϕʔε ͷσʔλදݱ
  25. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 25 ':*$234 &4Λద༻ͨ͠ϑϨʔϜϫʔΫ

    w +BWB4DSJQU w 'BDFCPPL'MVY w 3FEVY w 3FqVY+4 w +BWB w "YPO'SBNFXPSL w 3VCZ w 4FRVFOU w 4DBMB w -BHPN ίϚϯυଆ ΫΤϦଆ Fluxͷ৔߹
  26. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 26 ':*-BHPNͱ͸ w

    -JHIUCFOEࣾ چUZQFTBGFࣾ ͕ఏڙ͢ΔϚΠΫϩαʔϏεϑϨʔϜϫʔΫ w IUUQTXXXMJHIUCFOEDPNMBHPN w IUUQTHJUIVCDPNMBHPNMBHPN w $234 &4Λαϙʔτ w ελοΫߏ੒ w QMBZGSBNFXPSL w BLLB w BLLBBDUPS w BLLBIUUQ w BLLBTUSFBN w BLLBQFSTJTUFODF w BLLBQFSTJTUFODFRVFSZ w BLLBDMVTUFS
  27. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 27 $234 &4ΞʔΩςΫνϟ

     w γεςϜ͸ू໿Ͱൃੜͨ͠υϝΠϯΠϕϯτͰۦಈ͠Πϕϯτ͸δϟʔφϧ ͱͯ͠ӬଓԽ͞ΕΔɻΠϕϯτ͔ΒͷϦϓϨΠ͸4OBQTIPU +PVSOBMɻ Client Write Stack Read Stack Data Store DAO + DTO Read Records Aggregate Application Layer Infrastructure Layer Event Store Journals Snapshots Πϕϯτͷอଘઌ CartCreated CartItemAdded … ͢΂ͯͷΠϕϯτΛ ӬଓԽ Snapshot+journalsͰ ϦϓϨΠ
  28. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 28 %%% $234

    &4ΞʔΩςΫνϟ  w ίϚϯυʹΑͬͯੜͨ͡ू໿ͷঢ়ଶมԽΛΠϕϯτͱ͠ ͯ௨஌͢Δɻ Domain Layer Aggregate Aggregate Aggregate Command Command Event Command/Event Handler Event Event Event Command Command/Event Handler Command/Event Handler Event Event Event
  29. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 29 υϝΠϯΠϕϯτͱϚΠΫϩαʔϏε w

    #$ؒͷ࿈ܞʹ΋υϝΠϯΠϕϯτ͕ར༻ՄೳʹͳΔɻϚΠΫϩαʔϏεΞʔΩ ςΫνϟͱ૬ੑ͕Α͍ɻ w z֎෦ͷଞͷڥք͚ͮΒΕͨίϯςΩετʹ͸໌ࣔతͳΠϯλʔϑΣΠε͕͋ ΓɺͦͷΠϯλʔϑΣΠε͕ଞͷίϯςΩετͱڞ༗͢ΔϞσϧΛܾఆ͠· ͢ɻz ϚΠΫϩαʔϏεΞʔΩςΫνϟΑΓ  w Πϕϯτʹ͸#$಺෦͚ͩͰൃੜɾফඅ͞ΕΔӅΕΠϕϯτͱ֎෦ͷ#$͕ؔ৺ Λ࣋ͭڞ༗Πϕϯτ͕ଘࡏ͢Δɻ BC (2) Application Domain Layer Aggregate BC (1) Application Domain Layer Aggregate BC (3) Application Domain Layer Aggregate Event Event Event
  30. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 30 ίϚϯυͱΠϕϯτͷؔ܎ w

    ू໿͸ίϚϯυΛૢ࡞ΠϯλʔϑΣΠεͱͯ͠ड͚෇͚Δɻ w ίϚϯυ͸ϦΫΤετͱϨεϙϯεʹ෼͚Δ͜ͱ΋Ͱ͖Δɻ w ू໿ͷ಺෦ঢ়ଶΛม͑ΔίϚϯυ͸ɺΠϕϯτΛൃੜͤ͞Δɻ w Πϕϯτૹड৴͸1VCMJTIFS ૹ৴ଆ ͱ4VCTDSJCFS ड৴ଆ ʹ෼ ͔ΕΔɻ྆ऀͷؔ࿈͸1VCMJTIFS4VCTDSJCFS/ͱͳΔɻ Aggregate Command(Request/Response) Event Handler Any Any Event Handler Event Publisher Subscriber Subscriber Command Handler
  31. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 31 Πϕϯτόε w

    Πϕϯτόε͸1VCMJTIFS4VCTDSJCFSΛૄ݁߹ʹ͢ΔͨΊͷϞσ ϧɻτϐοΫϕʔεͰ࣮૷͞ΕΔ͜ͱ͕ଟ͍ɻ w 4VCTDSJCFS͸ؔ৺͕͋ΔτϐοΫͱࣗ෼ࣗ਎Λ&WFOU#VTʹొ࿥͢ Δɻ1VCMJTIFS͸τϐοΫʹରͯ͠&WFOUΛૹ৴͢Δɻ w "LLBʹ͸&WFOU#VTͷ࣮૷͕ෳ਺͋Δ &WFOU4USFBN %JTUSJCVUFE 1VC4VC ɻଞʹ΋,BGLBͳͲ΋બ୒ࢶʹͳΔɻ Aggregate Command(Request/Response) Event Handler Any Any Event Handler Publisher Subscriber Subscriber Command Handler EventBus eventBus.subscribe(topic, subscriber) eventBus.subscribe(topic, event)
  32. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 32 &4ʹ͸ܽ఺͸ͳ͍ͷ͔ʁ w

    *%&΍ϑϨʔϜϫʔΫ͕44Λલఏʹ͍ͯ͠Δ΋ͷ͕ଟ͍ɻ ͜ͷཱ৔͔Β͸શ͘σʔλΛอଘ͠ͳ͍Α͏ʹݟ͑Δɻ Ͱ ΋"LLBͳΒେৎ෉  w Πϕϯτͷܗࣜ͸ɺϏδωεϧʔϧ͕มԽͨ࣌͠ʹεΩʔ Ϛ͕มԽ͢ΔͨΊϦϨʔγϣφϧσʔλϕʔεʹ͸޲͔ ͳ͍ɻ,74͕޲͍͍ͯΔɻ w Ϗδωε্Ͱى͜ΔΠϕϯτΛҰͭ࢒ΒͣӬଓԽ͢Δͨ Ίɺण໋͕௕͍ΤϯςΟςΟΛѻ͏Ϣʔεέʔεʹ޲͍ ͍ͯΔ ΩϟύγςΟ͕ڐͤ͹ ɻ w ͳΜͰ΋͔ΜͰ΋&4ʹ͸Ͱ͖ͳ͍͜ͱ΋͋Δɻ$234Ͱ΋ 44Λબ୒Ͱ͖ͨํ͕͍͍͕ݱ࣮͸೉͍͔͠΋͠Εͳ͍ɻ
  33. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 33 "LLBͰͲͷΑ͏ʹ࣮૷͢Δͷ͔ʁ 8ܥத৺ͷղઆͰ͢

  34. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 34 "LLBͬͯԿʁ w

    IUUQBLLBJP w ݱMJHIUCFOEࣾͷ$50+POBT#POFSࢯ͕։ൃɻ w &SMBOH͔Βͷ༌ೖ w ෼ࢄγεςϜΛ؆୯ʹ࡞ΕΔ εέʔϧΞοϓ εέʔϧ Ξ΢τઓུΛಉ͡ϓϩάϥϛϯάϞσϧͰαϙʔτ͢Δ  w ಛ௃ w ඇಉظɾϊϯϒϩοΩϯά w ࣗݾ࣏༊ೳྗΛ࣋ͭ 3FTJMJFOUCZ%FTJHO  w ଎͍ɾޮ཰త w ΫϥελΛߏஙͰ͖Δɻ41P'͕ͳ͍ɻ
  35. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 35 ͳͥ"LLBͳͷ͔ʁ ٕज़తͳଆ໘

    w $,໰୊ͷղܾ w ૿͑ଓ͚ΔΫϥΠΞϯτΛͲͷΑ͏ʹࡹ͔͘ɻ໰୊ʹͳΔͷ͸ϒϩο Ωϯά*0 ΠϕϯτϧʔϓͱϊϯϒϩοΩϯά*0OHOJY OPEFKTͷ ୆಄  w $16ةػ w ΫϩοΫ਺Λ͋͛Δ͜ͱ͕ݶքʹͳΓγϯάϧίΞةػɻϜʔΞͷ๏ ଇ τϥϯδελͷूੵີ౓͸೥͝ͱʹഒʹͳΔ Λҡ࣋͢ΔͨΊʹ ϚϧνίΞԽ΁ɻ w ͦͯ͠ϚϧνίΞةػ͕౸དྷɻΞϓϦέʔγϣϯ͕ฒߦ౓͕௿͍ͱί ΞΛੜ͔͠੾Εͳ͍ ΞϜμʔϧͷ๏ଇ ɻ୯ҰίΞ͸௿଎ʹͳΔͨΊ ϚϧνίΞʹରԠ͠ͳ͍ͱύϑΥʔϚϯε͕௿Լ͠΍͍͢ɻ͔͠͠ɺ εϨουϓϩάϥϛϯά͸ඇৗʹ೉͍͠ɻσουϩοΫɺϨʔείϯ σΟγϣϯɺՄࢹੑ໰୊ͳͲɻ
  36. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 36 ͳͥ"LLBͳͷ͔ʁ ٕज़తͳଆ໘

    w ϚϧνίΞةػΛλʔήοτʹͨ͠4DBMBͱ&SMBOH w ෆมੑ΍ܰྔϓϩηε ΞΫλʔͳͲ  w &SMBOHͷ఻આ w ೥ʹ&SJDTTPO͕ΞΫλʔϞσϧʹج͍ͮͨݴޠ&SMBOHΛ։ൃɻ &SJDTTPOͷ"9%ి࿩ަ׵ػ͕ OJOFOJOFT ೥ؒͰఀࢭ͕࣌ؒඵ ͷՄ༻ੑΛୡ੒ɻ೥8IBUT"QQ ͷ&SMBOHγεςϜ͕̍୆ͷαʔόʔͰສΫϥΠΞϯτΛࡹ ͘ɻ w "LLBͷొ৔ w ೥"LLBͷϦϦʔε &SMBOH͔ΒΠϯεύΠΞ ɻϝοηʔ δύογϯά΋*0΋ϊϯϒϩοΩϯάͰ$,໰୊Λղܾɻ w "LLBͷύϑΥʔϚϯε͕&SMBOHΛѹ౗͢Δɻ
  37. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 37 ͳͥ"LLBͳͷ͔ʁ %%%తͳଆ໘

    w %%% w ू໿ΛΞΫλʔͱ࣮ͯ͠૷Ͱ͖Δɻ"LLB$MVTUFS4IBSEJOHΛద༻͢ ΔͱɺϊʔυؒΛލ͙ू໿ͱ࣮ͯ͠૷Մೳɻ w $234 &4 w "LLB1FSTJTUFODF 1FSTJTUFOU"DUPS Λ࢖ͬͯυϝΠϯΠϕϯτΛӬଓ ԽͰ͖Δɻ·ͨঢ়ଶͷ෮ݩ΋Մೳɻ w "LLB1FSTJTUFODF2VFSZΛ࢖ͬͯϦʔυϞσϧΛߏஙͰ͖Δɻ w υϝΠϯΠϕϯτΛϝοηʔδύογϯάͰ͖Δɻ &WFOU#VT &WFOU4USFBN %JTUSJCVUFE1VC4VCͳͲ Ͱ1VC4VCϞσ ϧ΋ར༻Ͱ͖Δɻ w ΞϓϦέʔγϣϯcυϝΠϯ αʔϏεΛ1FSTJTUFODF"DUPS΍ 1FSTJTUFOU'4.ͳͲΛ࢖ͬͯϓϩηεϚωʔδϟͱ࣮ͯ͠૷Ͱ͖Δɻ
  38. ΠϯλʔϑΣΠε૚ Ϣʔεέʔε૚ ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 38

    ΫϦʔϯΞʔΩςΫνϟXJUI"LLB w "LLBͰΫϦʔϯʹ͢Δ৔߹ͷϨΠΞ΢τͷҰྫ ίϚϯυ UseCase (CreateUser, UpdateUserName, GetUser) Persistent Actor <<Input Port>>
 akka-http <<(In|Out)putPort>>
 akka-persistence ΫΤϦ <<(In|Out)putPort>>
 UserDao UserDao Impl <<Input Port>>
 DBMS ௚઀ґଘ ActorRefʹґଘ͠ͳ͘ͳ͍৔߹
 trait UserPort { def createUser(cmd): Future[Response] = ref ? cmd
 } ܧঝ <<(In|Out)putPort>>
 ActorRef ؒ઀ґଘ ௚઀ґଘ (In|Out)putPort Flow[Request,Response]
 • υϝΠϯ૚͸ɺΠϯϑϥ૚(Scala)ʹ͚ͩґଘ͢Δɻ • Ϣʔεέʔε૚͸ɺυϝΠϯ૚ͱΠϯϑϥ૚(Scala, Akka)ʹґଘ͢Δɻ • ΠϯλʔϑΣΠε૚͸ɺϢʔεέʔε૚ͱΠϯϑϥ૚(Scala, Akka)ʹґଘ͢Δɻ υϝΠϯ૚ UserAggregateRoot ௚઀ґଘ ܧঝ <<Output Port>>
 akka-http Route + Json Route + Json ௚઀ґଘ ௚઀ґଘ
  39. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 39 ΞΫλʔͱ͸Կ͔ʁ w8JLJQFEJBΑΓҾ༻

    wΞΫλʔϞσϧͷجຊ͸ʮશͯͷ΋ͷ͸ΞΫλʔͰ͋Δʯͱ͍͏఩ ֶͰ͋Δɻ͜Ε͸ΦϒδΣΫτࢦ޲ϓϩάϥϛϯάʹ͓͚Δʮશͯͷ ΋ͷ͸ΦϒδΣΫτͰ͋Δʯͱ͍͏ߟ͑ํͱࣅ͍ͯΔ͕ɺΦϒδΣΫ τࢦ޲ιϑτ΢ΣΞͰ͸جຊతʹஞ࣍తʹ࣮ߦ͢Δͷʹରͯ͠ɺΞΫ λʔϞσϧͰ͸ຊ࣭తʹฒߦੑΛඋ͍͑ͯΔ఺͕ҟͳΔɻ wΞΫλʔ͸ฒߦతʹड৴͢ΔϝοηʔδʹରԠͨ͠ҎԼͷΑ͏ͳৼ Δ෣͍Λඋ͑ͨܭࢉ࣮ମʢ$PNQVUBUJPOBM&OUJUZʣͰ͋Δ w ଞͷ ΞΫλʔʹ༗ݶݸͷϝοηʔδΛૹ৴͢Δɻ w༗ݶݸͷ৽ͨͳΞΫλʔΛੜ੒͢Δɻ w࣍ʹड৴͢Δϝοηʔδʹର͢Δಈ࡞Λࢦఆ͢Δɻ w͜ΕΒͷৼΔ෣͍ʹ͸ஞ࣍ੑ͸લఏͱ͞Ε͓ͯΒͣɺฒྻతʹ͜ ΕΒΛ࣮ߦ͢Δɻ
  40. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 40 class MyActor

    extends Actor {
 val log = Logging(context.system, this)
 def receive = {
 case "test" => log.info("received test")
 case _ => log.info("received unknown message")
 }
 }
 
 object Main extends App {
 val system = ActorSystem()
 val actorRef = system.actorOf(Props[MyActor])
 actorRef ! "test"
 actorRef ! "hello"
 Await.result(system.terminate(), Duration.Inf)
 }
 "LLB"DUPSͷαϯϓϧ
  41. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 41 ௥Ճ͢Δґଘؔ܎ w

    BLLBBDUPS w BLLBQFSTJTUFODF w εςʔτϑϧΞΫλʔͷ಺෦ঢ়ଶΛӬଓԽ͢Δػೳɻ ΞΫλʔͷॳظԽ࣌΍ྫ֎ʹΑΔ࠶ىಈ࣌ɺ+7.ͷΫ ϥογϡ࣌ʹҎલͷঢ়ଶΛ෮ݩ͢Δ͜ͱ͕Ͱ͖Δ w ΞΫλʔͷ಺෦ঢ়ଶ͸ӬଓԽ͞ΕΔ͕ɺܾͯ͠ݱࡏͷ௚ ઀తͳঢ়ଶΛѻ͏Θ͚Ͱ͸ͳ͍ɻ
  42. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 42 ू໿Λ1FSTJTUFOU"DUPSͰ࣮૷͢Δ class

    UserAggregate(userId: UserId, eventBus: EventBus) extends PersistentActor {
 
 var state: Option[User] = None
 def initialized = state.isDefined
 override def persistenceId: String = userId.toPath
 override def receiveRecover: Receive = {
 case event: UserInitialized =>
 state = Some(User.applyState(event))
 case event: UserNameUpdated =>
 state = state.map(_.updateState(event))
 case event: UserDestroyed =>
 state = state.map(_.updateState(event))
 }
 override def receiveCommand: Receive = {
 case cmd: InitializeUser =>
 require(initialized)
 persist(cmd.toEvent) { event =>
 state = Some(User.applyState(event)) sender() ! CreateUserSucceeded(UUID.randomUUID, cmd.id)
 eventBus.publish(Topic(event), event)
 }
 case cmd: UpdateUserName =>
 require(!initialized)
 persist(cmd.toEvent) { event =>
 state = state.map(_.updateState(event)) sender() ! UpdateUserNameSucceeded(UUID.randomUUID, cmd.id)
 eventBus.publish(Topic(event), event)
 }
 case cmd: DestroyUser =>
 require(!initialized)
 persist(cmd.toEvent) { event =>
 state = state.map(_.updateState(event))
 sender() ! DestroyUserSucceeded(UUID.randomUUID, cmd.id)
 eventBus.publish(Topic(event), event)
 }
 }
 } w SFDFJWF$PNNBOE͸ίϚϯυϋϯυ ϥɻ w QFSTJTUͰυϝΠϯΠϕϯτΛӬଓԽ͢ Δɻ 3FQPTJUPSZTUPSF૬౰  w QFSTJTUޙʹ w ू໿ϧʔτͷঢ়ଶΛมߋ͢Δɻ w TFOEFSʹ"DL/BDLΛૹ৴͢Δɻ w υϝΠϯΠϕϯτΛ4VCTDSJCFSʹ ૹ৴͢Δɻ /  w SFDFJWF3FDPWFS͸"DUPSىಈ࣌ʹू໿ ϧʔτͷঢ়ଶ෮ݩΛߦ͏ͨΊͷϝιο υ 3FTQPTJUPSZSFTPMWF#Z JE ૬౰ɻ &WFOU͔Β4UBUFΛಘΔ ɻ w 4OBQTIPUΛ࢖͏ͱϦϓϨΠ͕ߴ଎ԽͰ ͖Δɻ w 1FSTJTUFOU"DUPSͰू໿ 3FQPTJUPSZ૬ ౰ͷػೳΛ࣮ݱՄೳɻ
  43. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 43 ίϚϯυͷ࣮૷ w

    ίϚϯυϦΫΤετΛ ࣮૷͢Δɻ w ϝιουۦಈܕͰ͍͏ ͱϝιου໊ͱҾ਺ͷ ू߹Λ·ͱΊͨ΋ͷ͕ ίϚϯυͱͳΔɻ w ίϚϯυ͔ΒΠϕϯτ Λੜ੒Ͱ͖ΔΑ͏ʹ͢ Δɻ object UserProtocol {
 object Commands { 
 trait UserCommandRequest extends CommandRequest {
 val userId: UserId
 } 
 case class InitializeUser(
 identifier: CommandRequestId = CommandRequestId(),
 userId: UserId,
 name: String
 ) extends UserCommandRequest with InitializeCommandRequest {
 override def toEvent: UserInitialized =
 UserInitialized(EventId(), userId, name)
 } 
 case class UpdateUserName(
 identifier: CommandRequestId = CommandRequestId(),
 userId: UserId,
 name: String
 ) extends UserCommandRequest with UpdateCommandRequest {
 override def toEvent: UserNameUpdated =
 UserNameUpdated(EventId(), userId, name)
 } 
 case class DestroyUser(
 identifier: CommandRequestId = CommandRequestId(),
 userId: UserId
 ) extends UserCommandRequest with DestroyCommandRequest {
 override def toEvent: UserDestroyed =
 UserDestroyed(EventId(), userId)
 }
 }
 }
  44. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 44 Πϕϯτͷ࣮૷ w

    ίϚϯυΛड͚෇͚ͨ ޙʹൃੜ͢ΔΠϕϯτ Λ࣮૷͢Δɻ w Πϕϯτ͸ΤϯςΟςΟ ͷҰछͰ͋ΔͨΊɺԿ ͔͠Βͷࣝผํ๏Λඋ ͑Δɻ w Πϕϯτ͸"LLB 1FSTJTUFODFͰӬଓԽ͞ ΕΔɻ object UserProtocol {
 
 object Events { 
 trait UserEvent extends Event
 
 case class UserInitialized(
 identifier: EventId = EventId(),
 userId: UserId,
 name: String,
 createAt: DateTime = DateTime.now
 ) extends InitializedEvent with UserEvent
 
 case class UserNameUpdated(
 identifier: EventId = EventId(),
 userId: UserId,
 name: String,
 createAt: DateTime = DateTime.now
 ) extends UpdatedEvent with UserEvent
 
 case class UserDestroyed(
 identifier: EventId = EventId(),
 userId: UserId,
 createAt: DateTime = DateTime.now
 ) extends DestroyedEvent with UserEvent
 
 } }
  45. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 45 &WFOU#VTͷ࣮૷ w

    "LLB&WFOU4USFBNͰͷ࣮ ૷ɻϩʔΧϧ༻ Ϋϥελ ্ͷଞͷϊʔυʹΠϕϯτ Λ഑ૹͰ͖ͳ͍ ɻ w 4VCTDSJCFS͸τϐοΫʹର ͯ͠4VCTDSJCF͢Δɻ w 1VCMJTIFS͸4VCTDSJCFSΛࢦ ఆͤͣʹΠϕϯτΛ1VCMJTI ͢Δɻ w ཻ౓͕େ͖͍5QPJDͷΠϕ ϯτΛߪಡ͢ΔͱΠϕϯτ ͷॲཧ͕௥͍͔ͭͳ͘ͳ Δɻ w ࣮ࡍ͸BLLBFWFOU&WFOU#VT Λ࣮૷͢ΔͱΑ͍ɻ trait EventBus {
 def subscribe(topic: Class[_], subscriber: ActorRef): Unit
 def publish(event: Event): Unit
 }
 
 class EventBusImpl(system: ActorSystem) extends EventBus {
 
 override def subscribe(topic: Class[_], subscriber: ActorRef): Unit = {
 system.eventStream.subscribe(subscriber, topic)
 }
 
 override def publish(event: Event): Unit ={
 system.eventStream.publish(event)
 }
 
 } object EventBus {
 
 def ofEventStream(system: ActorSystem): EventBus = new EventBusImpl(system)
 
 }

  46. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 46 υϝΠϯαʔϏεͱͯ͠ͷ1SPDFTT.BOBHFS w

    ޱ࠲ؒసૹͷυϝΠ ϯαʔϏεɻ w #BOL"DDPVOUؒͰ͓ ۚΛసૹ͢Δɻ w 1FSTJTUFODF'4.಺ ෦ঢ়ଶ͕ӬଓԽՄೳ ͳ༗ݶεςʔτϚγ ϯɻൃੜ͢ΔυϝΠϯ ΠϕϯτΛӬଓԽ͢ Δɻ class TransferDomainService(id: UUID) extends PersistentFSM[State, Data, Event] {
 override def persistenceId: String = id.toString
 override def domainEventClassTag: ClassTag[Event] = classTag[Event]
 override def applyEvent(domainEvent: Event, currentData: Data): Data = 
 domainEvent match {
 case Transferred(id, money, from, to, ref) => TransferData(id, money, from, to, ref)
 case Transferring(id, money, from, to, ref) => TransferData(id, money, from, to, ref)
 }
 
 startWith(Stopped, Empty)
 when(Stopped) {
 case Event(Transfer(requestId, money, from, to), _) =>
 context.system.eventStream.subscribe(self, classOf[Decreased])
 val ev = Transferring(UUID.randomUUID(), money, from, to, sender())
 goto(Started) applying ev andThen {
 case d: TransferData =>
 from ! Decrease(UUID.randomUUID(), money)
 sender() ! CommandSucceeded(UUID.randomUUID(), requestId)
 context.system.eventStream.publish(ev)
 }
 }
 when(Started) {
 case Event(Decreased(_, _), _) =>
 goto(FromDecreased) andThen {
 case d@TransferData(_, money, from, to) =>
 context.system.eventStream.unsubscribe(self, classOf[Decreased])
 context.system.eventStream.subscribe(self, classOf[Increased])
 to ! Increase(UUID.randomUUID(), money)
 }
 case ev@Event(CommandSucceeded(_, _),_) =>
 stay
 }
 when(FromDecreased) {
 case Event(Increased(_, _), TransferData(_, money, from, to, ref)) =>
 goto(Stopped) applying Transferred(UUID.randomUUID(), money, from, to, ref) andThen {
 case d: TransferData =>
 context.system.eventStream.unsubscribe(self, classOf[Increased])
 context.system.eventStream.publish(Transferred(UUID.randomUUID(), money, from, to))
 }
 case ev@Event(CommandSucceeded(_, _),_) =>
 stay
 }
 initialize()
 }
  47. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 47 ௥Ճ͢Δґଘؔ܎ w

    BLLBQFSTJTUFODFRVFSZ w ඇಉظετϦʔϜϕʔεͷΫΤϦ*'ʹΑͬͯఏڙ͞ΕΔΫΤϦʔίϯ ϙʔωϯτɻ w BLLBTUSFBN w ΞΫλʔΛͭͳ͗߹ΘͤΔͱඇಉظετϦʔϜॲཧΛ͢ΔͨΊͷ1JQF BOE'JMUFSΛ࡞Δ͜ͱ͕Ͱ͖Δ͕ɺ҆ఆͨ͠ετϦʔϛϯάॲཧΛ͢Δ ʹ͸ɺΤϥʔϋϯυϦϯάΛߦͬͨΓɺ͋Δϓϩηε্ͷόοϑΝ΍ ϝʔϧϘοΫε͕Φʔόʔϑϩʔ͠ͳ͍Α͏ʹ͢Δ CBDLQSFTTVSF ඞ ཁ͕͋Δɻ w ੩తܕͳཁૉΛѻ͏ετϦʔϜͰΞΫλʔ͸഑ઢϛεΛ๷ࢭ͢Δ੩త ܕ෇͚Λอূ͢Δํ๏͕ͳ͍ɻ w BLLBTUSFBN͸্هͷ໰୊Λղܾ͢Δɻ
  48. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 48 BLLBTUSFBN w

    4PVSDF< 0VU  .BU>ܕ͸ɺετϦʔϜͷ্ྲྀͰσʔλΛఏڙ͢ ΔɻҰͭ໨ͷܕҾ਺͸ιʔε͕Լྲྀʹఏڙ͢Δ஋ͷܕͰɺೋͭ ໨ͷܕҾ਺͸ɺιʔε࣮ߦ࣌ʹ͍͔ͭ͘ͷิॿ஋ΛఏڙͰ͖Δ ͨ ͱ͑͹ɺωοτϫʔΫιʔε͸ɺϐΞΞυϨε΍ό΢ϯυϙʔ τʹؔ͢Δ৘ใΛఏڙ͢Δ͔΋͠Εͳ͍ ɻ w 4JOL<*O  .BU>ܕ͸ɺετϦʔϜͷԼྲྀͰσʔλΛফඅ͢Δɻ Ұͭ໨ͷܕҾ਺͸্ྲྀ͔Βड͚औΔ஋ͷܕɻೋͭ໨ͷยҾ͖਺ ͸ಉ͡ɻ w 'MPX<*O  0VU  .BU>ܕ͸ɺ4PVSDFˠ'MPXˠ4JOLͷΑ͏ʹ ετϦʔϜͷதྲྀͰɺ্ྲྀ͔Βͷ஋ΛԼྲྀʹఏڙ͢Δɻ
  49. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 49 4USFBN4PVSDFˠ'MPXˠ4JOL w

    όοΫϓϨογϟ෇͖ͷετϦʔϜॲཧ͕Մೳɻ w ೖྗ஋ͷܕʹΑΒͣ౷Ұతʹ4PVSDFܕ͕ར༻Ͱ͖Δɻ Source[A, M] Flow[A, B, M] Sink[B, M] data data Request Request • Source#single[T](element: T) • Source#apply[T](iterable: Iterable[T]) • Source#maybe[T] • Source#fromFuture[T](future: Future[T]) • Source#actorPublisher(props: Props)
  50. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 50 BLLBTUSFBNͷαϯϓϧ w

    ετϦʔϜͰ͸ೖྗ஋Λݸʑʹॲཧ͢Δ val n = (1 to 10)
 .map { i => println(s"A: $i"); i }
 .map { i => println(s"B: $i"); i }
 .map { i => println(s"C: $i"); i }
 .sum A: 1 A: 2 … A: 9 A: 10 B: 1 B: 2 … B: 9 B: 10 C: 1 C: 2 … C: 9 C: 10 n = 55 val f = Source(1 to 10)
 .map{ i => println(s"A: $i"); i }
 .map{ i => println(s"B: $i"); i }
 .map{ i => println(s"C: $i"); i }
 .runWith(Sink.fold(0){ (l, e) => l + e}) A: 1 B: 1 C: 1 A: 2 B: 2 C: 2
 … A: 9 B: 9 C: 9 A: 10 B: 10 C: 10 n2 = 55
  51. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 51 BLLBTUSFBNͰϫʔυΧ΢ϯτ w

    NBQSFEVDFΛߦ͏4USFBNॲཧɻ val data: Seq[String] = Seq("apple banana", "apple banana", "orange apple mango", "kiwi papaya orange", "mango orange muscat apple").flatMap(_.split("\\s"))
 
 val source: Source[String, NotUsed] = Source(data.toVector)
 
 val flow: Flow[String, (String, Int), NotUsed] = Flow[String]
 .groupBy(data.distinct.size, identity)
 .map(_ -> 1)
 .reduce((l, r) => (l._1, l._2 + r._2))
 .mergeSubstreams
 
 val sink: Sink[(String, Int), Future[Map[String, Int]]] = Sink.fold[Map[String, Int], (String, Int)](Map.empty){ (result, e) => result + e }
 
 val g: RunnableGraph[Future[Map[String, Int]]] = source.via(flow).toMat(sink)(Keep.right)
 
 val f: Future[Map[String, Int]] = g.run()
 val v: Map[String, Int] = Await.result(f, Duration.Inf)
 
 println(v) Map(banana -> 2, muscat -> 1, orange -> 3, mango -> 2, apple -> 4, kiwi -> 1, papaya -> 1)
  52. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 52 3FBE.PEFM6QEBUFS w

    BLLBQFSTJTUFODFRVFSZ Λ࢖ͬͯϦʔυϞσϧΛ ߏங͢ΔඇಉظετϦʔ ϜॲཧΛ࣮૷͢Δɻ w ॻ͖ࠐΈετϨʔδʹ ߹Θ࣮ͤͨ૷͕ඞཁʹ ͳΔͷͰɺ࣮૷͕೉͠ ͍ɻ ͨͱ͑͹ɺ.Z42- ͰϚελʔҰ୆ͩͱ෼ ࢄॻ͖ࠐΈͯ͠΋͋·Γ ҙຯ͕ͳ͍ɻଞʹ΋ί ωΫγϣϯϓʔϧͷ໰୊ ΋͋ΔɻNBQ"TZODͰଟ ॏ౓Λઃఆ͢ΔͱΑ ͍ɻ class ReadModelUpdater(implicit val system: ActorSystem) {
 private val readJournal = PersistenceQuery(system).readJournalFor[LeveldbReadJournal] (LeveldbReadJournal.Identifier) 
 private val persistenceIdsSource = readJournal.allPersistenceIds()
 private val getPersistenceWithModelNameFlow = Flow[String].map { persistenceId =>
 val modelName = persistenceId.split("-").head
 PersistenceWithModelName(persistenceId, modelName)
 } 
 val getUserLatestVersionFlow = Flow[PersistenceWithModelName].map {
 case PersistenceWithModelName(pid, m) if m == UserAggregate.typeName =>
 PersistenceWithVersion(pid, DB.readOnly(InfraUser.getLatestVersion(_)).getOrElse(0L))
 } private val getEventsFlow = Flow[PersistenceWithVersion].flatMapConcat {
 case PersistenceWithVersion(pid, version) =>
 readJournal.eventsByPersistenceId(pid, version + 1, Long.MaxValue)
 } 
 val updateUserEventFlow = Flow[EventEnvelope].map {
 case v@EventEnvelope(_, _, sequenceNr, UserInitialized(_, id, name, datetime)) =>
 InfraUser.createWithAttributes('id -> InfraUserId(id.value.toString),'deleted -> false,'name -> name,'createAt -> datetime,'updateAt -> datetime,'version -> 1)
 case v@EventEnvelope(_, _, sequenceNr, UserNameUpdated(_, id, name, datetime)) =>
 val updated = InfraUser.updateByIdAndVersion(InfraUserId(id.value.toString), sequenceNr).withAttributes('name -> name,'updateAt -> datetime,'version -> sequenceNr)
 if (updated == 0) throw new Exception(s"cannot be updated: $id")
 case v@EventEnvelope(_, _, sequenceNr, UserDestroyed(_, id, datetime)) =>
 val updated = InfraUser.updateByIdAndVersion(InfraUserId(id.value.toString), sequenceNr).withAttributes('deleted -> true,'updateAt -> datetime,'version -> sequenceNr)
 if (updated == 0) throw new Exception(s"cannot be updated: $id")
 } 
 def start(): Future[Unit] = persistenceIdsSource
 .via(getPersistenceWithModelNameFlow)
 .via(getUserLatestVersionFlow)
 .via(getEventsFlow)
 .via(updateUserEventFlow)
 .toMat(Sink.last)(Keep.right)
 .run()
 
 }
  53. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 53 ௥Ճ͢Δґଘؔ܎ wBLLBIUUQ

    wBLLBIUUQͷ໨త͸ɺ"DUPSΛ)551Λհͯ͠΢Σ ϒʹެ։͢Δ͜ͱͱɺΫϥΠΞϯτͱͯ͠)551 αʔϏεΛফඅ͢Δ͜ͱΛՄೳʹ͢Δ͜ͱɻ w)551ϑϨʔϜϫʔΫͰ͸͋Γ·ͤΜɻ΢Σϒαʔ ϏεͱΫϥΠΞϯτ͕ର࿩͢ΔͨΊͷ"DUPSϕʔ εͷπʔϧΩοτͰ͢ɻ
  54. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 54 ΞϓϦέʔγϣϯͷ࣮૷ w

    BLLBIUUQΛ࢖ͬͨΞϓ Ϧέʔγϣϯͷ࣮૷ɻ w ίϯτϩʔϥͷ࣮૷͸ BLLBTUSFBNͰ࣮૷ɻ val userRoute: Route = path("users") {
 post {
 entity(as[CreateUserJson]) { createUserJson =>
 val result = Source.single(createUserJson)
 .via(userConverter.convertToCreateUser)
 .via(userUseCase.createUser)
 .via(userConverter.convertFromUserCreated)
 .toMat(Sink.head)(Keep.right)
 .run()
 onSuccess(result) { userCreatedJson =>
 complete(userCreatedJson)
 }
 }
 }
 } Http().bindAndHandle(
 handler = logRequestResult("log")(userRoute),
 interface = httpInterface,
 port = httpPort
 )
  55. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 55 $POWFSUFSͷ࣮૷ w

    Ϣʔεέʔεʹೖྗ͢ ΔϑϩʔΛఆٛɻ w PG+TPO͸+40/ϞσϧΛ ΞϓϦέʔγϣϯϞσ ϧʹίϯόʔδϣϯ͢ Δϑϩʔɻ trait UserConverter[A] {
 
 val convertToCreateUser: Flow[A, CreateUser, NotUsed]
 
 }
 
 object UserConverter {
 
 def ofJson: UserConverter[CreateUserJson] = 
 UserConverterOfJson
 
 private[flow] object UserInputFlowsOfJson extends UserConverter[CreateUserJson] {
 
 override val convertToCreateUser = {
 Flow[CreateUserJson].map { json =>
 CreateUser(json.name)
 }
 }
 
 }
 
 }
  56. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 56 6TF$BTFͷ࣮૷ w

    Ϣʔεέʔε༻ͷϑϩʔ ͷ࣮૷ɻ w PG"DUPS͸ू໿ΞΫλʔ ʹॻ͖ࠐΈ༻ϝοηʔ δΛૹ৴͢Δ ໭Γ஋ ͸ΠϕϯτͰ͸ͳ͘ɺ Ϩεϙϯε͕औಘͰ͖ Ε͹Α͍ ɻ trait UserUseCase {
 protected val convertToCreateUserDomainCommand: Flow[app.CreateUser, domain.CreateUser, NotUsed] =
 Flow[app.CreateUser].map { cmd =>
 domain.CreateUser(CommandRequestId(UUID.randomUUID()),
 UserId(UUID.randomUUID()),
 cmd.name)
 } 
 val createUser: Flow[app.CreateUser, app.UserCreated, NotUsed] }
 
 object UserUseCase { 
 def ofActor(userActorRef: ActorRef) (implicit timeout: Timeout): UserUseCase = new UserUseCaseOfActor(userActorRef) 
 private[usecase] case class UserUseCaseOfActor(userActorRef: ActorRef) (implicit timeout: Timeout) extends UserUseCase {
 override val createUser = {
 convertToCreateUserDomainCommand.mapAsync(1) { cmd =>
 (userActorRef ? cmd).mapTo[domain.CreateUserSucceeded]
 }.map { response =>
 app.UserCreated(response.aggregateId)
 }
 }
 
 } 

  57. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 57 $POWFSUFSͷ࣮૷ w

    Ϣʔεέʔεͷ໭Γ ஋Λग़ྗ͢Δϑϩʔ Λఆٛɻ w PG+TPO͸ΞϓϦέʔ γϣϯϞσϧ͔Β +40/Ϟσϧʹग़ྗ͢ Δɻ 
 trait UserConverter[A] {
 
 val convertFromUserCreated: Flow[UserCreated, A, NotUsed]
 
 }
 
 object UserConverter {
 
 def ofJson: UserConverter[UserCreatedJson] = UserConverterOfJson
 
 private[flow] case object UserConverterOfJson extends UserConverter[UserCreatedJson] {
 
 override val convertFromUserCreated =
 Flow[UserCreated].map { model =>
 UserCreatedJson(JsonUserId(model.userId.value))
 }
 }
 
 }
  58. ࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮૷ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 58 ·ͱΊ w

    %%% $234 w ઃܭ͕γϯϓϧʹͳΔɻϦʔυܥͷϢʔεέʔεΛ૝ఆͨ͠υϝ ΠϯϞσϦϯά͔Βղ์͞ΕΔɻ w %%% $234 &4 w $234Λ͞Βʹվྑ͢ΔɻυϝΠϯϞσϧ͸σʔλͱ͍͏ΑΓग़ དྷࣄΠϕϯτͷू߹Ͱ͋Δͱ͍͏ࢹ఺͕ಘΒΕΔɻ͢΂͕ͯΠ ϕϯτͰۦಈ͢Δੈքɻ"LLB͸ͦΕΛࢧ͑Δج൫తͳπʔϧΩο τɻ࣮ͨͩ૷্ͷ՝୊΋ଟ͍ɻ-BHPNͷίʔυΛಡΜͰΈΔͱ Α͍͔΋ w "LLBʹΑΔ࣮૷ w $234 &4ͷͨΊʹ"LLB͕͋Δͱݴͬͯ΋աݴͰ͸ͳ͍ɻ͔͠ ͠ɺֶश͢΂͖ίϯϙʔωϯτ͕ଟ͍ɻ·ͩεςʔϒϧͰͳ͍΋ ͷ΋ଟ͍ɻٯʹ͍͏ͱࠓͷ͏ͪʹ४උ͓͚ͯ͠͹͍͍ʂ