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

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

ピュアなドメインを支える技術/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. 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)
  2. ཪଆʹ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)) } } } }
  3. 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)) } } } }
  4. 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 = ??? }
  5. ͞Βʹ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] }
  6. 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] }
  7. ؔ਺Λ༻͍ͯγάωνϟΛม͑Δ $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] }
  8. 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] }
  9. ߴΧΠϯυܕΛ༻͍Δͱ͜͏ॻ͚Δ $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] }
  10. '<">͸ &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]
  11. ϐϡΞͳ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 } } }
  12. ந৅తͳ'<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>>
  13. 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
  14. ϐϡΞͰͳ͍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
  15. 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
  16. .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
  17. ໌ࣔతͳ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 } } }
  18. $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 } } }
  19. ܕΫϥεͷͨΊͷه๏Ͱɺ 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
  20. ͞Βʹ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 } } }
  21. 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
  22. ΞϓϦέʔγϣϯ૚ͷ࣮૷ʹ ٕज़తͳؔ৺ࣄ͕ೖ͍ͬͯͳ͍ 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 } } }
  23. ݁Ռͱͯ͠ϏδωεϩδοΫʹूத͢Δ͜ͱ͕ग़དྷͨ '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] }
  24. "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 => ??? } } }
  25. 'ͷ۩ମతͳܕͱͯ͠,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]
  26. ,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] = ??? }
  27. ,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] = ??? }
  28. $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) { ??? } } }
  29. 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 @ @>
  30. 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
  31. "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
  32. "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>
  33. ,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
  34. %*ʹ͍ͭͯ͸ ผͷϥΠϒϥϦΛ࢖࣮ͬͯݱ͢Δ͜ͱ΋Մೳ 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
  35. ؔ਺ͷྗͰυϝΠϯʹϑΥʔΧεग़དྷΔ %%%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] }
  36. ϏδωεϩδοΫ͸ΫϥΠΞϯτͷཁٻΛදݱ͢Δ΋ͷʁ 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]] }
  37. ෗ഊͯ͠͠·ͬͨυϝΠϯ $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]] }
  38. ҰͭͷϞσϧΛ$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
  39. 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 ) } }
  40. 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
  41. 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
  42. $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] }
  43. $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] }
  44. ҰͭͷϞσϧΛ$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
  45. υϝΠϯΛϐϡΞʹอͭͨΊͷςΫχοΫ '1 5BHMFTT'JOBM $234 5IBOLZPV ,FFQEPNBJOQVSFCZVTJOH w 'VODUJPOBMQSPHSBNNJOH w )JHIFSLJOEUZQF

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