Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか

 ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか

takkkun

May 27, 2017
Tweet

More Decks by takkkun

Other Decks in Programming

Transcript

  1. ΞʔΩςΫνϟͷ࡞༻ w ΞϓϦέʔγϣϯʹ͸༷ʑͳؔ৺͕͋Δ w ղܾ͍ͨ͠໰୊ʹର͢Δॲཧͦͷ΋ͷʢػೳཁ݅ʣ w ೖྗํࣜ w ग़ྗํࣜ w

    ӬଓԽ w FUD w ۪௚ʹॻ͍ͯ͸ؔ৺͕ࠞ͟Γ߹͏ w ؔ৺Λ෼ׂ͢Δ͜ͱ͕େࣄɻΞʔΩςΫνϟ͸ͦͷͨΊͷख๏
  2. ؔ৺Λ੔ཧ ٕज़ͷؔ৺ w σʔλετΞ͸1PTUHSF42- w ੩తϑΝΠϧ͸"NB[PO4 w ϦΫΤετ͸)551Ͱड͚෇͚Δ w FUD

    ۀ຿ͷؔ৺ w λΠτϧΛࢦఆͯ͠λεΫΛ࡞Δ w λεΫΛऴྃ͢Δ w λεΫ͕ऴྃࡁΈͷ৔߹͸Τϥʔ w FUD
  3. ؔ৺Λ੔ཧ ٕज़ͷؔ৺ w σʔλετΞ͸1PTUHSF42- w ੩తϑΝΠϧ͸"NB[PO4 w ϦΫΤετ͸)551Ͱड͚෇͚Δ w FUD

    Ϗδωε্ͷ֓೦΍ϧʔϧ ۀ຿ͷؔ৺ w λΠτϧΛࢦఆͯ͠λεΫΛ࡞Δ w λεΫΛऴྃ͢Δ w λεΫ͕ऴྃࡁΈͷ৔߹͸Τϥʔ w FUD
  4. έʔεΫϥεͷར༻ w GBNJMZ/BNF HJWFO/BNFΛ֎෦ʹॻ͖׵͑ෆՄೳͳܗͰެ։ w GBNJMZ/BNF HJWFO/BNFͦΕͧΕ͕౳Ձͷ৔߹ʹΦϒδΣΫτࣗମ͕౳Ձ ͱͳΔΑ͏FRVBMT IBTI$PEFΛΦʔόϥΠυ w

    DPQZϝιουΛఆٛ w /BNFΦϒδΣΫτʹBQQMZΛఆٛʢOFXͳ͠ͷΠϯελϯεੜ੒ʣ w /BNFΦϒδΣΫτʹVOBQQMZΛఆٛʢύλʔϯϚον΁ͷར༻ʣ w FUD case class Name(familyName: String, givenName: String)
  5. έʔεΫϥεͷར༻ w GBNJMZ/BNF HJWFO/BNFΛ֎෦ʹॻ͖׵͑ෆՄೳͳܗͰެ։ w GBNJMZ/BNF HJWFO/BNFͦΕͧΕ͕౳Ձͷ৔߹ʹΦϒδΣΫτࣗମ͕౳Ձ ͱͳΔΑ͏FRVBMT IBTI$PEFΛΦʔόϥΠυ w

    DPQZϝιουΛఆٛ w /BNFΦϒδΣΫτʹBQQMZΛఆٛʢOFXͳ͠ͷΠϯελϯεੜ੒ʣ w /BNFΦϒδΣΫτʹVOBQQMZΛఆٛʢύλʔϯϚον΁ͷར༻ʣ w FUD ஋ΦϒδΣΫτͷʮෆมʯͱʮଐੑʹΑΔ౳Ձʯɺ;ͨͭͷੑ࣭Λຬͨ͢ case class Name(familyName: String, givenName: String)
  6. έʔεΫϥεͷར༻ w GBNJMZ/BNF HJWFO/BNFΛ֎෦ʹॻ͖׵͑ෆՄೳͳܗͰެ։ w GBNJMZ/BNF HJWFO/BNFͦΕͧΕ͕౳Ձͷ৔߹ʹΦϒδΣΫτࣗମ͕౳Ձ ͱͳΔΑ͏FRVBMT IBTI$PEFΛΦʔόϥΠυ w

    DPQZϝιουΛఆٛ w /BNFΦϒδΣΫτʹBQQMZΛఆٛʢOFXͳ͠ͷΠϯελϯεੜ੒ʣ w /BNFΦϒδΣΫτʹVOBQQMZΛఆٛʢύλʔϯϚον΁ͷར༻ʣ w FUD case class Name(familyName: String, givenName: String) ֎෦ʹରͯ͠ෆཁͳΠϯλϑΣʔεΛ࢈Ή
  7. ஋ΦϒδΣΫτͷఆٛྫ sealed trait Name { def familyName: String def givenName:

    String } private case class NameImpl( familyName: String, givenName: String ) extends Name object Name { def apply(familyName: String, givenName: String): Name = NameImpl(familyName, givenName) }
  8. ஋ΦϒδΣΫτͷఆٛྫ sealed trait Name { def familyName: String def givenName:

    String } private case class NameImpl( familyName: String, givenName: String ) extends Name object Name { def apply(familyName: String, givenName: String): Name = NameImpl(familyName, givenName) } /BNF͸TFBMFEUSBJUͰఆٛ TFBMFEम০ࢠ/BNF͕ఆٛ͞ΕͨϑΝΠϧҎ֎Ͱͷܧঝېࢭ
  9. ஋ΦϒδΣΫτͷఆٛྫ sealed trait Name { def familyName: String def givenName:

    String } private case class NameImpl( familyName: String, givenName: String ) extends Name object Name { def apply(familyName: String, givenName: String): Name = NameImpl(familyName, givenName) } /BNFΛܧঝͨ͠έʔεΫϥεΛఆٛ
  10. ஋ΦϒδΣΫτͷఆٛྫ sealed trait Name { def familyName: String def givenName:

    String } private case class NameImpl( familyName: String, givenName: String ) extends Name object Name { def apply(familyName: String, givenName: String): Name = NameImpl(familyName, givenName) } /BNFܕͰ/BNF*NQMΫϥεͷΠϯελϯεΛฦ͢ ࣮ଶ͸/BNF*NQMΫϥεͳͷͰέʔεΫϥεͷಛੑΛ͕࣋ͭɺܕͱͯ͠͸/BNFܕͳͷͰɺ DPQZϝιουͳͲΛݺͼग़͢͜ͱ͸ग़དྷͳ͍
  11. ΤϯςΟςΟ w ஋ΦϒδΣΫτͱಉ༷υϝΠϯͷ֓೦Λදͨ͠΋ͷ w ඞͣࣝผࢠΛ࣋ͭ w ஋ΦϒδΣΫτͱൺ΂ͯ w Մมʢଐੑ͸มΘΓಘΔʣ w

    ࣝผࢠಉ͕࢜౳ՁͰ͋Ε͹ɺͦͷଞଐੑʹؔΘΒͣΤϯςΟςΟಉ࢜͸ಉ ҰͰ͋ΔͱΈͳ͢ w ྫ͑͹ʮλεΫʯλεΫ͕ऴྃͨ͠ͱͯ͠΋ɺݩͷλεΫͱ͸ಉҰͷଘࡏ Ͱ͋Δ
  12. ΤϯςΟςΟͷϕʔε trait Entity[ID] { def id: ID override def equals(obj:

    Any): Boolean = obj match { case that: Entity[_] => id == that.id case _ => false } override def hashCode(): Int = 31 * id.## }
  13. ΤϯςΟςΟͷϕʔε trait Entity[ID] { def id: ID override def equals(obj:

    Any): Boolean = obj match { case that: Entity[_] => id == that.id case _ => false } override def hashCode(): Int = 31 * id.## } ࣝผࢠJEΛ࣋ͪɺͦͷܕΛද͢*%ܕύϥϝʔλΛड͚औΔ
  14. ΤϯςΟςΟͷϕʔε trait Entity[ID] { def id: ID override def equals(obj:

    Any): Boolean = obj match { case that: Entity[_] => id == that.id case _ => false } override def hashCode: Int = 31 * id.## } JEʹΑͬͯFRVBMTͱIBTI$PEFΛΦʔόϥΠυ
  15. ΤϯςΟςΟͷఆٛྫ case class TaskId(value: UUID) class Task( val id: TaskId,

    val title: TaskTitle, val createdAt: DateTime, val finishedAt: Option[DateTime] ) extends Entity[TaskId] { def isFinished: Boolean = finishedAt.nonEmpty def finish(): Task = { if (isFinished) { throw new TaskAlreadyFinished(this) } new Task(id, title, createdAt, Some(DateTime.now)) } }
  16. case class TaskId(value: UUID) class Task( val id: TaskId, val

    title: TaskTitle, val createdAt: DateTime, val finishedAt: Option[DateTime] ) extends Entity[TaskId] { def isFinished: Boolean = finishedAt.nonEmpty def finish(): Task = { if (isFinished) { throw new TaskAlreadyFinished(this) } new Task(id, title, createdAt, Some(DateTime.now)) } } ΤϯςΟςΟͷఆٛྫ ࣝผࢠͱͳΔ5BTL*EΛ஋ΦϒδΣΫτͱͯ͠ఆٛ
  17. case class TaskId(value: UUID) class Task( val id: TaskId, val

    title: TaskTitle, val createdAt: DateTime, val finishedAt: Option[DateTime] ) extends Entity[TaskId] { def isFinished: Boolean = finishedAt.nonEmpty def finish(): Task = { if (isFinished) { throw new TaskAlreadyFinished(this) } new Task(id, title, createdAt, Some(DateTime.now)) } } ΤϯςΟςΟͷఆٛྫ &OUJUZ<5BTL*E>Λܧঝ͠ɺΤϯςΟςΟͷಛੑΛ࣋ͨͤΔ
  18. case class TaskId(value: UUID) class Task( val id: TaskId, val

    title: TaskTitle, val createdAt: DateTime, val finishedAt: Option[DateTime] ) extends Entity[TaskId] { def isFinished: Boolean = finishedAt.nonEmpty def finish(): Task = { if (isFinished) { throw new TaskAlreadyFinished(this) } new Task(id, title, createdAt, Some(DateTime.now)) } } ΤϯςΟςΟͷఆٛྫ 5BTL͕࣋ͭଐੑΛఆٛ
  19. case class TaskId(value: UUID) class Task( val id: TaskId, val

    title: TaskTitle, val createdAt: DateTime, val finishedAt: Option[DateTime] ) extends Entity[TaskId] { def isFinished: Boolean = finishedAt.nonEmpty def finish(): Task = { if (isFinished) { throw new TaskAlreadyFinished(this) } new Task(id, title, createdAt, Some(DateTime.now)) } } ΤϯςΟςΟͷఆٛྫ มߋ͢ΔଐੑΛઃఆͨ͠৽ͨͳΤϯςΟςΟΛฦ͢
  20. ϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] }
  21. ϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] } ֤ϦϙδτϦͷϕʔετϨΠτΛఆٛ *%ࣝผࢠͷܕ &ΤϯςΟςΟͷܕ 9ϦϙδτϦ༻ίϯςΩετͷܕ
  22. ϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] } ֤ϦϙδτϦʹڞ௨ͷΠϯλϑΣʔεΛఆٛ
  23. ϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] } ϕʔετϨΠτʹࣝผࢠͱΤϯςΟςΟͷܕΛ༩͑ͯܧঝ 9͸࣮૷ґଘͳͷͰ͜͜Ͱ͸ܾఆ͠ͳ͍
  24. ϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] } ϦϙδτϦݻ༗ͷΫΤϦ͕͋Ε͹ϝιουͱͯ͠ఆٛ
  25. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } }
  26. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } ΞϓϦέʔγϣϯαʔϏε΁ͷೖྗͱͳΔΫϥεΛఆٛ
  27. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } ΞϓϦέʔγϣϯαʔϏεΛఆٛ
  28. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } 'JOJTI5BTL$PNNBOEΛड͚औΓ5BTLΛฦ͢ϝιουΛఆٛ͠
  29. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } τϥϯβΫγϣϯΛ։࢝͠
  30. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } 5BTL3FQPTJUPSZ͔Βର৅ͷλεΫΛऔΓग़͠
  31. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } ͦͷλεΫΛऴྃͤ͞
  32. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } 5BTL3FQPTJUPSZΛ༻͍ͯλεΫͷมߋΛӬଓԽ͢Δ
  33. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } }
  34. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class FinishTaskCommand(val id: TaskId) class FinishTaskApplicationService() { val taskRepo:

    TaskRepository[DBSession] = new JDBCTaskRepository() // TaskRepository[DBSession] の実装クラス def apply(command: FinishTaskCommand): Task = DB.localTx { implicit session => val task = taskRepo.find(command.id).getOrElse( throw new TaskNotFound(command.id) ) val finishedTask = task.finish() taskRepo.store(finishedTask) finishedTask } } UBTL3FQPʹର࣮ͯ͠૷ΫϥεΛ୅ೖͯ͠͠·͍ͬͯΔ͍ͤͰɺΞϓϦ έʔγϣϯαʔϏεશମ͕ٕज़తৄࡉʹґଘɻ%*͠·͠ΐ͏
  35. ίϚϯυͱΫΤϦ ΫΤϦ w σʔλΛࢀর͢Δ΋ͷ w σʔλΛมߋͯ͠͸͍͚ͳ͍ʢ$24ʣ w ࢀর͢Δ಺༰͸ΫϥΠΞϯτʹද ࣔ͢Δը໘ͷཁૉʹґଘ͢Δ ίϚϯυ

    w σʔλͳͲʹ࡞༻Λٴ΅͢΋ͷ w ʮσʔλ͕Ͳ͏มߋ͞ΕΔ͔ʯͱ ͍͏ͷ͸Ϗδωε্ͷ֓೦΍ϧʔ ϧʹଇΔ
  36. ίϚϯυͱΫΤϦ ΫΤϦ w σʔλΛࢀর͢Δ΋ͷ w σʔλΛมߋͯ͠͸͍͚ͳ͍ʢ$24ʣ w ࢀর͢Δ಺༰͸ΫϥΠΞϯτʹද ࣔ͢Δը໘ͷཁૉʹґଘ͢Δ ίϚϯυ

    w σʔλͳͲʹ࡞༻Λٴ΅͢΋ͷ w ʮσʔλ͕Ͳ͏มߋ͞ΕΔ͔ʯͱ ͍͏ͷ͸Ϗδωε্ͷ֓೦΍ϧʔ ϧʹଇΔ ඞͣυϝΠϯΦϒδΣΫτΛར༻
  37. ίϚϯυͱΫΤϦ ΫΤϦ w σʔλΛࢀর͢Δ΋ͷ w σʔλΛมߋͯ͠͸͍͚ͳ͍ʢ$24ʣ w ࢀর͢Δ಺༰͸ΫϥΠΞϯτʹද ࣔ͢Δը໘ͷཁૉʹґଘ͢Δ ίϚϯυ

    w σʔλͳͲʹ࡞༻Λٴ΅͢΋ͷ w ʮσʔλ͕Ͳ͏มߋ͞ΕΔ͔ʯͱ ͍͏ͷ͸Ϗδωε্ͷ֓೦΍ϧʔ ϧʹଇΔ ௚઀σʔλετΞ͔Βࢀর
  38. ΫΤϦ༻ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ class GetTaskStatisticsQuery() case class GetTaskStatisticsDto( numberOfTasks: Int, numberOfUnfinishedTasks: Int,

    numberOfFinishedTasks: Int ) class GetTaskStatisticsApplicationService() { def apply(query: GetTaskStatisticsQuery): GetTaskStatisticsDto = DB.readOnly { implicit session => sql"""SELECT COUNT(*), COUNT(finished_at) FROM tasks""".map { rs => val numberOfTasks = rs.int(1) val numberOfFinishedTasks = rs.int(2) GetTaskStatisticsDto( numberOfTasks, numberOfTasks - numberOfFinishedTasks, numberOfFinishedTasks ) }.single.apply.getOrElse(GetTaskStatisticsDto(0, 0, 0)) } }
  39. *OCPVOEͷఆٛྫ object FinishTaskInbound { private implicit val jsonFormats: Formats =

    DefaultFormats ++ Seq(...) case class Body(id: TaskId) def apply(request: ScalatraRequest): FinishTaskCommand = { val body = request.bodyAsJson.extract[Body] FinishTaskCommand(body.id) } }
  40. object FinishTaskInbound { private implicit val jsonFormats: Formats = DefaultFormats

    ++ Seq(...) case class Body(id: TaskId) def apply(request: ScalatraRequest): FinishTaskCommand = { val body = request.bodyAsJson.extract[Body] FinishTaskCommand(body.id) } } *OCPVOEͷఆٛྫ 4DBMBUSB3FRVFTUʢ)551ϦΫΤετʣΛೖྗͱ͠ɺ 'JOJTI5BTL$PNNBOEʢೖྗ༻ΦϒδΣΫτʣΛग़ྗͱ͢Δ
  41. object FinishTaskInbound { private implicit val jsonFormats: Formats = DefaultFormats

    ++ Seq(...) case class Body(id: TaskId) def apply(request: ScalatraRequest): FinishTaskCommand = { val body = request.bodyAsJson.extract[Body] FinishTaskCommand(body.id) } } *OCPVOEͷఆٛྫ 4DBMBUSB3FRVFTU͔Βೖྗ༻ΦϒδΣΫτʹඞཁͳ஋ΛऔΓग़͢
  42. object FinishTaskInbound { private implicit val jsonFormats: Formats = DefaultFormats

    ++ Seq(...) case class Body(id: TaskId) def apply(request: ScalatraRequest): FinishTaskCommand = { val body = request.bodyAsJson.extract[Body] FinishTaskCommand(body.id) } } *OCPVOEͷఆٛྫ औΓग़ͨ͠஋Ͱ'JOJTI5BTL$PNNBOEΛ࡞੒
  43. ΞϓϦέʔγϣϯαʔϏεग़ྗˠ)551Ϩεϙϯε HTTP/1.1 200 OK Content-Type: application/json ... { "id": "79062e11-6c96-4487-95f5-d3acf7dea808",

    "title": "a title", "createdAt": "2017-05-27T10:00:00+09:00", "finishedAt": "2017-05-27T14:50:00+09:00" } Task( id = TaskId(79062e11-6c96-4487-95f5-d3acf7dea808), title = TaskTitle(a title), createdAt = DateTime(2017-05-27T10:00:00+09:00), finishedAt = Some(DateTime(2017-05-27T14:50:00+09:00)) )
  44. 0VUCPVOEͷఆٛྫ object FinishTaskOutbound { case class Body( id: TaskId, title:

    TaskTitle, createdAt: DateTime, finishedAt: Option[DateTime] ) def apply(task: Task): ActionResult = { val body = Body( id = task.id, title = task.title, createdAt = task.createdAt, finishedAt = task.finishedAt ) Ok(body) } }
  45. object FinishTaskOutbound { case class Body( id: TaskId, title: TaskTitle,

    createdAt: DateTime, finishedAt: Option[DateTime] ) def apply(task: Task): ActionResult = { val body = Body( id = task.id, title = task.title, createdAt = task.createdAt, finishedAt = task.finishedAt ) Ok(body) } } 0VUCPVOEͷఆٛྫ 5BTLʢ'JOJTI5BTL"QQMJDBUJPO4FSWJDFͷग़ྗʣΛೖྗͱ͠ɺ "DUJPO3FTVMUʢ4DBMBUSBʹ͓͚Δ)551ϨεϙϯεʣΛग़ྗͱ͢Δ
  46. object FinishTaskOutbound { case class Body( id: TaskId, title: TaskTitle,

    createdAt: DateTime, finishedAt: Option[DateTime] ) def apply(task: Task): ActionResult = { val body = Body( id = task.id, title = task.title, createdAt = task.createdAt, finishedAt = task.finishedAt ) Ok(body) } } 0VUCPVOEͷఆٛྫ )551Ϩεϙϯεຊจͷ+40/༻ͷέʔεΫϥεΛ࡞੒
  47. object FinishTaskOutbound { case class Body( id: TaskId, title: TaskTitle,

    createdAt: DateTime, finishedAt: Option[DateTime] ) def apply(task: Task): ActionResult = { val body = Body( id = task.id, title = task.title, createdAt = task.createdAt, finishedAt = task.finishedAt ) Ok(body) } } 0VUCPVOEͷఆٛྫ 0,ͳ"DUJPO3FTVMUΛ࡞੒
  48. 4DBMBUSBʹࡌͤΔ class Endpoints extends ScalatraServlet with JacksonJsonSupport { private implicit

    val formats: Formats = DefaultFormats ++ Seq(...) before { contentType = formats("json") } post("/FinishTask") { run(FinishTaskApplicationService, FinishTaskInbound, FinishTaskOutbound) } ... private def run(...): ActionResult = ... }
  49. $POUSPMMFSͷྫ class FinishTaskController( applicationService: FinishTaskApplicationService, presenter: FinishTaskPresenter ) { private

    implicit val jsonFormats: Formats = DefaultFormats ++ Seq(...) case class Body(id: TaskId) def apply(request: ScalatraRequest): ActionResult = { val body = request.bodyAsJson.extract[Body] val user = applicationService(body.id) presenter(user) } }
  50. 3&45GVMͳ63-ઃܭ 63- w (&5UBTLT5"4,@*% w 1045UBTLT w 1045TFTTJPO ͳΔ΄Ͳ 

    w ΞϓϦέʔγϣϯαʔϏε w (FU5BTL w $SFBUF5BTL w -PHJO w 4VN/VNCFST
  51. ࣮ࡍʹ࠾༻͍ͯ͠Δ63-ઃܭ 63- w (&5(FU5BTL w 1045$SFBUF5BTL w (&5-PHJO w (&54VN/VNCFST

    ΞϓϦέʔγϣϯαʔϏε w (FU5BTL w $SFBUF5BTL w -PHJO w 4VN/VNCFST
  52. ࠶ܝϦϙδτϦͷఆٛྫ trait Repository[ID, E <: Entity[ID], X] { def find(id:

    ID)(implicit context: X): Option[E] def store(entity: E)(implicit context: X): Unit } trait TaskRepository[X] extends Repository[TaskId, Task, X] { def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: X): Seq[Task] }
  53. ϦϙδτϦ࣮૷ͷఆٛྫ class JDBCTaskRepository() extends TaskRepository[DBSession] { def find(id: TaskId)(implicit context:

    DBSession): Option[Task] = { ... } def store(task: Task)(implicit context: DBSession): Unit = { ... } def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: DBSession): Seq[Task] = { ... } }
  54. ϦϙδτϦ࣮૷ͷఆٛྫ class JDBCTaskRepository() extends TaskRepository[DBSession] { def find(id: TaskId)(implicit context:

    DBSession): Option[Task] = { ... } def store(task: Task)(implicit context: DBSession): Unit = { ... } def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: DBSession): Seq[Task] = { ... } }
  55. ϦϙδτϦ࣮૷ͷఆٛྫ class JDBCTaskRepository() extends TaskRepository[DBSession] { def find(id: TaskId)(implicit context:

    DBSession): Option[Task] = { ... } def store(task: Task)(implicit context: DBSession): Unit = { ... } def findAllByCreatedAtRange( start: DateTime, end: DateTime )(implicit context: DBSession): Seq[Task] = { ... } }
  56. GJOEϝιουͷத਎ val t = TaskTable.syntax("t") withSQL { select .from(TaskTable as

    t) .where.eq(t.id, id) }.map { rs => val taskRecord = TaskRecord(t)(rs) taskRecord.toDomainObject }.single.apply
  57. ෳ਺ͷϓϩδΣΫτʹ෼ׂυϝΠϯ lazy val domain = Project( id = "domain", base

    = file("domain"), settings = defaultSettings ++ Seq( libraryDependencies ++= Seq( // Joda-Timeなどの基本的なクラスを提供してくれるライブラリ ) ) )
  58. ෳ਺ͷϓϩδΣΫτʹ෼ׂ࣮૷ lazy val jdbcImpl = Project( id = "jdbcImpl", base

    = file("jdbc_impl"), settings = defaultSettings ++ Seq( libraryDependencies ++= Seq( // ScalikeJDBCなどの実装に必要なライブラリ ) ) ).dependsOn(domain)
  59. ෳ਺ͷϓϩδΣΫτʹ෼ׂΞϓϦέʔγϣϯ lazy val app = Project( id = "app", base

    = file("app"), settings = defaultSettings ++ Seq( libraryDependencies ++= Seq( // Scalatraなどのプレゼンテーションに必要なライブラリ ) ) ).dependsOn(domain, jdbcImpl, ...)
  60. ࿩ͨ͜͠ͱ w ΞʔΩςΫνϟ w ϨΠϠʔυΞʔΩςΫνϟ w %*1 w υϝΠϯ૚ w

    ஋ΦϒδΣΫτ w ෆมͱଐੑʹΑΔ౳Ձ w ΤϯςΟςΟ w ࣝผࢠʹؔ͢Δཹҙࣄ߲ w αʔϏε w ϦϙδτϦ w %%%΁ͷऔΓ૊Έ w ΞϓϦέʔγϣϯ૚ w ΞϓϦέʔγϣϯαʔϏε w ίϚϯυͱΫΤϦ w ϓϨθϯςʔγϣϯ૚ w *OCPVOEͱ0VUCPVOE w 63-ઃܭ w ΠϯϑϥετϥΫνϟ૚ w ͦͷଞ w Ϗϧυ w ςετ