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
52
実践 Paging 3
ticktaku77
1
6.2k
Android Jetpack Component ~Navigation~
ticktaku77
0
44
Other Decks in Programming
See All in Programming
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
5
5.1k
まずはコードより業務と向き合う:テーブル解体のためにPdMを呼んだ話
sakawe_ee
0
170
AI時代のIaC入門 〜なぜインフラをコードで管理したいのか?〜 / Introduction to IaC in the AI Era
akihisaikeda
6
1.4k
複数ドメインを一つのアプリに統合するFlutterの設計と運用_FlutterKaigi2026登壇資料(須永高弘)
takahirosunaga
0
160
AIが実装を担う時代の「技術設計」と「チームマネジメント」
kamina_zzz
2
200
flutter_test のその先へ — Flutter アプリの LLM 機能に「評価」という品質保証を
kosukesaigusa
0
200
SODA - FACT BOOK(EN)
sodainc
1
210
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
1.2k
Modernizing iOS Apps
seyfoyun
0
150
Web Framework Benchmark Battle 2026, Any%
a_matsuda
3
3.1k
Railsバージョンを4倍にした話
optim
0
940
そのデザイントークン、Flutterでも同じ見た目になりますか?
nakamizu
0
310
Featured
See All Featured
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
470
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
290
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.5k
Mobile First: as difficult as doing things right
swwweet
225
10k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.4k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
900
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
970
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Building Flexible Design Systems
yeseniaperezcruz
330
41k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
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 !