Plan — Architecture patterns and tradeoffs documentation
Draft execution plan to author a dedicated rationale and trade-offs doc for multi-module Gradle, Clean Architecture, and MVI as used in Oter; resume from the checklist at the bottom.
⚠️ Superseded (2026-06): the Android MVI / per-feature Clean-Architecture modules described below (
*_domain/*_data/*_presentation,core-uiBaseViewModel, Hilt) have been removed by the mobile consolidation onto a single shared UI on Koin. All feature code now lives inshared/using a service-oriented MVVM pattern (StateFlow + Ktor services). This doc’s Android-specific patterns are historical reference only. See tech-debt/migrations.md for the current state; see Global architecture for the post-consolidation architecture.
Overview
Oter already describes module layout and data flow in Global architecture|Global architecture. This plan captures ground truth in the codebase (including where patterns differ, for example Android MVI vs KMP shared), why those choices are reasonable, improvements and alternatives, and concrete next steps to produce docs/architecture/PATTERNS_AND_TRADEOFFS.md and wire it into the index. Official Android references can be added later via Google Developer Knowledge MCP once API credentials are valid.
Architecture (documentation delivery)
High-level steps to complete the documentation work:
What the codebase does today (ground truth)
Multi-module Gradle
- Root
settings.gradle.ktstoday defines onlycomposeApp,server,shared, and (whenINCLUDE_ANDROIDis true)androidApp+core. Historically it also definedcore*,navigation, and each feature as a composite with*_domain,*_data,*_presentation; those were removed during the consolidation (feature code now lives inshared/). Android modules can be excluded for faster KMP-only builds. - This matches the execution-oriented description in
.cursor/skills/architecture-patterns/SKILL.md.
Clean Architecture (Android features)
- Per-feature split: domain (repository contracts, use cases), data (implementations, Hilt modules), presentation (Compose +
@HiltViewModel). - Example (historical, module removed):
tasks/tasks_presentation/.../TaskViewModel.ktinjectedTaskUseCasesfrom domain rather than talking to Retrofit/Room directly.
MVI (Android presentation)
core-ui/.../BaseViewModel.kt(historical, module removed) was explicitly documented as MVI:UserIntent→handleIntent,ViewStateviaMutableStateFlow+emitState, one-shotEffectviaChannel(navigation/snackbar patterns).- Feature VMs extend this (e.g.
TaskViewModelwithTaskIntent/TaskState/TaskEffect).
Important nuance: not one pattern everywhere
- KMP
sharedis described in the architecture skill as service-oriented MVVM:StateFlow+ direct calls into Ktor client services, without theBaseViewModelintent layer (seeshared/.../TasksViewModel.kt). - Ktor
serveruses its own clean layering: routes → services → repositories → Exposed entities (already summarized in Global architecture|Global architecture).
So the honest mental model is: multi-module + Clean + MVI on Android feature modules; shared client logic is thinner; server is layered backend. Any doc should state that explicitly to avoid the illusion of strict MVI everywhere.
Why these choices (content to write)
Document motivations in prose, tied to this repo:
| Choice | Typical rationale (to phrase for Oter) |
|---|---|
| Multi-module | Faster incremental builds, enforced module boundaries, optional Android-off CI, clearer ownership per feature. |
| Clean (Android) | Testable domain, swappable data sources (remote/local), keeps UI from owning business rules. |
MVI (BaseViewModel) | Single source of truth for UI state, explicit user actions (Intent), side effects isolated in Effect / snackbar channel. |
Lighter shared | Less ceremony for cross-platform HTTP clients; avoids duplicating three Gradle modules per feature on every target. |
| Server layering | Mirrors common backend style; keeps HTTP/auth thin vs persistence. |
Improvements and alternatives (content to write)
Balanced section for the same doc:
- Dual presentation patterns: Either (a) accept MVVM-in-
sharedas intentional and document boundaries, or (b) gradually introduce a small shared MVI primitive (intent + reducer) where complexity justifies it—trade-off is boilerplate and KMP test ergonomics. - ViewModel scope: Some presentation VMs pull many collaborators (e.g. cross-feature dashboard logic in task-related flows)—consider extracting coordinators or domain services when files grow hard to reason about (there is precedent in finance docs under
docs/finance/). - Alternatives to hand-rolled MVI: Libraries such as Orbit MVI (or similar) reduce boilerplate; MVVM-only on Android would drop
Intentindirection at the cost of less explicit action modeling; vertical-slice modules (single module per feature) simplify Gradle at the cost of weaker compile-time layer enforcement. - Multi-module alternatives: Fewer, larger modules for small teams; or stricter API modules (
:apijars) between features if cyclic dependency pressure appears.
Where to put documentation
Global architecture|Global architecture already describes structure and diagrams. To avoid a huge overview file and to satisfy .cursor/skills/docs-management/SKILL.md (frontmatter, index updates, templates):
- Add a new focused doc, e.g.
docs/architecture/PATTERNS_AND_TRADEOFFS.md, with YAML frontmatter (type: guideoroverviewper.cursor/skills/docs-management/templates.md), sections: Overview → Multi-module rationale → Clean (Android) → MVI (BaseViewModel) → Shared/server deviations → Improvements/alternatives → References. - Update Documentation index|Documentation index with a wiki or markdown link to the new file.
- Add a short pointer in Global Architecture (one paragraph + link) so readers discover the rationale doc without duplicating full content.
- Optional: Add one sentence + link in
.cursor/skills/architecture-patterns/SKILL.mdunder Context/Knowledge pointing to the new doc for “why / trade-offs” (skill stays execution-focused per skill-governance).
Google Developer Knowledge MCP
- A live
search_documentscall previously failed with “API key not valid” — fix the key in.cursor/mcp.json(or the provider’s secret); that path may stay untracked—configure secrets outside git as appropriate. - After the key works, run targeted queries (e.g. “Guide to app architecture”, “modularization”, “UI layer recommendations”) and add links + short quotes to an official “References” subsection.
Out of scope for this pass
- Refactoring code to unify MVI across
shared(only document unless scope expands later). - Large edits to every feature README—only link from global docs unless per-feature callouts are desired later.
Remaining work (checklist)
Post-consolidation note: The Android MVI patterns below are now historical. A new
PATTERNS_AND_TRADEOFFS.mdshould document the current shared-MVVM architecture instead. Recommended sections: shared ViewModel patterns, Koin module structure, when to split VMs (precedent: Finance split into 5 focused VMs), and state management conventions.
- Decide whether to still author
docs/architecture/PATTERNS_AND_TRADEOFFS.mdfor the current (shared-MVVM) architecture, or let this plan file serve as the record. - If authoring: describe shared-MVVM ground truth, rationale (simpler KMP testing, fewer modules), deviations from classic MVI, and when to reintroduce intent/reducer patterns as complexity grows.
- Update
docs/00 – Index.mdlink from this plan file to any new authoritative doc (if created). - Options: add a pointer in
.cursor/skills/architecture-patterns/SKILL.mdto the current architecture.