ぼくのかんがえた最強のUsecaseの作り方~あるいはビジネスロジックとはなにかという1つの回答~

 ぼくのかんがえた最強のUsecaseの作り方~あるいはビジネスロジックとはなにかという1つの回答~

ビジネスロジック、Domain層、UseCase。これらはMVVMやMVP、CleanArcitectureなど、3-Layeredアーキテクチャーではよく聞く単語だ。

しかし、いざ設計しようとすると
「つまり、ビジネスロジック/Domain/UseCaseって何?」
「レポジトリから取得したデータをそのまま渡すだけになるんだけど、これでいいの?」

などなど、首をひねりながら設計する人は多いのではないだろうか。かくいう私もその一人だった。

今回の発表では、CleanArchitectureにフォーカスし、「ビジネスロジック/Domain/UseCase is 何」という疑問を紐解いていこうと思う。
具体的には個人で開発しているアプリの「らくでん」の実装を元に、
・ビジネスロジックってどういうものを指すのか
・Domain層(UseCase)をどのように設計していくか
・何をDomain層に入れるか/入れないか
を話していく予定だ。

ReferenceURL:
https://github.com/kiuchikeisuke/RakutenCall

9d4a459864f822fb5aa7a5741e60e26b?s=128

Keisuke kiuchi

February 07, 2019
Tweet

Transcript

  1. ΅͘ͷ͔Μ͕͑ͨ࠷ڧͷ6TF$BTFͷ࡞Γํ
 d͋Δ͍͸ϏδωεϩδοΫͱ͸Կ͔ͱ͍͏̍ͭͷճ౴d ,FJTVLF,JVDIJ5XGFJ@LPNF
 d3PPN IUUQTHJUIVCDPNLJVDIJLFJTVLF3BLVUFO$BMM

  2. ໦಺ܒี w ৬ۀɿ:ͷ"OESPJEΤϯδχΞ݉ҭࣇύύ w झຯͰ࡞ͬͨϞϊɿʮΒ͘ͰΜ "QQ ʯɺ
 ʮ$MFBO"SDIJUFDUVSF5FNQMBUF'PS,PUMJO 044 ʯ

    w Ϟοτʔɿ໌೔ͷࣗ෼ʹ༏͍͠ίʔυΛॻ͘ w 4/4
 5XJUUFS!GFJ@LPNF
 (JUIVC!LJVDIJLFJTVLF
 2JJUB!L@LFJTVLF
  3. ࠷ۙͷΞΫςΟϏςΟ վળ͓͡͞Μܑ͓͞Μ ύού# ΢σϚΤ9੒ΓཱͯͷΠΧΨʔϧ

  4. ஫ҙࣄ߲ w ͜ͷൃදͰ͸$MFBO"SDJUFDUVSFͱ͸ͳΜͧ΍ʁతͳ࿩͸͋·Γ͠ͳ͍Ͱ͢ w ͜ͷൃද͸ϢʔεέʔεϏδωεϩδοΫͱ͸͔͋͘Δ΂͠ͱ
 ڧཁ͢Δ΋ͷͰ͸͋Γ·ͤΜ͠ɺઈରతͳਖ਼ղͰ΋͋Γ·ͤΜ w ͜ͷൃදΛฉ͘ͱૄ݁߹ͰՄಡੑͷߴ͍ίʔυ͕ॻ͚ΔΑ͏ʹͳΔ
 ͔΋͠Ε·ͤΜ w

    ͜ͷൃදͰ͸ཧղଅਐͷͨΊօ͞ΜʹखΛ্͛ͯ΋Β͏γʔϯ͕Կ౓͔͋Γ ·͢ɻͰ͖Ε͹ੵۃతʹࢀՃΛ͓ئ͍͠·͢
  5. ϏδωεϩδοΫϢʔεέʔεͬͯԿʁ d͋Δ͍͸ͨͩͷ໰୊ఏىd

  6. ϏδωϧϩδοΫϢʔεέʔε ͱ͸Կ͔Λҙࣝͯ͠࢖͍ͬͯΔਓ

  7. ϏδωεϩδοΫϢʔεέʔεͱ͸Կ͔Λ
 ʮΒ͘ͰΜʯΞϓϦΛྫʹߟ͍͑ͯ͜͏

  8. ʮΒ͘ͰΜʯΞϓϦͱ͸ʁ w ָఱͰΜΘαʔϏεͷαϙʔτΞϓϦʢࣗ࡞ʣ w ָఱͰΜΘαʔϏεͱ͸ɺి࿩͢Δࡍʹ
 ಛఆͷ1SFpYΛ಄ʹ͚ͭΔ͜ͱͰ
 ͋ΒΏΔి࿩͕෼·Ͱ͸ेԁʹͳΔαʔϏε ౰࣌  w

    Β͘ͰΜ͸ීஈͷి࿩$BMMΠϕϯτʹϑοΫͯ͠
 ࣗಈͰ1SFpYΛ͚ͭΔΞϓϦ w
  9. γϛϡϨʔτֶͯ͠Ϳ
 ϏδωεϩδοΫͱϢʔεέʔε d΋͋͠ͳ͕ͨΒ͘ͰΜͷ։ൃऀʹͳͬͨΒʁd

  10. ͔͜͜Β͸ɺ΋͋͠ͳ͕ͨ
 Β͘ͰΜͷ։ൃऀʹ೚໋͞ΕͨΒʁ
 ͱ͍͏γνϡΤʔγϣϯͰ࿩͕ਐΈ·͢'

  11. Β͘ͰΜΞϓϦͷ։ൃऀʹ೚໋͞ΕͨΒ·ͣԿΛ͢Δʁ  ·ͣ͸ίʔυΛॻ͖࢝ΊΔ✍  ·ͣ͸σβΠϯνʔϜʹσβΠϯΛฉ͖ʹߦ͘  ·ͣ͸ϏδωενʔϜ ࢓༷ݕ౼νʔϜ ʹ࢓༷ͷৄࡉΛ ฉ͖ʹߦ͘

  12. Β͘ͰΜΞϓϦͷ։ൃऀʹ೚໋͞ΕͨΒ·ͣԿΛ͢Δʁ  ·ͣ͸ίʔυΛॻ͖࢝ΊΔ✍  ·ͣ͸σβΠϯνʔϜʹσβΠϯΛฉ͖ʹߦ͘  ·ͣ͸ϏδωενʔϜ ࢓༷ݕ౼νʔϜ ʹ࢓༷ͷৄࡉΛ ฉ͖ʹߦ͘

    ଟ෼ΈΜͳ͜Ε͔Β΍Γ·͢ΑͶ
  13. ԿΛϏδωενʔϜʹ
 ࣭໰͠Α͏

  14. ϏδωενʔϜ΁ͷ࣭໰ྫ  1SFpYͷ൪߸͸ʁ
  6*σβΠϯ͸͍ͭࠒͰ͖Δʁ
  αʔόʔ࿈ܞͱ͔͸͋Δʁͳ͍ʁ
  ͱ͔ͱ͔ຊདྷి࿩ྉ͕ۚ
 ͔͔Βͳ͍ి࿩൪߸ͷѻ͍͸ʁ

    ࣭໰υ΢κ
  15. ϏδωενʔϜͷճ౴ྫ  1SFpYͷ൪߸͸ʁ
 ˠ๭3͔ࣾΒ࿈བྷ଴ͪͰ͢  6*σβΠϯ͸͍ͭࠒͰ͖Δʁ
 ˠ6*νʔϜʹฉ͍ͯΈͳ͍ͱԿͱ΋  αʔόʔ࿈ܞͱ͔͸͋Δʁͳ͍ʁ
 ˠࠓ࣌఺Ͱ͸Կͱ΋

     ͱ͔ͱ͔ຊདྷి࿩ྉ͕ۚ
 ͔͔Βͳ͍ి࿩൪߸ͷѻ͍͸ʁˠݕ౼͠·͢ʂʂ ճ౴σε
  16. ྉۚͷ͔͔Βͳ͍ి࿩൪߸ͬͯ Կ͕͋ΔͩΖ͏

  17. ྉۚͷ͔͔Βͳ͍ి࿩൪߸ w  ౳ͷٹٸͷి࿩൪߸
 w ౳ͷϑϦʔμΠΞϧͷి࿩൪߸
 w Ո଒ׂԁ౳Ͱࢦఆ͞Εͨɺಛผͳి࿩൪߸ ߟ͑த

  18. ͦΜͳંɺϏδωενʔϜ͔Β ࢓༷͕ग़͖ͯ·ͨ͠

  19. جຊํ਑͸ ಛఆͷ৚݅ʹ֘౰͢Δి࿩ ൪߸ͳΒ1SFpY͚ͭͳ͍Ͱ΄͍͠Ͱ͢ ίϨσ
 Ϥϩd

  20. ఏࣔ͞Εͨৄࡉͳ࢓༷ w  ౳ͷٹٸͷి࿩൪߸
 ˠઃఆ͕༗ޮͳΒ1SFpYΛ෇͚ͳ͍Α͏ʹͯ͠΄͍͠ w ౳ͷϑϦʔμΠΞϧͷి࿩൪߸
 ˠઃఆ͕༗ޮͳΒ1SFpYΛ෇͚ͳ͍Α͏ʹͯ͠΄͍͠ w Ո଒ׂԁ౳Ͱࢦఆ͞Εͨɺಛผͳి࿩൪߸


    ˠϢʔβ͕1SFpYΛ෇͚ͳ͍ѼઌΛબ୒Ͱ͖Δ ແࢹϦετ Α͏ʹͯ͠ɺ
 ɹ֘౰ͷѼઌͳΒ1SFpYΛ෇͚ͳ͍Α͏ʹͯ͠΄͍͠ LXTL
  21. ͸͍ɺ͜ΕΒ͕ϏδωεϩδοΫͱ ϢʔεέʔεͰ͢

  22. None
  23. ϏδωεϩδοΫͱ͸ dϏδωενʔϜ͕஌ͬͯΔ΂͖ϩδοΫ ࢓༷ d

  24. ઌ΄ͲͷγϛϡϨʔτͰɺ ϏδωενʔϜ͚ͩͰܾఆͰ͖ͨ͜ͱ͸ Կ͔ ࢥ͍ͩͨ͠

  25. ϏδωενʔϜͷճ౴  1SFpYͷ൪߸͸ʁ
 ˠ๭3͔ࣾΒ࿈བྷ଴ͪͰ͢  6*σβΠϯ͸͍ͭࠒͰ͖Δʁ
 ˠ6*νʔϜʹฉ͍ͯΈͳ͍ͱԿͱ΋  αʔόʔ࿈ܞͱ͔͸͋Δʁͳ͍ʁ
 ˠࠓ࣌఺Ͱ͸Կͱ΋

     ͱ͔ͱ͔ຊདྷి࿩ྉ͕ۚ
 ͔͔Βͳ͍ి࿩൪߸ͷѻ͍͸ʁ
 ˠ1SFpYΛ͚ͭͳ͍ํ਑Ͱ ճ૝த
  26. ϏδωενʔϜͷճ౴  1SFpYͷ൪߸͸ʁ
 ˠ๭3͔ࣾΒ࿈བྷ଴ͪͰ͢  6*σβΠϯ͸͍ͭࠒͰ͖Δʁ
 ˠ6*νʔϜʹฉ͍ͯΈͳ͍ͱԿͱ΋  αʔόʔ࿈ܞͱ͔͸͋Δʁͳ͍ʁ
 ˠࠓ࣌఺Ͱ͸Կͱ΋

     ͱ͔ͱ͔ຊདྷి࿩ྉ͕ۚ
 ͔͔Βͳ͍ి࿩൪߸ͷѻ͍͸ʁ
 ˠ1SFpYΛ͚ͭͳ͍ํ਑Ͱ 3͕ࣾ౴͑Λ࣋ͬͯΔ σβΠϯνʔϜ͕౴͑ Λ࣋ͬͯΔ ະདྷͷࢲ͕ͨͪ౴͑Λ ͍࣋ͬͯΔ ϏδωενʔϜ͕౴͑ Λ͍࣋ͬͯͨ ճ૝த
  27. ϏδωεϩδοΫͱ͸ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒ౳ͷٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒ౳ͷϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wՈ଒ׂ౳ͷϢʔβ͕ࢦఆͨ͠Ѽઌʹ͸1SFpYΛ͚ͭͳ͍ w ϏδωενʔϜ ࢓༷νʔϜ ͕஌͍ͬͯΔ΂͖࢓༷ ϩδοΫ

    ͷࣄ w ϏδωενʔϜ͚ͩͰܾ·Βͳ͍ࣄ 6*ͷσβΠϯ σʔλͷѻ͍ 1SFpYͷ ൪߸ͳͲ ͸ϏδωεϩδοΫʹ͸ؚΊͳ͍ 6*σβΠϯ 1SFpYͷ൪߸ σʔλอଘͷ࢓༷
  28. ϏδωεϩδοΫͱ͸ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒ౳ͷٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒ౳ͷϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wՈ଒ׂ౳ͷϢʔβ͕ࢦఆͨ͠Ѽઌʹ͸1SFpYΛ͚ͭͳ͍ w ϏδωενʔϜ ࢓༷νʔϜ ͕஌͍ͬͯΔ΂͖࢓༷ ϩδοΫ

    ͷࣄ w ϏδωενʔϜ͚ͩͰܾ·Βͳ͍ࣄ 6*ͷσβΠϯ σʔλͷѻ͍ 1SFpYͷ ൪߸ͳͲ ͸ϏδωεϩδοΫʹ͸ؚΊͳ͍ 6*σβΠϯ 1SFpYͷ൪߸ σʔλอଘͷ࢓༷
  29. ϏδωεϩδοΫͱ͸ w ϏδωενʔϜ ࢓༷νʔϜ ͕஌͍ͬͯΔ΂͖࢓༷ ϩδοΫ ͷࣄ w ϏδωενʔϜ͚ͩͰܾ·Βͳ͍ࣄ 6*ͷσβΠϯ

    σʔλͷѻ͍ 1SFpYͷ ൪߸ͳͲ ͸ϏδωεϩδοΫʹ͸ؚΊͳ͍ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wແࢹϦετͷѼઌʹ͸1SFpYΛ͚ͭͳ͍ 6*σβΠϯ 1SFpYͷ൪߸ σʔλอଘͷ࢓༷ ٹٸͷి࿩ ൪߸ ϑϦʔμΠΞϧͷ൪߸ Ϣʔβ͕ࢦఆ ͨ͠ແࢹϦετ ઃఆͷ༗ޮͷঢ়ଶ
  30. ͡Ό͋ϢʔεέʔεͬͯԿʁ
 ϏδωεϩδοΫͷ͜ͱʁ

  31. Ϣʔεέʔεͱ͸ dϏδωεϩδοΫΛ࢖ΘΕΔέʔε͝ͱʹ෼ׂͨ͠΍ͭd

  32. ϏδωεϩδοΫ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wແࢹϦετͷѼઌʹ͸1SFpYΛ͚ͭͳ͍

  33. ϏδωεϩδοΫ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wແࢹϦετͷѼઌʹ͸1SFpYΛ͚ͭͳ͍ ͜ΕΒ͕Ϣʔεέʔε

  34. Ϣʔεέʔεͱ͸ w ϏδωεϩδοΫΛ࢖͏ Ϣʔε έʔε͝ͱʹ෼ׂͨ͠΋ͷ w Ϣʔεέʔεͷू߹ମ͕ϏδωεϩδοΫʹͳΔ ಛఆͷి࿩൪߸Ͱ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒٹٸͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ wઃఆ͕༗ޮͳΒϑϦʔμΠϠϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍

    wແࢹϦετͷѼઌʹ͸1SFpYΛ͚ͭͳ͍
  35. ϏδωεϩδοΫϢʔεέʔεͷҰݴ·ͱΊ w ϏδωεϩδοΫͱ͸ɺ
 ϏδωενʔϜ ࢓༷νʔϜ ͕஌͍ͬͯΔ΂͖࢓༷ ϩδοΫ ͷࣄ w Ϣʔεέʔεͱ͸ɺ


    ϏδωεϩδοΫΛ࢖͏ Ϣʔε έʔε͝ͱʹ෼ׂͨ͠΋ͷ
  36. ͰɺϢʔεέʔεͱ͔
 ϏδωεϩδοΫ͕ղͬͯ
 Կ͕خ͍͠ͷʁ

  37. ϏδωεϩδοΫϢʔεέʔεͷϝϦοτ dίʔυ͕࢓༷ॻd

  38. ઃఆ͕༗ޮͳΒϑϦʔμΠΞϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍
 ͷ࣮૷ import io.reactivex.Observable import jp.rakutencall.data.datasource.contacts.ContactsDataSource import jp.rakutencall.data.datasource.settings.SettingDataSource import jp.rakutencall.data.entity.number.PhoneNumber

    import jp.rakutencall.data.entity.prefix.Prefix import jp.rakutencall.utils.commons.ExecutionThreads import jp.rakutencall.utils.commons.IoUseCase import jp.rakutencall.utils.commons.UseCase import javax.inject.Inject class FreeDial @Inject constructor( private val settingDataSource: SettingDataSource, private val contactsDataSource: ContactsDataSource, executionThreads: ExecutionThreads) : IoUseCase<FreeDial.Request, FreeDial.Response, Throwable>(executionThreads) { override fun execute(requestValue: Request): Observable<Response> { return settingDataSource.getFreeDialEnable() .flatMap { if (it) validateFreeDial(requestValue) else Observable.just(Response(requestValue.prefix)) } } private fun validateFreeDial(requestValue: Request): Observable<Response> { return contactsDataSource.getFreeDialPrefix().map { if (it.any { it.startWithPrefix(requestValue.phoneNumber) }) Response(Prefix.generateEmptyPrefix()) } else { Response(requestValue.prefix) } }.defaultIfEmpty(Response(requestValue.prefix)) } data class Request(val prefix: Prefix, val phoneNumber: PhoneNumber) UseCase.RequestValue data class Response(val prefix: Prefix) : UseCase.ResponseValue }
  39. "OESPJEʹ΋42-JUFʹ΋ґଘͯ͠ͳ͍ૄ݁߹ͳΫϥε import io.reactivex.Observable import jp.rakutencall.data.datasource.contacts.ContactsDataSource import jp.rakutencall.data.datasource.settings.SettingDataSource import jp.rakutencall.data.entity.number.PhoneNumber import

    jp.rakutencall.data.entity.prefix.Prefix import jp.rakutencall.utils.commons.ExecutionThreads import jp.rakutencall.utils.commons.IoUseCase import jp.rakutencall.utils.commons.UseCase import javax.inject.Inject
  40. ࢓༷ͦͷ··ͳ࣮૷ ઃఆ͕༗ޮͳΒd class FreeDial @Inject constructor( private val settingDataSource: SettingDataSource,

    private val contactsDataSource: ContactsDataSource, executionThreads: ExecutionThreads) : IoUseCase<FreeDial.Request, FreeDial.Response, Throwable>(executionThreads) { override fun execute(requestValue: Request): Observable<Response> { return settingDataSource.getFreeDialEnable() .flatMap { if (it) validateFreeDial(requestValue) else Observable.just(Response(requestValue.prefix)) } }
  41. ࢓༷ͦͷ··ͳ࣮૷ ϑϦʔμΠΞϧͷ൪߸ʹ͸1SFpYΛ͚ͭͳ͍ private fun validateFreeDial(requestValue: Request): Observable<Response> { return contactsDataSource.getFreeDialPrefix().map

    { if (it.any { it.startWithPrefix(requestValue.phoneNumber) }) { Response(Prefix.generateEmptyPrefix()) } else { Response(requestValue.prefix) } }.defaultIfEmpty(Response(requestValue.prefix)) } data class Request(val prefix: Prefix, val phoneNumber: PhoneNumber) : UseCase.RequestValue data class Response(val prefix: Prefix) : UseCase.ResponseValue }
  42. ϏδωεϩδοΫϢʔεέʔεͷϝϦοτ w ૄ݁߹ͳΫϥε 3Y+BWBͱ%BHHFSʹ͔͠ґଘͯ͠ͳ͍ 
 ˠJ04΍αʔόʔ΁ͷҠ২͕༰қʹͰ͖Δ
 ˠ࢓༷ʹ௚݁͢ΔϏδωεϩδοΫΛ؆୯ʹςετͰ୲อͰ͖Δ w ࢓༷ͦͷ··͕࣮૷ʹͳ͍ͬͯΔ
 ˠՄಡੑ͕ΊͪΌͪ͘Όߴ͍ίʔυʹͳΔ


    ˠJ04ΤϯδχΞ΍αʔόʔνʔϜͱιʔεΛݩʹͯٞ͠࿦Ͱ͖Δ
  ͋ΘΑ͘͹ϏδωενʔϜͱ΋
  43. Ұݴ·ͱΊ d΅͘ͷ͔Μ͕͑ͨ࠷ڧͷ6TF$BTFͷ࡞Γํͱ͔৭ʑd ࢓༷͸ ϏδωεϩδοΫ͸

  44. Ұݴ·ͱΊ w ϏδωεϩδοΫͱ͸ɺ
 ϏδωενʔϜ ࢓༷νʔϜ ͕஌͍ͬͯΔ΂͖࢓༷ ϩδοΫ ͷࣄ w Ϣʔεέʔεͱ͸ɺ


    ϏδωεϩδοΫΛ࢖͏ Ϣʔε έʔε͝ͱʹ෼ׂͨ͠΋ͷ w ͍͖͞ΐʔͳϢʔεέʔεͷ࡞Γํͱ͸ɺ
 ϏδωενʔϜ͚ͩͷձ࿩ͰܾఆͰ͖ͨ࢓༷͕Ϣʔεέʔε w ͍͖͞ΐʔͳϝϦοτͱ͸ɺ
 ΊͬͪΌૄ݁߹ͰՄಡੑͷߴ͍ίʔυ͕Ͱ͖Δ
  45. ͓ΘΓ