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

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

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

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

petitviolet

June 28, 2019
Tweet

More Decks by petitviolet

Other Decks in Programming

Transcript

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

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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

    View Slide

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


    8IPMFQSPEVDUDPEF
    6* UFDIOJDBMMBZFS
    %PNBJO

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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


    Domain
    Object
    Repository

    View Slide

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

    w A6TFS3FQPTJUPSZAQFSTJTUFODFJOUFSGBDFGPSA6TFSA
    w JNQMFNFOUFECZVTJOH3%# ,74 FUD


    UserRepository
    User
    UserId
    UserName

    View Slide

  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)

    View Slide

  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))
    }
    }
    }
    }

    View Slide

  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))
    }
    }
    }
    }

    View Slide

  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 = ???
    }

    View Slide

  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]
    }

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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

    View Slide

  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]
    }

    View Slide

  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]
    }

    View Slide

  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]
    }

    View Slide

  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]
    }

    View Slide

  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]

    View Slide

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


    View Slide

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

    View Slide

  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
    }
    }
    }

    View Slide

  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>>

    View Slide

  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

    View Slide

  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

    View Slide

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


    View Slide

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


    View Slide

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

    View Slide

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

    View Slide

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


    View Slide

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


    View Slide

  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

    View Slide

  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

    View Slide

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


    View Slide

  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
    }
    }
    }

    View Slide

  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
    }
    }
    }

    View Slide

  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

    View Slide

  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
    }
    }
    }

    View Slide

  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

    View Slide

  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
    }
    }
    }

    View Slide

  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]
    }

    View Slide

  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 => ??? }
    }
    }

    View Slide

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

    View Slide

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


    View Slide

  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]

    View Slide

  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] = ???
    }

    View Slide

  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] = ???
    }

    View Slide

  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) { ??? }
    }
    }

    View Slide

  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 @ @>

    View Slide

  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

    View Slide

  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

    View Slide

  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>

    View Slide

  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

    View Slide

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


    View Slide

  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

    View Slide

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


    View Slide

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


    %%%
    'VODUJPOBM1SPHSBNNJOH
    0CKFDU0SJFOUFE1SPHSBNNJOH
    DPODFQUVBMMZ
    UFDIOJDBMMZ

    View Slide

  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]
    }

    View Slide

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

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

  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]]
    }

    View Slide

  78. ఘΊΔ
    $BOOPU
    (JWFVQ


    View Slide

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


    View Slide

  80. $234
    !80
    $234

    View Slide

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


    IUUQTEPDTNJDSPTPGUDPNKBKQB[VSFBSDIJUFDUVSFQBUUFSOTDRST

    View Slide

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


    *UJTOPUQPTTJCMFUPDSFBUFBOPQUJNBMTPMVUJPOGPS
    TFBSDIJOH SFQPSUJOH BOEQSPDFTTJOHUSBOTBDUJPOT
    VUJMJ[JOHBTJOHMFNPEFM
    $234%PDVNFOUTCZ(SFH:PVOH
    [email protected]

    View Slide

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


    2VFSZ
    .PEFM
    $PNNBOE
    .PEFM
    (PE
    .PEFM

    View Slide

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


    2VFSZ
    .PEFM
    $PNNBOE
    .PEFM
    (PE
    .PEFM
    QVSF
    NBZCFOPUQVSF

    View Slide

  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]]
    }

    View Slide

  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

    View Slide

  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
    )
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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]
    }

    View Slide

  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]
    }

    View Slide

  92. ϐϡΞͳυϝΠϯͰؤுΔߋ৽ܥ
    ఘΊͯΫϥΠΞϯτͷཁٻʹ౴͑Δࢀরܥ
    $234XJUI%%%MBZFSFEBSDIJUFDUVSF


    $POUSPMMFS
    "QQMJDBUJPO
    %BUBCBTF"DDFTT %"0

    2VFSZ $PNNBOE
    %PNBJO
    3FQPTJUPSZ

    QVSF
    QVSF

    View Slide

  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

    View Slide

  94. 4VNNBSZ
    ·ͱΊ
    !94

    View Slide

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


    View Slide

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


    View Slide