ピュアなドメインを支える技術/pure domain model and the technology behind it

ピュアなドメインを支える技術/pure domain model and the technology behind it

Presented at ScalaMatsuri2019 https://2019.scalamatsuri.org

93bc8fb48f57c11e417dad9d26a2fb8a?s=128

petitviolet

June 28, 2019
Tweet

Transcript

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    w JNQMFNFOUFECZVTJOH3%# ,74 FUD   UserRepository User UserId UserName
  16. 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)
  17. ཪଆʹ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)) } } } }
  18. 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)) } } } }
  19. 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 = ??? }
  20. ͞Βʹ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] }
  21. τϥϯβΫγϣϯ΍&YFDVUJPO$POUFYU͸ υϝΠϯϏδωεϩδοΫͱ͍͑Δʁ "SFUIFZEPNBJO  

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

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

  24. 8IBUEPFT1VSFNFBO !24 ʮϐϡΞʯͱ͸

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

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

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

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

  29. 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] }
  30. ؔ਺Λ༻͍ͯγάωνϟΛม͑Δ $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] }
  31. 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] }
  32. ߴΧΠϯυܕΛ༻͍Δͱ͜͏ॻ͚Δ $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] }
  33. '<">͸ &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]
  34. ߴΧΠϯυܕΛ༻͍ٕͯज़తͳؔ৺ࣄΛӅ͢͜ͱ͕ग़དྷ·ͨ͠ A'AHSFBUXPSLT )JHIFSLJOEUZQFA'ATVDDFFEFEUPIJEFUIF UFDIOJDBMDPODFSOT  

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

  36. ϐϡΞͳ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 } } }
  37. ந৅తͳ'<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>>
  38. 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
  39. ϐϡΞͰͳ͍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
  40. 3FQPTJUPSZ͕ϐϡΞա͗ͯԿ΋ग़དྷͳ͍ )PXUPVTFQVSFSFQPTJUPSZ *NQPTTJCMFUPEPBOZPQFSBUJPOTCFDBVTFSFQPTJUPSZ JTUPPAQVSFA  

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

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

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

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

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

    
  46. 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
  47. .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
  48. A'Aͷ۩ମతͳܕΛ஌Βͳͯ͘΋.POBEͰ͋Γ͑͢͞Ε͹ NBQͱ͔qBU.BQ͕࢖͑Δ .POBE 8IFOA'AJTBNPOBEJOTUBODF DBODBMMANBQABOE AqBU.BQA FWFOJGXFEPOPULOPXXIBUA'AJT 5PCFQSFDJTF GVODUPSXJMMBMTPDPNFPVU CVUEPOU

    XPSSZBCPVUJUIFSF  
  49. ໌ࣔతͳ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 } } }
  50. $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 } } }
  51. ܕΫϥεͷͨΊͷه๏Ͱɺ 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
  52. ͞Βʹ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 } } }
  53. 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
  54. ΞϓϦέʔγϣϯ૚ͷ࣮૷ʹ ٕज़తͳؔ৺ࣄ͕ೖ͍ͬͯͳ͍ 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 } } }
  55. ݁Ռͱͯ͠ϏδωεϩδοΫʹूத͢Δ͜ͱ͕ग़དྷͨ '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] }
  56. "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 => ??? } } }
  57. *NQMFNFOUJOHUIFQVSF !57 ϐϡΞΛ࣮૷͢Δ

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

    1SPWJEF&YFDVUJPO$POUFYU  
  59. 'ͷ۩ମతͳܕͱͯ͠,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]
  60. ,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] = ??? }
  61. ,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] = ??? }
  62. $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) { ??? } } }
  63. 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 @ @>
  64. 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
  65. "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
  66. "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>
  67. ,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
  68. 5BHMFTT'JOBMελΠϧͳ%*ͱݺ͹ΕΔ 5BHMFTT'JOBMTUZMF%* *U`TDBMMFE5BHMFTT'JOBMTUZMF%*  

  69. %*ʹ͍ͭͯ͸ ผͷϥΠϒϥϦΛ࢖࣮ͬͯݱ͢Δ͜ͱ΋Մೳ 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
  70. େࣄͳͷ͸5BHMFTTpOBMͰ͸ͳ͘ ؔ਺ʹΑΔந৅ԽʹΑͬͯؔ৺ࣄΛ෼཭͢Δ͜ͱ "CTUSBDUJPOJTJNQPSUBOU 5BHMFTTpOBMJUTFMGEPFTOPUNBUUFST 4FQBSBUFDPODFSOTBOEIJEFXIBUZPVXBOUUPIJEF CZVTJOHGVODUJPOBMBCTUSBDUJPOT  

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

    UFDIOJDBMMZ
  72. ؔ਺ͷྗͰυϝΠϯʹϑΥʔΧεग़དྷΔ %%%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] }
  73. 3FTJTUEPNBJODPSSVQUJPO !73 υϝΠϯͷ෗ഊʹ଱͑Δ

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

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

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

     
  77. ϏδωεϩδοΫ͸ΫϥΠΞϯτͷཁٻΛදݱ͢Δ΋ͷʁ 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]] }
  78. ఘΊΔ $BOOPU (JWFVQ  

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

  80. $234 !80 $234

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

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

    $234%PDVNFOUTCZ(SFH:PVOH IUUQTDRSTpMFTXPSEQSFTTDPNDRST@EPDVNFOUTQEG
  83. $234Ͱ͸ɺߋ৽ܥͱࢀরܥͰϞσϧΛΘ͚Δ %#ΛΘ͚ͨΓ&4Λಋೖͨ͠Γ͸ඞཁ͕͋Ε͹ $234 w 4FHSFHBUFNPEFMTUPDPNNBOEBOERVFSZ w *GOFFE TFHSFHBUFEBUBCBTFT FWFOUTPVSDJOH FUD

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

    .PEFM $PNNBOE .PEFM (PE .PEFM QVSF NBZCFOPUQVSF
  85. ෗ഊͯ͠͠·ͬͨυϝΠϯ $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]] }
  86. ҰͭͷϞσϧΛ$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
  87. 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 ) } }
  88. 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
  89. 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
  90. $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] }
  91. $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] }
  92. ϐϡΞͳυϝΠϯͰؤுΔߋ৽ܥ ఘΊͯΫϥΠΞϯτͷཁٻʹ౴͑Δࢀরܥ $234XJUI%%%MBZFSFEBSDIJUFDUVSF   $POUSPMMFS "QQMJDBUJPO %BUBCBTF"DDFTT %"0 2VFSZ

    $PNNBOE %PNBJO 3FQPTJUPSZ QVSF QVSF
  93. ҰͭͷϞσϧΛ$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
  94. 4VNNBSZ ·ͱΊ !94

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

  96. υϝΠϯΛϐϡΞʹอͭͨΊͷςΫχοΫ '1 5BHMFTT'JOBM $234 5IBOLZPV ,FFQEPNBJOQVSFCZVTJOH w 'VODUJPOBMQSPHSBNNJOH w )JHIFSLJOEUZQF

    $POUFYUCPVOET w .POBE ,MFJTMJ w %* 5BHMFTT'JOBM w $234BSDIJUFDUVSFQBUUFSO w TFHSFHBUFDPNNBOETBOERVFSJFT w LFFQQVSFEPNBJOTPOMZJODPNNBOET