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

KMM story - from first feature to Mobile mono-repo at Sphere

KMM story - from first feature to Mobile mono-repo at Sphere

We will share our thought process on the business case of a multiplatform core for mobile apps, and why it might not make sense in some cases. We will also share our experience in incremental transition in a 9-month timeframe alongside product development in parallel, and a high-level glimpse of the architecture.

AppCore, our multi-platform framework:

The AppCore project powers the domain layer of both our Android and our iOS apps, and is enabled by Kotlin Multiplatform Mobile. It involves:

- A database-first, unidirectional data flow architecture, Kotlin Coroutines and SQLDelight.
- A strong emphasis on caching, synchronisation and persistence to facilitate the continuity and fluidity of our user experience.
- Various backend integrations through Apollo GraphQL, Google Cloud Firestore and WebSocket.

Huge shout out to Anders who is our iOS engineer, who was leading our Multiplatform efforts, and was key in the preparation of this session. Unfortunately, he didn’t make it to Berlin.

Blénesi Attila

October 20, 2021
Tweet

More Decks by Blénesi Attila

Other Decks in Programming

Transcript

  1. Attila Blénesi A KMM story From the first feature to

    the mobile monorepo Anders Ha Android @ Sphere iOS @ Sphere Droidcon Berlin ! 2021
  2. The Why and The How KMM for Sphere 1 2

    High-level Architecture Overview 3 Resources and Sample project
  3. Thin client Content-driven or online-only apps Satisfactory with simple HTTP/GraphQL

    caching schemes, or no caching GQL GQL GQL GQL Native React Native Cordova / PWA …where KMM might not make sense. Thin vs Thick client
  4. Thick client Data persistence for offline availability Performant native UI

    Deep platform integrations …where KMM may give an edge. Thin vs Thick client
  5. Platform specific implementations SQLite 30% LoCs Business-critical logic that is

    identical on both platforms no matter what Use Case Repository
  6. AppCore Platform specific implementations SQLite Push as much non-UI logic

    as possible into the shared layers Invest once while halving the risk Test coverage focus gives best bang for the buck
  7. Domain Conventional Native Domain Shared Domain KMM Flutter, React Native

    and Cordova UI Cross-platform Application Framework Custom bridges Domain
  8. Application & UI framework System Network … Compiler & toolchain

    Kotlin/Native Bidirectional interoperability with Objective-C Interfaces Class (via Objective-C) Protocol Class implements implements
  9. Application & UI framework System Network … Compiler & toolchain

    Kotlin/Native Familiar single-process multithreading model
  10. Application & UI framework System Network … Compiler & toolchain

    Kotlin/Native Minimal third-party toolchain and runtime reliance* * by not going past the Event Horizon into cross- platform UI
  11. Access your architecture Modularisation
 Cornerstone for actionable incremental migration Minimise

    inter-module and inter-flow data dependencies Feed Chat Event Profile Sphere Info
  12. Start small with impact Minimally scoped to prove architecture, build

    and workflow integration plans Demonstrating tangible improvement is a bonus point for buy-in
  13. 3-track AppCore migration Big systems Product features Side quests Architecture

    & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Mono- repo
  14. Small but impactful Big systems Product features Side quests Architecture

    & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Mono- repo Buy-in from all stakeholders
  15. Drop-as-we-go for big systems Big systems Product features Side quests

    Architecture & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Mono- repo
  16. Drop-as-we-go for big systems Big systems Product features Side quests

    Architecture & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Mono- repo Strive to drop the old stack. Make transitional APIs if needed be. % %
  17. Parallel development Big systems Product features Side quests Architecture &

    Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Mono- repo
  18. Parallel development Big systems Product features Side quests Architecture &

    Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Delivered on the (old) production stack Migrate Sphere Info Migrate Edit Profile Mono- repo Catch up with new features/requirements
 before changeover
  19. New isolated features Big systems Product features Side quests Architecture

    & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Started on KMM with no migration blocker Mono- repo
  20. Incremental migration deliverables Big systems Product features Side quests Architecture

    & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Migrate small isolated flows/modules in feature work stream off-time Mono- repo
  21. Incremental migration deliverables Big systems Product features Side quests Architecture

    & Prototype Realtime Feed Realtime Chat May 2021 Nov 2020 July 2020 DM discovery Events Message Reactions Video Sharing GIFs … Migrate Sphere Info Migrate Edit Profile Migrate small isolated flows/modules in feature work stream off-time Mono- repo Process improvements
  22. Convergence on architecture, practices and processes Immutability
 Functional reactive programming


    Unidirectional data flow
 Mobile monorepo Popular in iOS, esp. among SwiftUI projects Works well even under the strict K/N memory model
  23. Prioritise reducing friction for iOS Design app-facing APIs around ObjC

    export limitations
 Fast build-test-run cycles * especially during early stages of bring-up
  24. High level architecture overview The result of a common langue

    in architecture and patterns AppCore Chat UX Home UX Declarative UI and Design Library iOS: SwiftUI and Texture/ASDK Android: Groupie and Jetpack Compose Managing UI state as unidirectional data flows iOS: inspired by Swift Composable Architecture (TCA) Android: Internal Model View Intent (MVI) Both platforms integrate KMM AppCore directly … UI technologies and Design Library
  25. AppCore GraphQL actors Firestore actors Local domain Both mobile apps

    share the Use Cases Data from backend systems are adapted to the local domain. Local persistent domain is internal to AppCore 
 
 GraphQL 
 
 Cloud Firestore Use Cases (API)
  26. AppCore Use Case Operations Entities Store Actor AppCore Multiplatform Networking

    (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* CQRS architecture • Operation (Command) queue for internal comms • Listenable Queries Flexibility • Dependency inversion at both Use Case and Store Actor layer. • Both model definition and logic • Local domain evolution with minimal risk of app source breaking. • Swappable actors for backend system and/or schema migrations. Use Case Operations Entities Store Actor AppCore Multiplatform Networking (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* uses enqueues updates processes reads uses uses AppCore
  27. Use Case API layer Minimal & satisfying the expected UI

    & UX interface EventInfoUseCase { fun summary( eventId : String, ): DataSource<Summary> fun rsvp(id: String, action: SphereEventRSVP) } data class Summary( val eventName: String, val imageId: String?, val description: String?, val locationName: String?, val startDate: Instant, ...
  28. Use Case API layer Minimal & satisfying the expected UI

    & UX val dataSource = eventInfoUseCase.summary(eventId) dataSource.state.map { state -> when (state) { is Failed -> ... is Loading -> ... is Value -> ... } } … dataSource.refreshNow()
  29. AppCore Use Case Operations Entities Store Actor AppCore Multiplatform Networking

    (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* CQRS architecture • Listenable queries • Operation (command) queue for internal comms Flexibility • Dependency inversion at both Use Case and Store Actor layer. • Both model definition and logic • Local domain evolution with minimal risk of app source breaking. • Swappable actors for backend system and/or schema migrations. uses updates processes uses uses
  30. Use Case API layer Minimal & satisfying the expected UI

    & UX class …UseCaseImpl( private val database: SqlDatabaseGateway, private val operationUtils: OperationUtils ): …UseCase { override fun summary(eventId: String): DataSource<Summary> { return DataSource( operationUtils = operationUtils, operation = FetchEventOperation, input = FetchEventOperation.Input(eventId), query = database.eventQueries.getById(eventId), mapper = { eventToSummary(it) } ) } }
  31. AppCore Use Case Operations Entities Store Actor AppCore Multiplatform Networking

    (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* CQRS architecture • Listenable queries • Operation (command) queue for internal comms Flexibility • Dependency inversion at both Use Case and Store Actor layer. • Both model definition and logic • Local domain evolution with minimal risk of app source breaking. • Swappable actors for backend system and/or schema migrations. uses enqueues updates reads uses processes
  32. Store Actor layer Executing a specific domain action or behaviour

    class …OperationActor( private val httpClient: AgentHTTPClient, private val database: SqlDatabaseGateway, ){ override val definition = FetchEventOperation override suspend fun perform(input : Operation.Input) { val event = httpClient.requestGraphQL( GetSphereEventQuery(eventId = identity.eventId) ).toEvent() database.sphereEventQueries.upsert(event) } }
  33. AppCore Use Case Operations Entities Store Actor AppCore Multiplatform Networking

    (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* CQRS architecture • Listenable queries • Operation (command) queue for internal comms Flexibility • Dependency inversion at both Use Case and Store Actor layer. • Both model definition and logic • Local domain evolution with minimal risk of app source breaking. • Swappable actors for backend system and/or schema migrations. uses enqueues updates processes reads uses uses
  34. Use Case API layer Optimistic behaviour on user interactions interface

    EventInfoUseCase { ... fun rsvp(eventId: String, rsvp: RSVP) }
  35. Use Case API layer Optimistic behaviour on user interactions class

    EventInfoUseCaseImpl( private val operationUtils: OperationUtils ): EventInfoUseCase { ... override fun rsvp(eventId: String, rsvp: RSVP) = operationUtils.enqueue( operation = SphereEventRSVPOperation, input = Input(eventId, rsvp) ) }
  36. Store Actor layer Executing a specific domain action or behaviour

    ... override val definition = SphereEventRSVPOperation override suspend fun perform(input : Operation.Input) = database.optimisticAction { optimisticallyApply { ... } runAsync { val mutation = SphereEventRSVPActionMutation( sphereEventId = input.eventId, rsvp = input.rsvp ) httpClient.requestGraphQL(mutation).rsvpSphereEvent } onSuccess { sphereEvent -> ... } onFailure { ... } }
  37. AppCore Use Case Operations Entities Store Actor AppCore Multiplatform Networking

    (HTTPS and WebSocket) Firestore Gateway Use Case (API) layer appcore-api Domain layer appcore-sqlcore Store Actor layer appcore-actors-* Host SPI layer appcore-spi-* CQRS architecture • Listenable queries • Operation (command) queue for internal comms Flexibility • Dependency inversion at both Use Case and Store Actor layer. • Both model definition and logic • Local domain evolution with minimal risk of app source breaking. • Swappable actors for backend system and/or schema migrations. uses enqueues updates processes reads uses uses
  38. Resources and Sample An example project illustrating what was showcased

    github.com/heysphere/appcore-sample • Notification from Github paginated • Optimistic action to mark a notification as read • Uses Swift UI & Jetpack Compose Attila Blénesi @ablenessy Anders Ha @_andersha