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

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

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

4e69879387679d5c1791f980e058e1b2?s=128

takkkun

May 27, 2017
Tweet

Transcript

  1. 4DBMBͰ΢Σϒ"1*Λॻ͍͍ͯΔਓ͕ ઃܭ΍࣮૷΍ͦͷଞʹ͍ͭͯ࿩ͦ͏͔ JO:TQSJOHJO4IJCVZB

  2. ࣗݾ঺հ ໊લͨͬ͘Μ w (JU)VCUBLLLVO w 5XJUUFSUBLLLVOʢΞΠίϯ͸ˠʣ ॴଐגࣜձࣾϝσΟϩϜ ۀ຿4DBMBͰαʔόαΠυશൠ

  3. IUUQTHJUIVCDPNUBLLLVOZTBNQMFBQQ

  4. ΞʔΩςΫνϟ

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

    ӬଓԽ w FUD w ۪௚ʹॻ͍ͯ͸ؔ৺͕ࠞ͟Γ߹͏ w ؔ৺Λ෼ׂ͢Δ͜ͱ͕େࣄɻΞʔΩςΫνϟ͸ͦͷͨΊͷख๏
  6. ϨΠϠʔυΞʔΩςΫνϟ ϓϨθϯςʔγϣϯ૚ ΞϓϦέʔγϣϯ૚ υϝΠϯ૚ ΠϯϑϥετϥΫνϟ૚ ֤૚ͷґଘؔ܎ "ˠ#"͸#ʹґଘ͢Δ

  7. ֤૚ͷґଘؔ܎ "ˠ#"͸#ʹґଘ͢Δ ͢΂ͯͷ૚͕ΠϯϑϥετϥΫνϟ૚ ʹґଘ͍ͯ͠Δɻ͜͏ͳ͍ͬͯΔͱ w ΠϯϑϥετϥΫνϟ૚ͷ౎߹ʹҾ ͖ͣΒΕΔ w ςετ࣌ʹΠϯϑϥετϥΫνϟ૚ Λ४උ͢Δඞཁ͕͋Δ

    ϨΠϠʔυΞʔΩςΫνϟ ϓϨθϯςʔγϣϯ૚ ΞϓϦέʔγϣϯ૚ υϝΠϯ૚ ΠϯϑϥετϥΫνϟ૚
  8. ґଘؔ܎ٯసͷݪଇʢ%*1ʣ ఻౷తͳϨΠϠʔυΞʔΩςΫνϟ υϝΠϯ૚͕ΠϯϑϥετϥΫνϟ૚ Λ௚઀ར༻ υϝΠϯ૚ ΠϯϑϥετϥΫνϟ૚

  9. ґଘؔ܎ٯసͷݪଇʢ%*1ʣ ఻౷తͳϨΠϠʔυΞʔΩςΫνϟ υϝΠϯ૚͕ΠϯϑϥετϥΫνϟ૚ Λ௚઀ར༻ υϝΠϯ૚ ΠϯϑϥετϥΫνϟ૚ %*1ʹΑΔϨΠϠʔυΞʔΩςΫνϟ  υϝΠϯ૚ͰinterfaceΛఆٛ͠ 

    ͦΕΛΠϯϑϥετϥΫνϟ૚Ͱ ࣮૷ υϝΠϯ૚ ΠϯϑϥετϥΫνϟ૚
  10. ੔ཧ͞ΕͨϨΠϠʔυΞʔΩςΫνϟ ΠϯϑϥετϥΫνϟ૚ ϓϨθϯςʔγϣϯ૚ ΞϓϦέʔγϣϯ૚ υϝΠϯ૚

  11. ੔ཧ͞ΕͨϨΠϠʔυΞʔΩςΫνϟ ΠϯϑϥετϥΫνϟ૚ ϓϨθϯςʔγϣϯ૚ ΞϓϦέʔγϣϯ૚ υϝΠϯ૚ IUUQTUIMJHIUDPNCMPHVODMFCPCUIFDMFBO BSDIJUFDUVSFIUNM

  12. υϝΠϯ૚

  13. ؔ৺Λ੔ཧ ٕज़ͷؔ৺ w σʔλετΞ͸1PTUHSF42- w ੩తϑΝΠϧ͸"NB[PO4 w ϦΫΤετ͸)551Ͱड͚෇͚Δ w FUD

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

    Ϗδωε্ͷ֓೦΍ϧʔϧ ۀ຿ͷؔ৺ w λΠτϧΛࢦఆͯ͠λεΫΛ࡞Δ w λεΫΛऴྃ͢Δ w λεΫ͕ऴྃࡁΈͷ৔߹͸Τϥʔ w FUD
  15. υϝΠϯۦಈઃܭʢ%%%ʣ w ΞϓϦέʔγϣϯͷؔ܎ऀʢղܾ͍ͨ͠໰୊ͷઐ໳ՈυϝΠϯΤΩεύʔ τɺ։ൃऀɺFUDʣશһͷڞ௨ݴޠͱͳΔϢϏΩλεݴޠΛཱ֬ w ϢϏΩλεݴޠΛ༻͍ͯϏδωε্ͷ֓೦΍ϧʔϧΛϞσϦϯάʢυϝΠϯ Ϟσϧʣ w υϝΠϯϞσϧΛίʔυʹ൓ө͢Δ͜ͱͰɺݴޠͱϞσϧͱίʔυ͕࿈ಈ

  16. υϝΠϯۦಈઃܭʢ%%%ʣ w ΞϓϦέʔγϣϯͷؔ܎ऀʢղܾ͍ͨ͠໰୊ͷઐ໳ՈυϝΠϯΤΩεύʔ τɺ։ൃऀɺFUDʣશһͷڞ௨ݴޠͱͳΔϢϏΩλεݴޠΛཱ֬ w ϢϏΩλεݴޠΛ༻͍ͯϏδωε্ͷ֓೦΍ϧʔϧΛϞσϦϯάʢυϝΠϯ Ϟσϧʣ w υϝΠϯϞσϧΛίʔυʹ൓ө͢Δ͜ͱͰɺݴޠͱϞσϧͱίʔυ͕࿈ಈ ্هΛ܁Γฦ͠ߦ͍ɺΑΓ༗༻ͳϞσϧΛखʹೖΕΔ

  17. υϝΠϯϞσϧΛදݱ͢Δཁૉ w ஋ΦϒδΣΫτ w ΤϯςΟςΟ w αʔϏε w ϦϙδτϦ w

    ϑΝΫτϦ w ू໿ w υϝΠϯΠϕϯτ
  18. υϝΠϯϞσϧΛදݱ͢Δཁૉ w ஋ΦϒδΣΫτ w ΤϯςΟςΟ w αʔϏε w ϦϙδτϦ w

    ϑΝΫτϦ w ू໿ w υϝΠϯΠϕϯτ
  19. ஋ΦϒδΣΫτ w υϝΠϯͷ֓೦Λදͨ͢ΊͷෆՄ෼ͳଐੑͷू߹ w ྫ͑͹ʮλΠτϧʯͻͱͭͷଐੑ͔࣋ͨ͠ͳ͍͕ɺυϝΠϯͷ֓೦Λද͢ ͨΊʹ஋ΦϒδΣΫτΛར༻ w ྫ͑͹ʮ੏໊ʯ੏ͱ໊ɺ;ͨͭͷଐੑ͕͋ͬͯ࢝Ίͯ੒Γཱͭ

  20. ஋ΦϒδΣΫτͷಛ௃ ෆม ஋ΦϒδΣΫτΛ࡞੒ͨ͠ޙʹॴ༗͢ΔଐੑΛมߋ͢Δ͜ͱ͕ग़དྷͳ͍ɻ ଐੑʹΑΔ౳Ձ ผͷ஋ΦϒδΣΫτͱൺ΂ͨͱ͖ɺॴ༗͢ΔଐੑͦΕͧΕ͕౳ՁͰ͋Δ৔߹ ʹ஋ΦϒδΣΫτͦͷ΋ͷ͕౳ՁͰ͋ΔͱΈͳ͢ɻ

  21. έʔεΫϥεͷར༻ 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)
  22. έʔεΫϥεͷར༻ 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)
  23. έʔεΫϥεͷར༻ 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) ֎෦ʹରͯ͠ෆཁͳΠϯλϑΣʔεΛ࢈Ή
  24. ஋ΦϒδΣΫτͷఆٛྫ 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) }
  25. ஋ΦϒδΣΫτͷఆٛྫ 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͕ఆٛ͞ΕͨϑΝΠϧҎ֎Ͱͷܧঝېࢭ
  26. ஋ΦϒδΣΫτͷఆٛྫ 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Λܧঝͨ͠έʔεΫϥεΛఆٛ
  27. ஋ΦϒδΣΫτͷఆٛྫ 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ϝιουͳͲΛݺͼग़͢͜ͱ͸ग़དྷͳ͍
  28. ΤϯςΟςΟ w ஋ΦϒδΣΫτͱಉ༷υϝΠϯͷ֓೦Λදͨ͠΋ͷ w ඞͣࣝผࢠΛ࣋ͭ w ஋ΦϒδΣΫτͱൺ΂ͯ w Մมʢଐੑ͸มΘΓಘΔʣ w

    ࣝผࢠಉ͕࢜౳ՁͰ͋Ε͹ɺͦͷଞଐੑʹؔΘΒͣΤϯςΟςΟಉ࢜͸ಉ ҰͰ͋ΔͱΈͳ͢ w ྫ͑͹ʮλεΫʯλεΫ͕ऴྃͨ͠ͱͯ͠΋ɺݩͷλεΫͱ͸ಉҰͷଘࡏ Ͱ͋Δ
  29. ΤϯςΟςΟͷϕʔε 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.## }
  30. ΤϯςΟςΟͷϕʔε 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Λ࣋ͪɺͦͷܕΛද͢*%ܕύϥϝʔλΛड͚औΔ
  31. ΤϯςΟςΟͷϕʔε 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ΛΦʔόϥΠυ
  32. ΤϯςΟςΟͷఆٛྫ 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)) } }
  33. 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Λ஋ΦϒδΣΫτͱͯ͠ఆٛ
  34. 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>Λܧঝ͠ɺΤϯςΟςΟͷಛੑΛ࣋ͨͤΔ
  35. 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͕࣋ͭଐੑΛఆٛ
  36. 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)) } } ΤϯςΟςΟͷఆٛྫ มߋ͢ΔଐੑΛઃఆͨ͠৽ͨͳΤϯςΟςΟΛฦ͢
  37. ࣝผࢠʹؔ͢Δཹҙࣄ߲ w ࣝผࢠ͸ඞͣӬଓԽΑΓ΋લʹఆ·ΔΑ͏ʹ͢Δ w 3%#ͷΦʔτΠϯΫϦϝϯτͳ஋Λࣝผࢠͱ͢ΔͱɺϨίʔυͷૠೖΛ͢ Δ·Ͱࣝผࢠ͕ఆ·Βͳ͍ӬଓԽͷλΠϛϯάͰ͔͠ΤϯςΟςΟΛ࡞ ੒Ͱ͖ͳ͍ w ଐੑͷதʹࣝผࢠͱͯ͠ػೳ͢Δ΋ͷʢྫ͑͹ϝʔϧΞυϨε͕શϢʔβ಺ ͰҰҙͰมߋ͞Εͳ͍ͳΒ֘౰͢Δʣ͕͋ΔͳΒͦΕΛ࠾༻

    w ͦ͏Ͱͳ͍ͳΒ66*%Λ༻͍Δ
  38. αʔϏε w ஋ΦϒδΣΫτ΍ΤϯςΟςΟʹॲཧΛ࣋ͨͤΔʹ͸ෆࣗવͳͱ͖ʹ༻͍ Δ w ؔ࿈͠ͳ͍஋ΦϒδΣΫτ΍ΤϯςΟςΟɺͦΕΒͷίϨΫγϣϯΛॲཧ͢ ΔͨΊʹ༻͍Δ w ͋·Γଟ༻͢ΔͱυϝΠϯϞσϧශ݂঱ʹؕΔ w

    ٕज़తৄࡉʹґଘ͢Δ৔߹ɺηύϨʔτΠϯλϑΣʔεʹ͢Δ
  39. αʔϏεͷఆٛྫ trait NotifyTaskCreationService { def apply(task: Task): Unit } υϝΠϯϞσϧ͕ٻΊΔΠϯλϑΣʔεͷΈఆٛ

  40. ϦϙδτϦ w ΤϯςΟςΟʢ·ͨ͸ͦΕΒΛͱΓ·ͱΊͨू໿ʣΛӬଓԽ͢Δ໾ׂ w ࣝผࢠʹΑΔऔΓग़͠Ҏ֎ʹઐ༻ͷΫΤϦ͕͋ͬͯ΋ߏΘͳ͍ w ΋Ζʹٕज़తৄࡉʹґଘ͢ΔͷͰɺαʔϏεͱಉ༷ʹηύϨʔτΠϯλ ϑΣʔεʹ͢Δ

  41. ϦϙδτϦͷఆٛྫ 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] }
  42. ϦϙδτϦͷఆٛྫ 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ϦϙδτϦ༻ίϯςΩετͷܕ
  43. ϦϙδτϦͷఆٛྫ 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] } ֤ϦϙδτϦʹڞ௨ͷΠϯλϑΣʔεΛఆٛ
  44. ϦϙδτϦͷఆٛྫ 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͸࣮૷ґଘͳͷͰ͜͜Ͱ͸ܾఆ͠ͳ͍
  45. ϦϙδτϦͷఆٛྫ 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] } ϦϙδτϦݻ༗ͷΫΤϦ͕͋Ε͹ϝιουͱͯ͠ఆٛ
  46. %%%΁ͷऔΓ૊Έ w ࣮ફυϝΠϯۦಈઃܭʢ*%%%ʣΛνʔϜͰྠಡ͠ɺ%%%ͷ஌ࣝΛڞ༗ w िͷఆྫϛʔςΟϯάͰϢϏΩλεݴޠΛৼΓฦΓ w ͔͠͠༗༻ͳυϝΠϯϞσϧͷ֫ಘ͸·ͩ·ͩ೉͍͠ w ղܾ͢Δ໰୊΁ͷཧղ w

    ϞσϦϯά΁ͷཧղ w %%%΁ͷཧղ w ಉ࣌ʹίʔυϨϕϧͷઃܭ΋໛ࡧத
  47. υϝΠϯͱ޲͖߹͍ͬͯ͘ w %%%ͱ޲͖߹͍͔ͬͯ͘͠ͳ͍ɻগͣͭ͠Ͱ΋औΓ૊ΊΔ w ౰વυϝΠϯʢղܾ͍ͨ͠໰୊ɺۀ຿ʣͱ΋޲͖߹͍ͬͯ͘ w %%%͸໨తͰ͸ͳ͘खஈɻϏδωε΁ͷߩݙ͕໨త

  48. ΞϓϦέʔγϣϯ૚

  49. ΞϓϦέʔγϣϯ૚ͷ໾ׂ w ΞϓϦέʔγϣϯ͕͍࣋ͬͯͳ͖Ό͍͚ͳ͍ৼ෣͍ͷఏڙ w υϝΠϯ૚ͷΦϒδΣΫτΛར༻ͯ͠ৼ෣͍Λ࣮ݱ w ͋͘·ͰΞϓϦέʔγϣϯͷৼ෣͍Ͱ͋Γɺ࣮ࡍͷ஌ࣝ΍ϧʔϧ͸υϝΠ ϯ૚ʹ͋ΔͨΊɺ͜ͷ૚͸ඇৗʹബ͘ͳΔɻ൓ରʹް͘ͳΔͱυϝΠϯ૚ ʹ͋Δ΂͖΋ͷ͕࿙Ε͍ͯΔલஹ w

    ඞཁʹԠͯ͡ηΩϡϦςΟ΍τϥϯβΫγϣϯͷ੍ޚΛߦ͏͜ͱ΋
  50. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } }
  51. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } } ΞϓϦέʔγϣϯαʔϏε΁ͷೖྗͱͳΔΫϥεΛఆٛ
  52. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } } ΞϓϦέʔγϣϯαʔϏεΛఆٛ
  53. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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Λฦ͢ϝιουΛఆٛ͠
  54. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } } τϥϯβΫγϣϯΛ։࢝͠
  55. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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͔Βର৅ͷλεΫΛऔΓग़͠
  56. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } } ͦͷλεΫΛऴྃͤ͞
  57. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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Λ༻͍ͯλεΫͷมߋΛӬଓԽ͢Δ
  58. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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 } }
  59. ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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ʹର࣮ͯ͠૷ΫϥεΛ୅ೖͯ͠͠·͍ͬͯΔ͍ͤͰɺΞϓϦ έʔγϣϯαʔϏεશମ͕ٕज़తৄࡉʹґଘɻ%*͠·͠ΐ͏
  60. ίϚϯυͱΫΤϦ ΫΤϦ w σʔλΛࢀর͢Δ΋ͷ w σʔλΛมߋͯ͠͸͍͚ͳ͍ʢ$24ʣ w ࢀর͢Δ಺༰͸ΫϥΠΞϯτʹද ࣔ͢Δը໘ͷཁૉʹґଘ͢Δ ίϚϯυ

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

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

    w σʔλͳͲʹ࡞༻Λٴ΅͢΋ͷ w ʮσʔλ͕Ͳ͏มߋ͞ΕΔ͔ʯͱ ͍͏ͷ͸Ϗδωε্ͷ֓೦΍ϧʔ ϧʹଇΔ ௚઀σʔλετΞ͔Βࢀর
  63. ΫΤϦ༻ΞϓϦέʔγϣϯαʔϏεͷఆٛྫ 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)) } }
  64. ϓϨθϯςʔγϣϯ૚

  65. ϓϨθϯςʔγϣϯ૚ͷ໾ׂ w ར༻ऀ͔ΒͷσʔλΛͲ͏ड͚औΓɺར༻ऀ΁Ͳ͏σʔλΛฦ͔͢ʹؔ৺ Λ࣋ͭɻར༻ऀͱͷ઀఺ w ΢Σϒ"1*Ͱݴ͏ͱ)551ͷϦΫΤετͷղऍ͓ΑͼϨεϙϯε΁ͷม׵͕ ֘౰ w ϦΫΤετΛղऍ͠ΞϓϦέʔγϣϯαʔϏε΁౉͢ w

    ΞϓϦέʔγϣϯαʔϏε͔Βͷ݁ՌΛϨεϙϯεʹม׵ w 4DBMBUSB KTPOT Λ࠾༻ʢ"LLB)551 DJSDF΁ͷ৐Γ׵͑΋ݕ౼தʣ
  66. *OCPVOEͱ0VUCPVOE w ΞϓϦέʔγϣϯαʔϏε΁ͷೖྗํ๏ʢҾ਺ͷ਺ʣ͕౷Ұ͞Ε͍ͯΕ͹ɺ ػցతʹΞϓϦέʔγϣϯαʔϏεΛݺͼग़͢͜ͱ͕ग़དྷΔ w ͦͷͨΊʹೖྗ༻ΦϒδΣΫτʢ $PNNBOE  2VFSZʣΛ४උ w

    )551ϦΫΤετˠೖྗ༻ΦϒδΣΫτͷม׵Λ*OCPVOE w ΞϓϦέʔγϣϯαʔϏεग़ྗˠ)551Ϩεϙϯεͷม׵Λ0VUCPVOE
  67. )551ϦΫΤετˠೖྗ༻ΦϒδΣΫτ POST /path/to/api HTTP/1.1 Content-Type: application/json ... { "id": "79062e11-6c96-4487-95f5-d3acf7dea808"

    } FinishTaskCommand( id = TaskId(79062e11-6c96-4487-95f5-d3acf7dea808) )
  68. *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) } }
  69. 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ʢೖྗ༻ΦϒδΣΫτʣΛग़ྗͱ͢Δ
  70. 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͔Βೖྗ༻ΦϒδΣΫτʹඞཁͳ஋ΛऔΓग़͢
  71. 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Λ࡞੒
  72. ΞϓϦέʔγϣϯαʔϏεग़ྗˠ)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)) )
  73. 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) } }
  74. 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ϨεϙϯεʣΛग़ྗͱ͢Δ
  75. 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/༻ͷέʔεΫϥεΛ࡞੒
  76. 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Λ࡞੒
  77. 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 = ... }
  78. $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) } }
  79. 3&45GVMͳ63-ઃܭ 63- ΞϓϦέʔγϣϯαʔϏε w (FU5BTL w $SFBUF5BTL w -PHJO w

    4VN/VNCFST
  80. 3&45GVMͳ63-ઃܭ 63- w (&5UBTLT5"4,@*% w 1045UBTLT ΞϓϦέʔγϣϯαʔϏε w (FU5BTL w

    $SFBUF5BTL w -PHJO w 4VN/VNCFST
  81. 3&45GVMͳ63-ઃܭ 63- w (&5UBTLT5"4,@*% w 1045UBTLT w 1045TFTTJPO ͳΔ΄Ͳ ΞϓϦέʔγϣϯαʔϏε

    w (FU5BTL w $SFBUF5BTL w -PHJO w 4VN/VNCFST
  82. 3&45GVMͳ63-ઃܭ 63- w (&5UBTLT5"4,@*% w 1045UBTLT w 1045TFTTJPO ͳΔ΄Ͳ 

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

    ΞϓϦέʔγϣϯαʔϏε w (FU5BTL w $SFBUF5BTL w -PHJO w 4VN/VNCFST
  84. ΢Σϒ"1*ʹ͓͚Δ63-ઓུ w ܾͯ͠3&45GVM͕໾ʹཱͨͳ͍ͱ͍͏͜ͱͰ͸ͳ͍ w ΞϓϦͷͨΊͷ΢Σϒ"1*ͳΒ͹ɺΞϓϦͷϢʔεέʔεΛ࣮ݱ͢ΔͨΊʹ دΓఴͬͨ"1*ͱͳΔͨΊɺϢʔεέʔεΞϓϦέʔγϣϯαʔϏεͱ ͷ63-ͷํ͕౎߹͕ྑ͍ w αʔυύʔςΟʹఏڙ͢Δ΢Σϒ"1*ͳΒ͹ɺ޿͘ఏڙ͢ΔͨΊϢʔεέʔ εΛఆΊΒΕͳ͍ɻαʔϏε͕อ͍࣋ͯ͠ΔϦιʔεͷ$36%͚ͩΛఏڙ͠ɺ

    ࣗ༝ʹѻͬͯ΋Β͏ɻͦΕͳΒ3&45GVMͷํ͕౎߹͕ྑ͍
  85. ΠϯϑϥετϥΫνϟ૚

  86. ΠϯϑϥετϥΫνϟ૚ͷ໾ׂ w ٕज़తৄࡉΛఏڙ w σʔλετΞ w ϝʔϧૹ৴ w ϝοηʔδૹ৴ w

    FUD w υϝΠϯ૚ͳͲͰఆٛ͞ΕͨΠϯλϑΣʔεΛ࣮૷
  87. ࠶ܝϦϙδτϦͷఆٛྫ 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] }
  88. ϦϙδτϦ࣮૷ͷఆٛྫ 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] = { ... } }
  89. ϦϙδτϦ࣮૷ͷఆٛྫ 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] = { ... } }
  90. ϦϙδτϦ࣮૷ͷఆٛྫ 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] = { ... } }
  91. 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
  92. ͦͷଞ

  93. Ϗϧυ w TCUΛ࢖༻ w ίʔυϑΥʔϚολͱͯ͠TCUTDBMBSJGPSNΛ࢖༻ w KBSʹ·ͱΊΔͨΊʹTCUBTTFNCMZΛ࢖༻ w ෳ਺ͷϓϩδΣΫτʹ෼ׂ

  94. ෳ਺ͷϓϩδΣΫτʹ෼ׂυϝΠϯ lazy val domain = Project( id = "domain", base

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

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

    = file("app"), settings = defaultSettings ++ Seq( libraryDependencies ++= Seq( // Scalatraなどのプレゼンテーションに必要なライブラリ ) ) ).dependsOn(domain, jdbcImpl, ...)
  97. ςετ w 4DBMB5FTUΛ࢖༻ w ݱঢ়͸υϝΠϯΦϒδΣΫτͷςετ͓Αͼ&&ςετΛهड़ w ࣮૷ͷςετ͸͋ͬͨํ͕ྑ͍

  98. એ఻ w גࣜձࣾϝσΟϩϜIUUQTNFEJSPNDPKQ͸։ൃऀΛืू͍ͯ͠·͢ w ։ൃͱͪΌΜͱ޲͖߹͍͍ͨํେ׻ܴ w 4DBMBͰαʔόαΠυ w 4XJGUͰJ04ΞϓϦ w

    +BWBʢ,PUMJO΋࢖͏͔΋ʁʣͰ"OESPJEΞϓϦ w +BWB4DSJQUʢ4DBMBKT΋࢖͏͔΋ʁʣͰ΢ΣϒΞϓϦ
  99. ࿩ͨ͜͠ͱ 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 ςετ