iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
生成式 AI

30 天一人公司的 AI 開發實戰系列 第 6

Day 06: 兼任架構師翻車現場:初次見面的血淚史

  • 分享至 

  • xImage
  •  

「我有 10 年的 Spring Boot 經驗,整合個 Compose Desktop 能有多難?」

——翻車前一小時的我

前情提要:自信滿滿的架構師

昨天的選型會議上,我信心滿滿地選擇了 Kotlin Multiplatform + Compose Desktop + Spring AI 的技術組合。

理由很充分。Kotlin 統一前後端,減少語言切換成本。Compose Desktop 提供原生 macOS 體驗。Spring Boot 是我的老朋友,閉著眼睛都能寫。Spring AI 提供了完整的 AI 模型整合。

看起來完美無缺,對吧?

第一個天真的想法

既然都是 JVM 生態,為什麼不把 Spring Boot 和 Compose Desktop 放在同一個 JVM 裡運行呢?

這樣可以減少記憶體佔用,只有一個 JVM。前後端直接方法調用,沒有 HTTP 開銷。部署簡單,只有一個執行檔。我甚至畫了一個「優雅」的架構圖:

┌─────────────────────────────────┐
│        Single JVM Process        │
├─────────────────────────────────┤
│     Compose Desktop (UI)        │
│              ↓                   │
│      Direct Method Call          │
│              ↓                   │
│     Spring Boot (Backend)        │
│              ↓                   │
│          SQLite DB               │
└─────────────────────────────────┘

看起來很美好。

翻車現場一:誰是老大?

第一個問題來得比預期還快。

當我寫下 main 函數時,愣住了。Spring Boot 說「我要這樣啟動」,Compose Desktop 說「不行,我要這樣」。兩個框架都要控制 main 函數,都要管理應用程式生命週期。

// Spring Boot 說:我要這樣啟動
fun main(args: Array<String>) {
    runApplication<BackendApplication>(*args)
}

// Compose Desktop 說:不行,我要這樣
fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

// 我:???

這就像是兩個 CEO 在同一家公司。誰聽誰的?

翻車現場二:Spring 先生,您醒了嗎?

好不容易寫了個 SpringManager 來協調兩者。

object SpringManager {
    fun start() {
        springThread = Thread {
            context = SpringApplicationBuilder(BackendApplication::class.java)
                .web(WebApplicationType.NONE)  // 不要 Web 服務器
                .headless(false)  // 我們有 UI
                .run()
        }
        // 等 Spring 啟動完成
        while (context == null || !context!!.isRunning) {
            Thread.sleep(100)  // 這行程式碼後來讓我後悔萬分
        }
    }
}

然後在 Compose 中調用。結果呢?

有時候 Spring 啟動太慢,UI 已經在訪問服務,NullPointerException。有時候等待邏輯死循環,整個應用程式卡死。關閉視窗時,Spring Context 不知道該不該關,資源洩漏警告滿天飛。

翻車現場三:資料庫的靈魂拷問

Spring Boot 作為一個 server-side 框架,它問我:「PostgreSQL 在哪?MySQL 在哪?Redis 在哪?」

我:「不好意思,這是個桌面應用,我們用 SQLite...」

Spring Data JPA:「SQLite?那是什麼?我只認識企業級資料庫!」

於是我被迫用 H2 Database 做妥協。但 H2 不是為生產環境設計的,而且 Compose 端用的是 SQLDelight + SQLite。現在有兩套資料層,兩套資料模型、兩種查詢語言、兩個事務管理器。

// Compose 端:優雅的 SQLDelight
val database = JdbcSqliteDriver("jdbc:sqlite:$databasePath")

// Spring 端:彆扭的 JPA
@Entity
class Project {
    @Id @GeneratedValue
    var id: Long? = null
}

我在寫什麼?精神分裂應用程式?

翻車現場四:除錯的藝術(絕望)

當應用程式崩潰時,錯誤堆疊是這樣的:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException
    at org.springframework.boot.SpringApplication.run
    at compose.desktop.ComposeLayer.invoke
    at org.springframework.context.annotation.ConfigurationClassParser
    at androidx.compose.runtime.internal.ComposableLambda
    at kotlin.coroutines.intrinsics.IntrinsicsKt
    at org.springframework.beans.factory.support.AbstractBeanFactory
    at androidx.compose.ui.platform.DesktopOwner
    ... 200 more lines

這是 Spring 的問題?Compose 的問題?還是我的問題?

答案:都是。

血淚教訓

經過 48 小時的奮戰,無數杯咖啡,以及差點砸爛鍵盤的衝動後,我終於明白了一個道理。

框架不是樂高積木,不能隨便拼在一起。

每個框架都有自己的設計哲學。Spring Boot 認為自己是宇宙的中心,要控制一切。Compose Desktop 認為 UI 才是王道,其他都是配角。我天真地以為可以讓它們和平共處。

這就像讓梅西和 C 羅在同一支球隊踢同一個位置。理論上很美好,實際上... 你懂的。

最痛的領悟

當我終於放棄這個「巧妙」的架構,回頭看 AI 助手(Gemini)最初的建議時,發現它一開始就建議用分離進程架構。

它甚至委婉地警告過:「將 Spring Boot 和 Compose Desktop 整合在同一個 JVM 中可能會遇到一些挑戰...」

「一些挑戰」...

這是我見過最保守的描述了。

翻車之後的初步反思

經過這次慘痛的教訓,我開始意識到問題不只是技術整合那麼簡單。

更深層的問題是:我一直在用 Java/Spring 的思維寫 Kotlin 程式碼。

這就像用英文文法說中文,表面上能溝通,但總是怪怪的。Kotlin 不只是「更好的 Java」,它有自己的哲學和最佳實踐。

明天,我得好好反思一下,重新學習 Kotlin 的思維方式。一人公司的架構師,不只要懂技術,還要懂得何時該放下固執,擁抱新的思維。

現在,就讓我們為這次壯烈的翻車默哀三秒鐘吧。

今日金句

「經驗有時候是最大的陷阱,它讓你以為熟悉的道路永遠正確。」

關於作者:Sam,一人公司創辦人。正在打造 Grimo,一個智能任務管理和分配平台。

專案連結GitHub - grimostudio


上一篇
Day 05: 兼任架構師的選型會議:Kotlin Multiplatform 生態與取捨
系列文
30 天一人公司的 AI 開發實戰6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言