Share
Embed
Transcript
58 slides
-
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/3/26 © ChatWork All rights reserved. ࣗݾհ w!KJLP w$IBU8PSL͔Βདྷ·ͨ͠ɻ
wࡽ͍ͯ͠Δίʔυ wIUUQTHJUIVCDPNKJLP wIUUQTHJUIVCDPNTJTJPI wIUUQTHJUIVCDPNDIBUXPSLTCUBXT wIUUQTHJUIVCDPNDIBUXPSLTCUEPDLFS w࠷ۙ$234 &4 "LLBͳͲʹ͍ͯ͠·͢ɻ w%%%Ͱ໎ࢠʹͳͬͯͨΒ͔͚͍ͯͩ͘͞ɻΞυό Πε͠·͢ɻ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/3/26 © ChatWork All rights reserved. ΞδΣϯμ w4DBMBͰͷ࠷৽%%%ΞʔΩςΫνϟͱ"LLBͰͷ࣮ ώϯτʹ͍ͭͯ͠·͢ɻ
wΞδΣϯμ w%%% $234 &WFOU4PVSDJOHʹ͍ͭͯ w%%%ͱԿ͔ʁ w$234ͱԿ͔ʁ w&WFOU4PVSDJOHͱԿ͔ʁ w"LLBͰͲͷΑ͏ʹ࣮͢Δͷ͔ʁ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 5 ':*%%%ؔ࿈͓͢͢Ίਤॻ w
%%% w ʹൃץɻ w ຊޠ൛ w %&"" w ʹൃץɻ w ຊޠ൛ɻ w *%%% w ʹൃץɻ w ຊޠ൛ɻ w 3.1 w ʹൃץɻ w ಡॻձ͋Γ·͢ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 6 %%%ͱԿ͔ʁ w
ϏδωεͷࣝΛөͨ͠ιϑτΣΞϞσϧ ϢϏΩλεݴޠ ڥք͚ͮΒΕͨ ίϯςΩετ αϒυϝΠϯ ϨΠϠʔԽ ΞʔΩςΫνϟ ϏϧσΟϯά ϒϩοΫ υϝΠϯϞσϧ ϥΠϑαΠΫϧཧ ઓུతϞσϦϯά ઓज़తϞσϦϯά ରԠ͘ υϝΠϯͷִ •υϝΠϯର͢ΔιϦϡʔγϣϯ •υϝΠϯϞσϧͷ/౷߹ઓུ ͱͦͷ༏ઌॱҐͷఆٛ ઐՈͱ։ൃऀͷڞ௨ݴޠ -
࠷৽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 -
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 … … … … … … … … ΠϯϐʔμϯεϛεϚον -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 10 ϔΩαΰφϧcΫϦʔϯcΦχΦϯ ΞʔΩςΫνϟ
w ґଘؔͷٯసͱ֎෦ɾ෦ͷ֓೦Λߟྀͨ͠ύλʔϯ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 11 ϔΩαΰφϧܥͰґଘ͕ؔ%*1ʹͳΔ w
ґଘؔٯసͷݪଇ %*1%FQFOEFODZ*OWFSTJPO1SJODJQMF ΛϨΠϠʔԽ ΞʔΩςΫνϟʹద༻͠ԁঢ়ʹల։ͨ͠ͷɻυϝΠϯ͕Πϯϑϥετ ϥΫνϟʹʢʣґଘ͠ͳ͘ͳΔ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 13 ࠓ·ͰͱԿ͕ҧ͏ͷ͔ʁ wυϝΠϯ͕֎ʹ࿙Εग़͞ͳ͍Α͏ʹ͢Δͱ͍͏
͕ڧௐ͞Ε͍ͯΔɻ֎ଆ͔Βଆʹ͔͠త ʹґଘͰ͖ͳ͍ɻଆ͔Β֎ଆʹґଘ͢Δ߹* 'Λ௨ͯؒ͠తʹґଘ͢Δɻ ࣮ɺ͜ΕϨΠ ϠʔԽΞʔΩςΫνϟͰݴٴ͞Ε͍ͯΔ͜ͱ wݸਓతʹɺυϝΠϯͷՄൖੑΛҙࣝͯ͠ଟछ ଟ༷ͳΞμϓλʹରԠ͢Δߟ͑ํʹݟ͑Δɻ wͰɺՄൖɾඇՄൖͷஅج४ͱԿ͔ʁ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 14 υϝΠϯͷՄൖੑͱ w
ೖग़ྗσόΠεɺཁ݅ʹΑͬͯมΘΔͷ Ͱɺཁ݅ʹΑͬͯมΘΒͳ͍ϙʔτʹґଘ ͠ɺΞμϓλସՄೳʹɻ w υϝΠϯͷՄൖੑґଘઌʹىҼ͢Δ ɻΠϯϑϥετϥΫνϟʹ4DBMB͚ͩ Ͱͳ͘"LLBؚΊΔͱ͍͏ߟ͑ํ͋Δ͕ɺ *0σόΠεʹ͍ۙ෦ͰϙʔτͷΑ͏ ͳநʹґଘґଘ͖ͩ͢ͱߟ͑Δɻ ΠϯϑϥετϥΫνϟ ཁ݅ʹΑͬͯ มΘΒͳ͍෦ υϝΠϯ (Ϣʔεέʔεྫ֎Ͱͳ͍) Ξμϓλ ཁ݅ʹΑͬͯ มΘΔ෦ ϙʔτ(I/F) ཁ݅ʹΑͬͯ มΘΒͳ͍෦ w ΠϯϑϥετϥΫνϟɺ্ҐͷϨΠϠʔΛࢧ͑ΔҰൠతͳٕज़తͳػೳΛఏڙ͢ Δɻ͜ΕʹɺΞϓϦέʔγϣϯͷͨΊͷϝοηʔδૹ৴ɺυϝΠϯͷͨΊͷӬଓ ԽɺϢʔβΠϯλʔϑΣΠεͷͨΊͷΟδοτඳըͳͲ͕͋Δɻ &WBOT w ϔΩαΰφϧܥυϝΠϯͷՄൖੑΛҙࣝ͢ΔɻΠϯϑϥετϥΫνϟཁ݅ʹ ΑͬͯมΘΒͳ͍෦Ͱ͋ΔͨΊɺυϝΠϯΠϯϑϥετϥΫνϟʹґଘͯ͠ υϝΠϯͷՄൖੑԼ͠ͳ͍ͱߟ͑ΒΕΔɻྫ͑ɺTDBMBMBOH ͳͲɻ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 15 $234ͱ w
$PNNBOEBOE2VFSZ3FTQPOTJCJMJUZ4FHSFHBUJPO wίϚϯυɾΫΤϦ w (SFH:PVOHࢯ͕ߟҊͨ͠ύλʔϯɻ w ʹ#FSUSBOE.FZFSࢯ͕ߟҊͨ͠ίϚϯυΫΤϦ ݪଇ $PNNBOE2VFSZ4FQBSBUJPO$24 ΛΞʔΩς Ϋνϟʹద༻ͨ͠ͷ͕$234ɻ w ʮ͋ΒΏΔϝιουɺΞΫγϣϯΛ࣮ߦ͢ΔίϚϯ υ͔ɺݺͼग़͠ݩʹσʔλΛฦ͢ΫΤϦ͔ͷ͍ͣΕ͔ Ͱ͋ͬͯɺ྆ํΛߦͬͯͳΒͳ͍ɻ͜Εɺ࣭Λ ͢Δ͜ͱͰճΛมԽͤͯ͞ͳΒͳ͍ͱ͍͏͜ͱ ͩɻʯ -
࠷৽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 -
࠷৽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 ܗࣜมΛߦ͏ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 18 ͳͥ$234ͳͷ͔ʁ wυϝΠϯϞσϧͷ୯७Խ
wϦʔυଆʹυϝΠϯϞσϧ͕ෆཁʹͳ ΔɻΫΤϦʹඞཁͳ%50͕͋ΕΑ͍ɻ wϦʔυ͞ΕΔ͜ͱΛલఏʹूΛߟ͑Δ ඞཁ͕ͳ͍ɻ wϥΠτଆͷϞσϧৼΔ͍͚ͩʹྗ Ͱ͖ΔͷͰɺ୯७ͳͷʹͳΔɻ -
࠷৽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 } -
࠷৽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 -
࠷৽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 -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 22 $234ΞʔΩςΫνϟΛ࠾༻͢ΔϝϦοτ w
ઃܭ͕γϯϓϧʹͳΔ w 38ʹ͓͍ͯ૬ޓʹແବͳૢ࡞Λ࣮ߦ͠ͳ͍Α͏ʹ͢Δ͜ͱ͕͍͕͠ɺίϚϯυ ͱΫΤϦΛ͢Δ͜ͱͰ͜ͷෳࡶ͞ΛܰݮͰ͖Δɻ w ϓϩδΣΫγϣϯ͕͋ΕϦʔυଆͷΠϯϐʔμϯεϛεϚονͷղফ͕ෆཁͱͳ Δɻ w υϝΠϯϞσϧΛλεΫॏࢹͰߟ͑ΒΕΔΑ͏ʹͳΔ w ৼΔ·͍ λεΫ Λத৺ʹ͓͍ͨυϝΠϯϞσϦϯά͕Ͱ͖ΔΑ͏ʹͳΔɻ w ίʔυ͕ཧղ͘͢͠ͳΔ w ϥΠτͱϦʔυͦΕͧΕʹγεςϜͷܥ͕͔ΕΔͷͰίʔυ͕ಡΈ͘͢ͳΔɻ w ϦάϨογϣϯ͠ʹ͍͘ w ยํͷܥͷมߋ͕͏ҰํͷܥʹӨڹΛ༩͑ʹ͍͘ɻϦάϨογϣϯͷϦεΫΛܰ ݮͰ͖Δɻ w εέʔϥϏϦςΟͷد༩ w Ϣʔβ͕૿͑ͯಉ͡ϨϕϧͰύϑΥʔϚϯεΛҡ࣋͢ΔͨΊʹɺ38ͦΕͧΕ ʹಛੑͷҧ͏࠷దԽ͕͘͢͠ͳΔɻ ྫඇಉظॻ͖ࠐΈϦʔυΩϟογϡͳͲ -
࠷৽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 } … -
࠷৽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) Πϕϯτϕʔε ͷσʔλදݱ -
࠷৽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ͷ߹ -
࠷৽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 -
࠷৽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Ͱ ϦϓϨΠ -
࠷৽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 -
࠷৽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 -
࠷৽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 -
࠷৽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) -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 32 &4ʹܽͳ͍ͷ͔ʁ w
*%&ϑϨʔϜϫʔΫ͕44Λલఏʹ͍ͯ͠Δͷ͕ଟ͍ɻ ͜ͷཱ͔Βશ͘σʔλΛอଘ͠ͳ͍Α͏ʹݟ͑Δɻ Ͱ "LLBͳΒେৎ w ΠϕϯτͷܗࣜɺϏδωεϧʔϧ͕มԽͨ࣌͠ʹεΩʔ Ϛ͕มԽ͢ΔͨΊϦϨʔγϣφϧσʔλϕʔεʹ͔ ͳ͍ɻ,74͕͍͍ͯΔɻ w Ϗδωε্Ͱى͜ΔΠϕϯτΛҰͭΒͣӬଓԽ͢Δͨ Ίɺण໋͕͍ΤϯςΟςΟΛѻ͏Ϣʔεέʔεʹ͍ ͍ͯΔ ΩϟύγςΟ͕ڐͤ ɻ w ͳΜͰ͔ΜͰ&4ʹͰ͖ͳ͍͜ͱ͋Δɻ$234Ͱ 44ΛબͰ͖ͨํ͕͍͍͕ݱ࣮͍͔͠͠Εͳ͍ɻ -
࠷৽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'͕ͳ͍ɻ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 35 ͳͥ"LLBͳͷ͔ʁ ٕज़తͳଆ໘
w $,ͷղܾ w ૿͑ଓ͚ΔΫϥΠΞϯτΛͲͷΑ͏ʹࡹ͔͘ɻʹͳΔͷϒϩο Ωϯά*0 ΠϕϯτϧʔϓͱϊϯϒϩοΩϯά*0OHOJY OPEFKTͷ ಄ w $16ةػ w ΫϩοΫΛ͋͛Δ͜ͱ͕ݶքʹͳΓγϯάϧίΞةػɻϜʔΞͷ๏ ଇ τϥϯδελͷूੵີ͝ͱʹഒʹͳΔ Λҡ࣋͢ΔͨΊʹ ϚϧνίΞԽɻ w ͦͯ͠ϚϧνίΞةػ͕౸དྷɻΞϓϦέʔγϣϯ͕ฒߦ͕͍ͱί ΞΛੜ͔͠Εͳ͍ ΞϜμʔϧͷ๏ଇ ɻ୯ҰίΞʹͳΔͨΊ ϚϧνίΞʹରԠ͠ͳ͍ͱύϑΥʔϚϯε͕Լ͍͢͠ɻ͔͠͠ɺ εϨουϓϩάϥϛϯάඇৗʹ͍͠ɻσουϩοΫɺϨʔείϯ σΟγϣϯɺՄࢹੑͳͲɻ -
࠷৽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Λѹ͢Δɻ -
࠷৽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.ͳͲΛͬͯϓϩηεϚωʔδϟͱ࣮ͯ͠Ͱ͖Δɻ -
ΠϯλʔϑΣΠε Ϣʔεέʔε ࠷৽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 ґଘ ґଘ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 39 ΞΫλʔͱԿ͔ʁ w8JLJQFEJBΑΓҾ༻
wΞΫλʔϞσϧͷجຊʮશͯͷͷΞΫλʔͰ͋Δʯͱ͍͏ ֶͰ͋Δɻ͜ΕΦϒδΣΫτࢦϓϩάϥϛϯάʹ͓͚Δʮશͯͷ ͷΦϒδΣΫτͰ͋Δʯͱ͍͏ߟ͑ํͱࣅ͍ͯΔ͕ɺΦϒδΣΫ τࢦιϑτΣΞͰجຊతʹஞ࣍తʹ࣮ߦ͢Δͷʹରͯ͠ɺΞΫ λʔϞσϧͰຊ࣭తʹฒߦੑΛඋ͍͑ͯΔ͕ҟͳΔɻ wΞΫλʔฒߦతʹड৴͢ΔϝοηʔδʹରԠͨ͠ҎԼͷΑ͏ͳৼ Δ͍Λඋ͑ͨܭࢉ࣮ମʢ$PNQVUBUJPOBM&OUJUZʣͰ͋Δ w ଞͷ ΞΫλʔʹ༗ݶݸͷϝοηʔδΛૹ৴͢Δɻ w༗ݶݸͷ৽ͨͳΞΫλʔΛੜ͢Δɻ w࣍ʹड৴͢Δϝοηʔδʹର͢Δಈ࡞Λࢦఆ͢Δɻ w͜ΕΒͷৼΔ͍ʹஞ࣍ੑલఏͱ͞Ε͓ͯΒͣɺฒྻతʹ͜ ΕΒΛ࣮ߦ͢Δɻ -
࠷৽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ͷαϯϓϧ -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 41 Ճ͢Δґଘؔ w
BLLBBDUPS w BLLBQFSTJTUFODF w εςʔτϑϧΞΫλʔͷ෦ঢ়ଶΛӬଓԽ͢Δػೳɻ ΞΫλʔͷॳظԽ࣌ྫ֎ʹΑΔ࠶ىಈ࣌ɺ+7.ͷΫ ϥογϡ࣌ʹҎલͷঢ়ଶΛ෮ݩ͢Δ͜ͱ͕Ͱ͖Δ w ΞΫλʔͷ෦ঢ়ଶӬଓԽ͞ΕΔ͕ɺܾͯ͠ݱࡏͷ తͳঢ়ଶΛѻ͏Θ͚Ͱͳ͍ɻ -
࠷৽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૬ ͷػೳΛ࣮ݱՄೳɻ -
࠷৽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) } } } -
࠷৽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 } } -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 45 &WFOU#VTͷ࣮ w
"LLB&WFOU4USFBNͰͷ࣮ ɻϩʔΧϧ༻ Ϋϥελ ্ͷଞͷϊʔυʹΠϕϯτ ΛૹͰ͖ͳ͍ ɻ w 4VCTDSJCFSτϐοΫʹର ͯ͠4VCTDSJCF͢Δɻ w 1VCMJTIFS4VCTDSJCFSΛࢦ ఆͤͣʹΠϕϯτΛ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) } -
࠷৽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() } -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 47 Ճ͢Δґଘؔ w
BLLBQFSTJTUFODFRVFSZ w ඇಉظετϦʔϜϕʔεͷΫΤϦ*'ʹΑͬͯఏڙ͞ΕΔΫΤϦʔίϯ ϙʔωϯτɻ w BLLBTUSFBN w ΞΫλʔΛͭͳ͗߹ΘͤΔͱඇಉظετϦʔϜॲཧΛ͢ΔͨΊͷ1JQF BOE'JMUFSΛ࡞Δ͜ͱ͕Ͱ͖Δ͕ɺ҆ఆͨ͠ετϦʔϛϯάॲཧΛ͢Δ ʹɺΤϥʔϋϯυϦϯάΛߦͬͨΓɺ͋Δϓϩηε্ͷόοϑΝ ϝʔϧϘοΫε͕Φʔόʔϑϩʔ͠ͳ͍Α͏ʹ͢Δ CBDLQSFTTVSF ඞ ཁ͕͋Δɻ w ੩తܕͳཁૉΛѻ͏ετϦʔϜͰΞΫλʔઢϛεΛࢭ͢Δ੩త ܕ͚Λอূ͢Δํ๏͕ͳ͍ɻ w BLLBTUSFBN্هͷΛղܾ͢Δɻ -
࠷৽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ͷΑ͏ʹ ετϦʔϜͷதྲྀͰɺ্ྲྀ͔ΒͷΛԼྲྀʹఏڙ͢Δɻ -
࠷৽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) -
࠷৽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 -
࠷৽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) -
࠷৽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() } -
࠷৽DDDΞʔΩςΫνϟͱAkkaͰͷ࣮ώϯτʹ͍ͭͯ 2016/03/26 © ChatWork All rights reserved. 53 Ճ͢Δґଘؔ wBLLBIUUQ
wBLLBIUUQͷతɺ"DUPSΛ)551Λհͯ͠Σ ϒʹެ։͢Δ͜ͱͱɺΫϥΠΞϯτͱͯ͠)551 αʔϏεΛফඅ͢Δ͜ͱΛՄೳʹ͢Δ͜ͱɻ w)551ϑϨʔϜϫʔΫͰ͋Γ·ͤΜɻΣϒαʔ ϏεͱΫϥΠΞϯτ͕ର͢ΔͨΊͷ"DUPSϕʔ εͷπʔϧΩοτͰ͢ɻ -
࠷৽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 ) -
࠷৽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) } } } } -
࠷৽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) } } } -
࠷৽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)) } } } -
࠷৽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͕͋ΔͱݴͬͯաݴͰͳ͍ɻ͔͠ ͠ɺֶश͖͢ίϯϙʔωϯτ͕ଟ͍ɻ·ͩεςʔϒϧͰͳ͍ ͷଟ͍ɻٯʹ͍͏ͱࠓͷ͏ͪʹ४උ͓͚͍͍ͯ͠ʂ