Upgrade to Pro — share decks privately, control downloads, hide ads and more …

中〜大規模アプリの minne はどうアーキテクチャを選定したか

tick-taku
August 05, 2021

中〜大規模アプリの minne はどうアーキテクチャを選定したか

https://pepabo.connpass.com/event/219431/

で話したスライドです。

tick-taku

August 05, 2021
Tweet

More Decks by tick-taku

Other Decks in Programming

Transcript

  1. WHO ARE YOU? 伊藤 拓海 tick-taku ▸ ฌݿݝ໌ੴࢢग़਎ ▸ 2020/12

    GMO ϖύϘ த్ೖࣾ ▸ Android ΞϓϦΤϯδχΞ (໿4೥) ▸ લ৬͸ SIer Ͱ IoT ܥͷΞϓϦΛ։ൃ ▸ ޷͖ͳݴޠ: Kotlin, Python ▸ झຯ : ࣗసं, ఱମ؍ଌ, ྉཧ, ΨδΣοτूΊ ▸ Twitter: @tick_taku77
  2. ΞδΣϯμ ▸ ͳͥΞʔΩςΫνϟΛ࠾༻͢Δͷ͔ ▸ minne ʹ͓͚ΔΞʔΩςΫνϟͷྺ࢙ ▸ MVC / MVP

    / MVVM ▸ Ͳ͏ΞʔΩςΫνϟΛબఆ͢Δ͔ ▸ minne Ͱ͸Ͳ͏બఆ͔ͨ͠ ▸ Repository / UseCase
  3. લఏ ▸ ࠓճͷ࿩͸ GUI ΞϓϦέʔγϣϯͷΞʔΩςΫνϟʹ͍ͭͯͰ͢ ▸ MVC ΍ MVVM ͳͲΛɺΞʔΩςΫνϟύλʔϯͱݺͼ·͢

    ▸ σβΠϯύλʔϯ͸Φϒβʔόʔύλʔϯͱ͔΋ͬͱਂ͍ͱ͜ΖΛࢦ͢ͷͰ ▸ αϯϓϧίʔυ͸งғؾΛ͔ͭΜͰ΋Β͏ͨΊͳͷͰॾʑ͸͠ΐͬͨΓͯ͠Δͱ͜Ζ͕͋Γ·͢… ▸ αϯϓϧίʔυ಺Ͱग़ͯ͘Δॲཧ͸ҎԼͷϥΠϒϥϦΛ࢖༻͍ͯ͠·͢ ▸ REST API ΫϥΠΞϯτ : Retrofit2 ▸ ඇಉظॲཧ : Kotlin Coroutine ▸ Jetpack : LiveData, ViewModel
  4. ࠞಱͷ࣌୅ 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 ಺Ͱશ෦΍Δ อकੑ΋ͳʹ΋͋ͬͨ΋ͷͰ͸…
  5. 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) } }
  6. 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) } } }
  7. MVC class HogeActivity: AppCompatActivity() { private val model: HogeModel =

    HogeModel() override fun onCreate() { binding.hogeView.setOnClickListener { lidecycleScope.launchWhenStarted { model.post(Hoge()) binding.hogeView.update() } } } }
  8. MVP

  9. MVP

  10. MVP interface HogeContract { interface View { fun show(hoge: Hoge)

    } interface Presenter { suspend fun fetch() suspend fun post(hoge: Hoge) } }
  11. 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) } }
  12. 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) } }
  13. 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) } } }
  14. 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) } } }
  15. MVVM MVP ͱൺ΂ͯ ▸ ViewModel ͔ΒͷΦϒβʔόʔύλʔϯʹͳͬͨ ▸ ެࣜ (Google) ͕

    MVVM Λҙࣝͨ͠ϥΠϒϥϦΛఏڙͯ͘͠Ε͍ͯΔ ▸ ViewModelɺLiveDataɺDataBindingɺetc … ▸ ࣮૷ɾֶशίετͷ૿Ճ
  16. minne Ͱ͸ ▸ தن໛ͱେن໛ͷதؒ͘Β͍ ▸ νʔϜϝϯόʔ͸গ਺ਫ਼Ӷ🤔 ▸ εΫϥϜ։ൃ ( 1εϓϦϯτ

    = 1िؒ ) ▸ ୹͍εύϯͰਝ଎ͳίϯςϯπσϦόϦʔ͕ٻΊΒΕΔ ▸ ͋Δఔ౓ࣗ༝ʹϥΠϒϥϦΛಋೖͰ͖Δ
  17. 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) } } }
  18. 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) } }
  19. 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) } } }
  20. 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) } } }