Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
昨年の振り返りと non-transitive R classes について
ticktaku77
0
28
実践 Paging 3
ticktaku77
1
5.8k
Android Jetpack Component ~Navigation~
ticktaku77
0
34
Other Decks in Programming
See All in Programming
20250704_教育事業におけるアジャイルなデータ基盤構築
hanon52_
5
790
NPOでのDevinの活用
codeforeveryone
0
840
Flutterで備える!Accessibility Nutrition Labels完全ガイド
yuukiw00w
0
160
PicoRuby on Rails
makicamel
2
130
明示と暗黙 ー PHPとGoの インターフェイスの違いを知る
shimabox
2
510
#QiitaBash MCPのセキュリティ
ryosukedtomita
1
1.3k
ISUCON研修おかわり会 講義スライド
arfes0e2b3c
1
450
なんとなくわかった気になるブロックテーマ入門/contents.nagoya 2025 6.28
chiilog
1
270
なぜ「共通化」を考え、失敗を繰り返すのか
rinchoku
1
650
テストから始めるAgentic Coding 〜Claude Codeと共に行うTDD〜 / Agentic Coding starts with testing
rkaga
12
4.5k
The Modern View Layer Rails Deserves: A Vision For 2025 And Beyond @ RailsConf 2025, Philadelphia, PA
marcoroth
1
170
Railsアプリケーションと パフォーマンスチューニング ー 秒間5万リクエストの モバイルオーダーシステムを支える事例 ー Rubyセミナー 大阪
falcon8823
5
1.1k
Featured
See All Featured
A Tale of Four Properties
chriscoyier
160
23k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
130
19k
The Pragmatic Product Professional
lauravandoore
35
6.7k
Designing for Performance
lara
610
69k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Navigating Team Friction
lara
187
15k
How to Think Like a Performance Engineer
csswizardry
25
1.7k
Why You Should Never Use an ORM
jnunemaker
PRO
58
9.4k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
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 !