最近在看 Grimo 專案時,我一直在思考一個問題。
shared 模組到底該放什麼?
看了一下 build.gradle.kts,shared 裡面有 compose 相關的依賴:
// shared/build.gradle.kts
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
// ...
}
但實際的目錄結構裡,presentation 層都在 desktopApp:
shared/src/commonMain/
├── core/ # 核心工具
├── data/ # 資料層
├── di/ # 依賴注入
└── domain/ # 業務邏輯
desktopApp/src/jvmMain/
├── presentation/ # 所有 UI 都在這
├── event/ # Desktop 特定功能
└── Main.kt
這是對的嗎?我需要研究一下。
「Claude Code,幫我研究一下 KMP 專案的 shared 和平台模組該如何劃分。」
Claude Code 開始分析:
「讓我看看你的專案結構...」它掃描了整個專案。
「你現在的架構其實是正確的,」Claude Code 解釋,「shared 只包含業務邏輯,UI 都在 desktopApp。但 shared 的 build.gradle.kts 有 compose 依賴,這可能是歷史遺留或未來考量。」
「幫我看看其他 KMP 專案是怎麼做的。」
Claude Code 找了幾個參考:
「看起來有兩種流派,」我總結,「一種是 UI 完全分離,一種是用 Compose Multiplatform 共享 UI。」
我讓 Claude Code 檢查 shared 模組裡到底有沒有 UI:
find shared/src -name "*.kt" | xargs grep -l "@Composable" | head -5
結果是空的。shared 裡確實沒有任何 @Composable。
那為什麼 build.gradle.kts 有 compose 依賴?
「可能的原因有哪些?」我問 Claude Code。
Claude Code 分析:
我檢查了一下 Decompose 的依賴:
// shared 有用 Decompose
implementation(libs.decompose)
implementation(libs.decompose.extensions.compose) // 這個需要 compose
原來如此!Decompose 的 compose 擴展需要 compose 依賴。
我用 Day 11 提到的 ARS(架構研究專家)思維來分析。
「Claude Code,從架構角度分析,shared 裡的 compose 依賴該不該移除?」
Claude Code 給出了詳細分析:
保留的理由:
移除的理由:
我的決定:暫時保留,但要加註釋說明。
我請 Claude Code 幫我建立一個架構檢查腳本:
#!/bin/bash
# check-architecture.sh
echo "檢查架構規範..."
# 檢查 shared 不該有 @Composable
if grep -r "@Composable" shared/src/commonMain --include="*.kt"; then
echo "❌ 錯誤:shared 模組不應包含 @Composable"
exit 1
fi
# 檢查 presentation 都在 desktopApp
if [ ! -d "desktopApp/src/jvmMain/kotlin/*/presentation" ]; then
echo "❌ 錯誤:presentation 層應該在 desktopApp"
exit 1
fi
echo "✅ 架構檢查通過"
參考 Day 20 的 MADR,我建立了架構決策記錄:
# ADR-003: UI 層與業務邏輯分離
## 狀態
已採納
## 背景
KMP 專案需要明確的模組邊界
## 決策
- shared:只包含 domain、data、core
- desktopApp:包含所有 UI(presentation)
- compose 依賴:因 Decompose 需要而保留在 shared
## 結果
- ✅ 清晰的架構邊界
- ✅ 未來容易擴展到其他平台
- ⚠️ shared 有 compose 依賴但無 UI 程式碼
雖然決定保留 compose,但可以精簡:
// shared/build.gradle.kts
val commonMain by getting {
dependencies {
// 只保留必要的 compose
implementation(compose.runtime) // Decompose 需要
// 移除這些
// implementation(compose.foundation)
// implementation(compose.material3)
// implementation(compose.ui)
}
}
Claude Code 幫我測試編譯:
./gradlew clean build
成功!Decompose 只需要 compose.runtime。
我請 Claude Code 幫我測量優化前後的差異:
# 優化前
du -sh shared/build/libs/
3.2M
# 優化後
./gradlew clean build
du -sh shared/build/libs/
2.8M
減少了 400KB。不多,但是個進步。
我建立了 docs/architecture/module-guidelines.md
:
# 模組劃分指引
## shared 模組
✅ 可以放:
- Domain models (Project, Task, etc.)
- Use cases
- Repository interfaces
- Core utilities
❌ 不要放:
- @Composable 函數
- ViewModels
- UI 相關程式碼
## desktopApp 模組
✅ 應該放:
- 所有 @Composable
- ViewModels
- Theme/Colors/Typography
- Platform-specific code
最近要加一個新的任務詳情畫面,架構分離讓開發很順暢:
每一層都很清楚自己的職責。
如果要支援 Android:
androidApp/
└── src/main/kotlin/
└── presentation/ # Android 特定 UI
├── theme/ # Material You
└── screens/ # Android Composables
shared 完全不用動,這就是好架構的威力。
這次架構研究,Claude Code 幫了很多忙:
但最重要的是,它讓我學會了架構思考的方法。
開始時我以為 shared 有 compose 依賴是錯的,研究後發現有其道理。
架構決策沒有絕對的對錯,要根據專案需求和發展階段來調整。
寫下架構決策的原因,未來的自己(或團隊成員)會感謝你。
特別是那些「看起來怪怪的」決定,一定要記錄為什麼這樣做。
今天深入研究了 KMP 的模組劃分,理解了 shared 和 desktopApp 的職責邊界。
雖然沒有大規模重構(架構已經是對的),但通過優化依賴、建立規範、文件化決策,讓專案架構更加清晰。
最大的收穫是學會了用 Claude Code 做架構研究。它像一個架構顧問,幫我分析問題、研究方案、驗證決策。
明天會繼續探索其他架構優化的可能性。
「好的架構不是完美的架構,而是適合的架構。」
關於作者:Sam,一人公司創辦人。正在打造 Grimo,智能任務管理平台。
專案連結:GitHub - grimostudio