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 !