Share
Embed
ピュアなドメインを支える技術/pure domain model and the technology behind it
Presented at ScalaMatsuri2019 https://2019.scalamatsuri.org
petitviolet
June 28, 2019
Transcript
96 slides
-
6TFSͱ͍͏&OUJUZʹର͢Δ6TFS3FQPTJUPSZ 3%#,74ͳͲΛ࣮ͬͯ͞ΕΔ 8IBU3FQPTJUPSZJT *OUFSGBDFGPSEPNBJOPCKFDUQFSTJTUFODF w A6TFSAEPNBJOPCKFDU "HHSFHBUF3PPU w A6TFS3FQPTJUPSZAQFSTJTUFODFJOUFSGBDFGPSA6TFSA
w JNQMFNFOUFECZVTJOH3%# ,74 FUD UserRepository User UserId UserName -
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) -
ཪଆʹ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)) } } } } -
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)) } } } } -
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 = ??? } -
͞Βʹ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] } -
ٕज़తͳؔ৺ࣄͳͲؚ͕·Εͯ͠·ͬͨ υϝΠϯϐϡΞͰͳ͍ͱ͍͑Δ 8IBUEPFT1VSFNFBO w 1VSFEPNBJOSFQSFTFOUTPOMZCVTJOFTTMPHJDT w 0OMZBCTUSBDUJNQMFNFOUBUJPOT w *NQVSFEPNBJOSFQSFTFOUTOPUPOMZCVTJOFTT MPHJDTCVUBMTPUFDIOJDBMBOEPUIFSDPODFSOT
w *ODMVEFDPODSFUFJNQMFNFOUBUJPOT w 3FQSFTFOUBTZODISPOPVT USBOTBDUJPOBOEFUD -
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] } -
ؔΛ༻͍ͯγάωνϟΛม͑Δ $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] } -
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] } -
ߴΧΠϯυܕΛ༻͍Δͱ͜͏ॻ͚Δ $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] } -
'<"> &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] -
ϐϡΞͳ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 } } } -
நతͳ'<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>> -
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 -
ϐϡΞͰͳ͍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 -
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 -
.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 -
໌ࣔతͳ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 } } } -
$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 } } } -
ܕΫϥεͷͨΊͷه๏Ͱɺ 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 -
͞Βʹ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 } } } -
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 -
ΞϓϦέʔγϣϯͷ࣮ʹ ٕज़తͳؔ৺ࣄ͕ೖ͍ͬͯͳ͍ 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 } } } -
݁Ռͱͯ͠ϏδωεϩδοΫʹूத͢Δ͜ͱ͕ग़དྷͨ '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] } -
"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 => ??? } } } -
'ͷ۩ମతͳܕͱͯ͠,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] -
,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] = ??? } -
,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] = ??? } -
$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) { ??? } } } -
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 @ @> -
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 -
"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 -
"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> -
,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 -
%*ʹ͍ͭͯ ผͷϥΠϒϥϦΛ࣮ͬͯݱ͢Δ͜ͱՄೳ 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 -
ؔͷྗͰυϝΠϯʹϑΥʔΧεग़དྷΔ %%%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] } -
ϏδωεϩδοΫΫϥΠΞϯτͷཁٻΛදݱ͢Δͷʁ 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]] } -
ݕࡧͱ͔Ϩϙʔτͱ͔·ͰؚΊͯҰͭͷϞσϧͰ දݱ͢ΔͷෆՄೳͰ͋ΔCZ(SFH:PVOH $234 *UJTOPUQPTTJCMFUPDSFBUFBOPQUJNBMTPMVUJPOGPS TFBSDIJOH SFQPSUJOH BOEQSPDFTTJOHUSBOTBDUJPOT VUJMJ[JOHBTJOHMFNPEFM
$234%PDVNFOUTCZ(SFH:PVOH IUUQTDRSTpMFTXPSEQSFTTDPNDRST@EPDVNFOUTQEG -
$234Ͱɺߋ৽ܥͱࢀরܥͰϞσϧΛΘ͚Δ %#ΛΘ͚ͨΓ&4Λಋೖͨ͠Γඞཁ͕͋Ε $234 w 4FHSFHBUFNPEFMTUPDPNNBOEBOERVFSZ w *GOFFE TFHSFHBUFEBUBCBTFT FWFOUTPVSDJOH FUD
2VFSZ .PEFM $PNNBOE .PEFM (PE .PEFM -
ߋ৽ܥͰυϝΠϯΛϐϡΞʹอͭ ࢀরܥͰϐϡΞʹอͭͷΛఘΊΔ )PXUPHJWFVQXJUI$234 w ,FFQEPNBJOTQVSFPO$PNNBOETJEF w (JWFVQUPLFFQQVSFPO2VFSZTJEF 2VFSZ
.PEFM $PNNBOE .PEFM (PE .PEFM QVSF NBZCFOPUQVSF -
ഊͯ͠͠·ͬͨυϝΠϯ $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]] } -
ҰͭͷϞσϧΛ$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 -
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 ) } } -
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 -
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 -
$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] } -
$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] } -
ϐϡΞͳυϝΠϯͰؤுΔߋ৽ܥ ఘΊͯΫϥΠΞϯτͷཁٻʹ͑Δࢀরܥ $234XJUI%%%MBZFSFEBSDIJUFDUVSF $POUSPMMFS "QQMJDBUJPO %BUBCBTF"DDFTT %"0 2VFSZ
$PNNBOE %PNBJO 3FQPTJUPSZ QVSF QVSF -
ҰͭͷϞσϧΛ$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 -
υϝΠϯΛϐϡΞʹอͭͨΊͷςΫχοΫ '1 5BHMFTT'JOBM $234 5IBOLZPV ,FFQEPNBJOQVSFCZVTJOH w 'VODUJPOBMQSPHSBNNJOH w )JHIFSLJOEUZQF
$POUFYUCPVOET w .POBE ,MFJTMJ w %* 5BHMFTT'JOBM w $234BSDIJUFDUVSFQBUUFSO w TFHSFHBUFDPNNBOETBOERVFSJFT w LFFQQVSFEPNBJOTPOMZJODPNNBOET