Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
中〜大規模アプリの minne はどうアーキテクチャを選定したか
Search
tick-taku
August 05, 2021
Programming
0
1.5k
中〜大規模アプリの minne はどうアーキテクチャを選定したか
https://pepabo.connpass.com/event/219431/
で話したスライドです。
tick-taku
August 05, 2021
Tweet
Share
More Decks by tick-taku
See All by tick-taku
マイナンバーカード活用術
ticktaku77
0
290
昨年の振り返りと non-transitive R classes について
ticktaku77
0
33
実践 Paging 3
ticktaku77
1
6k
Android Jetpack Component ~Navigation~
ticktaku77
0
35
Other Decks in Programming
See All in Programming
Module Harmony
petamoriken
2
580
ソフトウェア設計の課題・原則・実践技法
masuda220
PRO
24
19k
Duke on CRaC with Jakarta EE
ivargrimstad
0
310
Building AI with AI
inesmontani
PRO
1
390
AWS CDKの推しポイントN選
akihisaikeda
1
210
これだけで丸わかり!LangChain v1.0 アップデートまとめ
os1ma
4
360
All(?) About Point Sets
hole
0
230
JJUG CCC 2025 Fall: Virtual Thread Deep Dive
ternbusty
3
500
dnx で実行できるコマンド、作ってみました
tomohisa
0
110
モダンJSフレームワークのビルドプロセス 〜なぜReactは503行、Svelteは12行なのか〜
fuuki12
0
140
Reactive Thinking with Signals and the new Resource API
manfredsteyer
PRO
0
130
競馬で学ぶ機械学習の基本と実践 / Machine Learning with Horse Racing
shoheimitani
14
14k
Featured
See All Featured
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.1k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
It's Worth the Effort
3n
187
29k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.7k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
359
30k
Fireside Chat
paigeccino
41
3.7k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
RailsConf 2023
tenderlove
30
1.3k
BBQ
matthewcrist
89
9.9k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
jQuery: Nuts, Bolts and Bling
dougneiner
65
8k
Transcript
どうアーキテクチャを 選定したか 中〜大規模アプリの minne は GMO ϖύϘ ҏ౻ ւ (tick-taku)
WHO ARE YOU? 伊藤 拓海 tick-taku ▸ ฌݿݝ໌ੴࢢग़ ▸ 2020/12
GMO ϖύϘ த్ೖࣾ ▸ Android ΞϓϦΤϯδχΞ (4) ▸ લ৬ SIer Ͱ IoT ܥͷΞϓϦΛ։ൃ ▸ ͖ͳݴޠ: Kotlin, Python ▸ झຯ : ࣗసं, ఱମ؍ଌ, ྉཧ, ΨδΣοτूΊ ▸ Twitter: @tick_taku77
minne
Android meetup #android_meetup Twitter でハッシュタグをつけてコメントして イベントを盛り上がりましょう!!
ΞδΣϯμ ▸ ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ minne ʹ͓͚ΔΞʔΩςΫνϟͷྺ࢙ ▸ MVC / MVP
/ MVVM ▸ Ͳ͏ΞʔΩςΫνϟΛબఆ͢Δ͔ ▸ minne ͰͲ͏બఆ͔ͨ͠ ▸ Repository / UseCase
લఏ ▸ ࠓճͷ GUI ΞϓϦέʔγϣϯͷΞʔΩςΫνϟʹ͍ͭͯͰ͢ ▸ MVC MVVM ͳͲΛɺΞʔΩςΫνϟύλʔϯͱݺͼ·͢
▸ σβΠϯύλʔϯΦϒβʔόʔύλʔϯͱ͔ͬͱਂ͍ͱ͜ΖΛࢦ͢ͷͰ ▸ αϯϓϧίʔυงғؾΛ͔ͭΜͰΒ͏ͨΊͳͷͰॾʑ͠ΐͬͨΓͯ͠Δͱ͜Ζ͕͋Γ·͢… ▸ αϯϓϧίʔυͰग़ͯ͘ΔॲཧҎԼͷϥΠϒϥϦΛ༻͍ͯ͠·͢ ▸ REST API ΫϥΠΞϯτ : Retrofit2 ▸ ඇಉظॲཧ : Kotlin Coroutine ▸ Jetpack : LiveData, ViewModel
ఆࢹௌऀ Android 初級者 ~ 中級者 向け Android ΞϓϦ࢝ΊͯΈͨ ͬͱεοΩϦίʔυ͕ॻ͖͍ͨ ΞʔΩςΫνϟͲ͏͍͔ͯ͘͠໎͏
ͳͲͳͲ
どうアーキテクチャを 選定したか 中〜大規模アプリの minne は
ͳͥΞʔΩςΫνϟ Λ࠾༻͢Δͷ͔
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ ▸ ػೳमਖ਼ʹ͔͔Δίετ͕Լ͕Δ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ νʔϜ։ൃ࣌ͷ࣮ͷόϥπΩΛ͗ɺϦϢʔβϏϦςΟΛߴΊΔ ▸ ֤ϨΠϠΛͦͷ··Ϟδϡʔϧʹམͱ͠ࠐΊΔ ▸ εϜʔζʹΞϓϦέʔγϣϯͷѲɾղੳ͕Ͱ͖Δ ▸ ػೳमਖ਼ʹ͔͔Δίετ͕Լ͕Δ ▸
ςελϏϦςΟ্͕͢Δ ͳͲ…
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ ࠶ར༻ੑ ▸ Ϟδϡʔϧੑ ▸ ղੳੑ ▸ मਖ਼ੑ ▸
ࢼݧੑ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ อकੑΛߴΊΔ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ΞϓϦέʔγϣϯͷΞδϦςΟΛߴΊΔ
ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ Ϣʔβ ΑΓߴ࣭ͳαʔϏεΛ ਝʹఏڙ͢ΔͨΊ
minne ʹ͓͚Δ ΞʔΩςΫνϟͷྺ࢙
ΞʔΩςΫνϟͷྺ࢙ ࠞಱͷ࣌
ࠞಱͷ࣌ 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 Ͱશ෦Δ อकੑͳʹ͋ͬͨͷͰ…
ΞʔΩςΫνϟͷྺ࢙ MVC
MVC Model ϏδωεϩδοΫ View UI ϩδοΫ Controller ϓϨθϯςʔγϣϯ ϩδοΫ ※ҹσʔλϑϩʔ
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) } }
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) } } }
MVC class HogeActivity: AppCompatActivity() { private val model: HogeModel =
HogeModel() override fun onCreate() { binding.hogeView.setOnClickListener { lidecycleScope.launchWhenStarted { model.post(Hoge()) binding.hogeView.update() } } } }
MVC ▸ ͕͔ΕΔ͜ͱͰͲ͜ͰԿΛ͍ͯ͠Δ͔͔Γ͘͢ͳͬͨ ▸ Model ͕࠶ར༻Ͱ͖ΔࣄʹΑΔ࣮εϐʔυͷ্ ▸ Git ΛͬͯΔͱෳਓͰಉ࣌࡞ۀ͕Γ͘͢ͳΔ
MVC Activity は Controller ? View ? 🤔
MVC View + Controller = Activity ? 🤔
MVC 🤔 Controller の概念が曖昧
MVC 😇 FatActivity になる 実際にビルドして確認するまでロジックが正しいか わからない ʹ
MVC 😇 規模が大きい Android アプリに向いていないのでは
ΞʔΩςΫνϟͷྺ࢙ MVP
MVP
MVP
MVP interface HogeContract { interface View { fun show(hoge: Hoge)
} interface Presenter { suspend fun fetch() suspend fun post(hoge: Hoge) } }
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) } }
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) } }
MVP MVC ͱൺͯ ▸ σʔλͷྲྀΕ͕୯Ұํʹͳͬͨ ▸ ϓϨθϯςʔγϣϯϩδοΫΛ Activity ͔ΒΓग़͍͢͠ ▸
ςετ͕ॻ͖͍͢ ▸ ԽʹΑΓظతͳ࣮ͷεϐʔυ͕མͪΔ
ΞʔΩςΫνϟͷྺ࢙ MVVM
MVVM
MVVM
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) } } }
MVVM class HogeViewModel( private val model: HogeModel ): ViewModel() {
private val _hoge: MutableLiveData<Hoge> = MutableLiveData() val hoge: LiveData<Hoge> = _hoge fun fetch() { viewModelScope.launch { _hoge.value = model.fetch() } } fun post(hoge: Hoge) { viewModelScope.launch { model.post(hoge) } } }
MVVM MVP ͱൺͯ ▸ ViewModel ͔ΒͷΦϒβʔόʔύλʔϯʹͳͬͨ ▸ ެࣜ (Google) ͕
MVVM Λҙࣝͨ͠ϥΠϒϥϦΛఏڙͯ͘͠Ε͍ͯΔ ▸ ViewModelɺLiveDataɺDataBindingɺetc … ▸ ࣮ɾֶशίετͷ૿Ճ
Ͳ͏બఆ͢Δͷʁ
Ͳ͏બఆ͢Δͷʁ MVC ▸ খنΞϓϦظ։ൃ MVP ▸ ࣗ༝ʹϥΠϒϥϦ͕બɾಋೖͰ͖ͳ͍ ▸ MVVM ʹൺͯɺ࣮εϐʔυΛॏࢹ͢Δ߹
MVVM ▸ தʙେنͳΞϓϦͰ Google ͷαϙʔτ͕ड͚ΒΕΔ
minne Ͱ
minne Ͱ ▸ தنͱେنͷதؒ͘Β͍ ▸ νʔϜϝϯόʔগਫ਼Ӷ🤔 ▸ εΫϥϜ։ൃ ( 1εϓϦϯτ
= 1िؒ ) ▸ ͍εύϯͰਝͳίϯςϯπσϦόϦʔ͕ٻΊΒΕΔ ▸ ͋Δఔࣗ༝ʹϥΠϒϥϦΛಋೖͰ͖Δ
minne Ͱ ▸ View ͷΞΫγϣϯΛϋϯυϦϯά/εςʔτΛཧ͢Δ ViewModel ▸ ΞϓϦέʔγϣϯݻ༗ͷϏδωεϩδοΫΛ୲͢Δ UseCase ▸
σʔλΞΫηεΛ୲͏ Repository MVVM + UseCase + Repository 3 Layered Architecture
Repository = ґଘઌ
Repository
Repository Repository って何? 🤔
Repository
Repository なぜ Repository がいるの? 🤔
Repository ύλʔϯΛ࠾༻͢Δత
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλͷऔಘઌΛӅṭ͠ɺ֎ͷϨΠϠʔʹҙࣝͤ͞ͳ͍ ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Repository ύλʔϯΛ࠾༻͢Δత ▸ σʔλͷऔಘઌΛӅṭ͠ɺ֎ͷϨΠϠʔʹҙࣝͤ͞ͳ͍ ▸ σʔλΞΫηεͷΫϥΠΞϯτϥΠϒϥϦͷґଘؔΛղܾ ▸ σʔλΞΫηεΛ࠶ར༻͢Δ
Repository class HogeRepository { suspend fun fetch(): MyResult<Hoge> = 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) } } }
UseCase
UseCase UseCase って何? 🤔
UseCase https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
UseCase https://github.com/android10/Android-CleanArchitecture
UseCase https://github.com/android10/Android-CleanArchitecture 1 2 3
UseCase なぜ UseCase がいるの? 🤔
UseCase
UseCase Repository σʔλΞΫηε
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ Repository σʔλΞΫηε
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ FatViewModel ͷੜ Repository σʔλΞΫηε
UseCase ݁ՌͦΕҎ֎ͷϏδωεϩδοΫ͕ ViewModel ʹೖΓࠐΉ FatViewModel ͷੜ Repository σʔλΞΫηε UseCase
UseCase class HogeAndFugaUseCase( private val hogeRepository: HogeRepository, private val fugaRepository:
FugaRepository ) { suspend fun fetch(): MyResult<HogeAndFuga> { val result = HogeAndFuga( hoge = hogeRepository.fetch().await(), fuga = fugaRepository.fetch().await() ) return MyResult.success(result) } }
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ
Repository class HogeRepository { suspend fun fetch(): MyResult<Hoge> = 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) } } }
Repository class HogeRepository { suspend fun fetch(): MyResult<Hoge> = 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) } } }
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ ▸ ػೳՃɾमਖ਼ͷ
ݟੵΓ/࣮ ͕͘͢͠ͳͬͨ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ σʔλΞΫηε͕࠶ར༻Ͱ͖ͨΓมߋʹڧ͘ͳͬͨ ▸ ΑΓࡉཻ͔͍ͰϞδϡʔϧ͕࡞Εͨ ▸ ViewModel ͷݟ௨͕͠ྑ͘ͳͬͨ ▸ ػೳՃɾमਖ਼ͷ
ݟੵΓ/࣮ ͕͘͢͠ͳͬͨ ▸ ߋʹࡉ͔͘ςετΛॻ͘ࣄͰ࣭ͷ୲อʹܨ͕͍ͬͯΔ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ▸ ࠶ར༻ੑ ▸ Ϟδϡʔϧੑ ▸ ղੳੑ ▸ मਖ਼ੑ ▸
ࢼݧੑ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ΞϓϦέʔγϣϯͷΞδϦςΟ͕ߴ·ͬͨ
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ΞʔΩςΫςΟϯάʹΰʔϧ͋Γ·ͤΜ ‣ ΞʔΩςΫνϟύλʔϯ࠷ݶͷอकੑ୲อͯ͘͠ΕΔ ‣ ͕ɺΞϓϦέʔγϣϯʹͱͬͯඞཁेͰͳ͍ࣄ͕ଟ͍
࠾༻ͯ͠ΈͯಘΒΕͨϝϦοτ ܧଓతʹϦΞʔΩςΫςΟϯάͯ͠ ঢ՚͍͖ͯ͠·͠ΐ͏ʂ
LET’S ENJOY ANDROID !