中〜大規模アプリの minne はどうアーキテクチャを選定したか
by
tick-taku
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
どうアーキテクチャを 選定したか 中〜大規模アプリの minne は GMO ϖύϘ ҏ౻ ւ (tick-taku)
Slide 2
Slide 2 text
WHO ARE YOU? 伊藤 拓海 tick-taku ▸ ฌݿݝ໌ੴࢢग़ ▸ 2020/12 GMO ϖύϘ த్ೖࣾ ▸ Android ΞϓϦΤϯδχΞ (4) ▸ લ৬ SIer Ͱ IoT ܥͷΞϓϦΛ։ൃ ▸ ͖ͳݴޠ: Kotlin, Python ▸ झຯ : ࣗసं, ఱମ؍ଌ, ྉཧ, ΨδΣοτूΊ ▸ Twitter: @tick_taku77
Slide 3
Slide 3 text
minne
Slide 4
Slide 4 text
Android meetup #android_meetup Twitter でハッシュタグをつけてコメントして イベントを盛り上がりましょう!!
Slide 5
Slide 5 text
ΞδΣϯμ ▸ ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ minne ʹ͓͚ΔΞʔΩςΫνϟͷྺ࢙ ▸ MVC / MVP / MVVM ▸ Ͳ͏ΞʔΩςΫνϟΛબఆ͢Δ͔ ▸ minne ͰͲ͏બఆ͔ͨ͠ ▸ Repository / UseCase
Slide 6
Slide 6 text
લఏ ▸ ࠓճͷ GUI ΞϓϦέʔγϣϯͷΞʔΩςΫνϟʹ͍ͭͯͰ͢ ▸ MVC MVVM ͳͲΛɺΞʔΩςΫνϟύλʔϯͱݺͼ·͢ ▸ σβΠϯύλʔϯΦϒβʔόʔύλʔϯͱ͔ͬͱਂ͍ͱ͜ΖΛࢦ͢ͷͰ ▸ αϯϓϧίʔυงғؾΛ͔ͭΜͰΒ͏ͨΊͳͷͰॾʑ͠ΐͬͨΓͯ͠Δͱ͜Ζ͕͋Γ·͢… ▸ αϯϓϧίʔυͰग़ͯ͘ΔॲཧҎԼͷϥΠϒϥϦΛ༻͍ͯ͠·͢ ▸ REST API ΫϥΠΞϯτ : Retrofit2 ▸ ඇಉظॲཧ : Kotlin Coroutine ▸ Jetpack : LiveData, ViewModel
Slide 7
Slide 7 text
ఆࢹௌऀ Android 初級者 ~ 中級者 向け Android ΞϓϦ࢝ΊͯΈͨ ͬͱεοΩϦίʔυ͕ॻ͖͍ͨ ΞʔΩςΫνϟͲ͏͍͔ͯ͘͠໎͏ ͳͲͳͲ
Slide 8
Slide 8 text
どうアーキテクチャを 選定したか 中〜大規模アプリの minne は
Slide 9
Slide 9 text
ͳͥΞʔΩςΫνϟ Λ࠾༻͢Δͷ͔
Slide 10
Slide 10 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔
Slide 11
Slide 11 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ
Slide 12
Slide 12 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ
Slide 13
Slide 13 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ
Slide 14
Slide 14 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ ▸ ػೳमਖ਼ʹ͔͔Δίετ͕Լ͕Δ
Slide 15
Slide 15 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ ▸ ػೳमਖ਼ʹ͔͔Δίετ͕Լ͕Δ ▸ ςελϏϦςΟ্͕͢Δ ͳͲ…
Slide 16
Slide 16 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ ࠶ར༻ੑ ▸ Ϟδϡʔϧੑ ▸ ղੳੑ ▸ मਖ਼ੑ ▸ ࢼݧੑ
Slide 17
Slide 17 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ อकੑΛߴΊΔ
Slide 18
Slide 18 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ΞϓϦέʔγϣϯͷΞδϦςΟΛߴΊΔ
Slide 19
Slide 19 text
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ Ϣʔβ ΑΓߴ࣭ͳαʔϏεΛ ਝʹఏڙ͢ΔͨΊ
Slide 20
Slide 20 text
minne ʹ͓͚Δ ΞʔΩςΫνϟͷྺ࢙
Slide 21
Slide 21 text
ΞʔΩςΫνϟͷྺ࢙ ࠞಱͷ࣌
Slide 22
Slide 22 text
ࠞಱͷ࣌ private typealias HogeTaskListener = () -> Hoge class HogeActivity: AppCompatActivity() { inner class HogeTask: AsyncTask() { private var listener: HogeTaskListener? = null private fun setListener(l: HogeTaskListener) { listener = l } override fun doInBackground() { Retrofit.Builder() .baseUrl("https://hoge.com/") .build() .create(HogeService::class.java) .fetch() .subscribe( onSuccess = { listener?.invoke(it) } ) } } private var task: HogeTask? = null override fun onCreate() { button.setOnClickListener { task = HogeTask().also { it.setListener { hoge -> binding.textView.text = hoge.fuga } it.execute() } } } } ‣ ΠϕϯτΛॲཧ ‣ AsyncTask ͰඇಉظॲཧΛ࣮ߦ ‣ σʔλͷऔಘɾ௨৴ ‣ σʔλΛϨΠΞτ ‣ etc … Activity Ͱશ෦Δ อकੑͳʹ͋ͬͨͷͰ…
Slide 23
Slide 23 text
ΞʔΩςΫνϟͷྺ࢙ MVC
Slide 24
Slide 24 text
MVC Model ϏδωεϩδοΫ View UI ϩδοΫ Controller ϓϨθϯςʔγϣϯ ϩδοΫ ※ҹσʔλϑϩʔ
Slide 25
Slide 25 text
MVC class HogeModel { private val client = Retrofit.Builder() .baseUrl("https://hoge.com/") .build() .create(HogeService::class.java) suspend fun fetch(): Hoge { return client.fetch() } suspend fun post(hoge: Hoge) { client.post(hoge) } }
Slide 26
Slide 26 text
MVC class HogeView: View() { private val model = HogeModel() override fun onAttachedToWindow() { update() } fun setHoge(hoge: Hoge) { textView.text = hoge.fuga } fun update() { findViewTreeLifecycleOwner()?.lifecycleScope ?.launchWhenStarted { val hoge = model.fetch() setHoge(hoge) } } }
Slide 27
Slide 27 text
MVC class HogeActivity: AppCompatActivity() { private val model: HogeModel = HogeModel() override fun onCreate() { binding.hogeView.setOnClickListener { lidecycleScope.launchWhenStarted { model.post(Hoge()) binding.hogeView.update() } } } }
Slide 28
Slide 28 text
MVC ▸ ͕͔ΕΔ͜ͱͰͲ͜ͰԿΛ͍ͯ͠Δ͔͔Γ͘͢ͳͬͨ ▸ Model ͕࠶ར༻Ͱ͖ΔࣄʹΑΔ࣮εϐʔυͷ্ ▸ Git ΛͬͯΔͱෳਓͰಉ࣌࡞ۀ͕Γ͘͢ͳΔ
Slide 29
Slide 29 text
MVC Activity は Controller ? View ? 🤔
Slide 30
Slide 30 text
MVC View + Controller = Activity ? 🤔
Slide 31
Slide 31 text
MVC 🤔 Controller の概念が曖昧
Slide 32
Slide 32 text
MVC 😇 FatActivity になる 実際にビルドして確認するまでロジックが正しいか わからない ʹ
Slide 33
Slide 33 text
MVC 😇 規模が大きい Android アプリに向いていないのでは
Slide 34
Slide 34 text
ΞʔΩςΫνϟͷྺ࢙ MVP
Slide 35
Slide 35 text
MVP
Slide 36
Slide 36 text
MVP
Slide 37
Slide 37 text
MVP interface HogeContract { interface View { fun show(hoge: Hoge) } interface Presenter { suspend fun fetch() suspend fun post(hoge: Hoge) } }
Slide 38
Slide 38 text
MVP class HogeActivity: AppCompatActivity(), HogeContract.View { private val presenter: HogeContract.Presneter by lazy { HogePresenter(this, HogeModel()) } override fun onCreate() { lifecycleScope.launchWhenStarted { presenter.fetch() } binding.hogeView.setOnClickListener { hoge -> lifecycleScope.launchWhenStarted { presenter.post(hoge) } } } override fun show(hoge: Hoge) { binding.hogeView.setHoge(hoge) } }
Slide 39
Slide 39 text
MVP class HogePresenter( private val view: HogeContract.View, private val model: HogeModel ): HogeContract.Presenter { override suspend fun fetch() { val hoge = model.fetch() withContext(Dispatcher.Main) { view.show(hoge) } } override suspend fun post(hoge: Hoge) { model.post(hoge) } }
Slide 40
Slide 40 text
MVP MVC ͱൺͯ ▸ σʔλͷྲྀΕ͕୯Ұํʹͳͬͨ ▸ ϓϨθϯςʔγϣϯϩδοΫΛ Activity ͔ΒΓग़͍͢͠ ▸ ςετ͕ॻ͖͍͢ ▸ ԽʹΑΓظతͳ࣮ͷεϐʔυ͕མͪΔ
Slide 41
Slide 41 text
ΞʔΩςΫνϟͷྺ࢙ MVVM
Slide 42
Slide 42 text
MVVM
Slide 43
Slide 43 text
MVVM
Slide 44
Slide 44 text
MVVM class HogeActivity: AppCompatActivity() { private val viewModel: HogeViewModel by viewModels() override fun onCreate() { viewModel.hoge.observe(this) { hoge -> binding.hogeView.setHoge(hoge) } binding.hogeView.setOnClickListener { hoge -> viewModel.post(hoge) } } }
Slide 45
Slide 45 text
MVVM class HogeViewModel( private val model: HogeModel ): ViewModel() { private val _hoge: MutableLiveData = MutableLiveData() val hoge: LiveData = _hoge fun fetch() { viewModelScope.launch { _hoge.value = model.fetch() } } fun post(hoge: Hoge) { viewModelScope.launch { model.post(hoge) } } }
Slide 46
Slide 46 text
MVVM MVP ͱൺͯ ▸ ViewModel ͔ΒͷΦϒβʔόʔύλʔϯʹͳͬͨ ▸ ެࣜ (Google) ͕ MVVM Λҙࣝͨ͠ϥΠϒϥϦΛఏڙͯ͘͠Ε͍ͯΔ ▸ ViewModelɺLiveDataɺDataBindingɺetc … ▸ ࣮ɾֶशίετͷ૿Ճ
Slide 47
Slide 47 text
Ͳ͏બఆ͢Δͷʁ
Slide 48
Slide 48 text
Ͳ͏બఆ͢Δͷʁ MVC ▸ খنΞϓϦظ։ൃ MVP ▸ ࣗ༝ʹϥΠϒϥϦ͕બɾಋೖͰ͖ͳ͍ ▸ MVVM ʹൺͯɺ࣮εϐʔυΛॏࢹ͢Δ߹ MVVM ▸ தʙେنͳΞϓϦͰ Google ͷαϙʔτ͕ड͚ΒΕΔ
Slide 49
Slide 49 text
minne Ͱ
Slide 50
Slide 50 text
minne Ͱ ▸ தنͱେنͷதؒ͘Β͍ ▸ νʔϜϝϯόʔগਫ਼Ӷ🤔 ▸ εΫϥϜ։ൃ ( 1εϓϦϯτ = 1िؒ ) ▸ ͍εύϯͰਝͳίϯςϯπσϦόϦʔ͕ٻΊΒΕΔ ▸ ͋Δఔࣗ༝ʹϥΠϒϥϦΛಋೖͰ͖Δ
Slide 51
Slide 51 text
minne Ͱ ▸ View ͷΞΫγϣϯΛϋϯυϦϯά/εςʔτΛཧ͢Δ ViewModel ▸ ΞϓϦέʔγϣϯݻ༗ͷϏδωεϩδοΫΛ୲͢Δ UseCase ▸ σʔλΞΫηεΛ୲͏ Repository MVVM + UseCase + Repository 3 Layered Architecture
Slide 52
Slide 52 text
Repository = ґଘઌ
Slide 53
Slide 53 text
Repository
Slide 54
Slide 54 text
Repository Repository って何? 🤔
Slide 55
Slide 55 text
Repository
Slide 56
Slide 56 text
Repository なぜ Repository がいるの? 🤔
Slide 57
Slide 57 text
Repository ύλʔϯΛ࠾༻͢Δత
Slide 58
Slide 58 text
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Slide 59
Slide 59 text
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλͷऔಘઌΛӅṭ͠ɺ֎ͷϨΠϠʔʹҙࣝͤ͞ͳ͍ ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Slide 60
Slide 60 text
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλͷऔಘઌΛӅṭ͠ɺ֎ͷϨΠϠʔʹҙࣝͤ͞ͳ͍ ▸ σʔλΞΫηεͷΫϥΠΞϯτϥΠϒϥϦͷґଘؔΛղܾ ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Slide 61
Slide 61 text
Repository class HogeRepository { suspend fun fetch(): MyResult = withContext(Dispatchers.IO) { try { val res = Retrofit.Builder().build().create(HogeService::class.java).fetch() when (val body = res.takeIf { it.isSuccessful }?.body()) { null -> MyResult.failure(NetworkError(res.message())) else -> MyResult.success(body) } } catch (e: Exception) { MyResult.failure(e) } } }
Slide 62
Slide 62 text
UseCase
Slide 63
Slide 63 text
UseCase UseCase って何? 🤔
Slide 64
Slide 64 text
UseCase https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Slide 65
Slide 65 text
UseCase https://github.com/android10/Android-CleanArchitecture
Slide 66
Slide 66 text
UseCase https://github.com/android10/Android-CleanArchitecture 1 2 3
Slide 67
Slide 67 text
UseCase なぜ UseCase がいるの? 🤔
Slide 68
Slide 68 text
UseCase
Slide 69
Slide 69 text
UseCase Repository σʔλΞΫηε
Slide 70
Slide 70 text
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ Repository σʔλΞΫηε
Slide 71
Slide 71 text
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ FatViewModel ͷੜ Repository σʔλΞΫηε
Slide 72
Slide 72 text
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ FatViewModel ͷੜ Repository σʔλΞΫηε UseCase
Slide 73
Slide 73 text
UseCase class HogeAndFugaUseCase( private val hogeRepository: HogeRepository, private val fugaRepository: FugaRepository ) { suspend fun fetch(): MyResult { val result = HogeAndFuga( hoge = hogeRepository.fetch().await(), fuga = fugaRepository.fetch().await() ) return MyResult.success(result) } }
Slide 74
Slide 74 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ
Slide 75
Slide 75 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ
Slide 76
Slide 76 text
Repository class HogeRepository { suspend fun fetch(): MyResult = withContext(Dispatchers.IO) { try { val res = Retrofit.Builder().build().create(HogeService::class.java).fetch() when (val body = res.takeIf { it.isSuccessful }?.body()) { null -> MyResult.failure(NetworkError(res.message())) else -> MyResult.success(body) } } catch (e: Exception) { MyResult.failure(e) } } }
Slide 77
Slide 77 text
Repository class HogeRepository { suspend fun fetch(): MyResult = withContext(Dispatchers.IO) { try { val res = ApolloClient.builder().build().query(HogeQuery.builder().build()) when (val data = res.takeUnless { it.hasErrors() }?.data()) { null -> MyResult.failure(NetworkError(res.errors().first().message())) else -> MyResult.success(data) } } catch (e: Exception) { MyResult.failure(e) } } }
Slide 78
Slide 78 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ
Slide 79
Slide 79 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ
Slide 80
Slide 80 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ ▸ ػೳՃɾमਖ਼ͷ ݟੵΓ/࣮ ͕͘͢͠ͳͬͨ
Slide 81
Slide 81 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ ▸ ػೳՃɾमਖ਼ͷ ݟੵΓ/࣮ ͕͘͢͠ͳͬͨ ▸ ߋʹࡉ͔͘ςετΛॻ͘ࣄͰ࣭ͷ୲อʹܨ͕͍ͬͯΔ
Slide 82
Slide 82 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ ࠶ར༻ੑ ▸ Ϟδϡʔϧੑ ▸ ղੳੑ ▸ मਖ਼ੑ ▸ ࢼݧੑ
Slide 83
Slide 83 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ΞϓϦέʔγϣϯͷΞδϦςΟ͕ߴ·ͬͨ
Slide 84
Slide 84 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ΞʔΩςΫςΟϯάʹΰʔϧ͋Γ·ͤΜ ‣ ΞʔΩςΫνϟύλʔϯ࠷ݶͷอकੑ୲อͯ͘͠ΕΔ ‣ ͕ɺΞϓϦέʔγϣϯʹͱͬͯඞཁेͰͳ͍ࣄ͕ଟ͍
Slide 85
Slide 85 text
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ܧଓతʹϦΞʔΩςΫςΟϯάͯ͠ ঢ՚͍͖ͯ͠·͠ΐ͏ʂ
Slide 86
Slide 86 text
LET’S ENJOY ANDROID !