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

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

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

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

Avatar for takkkun

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 ςετ