Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
中〜大規模アプリの minne はどうアーキテクチャを選定したか
Search
tick-taku
August 05, 2021
Programming
1.6k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
中〜大規模アプリの minne はどうアーキテクチャを選定したか
https://pepabo.connpass.com/event/219431/
で話したスライドです。
tick-taku
August 05, 2021
More Decks by tick-taku
See All by tick-taku
昨年の振り返りと non-transitive R classes について
ticktaku77
0
50
実践 Paging 3
ticktaku77
1
6.2k
Android Jetpack Component ~Navigation~
ticktaku77
0
43
Other Decks in Programming
See All in Programming
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1k
AI時代のコードレビューは人に向けるな、仕組みに向けろ
texmeijin
5
3k
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
430
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
270
速習iPhone Duo対応
yuukiw00w
2
910
setup-vp GitLab対応の裏側
naokihaba
0
140
FreeBSDでZabbixを動かす
kenkino
0
340
Are APIs Still Relevant in the AI Era?
soyuka
0
380
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
2.5k
Turning Architecture into Unit Tests in the AI Era (NSSpain XIV)
steliosf
PRO
1
120
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
240
Featured
See All Featured
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8k
KATA
mclloyd
PRO
35
16k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
750
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
500
sira's awesome portfolio website redesign presentation
elsirapls
0
430
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.9k
Marketing to machines
jonoalderson
1
5.8k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
560
We Are The Robots
honzajavorek
0
380
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 !