Slide 1

Slide 1 text

1VSFEPNBJONPEFM BOEUIFUFDIOPMPHZCFIJOEJU 4DBMB.BUTVSJ )JSPLJ,PNVSBTBLJ!QFUJUWJPMFU ϐϡΞͳυϝΠϯΛࢧ͑Δٕज़

Slide 2

Slide 2 text

001ͱ'1Λ૊Έ߹Θͤͨ੩తܕΛ࣋ͭ ෳࡶͳΞϓϦέʔγϣϯͷόάΛճආͰ͖Δݴޠ *OUSPEVDUJPO

Slide 3

Slide 3 text

4DBMB͸ࠓ೔ͷෳࡶͳΞϓϦΛ։ൃ͢ΔͷʹڧྗͳݴޠͰ͢ *OUSPEVDUJPO 4DBMBJTBIJHIMFWFMMBOHVBHFGPSJNQMFNFOUJOH UPEBZ`TDPNQMFYBQQMJDBUJPOT

Slide 4

Slide 4 text

ಉ͘͡ɺ%%%΋ෳࡶͳ࢓༷ʹཱͪ޲͔͏ͷʹ৺ڧ͍ຯํͰ͢ *OUSPEVDUJPO %PNBJO%SJWFO%FTJHO %%% JTBMTPQPXFSGVMUP DPOGSPOUTVDIDPNQMFYTQFDJpDBUJPOT

Slide 5

Slide 5 text

%%%ʹ͓͍ͯ͸υϝΠϯͷઃܭ࣮૷͕ඇৗʹॏཁͰ͢ *OUSPEVDUJPO 'PSQSBDUJDJOH%%% EPNBJOEFTJHOBOE JNQMFNFOUBUJPOBSFTPJNQPSUBOU

Slide 6

Slide 6 text

ͦΜͳେࣄͳυϝΠϯͷઃܭ΍࣮૷ʹ ٕज़తͳ੍໿΍େਓͷࣄ৘͕ೖΓࠐΜͰ͘Δͷ͸ੈͷৗͰ͢ *OUSPEVDUJPO )BWFUPDPOTJEFSOPUPOMZEPNBJOEFTJHOTCVUBMTP UFDIOJDBMSFTUSJDUJPOT CVTJOFTTDJSDVNTUBODFT FUD JOEFWFMPQJOHBQQMJDBUJPOT

Slide 7

Slide 7 text

͜ͷηογϣϯͰ͸ɺυϝΠϯΛϐϡΞʹ࣮૷͢ΔͨΊͷ ςΫχοΫ΍ߟ͑ํΛ঺հ͠·͢ *OUSPEVDUJPO )PXUPLFFQEPNBJOTQVSFXJUIQSPHSBNJOH UFDIOJRVFT 4DBMBMBOHVBHFGFBUVSFBOEBQQMJDBUJPO BSDIJUFDUVSF

Slide 8

Slide 8 text

%%%΍'1ʹ͍ͭͯৄ͘͠͸࿩͠·ͤΜ αϯϓϧίʔυ͸งғؾΛ఻͑ΔͨΊͷμϛʔͰ͢ %JTDMBJNFS /PUDPWFSFE EFFQEJWFJOUP%%% '1 4BNQMFDPEF 4BNQMFDPEFTJOUIJTQSFTFOUBUJPOBSFKVTUEVNNZ

Slide 9

Slide 9 text

8IBUEPFTEPNBJONFBO !9 ʮυϝΠϯʯͱ͸

Slide 10

Slide 10 text

υϝΠϯͱ͸ϓϩμΫτͦͷ΋ͷɺϏδωεϩδοΫΛදݱ͠·͢ 8IBUEPFT%PNBJONFBO %PNBJOJTXIBUUIFQSPEVDUJT %PNBJOSFQSFTFOUJUTCVTJOFTTMPHJDT 8IPMFQSPEVDUDPEF 6* UFDIOJDBMMBZFS %PNBJO

Slide 11

Slide 11 text

͜͏͍ͬͨਤ͸ݟͨ͜ͱ͕͋Γ·͔͢ʁ )BWFZPVFWFSTFFO

Slide 12

Slide 12 text

͜Ε͕ʮυϝΠϯʯ 8IBU%PNBJOJT

Slide 13

Slide 13 text

͜ͷηογϣϯͰ͸%%%ͷதͰ΋ 3FQPTJUPSZΛऔΓ্͍͛ͨͱࢥ͍·͢ 8IBUEPFT%PNBJONFBO %PNBJOJTXIBUUIFQSPEVDUJT %PNBJOSFQSFTFOUJUTCVTJOFTTMPHJDT .BOZUFSNTBSFJO%%% CVU*FTQFDJBMMZUBML BCPVUA3FQPTJUPSZAJOUIJTTFTTJPO

Slide 14

Slide 14 text

%%%ʹ͓͚Δ3FQPTJUPSZ͸υϝΠϯΦϒδΣΫτͷӬଓԽΛ ߴ౓ʹந৅Խͨ͠υϝΠϯΦϒδΣΫτ 8IBU3FQPTJUPSZJT A3FQPTJUPSZAJO%%%JTBEPNBJOPCKFDUUIBUJT IJHIFSMFWFMBCTUSBDUJPOPGEPNBJOPCKFDU QFSTJTUFODF Domain Object Repository

Slide 15

Slide 15 text

6TFSͱ͍͏&OUJUZʹର͢Δ6TFS3FQPTJUPSZ 3%#΍,74ͳͲΛ࢖࣮ͬͯ૷͞ΕΔ 8IBU3FQPTJUPSZJT *OUFSGBDFGPSEPNBJOPCKFDUQFSTJTUFODF w A6TFSAEPNBJOPCKFDU "HHSFHBUF3PPU w A6TFS3FQPTJUPSZAQFSTJTUFODFJOUFSGBDFGPSA6TFSA w JNQMFNFOUFECZVTJOH3%# ,74 FUD UserRepository User UserId UserName

Slide 16

Slide 16 text

3FQPTJUPSZ͸ӬଓԽ૚΁ͷ*'͕੹຿ 8IBU3FQPTJUPSZJT trait UserRepository { def findById(userId: UserId): Option[User] def store(user: User): Unit } case class UserId(value: String) extends AnyVal case class UserName(value: String) extends AnyVal case class User(id: UserId, name: UserName)

Slide 17

Slide 17 text

ཪଆʹ3%#ͱ4DBMJLF+%#$Λ࢖࣮ͬͯ૷͢ΔͳΒ͜Μͳײ͡ 3FQPTJUPSZJNQMFNFOUBUJPO object UserRepositoryImpl extends UserRepository { override def findById(userId: UserId): Option[User] = { DB readOnly { implicit dbSession => UserDao.findById(userId.value) map { dto2Domain } } } override def store(user: User): Unit = { DB localTx { implicit dbSession => UserDao.findById(user.id.value) match { case Some(_) => UserDao.update(domain2Dto(user)) case None => UserDao.insert(domain2Dto(user)) } } } }

Slide 18

Slide 18 text

3FQPTJUPSZͷ಺ଆͰτϥϯβΫγϣϯΛ੍ޚ͢Δʁ *TUIJTQSPQFSUSBOTBDUJPO object UserRepositoryImpl extends UserRepository { override def findById(userId: UserId): Option[User] = { DB readOnly { implicit dbSession => UserDao.findById(userId.value) map { dto2Domain } } } override def store(user: User): Unit = { DB localTx { implicit dbSession => UserDao.findById(user.id.value) match { case Some(_) => UserDao.update(domain2Dto(user)) case None => UserDao.insert(domain2Dto(user)) } } } }

Slide 19

Slide 19 text

3FQPTJUPSZͷ֎ͰτϥϯβΫγϣϯΛ੍ޚ͢Δ৔߹ɺ 3FQPTJUPSZͷΠϯλϑΣʔεʹτϥϯβΫγϣϯ৘ใ͕ඞཁ $POUSPMUSBOTBDUJPOTPVUTJEFSFQPTJUPSZ 5SBOTBDUJPOPCKFDUOFFEFECZA3FQPTJUPSZAJOUFSGBDF trait UserRepository { def findById(userId: UserId)(implicit s: DBSession): Option[User] def store(user: User)(implicit s: DBSession): Unit } object UserRepositoryImpl extends UserRepository { override def findById( userId: UserId)(implicit s: DBSession): Option[User] = ??? override def store(user: User)(implicit s: DBSession): Unit = ??? }

Slide 20

Slide 20 text

͞Βʹ3FQPTJUPSZ͸*0͢Δ͔Βඇಉظʹ͍ͨ͠ ͦͷ݁Ռɺ&YFDVUJPO$POUFYU΋ඞཁʹͳΔ /FFEUPCFBTZODISPOPVT A3FQPTJUPSZAVTVBMMZEP*0 TPXFOFFEJUJTBTZODISPOPVT trait UserRepository { def findById(userId: UserId)( implicit ec: ExecutionContext, s: DBSession): Future[Option[User]] def store(user: User)( implicit ec: ExecutionContext, s: DBSession): Future[Unit] }

Slide 21

Slide 21 text

τϥϯβΫγϣϯ΍&YFDVUJPO$POUFYU͸ υϝΠϯϏδωεϩδοΫͱ͍͑Δʁ "SFUIFZEPNBJO

Slide 22

Slide 22 text

ϏδωεϩδοΫΛදݱ͢ΔυϝΠϯΦϒδΣΫτʹ ٕज़తͳؔ৺ࣄ͕ೖΓࠐΜͰ͠·ͬͨ /POEPNBJODPODFSOTDBNFJOUPEPNBJO %PNBJOPCKFDUTMJLFSFQPTJUPSZSFQSFTFOUTCVTJOFTT MPHJDT CVUUFDIOJDBMDPODFSOTMJLFUSBOTBDUJPOBOE BTZODISPOPVTFYFDVUJPODBNFJOUPEPNBJO

Slide 23

Slide 23 text

ϐϡΞ͡Όͳ͍Ͷʂ /POEPNBJODPODFSOTDBNFJOUPEPNBJO %PNBJOPCKFDUTMJLFSFQPTJUPSZSFQSFTFOUTCVTJOFTT MPHJDT CVUUFDIOJDBMDPODFSOTMJLFUSBOTBDUJPOBOE BTZODISPOPVTFYFDVUJPODBNFJOUPEPNBJO *UJTOPUQVSF

Slide 24

Slide 24 text

8IBUEPFT1VSFNFBO !24 ʮϐϡΞʯͱ͸

Slide 25

Slide 25 text

ʮϐϡΞʯͱ͸ɺඞཁͰͳ͍ཁૉؚ͕·Εͯͳ͍͜ͱ 8IBUEPFT1VSFNFBO 1VSFEPFTOPUJODMVEFVOOFDFTTBSZFMFNFOUT

Slide 26

Slide 26 text

ٕज़తͳؔ৺ࣄͳͲؚ͕·Εͯ͠·ͬͨ υϝΠϯ͸ϐϡΞͰ͸ͳ͍ͱ͍͑Δ 8IBUEPFT1VSFNFBO w 1VSFEPNBJOSFQSFTFOUTPOMZCVTJOFTTMPHJDT w 0OMZBCTUSBDUJNQMFNFOUBUJPOT w *NQVSFEPNBJOSFQSFTFOUTOPUPOMZCVTJOFTT MPHJDTCVUBMTPUFDIOJDBMBOEPUIFSDPODFSOT w *ODMVEFDPODSFUFJNQMFNFOUBUJPOT w 3FQSFTFOUBTZODISPOPVT USBOTBDUJPOBOEFUD

Slide 27

Slide 27 text

1VSFͳํ͕όάΛආ͚΍͍͢͠ςετॻ͖΍͍͢ͷͰ͸ʁ 8IZQVSFNBUUFST w 1VSFEPNBJOSFQSFTFOUTPOMZCVTJOFTTMPHJDT w *NQVSFEPNBJOSFQSFTFOUTOPUPOMZCVTJOFTT MPHJDTCVUBMTPUFDIOJDBMBOEPUIFSDPODFSOT 8IJDIPOFJTTJNQMFS 8IJDIPOFJTCFUUFSUPBWPJECVHT

Slide 28

Slide 28 text

)JEFJNQVSFXJUIBCTUSBDUJPO !28 ϐϡΞͰͳ͍΋ͷΛந৅Խͯ͠Ӆ͢

Slide 29

Slide 29 text

3FQPTJUPSZͷΠϯλϑΣʔεΛվΊͯ $VSSFOU3FQPTJUPSZJOUFSGBDF trait UserRepository { def findById(userId: UserId)( implicit ec: ExecutionContext, s: DBSession): Future[Option[User]] def store(user: User)( implicit ec: ExecutionContext, s: DBSession): Future[Unit] }

Slide 30

Slide 30 text

ؔ਺Λ༻͍ͯγάωνϟΛม͑Δ $IBOHFUIFTJHOBUVSFXJUIGVODUJPO trait UserRepository { def findById(userId: UserId): (ExecutionContext, DBSession) => Future[Option[User]] def store(user: User): (ExecutionContext, DBSession) => Future[Unit] } trait UserRepository { def findById(userId: UserId)( implicit ec: ExecutionContext, s: DBSession): Future[Option[User]] def store(user: User)( implicit ec: ExecutionContext, s: DBSession): Future[Unit] }

Slide 31

Slide 31 text

UZQFBMJBTΛ༻͍ͯॻ͖௚͢ $IBOHFUIFTJHOBUVSFXJUIUZQFBMJBT trait UserRepository { def findById(userId: UserId): (ExecutionContext, DBSession) => Future[Option[User]] def store(user: User): (ExecutionContext, DBSession) => Future[Unit] } trait UserRepository { type F[A] = (ExecutionContext, DBSession) => Future[A] def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 32

Slide 32 text

ߴΧΠϯυܕΛ༻͍Δͱ͜͏ॻ͚Δ $IBOHFUIFTJHOBUVSFXJUIIJHIFSLJOEUZQF trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } trait UserRepository { type F[A] = (ExecutionContext, DBSession) => Future[A] def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 33

Slide 33 text

'<">͸ &YFDVUJPO$POUFYU %#4FTTJPO 'VUVSF<">͕ͩɺ υϝΠϯ૚ͷ3FQPTJUPSZ͸ৄ͘͠͸஌Βͳ͍ *NQVSFIJEEFOCZBCTUSBDUJPO A'<">AJTBUZQF CVUSFQPTJUPSZDBOOPULOPXXIBUA'AJT trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } (ExecutionContext, DBSession) => Future[A]

Slide 34

Slide 34 text

ߴΧΠϯυܕΛ༻͍ٕͯज़తͳؔ৺ࣄΛӅ͢͜ͱ͕ग़དྷ·ͨ͠ A'AHSFBUXPSLT )JHIFSLJOEUZQFA'ATVDDFFEFEUPIJEFUIF UFDIOJDBMDPODFSOT

Slide 35

Slide 35 text

6TBHFPGQVSFSFQPTJUPSZ !35 ϐϡΞͳ3FQPTJUPSZΛ࢖͏

Slide 36

Slide 36 text

ϐϡΞͳ3FQPTJUPSZΛͲ͏΍ͬͯ࢖͏͔ 6TBHFPGQVSFSFQPTJUPSZ trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } class UserApplication[F[_]](userRepository: UserRepository[F]) { // DI def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) userOptF.map { userOpt => ??? // update user name process } } }

Slide 37

Slide 37 text

ந৅తͳ'<0QUJPO<6TFS>>͕खʹೖͬͨ 6TBHFPGQVSFSFQPTJUPSZ trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } class UserApplication[F[_]](userRepository: UserRepository[F]) { // DI def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) userOptF.map { userOpt => ??? // update user name process } } } 3FUSJFWFEQVSF '<0QUJPO<6TFS>>

Slide 38

Slide 38 text

A'Aʹ͍ͭͯANBQAͳΜͯग़དྷͳ͍ͷͰ٧Ή 6TBHFPGQVSFSFQPTJUPSZ trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } class UserApplication[F[_]](userRepository: UserRepository[F]) { // DI def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) userOptF.map { userOpt => ??? // update user name process } } } $BOOPUA'NBQA

Slide 39

Slide 39 text

ϐϡΞͰͳ͍3FQPTJUPSZͳΒNBQग़དྷͯ؆୯ 6TBHFPGJNQVSFSFQPTJUPSZ trait UserRepository { def findById(userId: UserId)( implicit ec: ExecutionContext, s: DBSession): Future[Option[User]] def store(user: User)( implicit ec: ExecutionContext, s: DBSession): Future[Unit] } class UserApplication(userRepository: UserRepository) { // DI def updateName(userId: String, newName: String)( implicit s: DBSession, ec: ExecutionContext) = { val userOpt: Future[Option[User]] = userRepository.findById(UserId(userId)) userOpt.map { userOpt => ??? // update user name process } } } $BOA'VUVSFNBQA

Slide 40

Slide 40 text

3FQPTJUPSZ͕ϐϡΞա͗ͯԿ΋ग़དྷͳ͍ )PXUPVTFQVSFSFQPTJUPSZ *NQPTTJCMFUPEPBOZPQFSBUJPOTCFDBVTFSFQPTJUPSZ JTUPPAQVSFA

Slide 41

Slide 41 text

ͭ·ΓA'Aͷ۩ମతͳܕΛ஌Βͳ͍ͱ͍͚ͳ͍ʁ )PXUPVTFQVSFSFQPTJUPSZ *NQPTTJCMFUPEPBOZPQFSBUJPOTCFDBVTFSFQPTJUPSZ JTUPPAQVSFA )BWFUPLOPXDPODSFUFUZQFPGA'A )BWFUPEFDJEFXIBUA'AJT

Slide 42

Slide 42 text

'VODUJPOBM1SPHSBNNJOH !42 ͦ͜Ͱؔ਺ܕϓϩάϥϛϯά

Slide 43

Slide 43 text

ΈΜͳେ޷͖Ϟφυͷग़൪ .POBE !43

Slide 44

Slide 44 text

.POBE͸ࣗݾؔखͷݍʹ͓͚ΔϞϊΠυର৅ͩΑ .POBE "NPOBEJTBNPOPJEJOUIFDBUFHPSZPG FOEPGVODUPST XIBUTUIFQSPCMFN

Slide 45

Slide 45 text

.POBE͸NBQͱ͔qBU.BQΛఏڙͯ͘͠ΕΔ୯ͳΔܕΫϥε .POBE "NPOBEJTBNPOPJEJOUIFDBUFHPSZPG FOEPGVODUPST XIBUTUIFQSPCMFN .POBEJTKVTUBUZQFDMBTTUPQSPWJEF"1*TMJLF ANBQA AqBU.BQABOETPPO

Slide 46

Slide 46 text

A'Aʹ͍ͭͯANBQAͳΜͯग़དྷͳͯ͘٧ΜͰͨ XJUIPVU.POBE class UserApplication[F[_]](userRepository: UserRepository[F]) { // DI def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) userOptF.map { userOpt => ??? // update user name process } } } $BOOPUA'NBQA

Slide 47

Slide 47 text

.POBE͕͋Ε͹NBQ͕ग़དྷΔ XJUI.POBE class UserApplication[F[_]](userRepository: UserRepository[F])( // DI implicit M: Monad[F]) { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) M.map(userOptF) { userOpt => ??? // update user name process } } } .POBEFOBCMFTUPA'NBQA

Slide 48

Slide 48 text

A'Aͷ۩ମతͳܕΛ஌Βͳͯ͘΋.POBEͰ͋Γ͑͢͞Ε͹ NBQͱ͔qBU.BQ͕࢖͑Δ .POBE 8IFOA'AJTBNPOBEJOTUBODF DBODBMMANBQABOE AqBU.BQA FWFOJGXFEPOPULOPXXIBUA'AJT 5PCFQSFDJTF GVODUPSXJMMBMTPDPNFPVU CVUEPOU XPSSZBCPVUJUIFSF

Slide 49

Slide 49 text

໌ࣔతͳJNQMJDJUQBSBNFUFSͰ౉͢ॻ͖ํ &YQMJDJUJNQMJDJUQBSBNFUFS class UserApplication[F[_]](userRepository: UserRepository[F])( // DI implicit M: Monad[F]) { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) M.map(userOptF) { userOpt => ??? // update user name process } } }

Slide 50

Slide 50 text

$POUFYUCPVOETͱ͍͏4DBMBݴޠػೳͰ ໌ࣔతͳJNQMJDJUQBSBNFUFSΛ҉໧తʹ͢Δ͜ͱ͕ग़དྷΔ 3FGBDUPSJOHVTJOHDPOUFYUCPVOET 3FQMBDFFYQMJDJUJNQMJDJUQBSBNFUFSUP JNQMJDJUJNQMJDJUQBSBNFUFSCZDPOUFYUCPVOET class UserApplication[F[_] : Monad](userRepository: UserRepository[F]) { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = userRepository.findById(UserId(userId)) implicitly[Monad[F]].map(userOptF) { userOpt => ??? // update user name process } } }

Slide 51

Slide 51 text

ܕΫϥεͷͨΊͷه๏Ͱɺ JNQMJDJUQBSBNFUFSΛলུͯ͠ॻ͘͜ͱ͕ग़དྷΔ $POUFYUCPVOET $POUFYUCPVOETJTBTZOUBYTVHBSGPSUZQFDMBTT trait Hoge[A] class Foo[T](implicit val H: Hoge[T]) {} class Foo[T : Hoge] { val H = implicitly[Hoge[T]] } https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html

Slide 52

Slide 52 text

͞Βʹ6TFS3FQPTJUPSZ΋$POUFYUCPVOETΛ࢖ͬͯ౉͢ 3FGBDUPSJOHVTJOHDPOUFYUCPVOET 1SPWJEFA6TFS3FQPTJUPSZAUISPVHIDPOUFYUCPVOET class UserApplication[F[_] : Monad : UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = implicitly[UserRepository[F]].findById(UserId(userId)) implicitly[Monad[F]].map(userOptF) { userOpt => ??? // update user name process } } }

Slide 53

Slide 53 text

JNQMJDJUMZΛӅ͢ϔϧύʔΛ༻ҙ͢Δͱ͜͏ॻ͚Δ )FMQFSUPIJEFAJNQMJDJUMZA object UserRepository { @inline def apply[F[_] : UserRepository]: UserRepository[F] = implicitly } class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) cats.Monad[F].map(userOptF) { userOpt => ??? // update user name process } } } IFMQFSGPSJNQMJDJUSFTPMVUJPO

Slide 54

Slide 54 text

ΞϓϦέʔγϣϯ૚ͷ࣮૷ʹ ٕज़తͳؔ৺ࣄ͕ೖ͍ͬͯͳ͍ 1VSF JTO`UJU 5IFSFBSFOPUFDIOJDBMDPODFSOTJOBQQMJDBUJPOMBZFS class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? // update user name process } } }

Slide 55

Slide 55 text

݁Ռͱͯ͠ϏδωεϩδοΫʹूத͢Δ͜ͱ͕ग़དྷͨ 'PDVTPOCVTJOFTTMPHJDT class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? // update user name process } } } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 56

Slide 56 text

"QQMJDBUJPOMBZFSJOMBZFSFEBSDIJUFDUVSFNBZ DPOUSPMEBUBCBTFUSBOTBDUJPOT BQQMJDBUJPO૚ͰτϥϯβΫγϣϯ੍ޚ͢Δ৔߹͸ ͜ͷΑ͏ͳ࣮૷ʹͳΔ 8IJDIMBZFSTIPVMEDPOUSPMUSBOTBDUJPOT type X[A] = (ExecutionContext, IOContext) => Future[A] class UserApplication(userRepository: UserRepository[X]) { // DI implicit val ec:ExecutionContext = ??? def updateName(userId: String, newName: String) = { val userOpt: Future[Option[User]] = withTransaction { implicit ioContext: IOContext => userRepository.findById(UserId(userId))(ec, ioContext) } userOpt.map { userOpt => ??? } } }

Slide 57

Slide 57 text

*NQMFNFOUJOHUIFQVSF !57 ϐϡΞΛ࣮૷͢Δ

Slide 58

Slide 58 text

A'A͸͜ΕΒͷੑ࣭Λ࣋ͬͨܕͱͳΔ $PODSFUFUZQFGPSA'A A'AJTBUZQFBT w .POBEJOTUBODF w "TZODISPOPVT w 1SPWJEF%#USBOTBDUJPOTDPQF w 1SPWJEF&YFDVUJPO$POUFYU

Slide 59

Slide 59 text

'ͷ۩ମతͳܕͱͯ͠,MFJTMJΛ༻͍Δ ,MFJTMJ͸ϞφσΟοΫͳؔ਺Λදݱ͢Δܕ $PODSFUFUZQFGPSA'AXJUI,MFJTMJ ,MFJTMJJTBNPOBEJDGVODUJPOUZQF 3FBEFSNPOBE 4JNQMJpFEA"TZOD*0AJT type Ctx = (DBSession, ExecutionContext) type AsyncIO[A] = Kleisli[Future, Ctx, A] // package cats.data final case class Kleisli[F[_], A, B](run: A => F[B]) { self => ... } type AsyncIO[A] = (DBSession, ExecutionContext) => Future[A]

Slide 60

Slide 60 text

,MFJTMJΛ࢖ͬͯ3FQPTJUPSZΛ࣮૷͢Δ $PODSFUFSFQPTJUPSZXJUI,MFJTMJ type AsyncIO[A] = Kleisli[Future, Ctx, A] // type AsyncIO[A] = (DBSession, ExecutionContext) => Future[A] object UserRepositoryImpl extends UserRepository[AsyncIO] { override def findById(id: Id[User]): AsyncIO[Option[User]] = { Kleisli { case (dbSession, ec) => Future.apply { daos.User.findById(id.value)(dbSession) map dto2domain }(ec) } } override def store(entity: User): AsyncIO[User] = ??? }

Slide 61

Slide 61 text

,MFJTMJΛ࢖ͬͯ3FQPTJUPSZΛ࣮૷͢Δ $PODSFUFSFQPTJUPSZXJUI,MFJTMJ type AsyncIO[A] = Kleisli[Future, Ctx, A] // type AsyncIO[A] = (DBSession, ExecutionContext) => Future[A] object UserRepositoryImpl extends UserRepository[AsyncIO] { override def findById(id: Id[User]): AsyncIO[Option[User]] = { Kleisli { case (dbSession, ec) => Future.apply { daos.User.findById(id.value)(dbSession) map dto2domain }(ec) } } override def store(entity: User): AsyncIO[User] = ??? }

Slide 62

Slide 62 text

$POUSPMMFSͷ࣮૷ $POUSPMMFSDPEFT object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } }

Slide 63

Slide 63 text

object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } } "TZOD*0ͷ.POBEΠϯελϯεΛఆٛ DBUTʹ༻ҙ͞Ε͍ͯΔͷΛJNQPSU͢Δ͚ͩ $POUSPMMFSDPEFT .POBEJOTUBODFGPS"TZOD*0 ,MFJTMJ<'VUVSF @ @>

Slide 64

Slide 64 text

6TFS3FQPTJUPSZ<"TZOD*0>Λ༻ҙ͢Δ $POUSPMMFSDPEFT object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } } 3FQPTJUPSZJOTUBODF

Slide 65

Slide 65 text

"QQMJDBUJPOΛOFX͢ΔࡍʹJNQMJDJUͰ NPOBEΠϯελϯεͱSFQPTJUPSZΛ஫ೖ $POUSPMMFSDPEFT object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } } DSFBUFBQQMJDBUJPOJOTUBODF JOKFDUNPOBEBOESFQPTJUPSZJOTUBODFT

Slide 66

Slide 66 text

"QQMJDBUJPOͷ࣮ߦ $POUSPMMFSDPEFT object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } } ,MFJTMJ<'VUVSF $UY 6TFS%UP>

Slide 67

Slide 67 text

,MFJTMJͷؔ਺Λ࣮ߦ͢Δ $POUSPMMFSDPEFT object UpdateUserController extends Controller { import cats.instances.future.catsStdInstancesForFuture import cats.data.Kleisli.catsDataMonadForKleisli implicit val userRepository: UserRepository[AsyncIO] = UserRepositoryImpl val application = new UserApplication[AsyncIO] def route: Route = (post & path("user") & entity(as[UpdateUserNameParam])) { param => val result: Future[UserDto] = DB futureLocalTx { s => application .updateName(param) .run((s, executionContext)) } onComplete(result) { ??? } } } JOWPLF,MFJTMJGVODUJPO

Slide 68

Slide 68 text

5BHMFTT'JOBMελΠϧͳ%*ͱݺ͹ΕΔ 5BHMFTT'JOBMTUZMF%* *U`TDBMMFE5BHMFTT'JOBMTUZMF%*

Slide 69

Slide 69 text

%*ʹ͍ͭͯ͸ ผͷϥΠϒϥϦΛ࢖࣮ͬͯݱ͢Δ͜ͱ΋Մೳ 6TF%*MJCSBSZJOTUFBE trait UserApplication[F[_]] { import wvlet.airframe.bind implicit val M: Monad[F] = bind[Monad[F]] val repo: UserRepository[F] = bind[UserRepository[F]] def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = repo.findById(UserId(userId)) cats.Monad[F].map(userOptF) { userOpt => ??? // update user name process } } } VTFXWMFU"JSGSBNFJOTUFBEPG5BHMFTTpOBM

Slide 70

Slide 70 text

େࣄͳͷ͸5BHMFTTpOBMͰ͸ͳ͘ ؔ਺ʹΑΔந৅ԽʹΑͬͯؔ৺ࣄΛ෼཭͢Δ͜ͱ "CTUSBDUJPOJTJNQPSUBOU 5BHMFTTpOBMJUTFMGEPFTOPUNBUUFST 4FQBSBUFDPODFSOTBOEIJEFXIBUZPVXBOUUPIJEF CZVTJOHGVODUJPOBMBCTUSBDUJPOT

Slide 71

Slide 71 text

υϝΠϯۦಈઃܭ͸ΦϒδΣΫτࢦ޲͕֓೦తʹجૅͱͳΔ ؔ਺ܕϓϩάϥϛϯά͸%%%Λٕज़తʹࢧ͑Δ͜ͱ͕ग़དྷΔ %%% 001BOE'1 %%% 'VODUJPOBM1SPHSBNNJOH 0CKFDU0SJFOUFE1SPHSBNNJOH DPODFQUVBMMZ UFDIOJDBMMZ

Slide 72

Slide 72 text

ؔ਺ͷྗͰυϝΠϯʹϑΥʔΧεग़དྷΔ %%%NFFUT'1 class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? // update user name process } } } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 73

Slide 73 text

3FTJTUEPNBJODPSSVQUJPO !73 υϝΠϯͷ෗ഊʹ଱͑Δ

Slide 74

Slide 74 text

αʔϏε͕੒௕͢Δͱػೳ͸૿͍͑ͯ͘ EPNBJODPSSVQUJPO 1SPEVDUHSPXT OVNCFSPGGFBUVSFTBMTPHSPXT

Slide 75

Slide 75 text

ͦΕʹैͬͯΫϥΠΞϯτ͔Βͷཁٻ΋ෳࡶԽ͢Δ ϚΠϖʔδͷ৘ใ͕૿͑ͨΓɺݕࡧػೳ͕͍ͭͨΓʜɻ EPNBJODPSSVQUJPO $MJFOUSFRVJSFNFOUTHFUNPSFDPNQMJDBUFE GPS FYBNQMF FOSJDINZQBHF TFBSDI SFQPSUJOH FUD

Slide 76

Slide 76 text

3FQPTJUPSZ͸ӬଓԽ૚΁ͷΞΫηεΛ੹຿ͱ͢ΔͷͰ υϝΠϯͰѻ͏σʔλ͕ෳࡶʹͳΔͱͲΜͲΜෳࡶʹͳΔ $BOXFSFTJTUEPNBJODPSSVQUJPO 5IFSFQPTJUPSZIBTBSFTQPOTJCJMJUZGPSBDDFTTUPUIF QFSTJTUFODFMBZFS 5IFSFGPSF BTUIFQFSTJTUFODFMBZFSIBOEMFTNPSF DPNQMJDBUFEEBUB SFQPTJUPSZBMTPCFDPNFTNPSF DPNQMJDBUFE

Slide 77

Slide 77 text

ϏδωεϩδοΫ͸ΫϥΠΞϯτͷཁٻΛදݱ͢Δ΋ͷʁ 1VSFʜ #VTJOFTTMPHJDTSFQSFTFOUTDMJFOUSFRVFTU trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int): F[Seq[User]] }

Slide 78

Slide 78 text

ఘΊΔ $BOOPU (JWFVQ

Slide 79

Slide 79 text

ҰͭͷϞσϧͰ͢΂ͯΛදݱ͢Δ͜ͱΛఘΊΔ (JWFVQ (JWFVQUPCVJMEVQBTJOHMFNPEFMUPSFQSFTFOU FWFSZUIJOH

Slide 80

Slide 80 text

$234 !80 $234

Slide 81

Slide 81 text

$234Λ࢖͏ )PXUPHJWFVQ $PNNBOE 2VFSZ 3FTQPOTJCJMJUZ 4FHSFHBUJPO $234QBUUFSO IUUQTEPDTNJDSPTPGUDPNKBKQB[VSFBSDIJUFDUVSFQBUUFSOTDRST

Slide 82

Slide 82 text

ݕࡧͱ͔Ϩϙʔτͱ͔·ͰؚΊͯҰͭͷϞσϧͰ දݱ͢Δͷ͸ෆՄೳͰ͋ΔCZ(SFH:PVOH $234 *UJTOPUQPTTJCMFUPDSFBUFBOPQUJNBMTPMVUJPOGPS TFBSDIJOH SFQPSUJOH BOEQSPDFTTJOHUSBOTBDUJPOT VUJMJ[JOHBTJOHMFNPEFM $234%PDVNFOUTCZ(SFH:PVOH IUUQTDRSTpMFTXPSEQSFTTDPNDRST@EPDVNFOUTQEG

Slide 83

Slide 83 text

$234Ͱ͸ɺߋ৽ܥͱࢀরܥͰϞσϧΛΘ͚Δ %#ΛΘ͚ͨΓ&4Λಋೖͨ͠Γ͸ඞཁ͕͋Ε͹ $234 w 4FHSFHBUFNPEFMTUPDPNNBOEBOERVFSZ w *GOFFE TFHSFHBUFEBUBCBTFT FWFOUTPVSDJOH FUD 2VFSZ .PEFM $PNNBOE .PEFM (PE .PEFM

Slide 84

Slide 84 text

ߋ৽ܥͰ͸υϝΠϯΛϐϡΞʹอͭ ࢀরܥͰ͸ϐϡΞʹอͭͷΛఘΊΔ )PXUPHJWFVQXJUI$234 w ,FFQEPNBJOTQVSFPO$PNNBOETJEF w (JWFVQUPLFFQQVSFPO2VFSZTJEF 2VFSZ .PEFM $PNNBOE .PEFM (PE .PEFM QVSF NBZCFOPUQVSF

Slide 85

Slide 85 text

෗ഊͯ͠͠·ͬͨυϝΠϯ $PSSVQUFEEPNBJO trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int): F[Seq[User]] }

Slide 86

Slide 86 text

ҰͭͷϞσϧΛ$PNNBOEͱ2VFSZʹΘ͚Δ 4QMJUJOUP$PNNBOEBOE2VFSZ #VTJOFTTMPHJDTSFQSFTFOUTDMJFOUSFRVFTU trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int): F[Seq[User]] } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } object UserQuery { def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int )(implicit ec: ExecutionContext, s: DBSession): Future[UsersResult] = ??? } $PNNBOE 2VFSZ

Slide 87

Slide 87 text

2VFSZ͸ΫϥΠΞϯτͷཁٻʹ౴͑Δ"1*Λఏڙ͢Δ $234RVFSZ 2VFSZQSPWJEFT"1*TNFFUTUIFSFRVJSFNFOUTPGUIF DMJFOUT object UserQuery { def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int )(implicit ec: ExecutionContext, s: DBSession): Future[UsersResult] = Future { UsersResult( Users.findAllByWithLimitOffset( where = sqls.eq(Users.defaultAlias.status, User.Status.Activated.value), limit = limit, offset = offset ) map toDto ) } }

Slide 88

Slide 88 text

2VFSZ͸ΫϥΠΞϯτͷཁٻʹ౴͑Δ"1*Λఏڙ͢Δ $234RVFSZ object UserQuery { def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int )(implicit ec: ExecutionContext, s: DBSession): Future[UsersResult] = Future { UsersResult( Users.findAllByWithLimitOffset( where = sqls.eq(Users.defaultAlias.status, User.Status.Activated.value), limit = limit, offset = offset ) map toDto ) } } 2VFSZQSPWJEFT"1*TNFFUTUIFSFRVJSFNFOUTPGUIF DMJFOUT

Slide 89

Slide 89 text

2VFSZͷ࣮૷ʹ͸42-ͳͲͷ۩ମతͳٕज़Λ࢖͏ $234RVFSZ 2VFSZJNQMFNFOUBUJPO XFDBOVTFDPODSFUF UFDIOPMPHZMJLF42- object UserQuery { def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int )(implicit ec: ExecutionContext, s: DBSession): Future[UsersResult] = Future { UsersResult( Users.findAllByWithLimitOffset( where = sqls.eq(Users.defaultAlias.status, User.Status.Activated.value), limit = limit, offset = offset ) map toDto ) } } SFUSJFWFEBUBGPSDMJFOU CZVTJOHDPODSFUFUFDIOPMPHZ

Slide 90

Slide 90 text

$PNNBOE͸υϝΠϯʹج͍ͮͨૢ࡞Λఏڙ͢Δ $234DPNNBOE $PNNBOEQSPWJEFDPNNBOETCBTFEPOEPNBJO class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? } } } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 91

Slide 91 text

$PNNBOEͷ࣮૷Ͱ͸ϏδωεϩδοΫΛදݱ͢ΔͨΊʹ υϝΠϯΛϐϡΞʹอͪ·͢ $234DPNNBOE $PNNBOEJNQMFNFOUBUJPO NBLFEPNBJOTQVSFUP EFDMBSFDMFBSCVTJOFTTMPHJDT class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? } } } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] }

Slide 92

Slide 92 text

ϐϡΞͳυϝΠϯͰؤுΔߋ৽ܥ ఘΊͯΫϥΠΞϯτͷཁٻʹ౴͑Δࢀরܥ $234XJUI%%%MBZFSFEBSDIJUFDUVSF $POUSPMMFS "QQMJDBUJPO %BUBCBTF"DDFTT %"0 2VFSZ $PNNBOE %PNBJO 3FQPTJUPSZ QVSF QVSF

Slide 93

Slide 93 text

ҰͭͷϞσϧΛ$PNNBOEͱ2VFSZʹΘ͚Δ͜ͱͰ υϝΠϯͷ෗ഊΛಷԽͤ͞Δ 3FTJTUEPNBJODPSSVQUJPOCZ$234 #VTJOFTTMPHJDTSFQSFTFOUTDMJFOUSFRVFTU trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int): F[Seq[User]] } trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] def store(user: User): F[Unit] } object UserQuery { def findStatusActiveWithLimitAndOffset( limit: Int, offset: Int )(implicit ec: ExecutionContext, s: DBSession): Future[UsersResult] = ??? } $PNNBOE 2VFSZ

Slide 94

Slide 94 text

4VNNBSZ ·ͱΊ !94

Slide 95

Slide 95 text

αʔϏε͕੒௕ͯ͠΋ υϝΠϯΛϐϡΞʹอ͍ͪͨ 4VNNBSZ ,FFQEPNBJOTQVSFFWFOBTUIFTFSWJDFHSPXUI w &MJNJOBUFOPOEPNBJODPEFGSPNUIFEPNBJO w 'PDVTPOUIFDPNNBOETJEF

Slide 96

Slide 96 text

υϝΠϯΛϐϡΞʹอͭͨΊͷςΫχοΫ '1 5BHMFTT'JOBM $234 5IBOLZPV ,FFQEPNBJOQVSFCZVTJOH w 'VODUJPOBMQSPHSBNNJOH w )JHIFSLJOEUZQF $POUFYUCPVOET w .POBE ,MFJTMJ w %* 5BHMFTT'JOBM w $234BSDIJUFDUVSFQBUUFSO w TFHSFHBUFDPNNBOETBOERVFSJFT w LFFQQVSFEPNBJOTPOMZJODPNNBOET