版本編號規範
本文件說明 AppFuse 專案的版本編號策略,包含設計理念、適用範圍與實際範例。
設計理念
版本號的主要目的是傳達資訊給使用者。不同類型的軟體需要傳達不同的資訊:
| 軟體類型 | 使用者關心的問題 | 適合的版本策略 |
|---|---|---|
| Library(被依賴) | 升級會不會破壞我的程式? | SemVer(語義化版本) |
| Application(最終產物) | 這是什麼時候的版本? | CalVer(日曆版本) |
基於此,AppFuse 專案根據模組性質採用不同的版本策略。
框架層(Library)→ SemVer
適用模組
被其他專案依賴的 Library 使用 語義化版本 (Semantic Versioning):
| 模組 | 版本格式 | 當前版本 | 說明 |
|---|---|---|---|
| appfuse-server | MAJOR.MINOR.PATCH | 4.0.0-SNAPSHOT | Spring Boot 工具集(跟隨 Spring Boot) |
| appfuse-web | MAJOR.MINOR.PATCH | 19.0.0 | React 元件庫(跟隨 React) |
| appfuse-docs | MAJOR.MINOR.PATCH | 4.0.0 | 框架文檔站(跟隨 appfuse-server) |
| appfuse-docs-host | MAJOR.MINOR.PATCH | 4.0.0-SNAPSHOT | 框架文檔托管(跟隨 appfuse-server) |
版本號意義
MAJOR.MINOR.PATCH[-SNAPSHOT]
│ │ │ │
│ │ │ └─ 開發中版本標記
│ │ └───────── 向後相容的 Bug 修復
│ └─────────────── 向後相容的功能新增
└───────────────────── 不相容的 API 變更
跟隨 Spring Boot 主版本
appfuse-server 採用跟隨 Spring Boot 主版本的策略:
| appfuse-server 版本 | 對應 Spring Boot | 最低 JDK |
|---|---|---|
| 3.x.x | Spring Boot 3.x | JDK 17+ |
| 4.x.x | Spring Boot 4.x | JDK 25+ |
理由:
- 讓消費端清楚知道所需的運行環境
- Spring Boot 大版本升級通常伴隨 breaking changes
- 版本號直接反映平台需求,降低升級時的意外
跟隨 React 主版本
appfuse-web 採用跟隨 React 主版本的策略:
| appfuse-web 版本 | 對應 React | 說明 |
|---|---|---|
| 18.x.x | React 18.x | 支援 Concurrent Rendering |
| 19.x.x | React 19.x | 支援 Server Components、React Compiler |
理由:
- 與 appfuse-server 跟隨 Spring Boot 的策略一致
- 讓消費端清楚知道需要哪個 React 版本
- React 大版本升級可能引入新特性(如 Server Components)
API 演進政策
框架的 public API 會隨時間演進。為了在「持續改進」與「消費端穩定」之間取得平衡,appfuse-server 與 appfuse-web 採用 Spring Boot 風格的 deprecation cycle,而非純守 SemVer。
MAJOR / MINOR / PATCH 各能做什麼
| 段位 | 允許的變更 |
|---|---|
| PATCH | Bug 修復;不新增、不 deprecate、不移除 API |
| MINOR | 新增 API;deprecate 既有 API;移除已 deprecated 至少兩個 minor 的 API |
| MAJOR | 跨底層框架主版本(Spring Boot / React);或框架自身的重大架構變更 |
Deprecation Cycle
API 從「標記 deprecated」到「移除」必須跨越 至少兩個 minor 版本 的緩衝期,給消費端足夠時間遷移。
| 標記 deprecated 的版本 | 最早可移除版本 | 說明 |
|---|---|---|
4.1.0 | 4.3.0 | 標記後跨兩個 minor |
4.1.x(4.2 已發布後才補上 deprecated) | 4.4.0 | 從 4.2 起算兩個 minor |
此規則參考 Spring Boot Deprecations Policy:「deprecated code should not be removed until two minors after the latest minor in which the API exists」。
標準寫法
Java(appfuse-server)
/**
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of {@link NewThing}
*/
@Deprecated(since = "4.1.0", forRemoval = true)
public void oldMethod() { ... }
TypeScript(appfuse-web)
/**
* @deprecated since 19.1.0 for removal in 19.3.0 in favor of {@link NewComponent}
*/
export const OldComponent = ...
Pre-release 例外
帶連字號後綴的版本(-alpha、-beta、-rc、-SNAPSHOT)屬於 pre-release,不受 deprecation cycle 約束——可在任意 pre-release 之間 breaking。此規則僅適用於正式版(無後綴)之間的演進。
設計理由
- 純守 SemVer 過於嚴格:每次 API 移除都升 MAJOR 會讓 MAJOR 變動過於頻繁,與「跟隨底層框架主版本」的策略衝突。
- 完全不破壞過於僵化:API 一旦犯錯就永久背負,累積的 deprecated 內容會持續膨脹。
- Spring Boot 已驗證可行:兩個 minor 的緩衝期是 Spring Boot 多年實踐的結果,社群與工具鏈習慣這套節奏。
框架文檔版本
appfuse-docs 與 appfuse-docs-host 跟隨 appfuse-server 版本,讓使用者知道這是哪個版本框架的文檔。
參考實作層(Application)→ CalVer
適用模組
最終應用程式使用 日曆版本 (Calendar Versioning):
| 模組 | 版本格式 | 當前版本 | 說明 |
|---|---|---|---|
| app-server | YYYY.N.P-SNAPSHOT | 2026.1.0-SNAPSHOT | 後端 RESTful API |
| app-office | YYYY.N.P | 2026.1.0 | 前端 SPA |
| app-office-mockup | YYYY.N.P | 2026.1.0 | Prototype |
| app-docs | YYYY.N.P | 2026.1.0 | 應用文檔站 |
| app-office-host | YYYY.N.P-SNAPSHOT | 2026.1.0-SNAPSHOT | 前端托管層 |
| app-docs-host | YYYY.N.P-SNAPSHOT | 2026.1.0-SNAPSHOT | 文檔托管層 |
版本號格式
YYYY.N.P[-SNAPSHOT]
│ │ │ │
│ │ │ └─ 開發中版本標記(Gradle 專案)
│ │ └─────── 問題修訂次數(從 0 開始)
│ └───────── 該年度功能更新次數(從 1 開始)
└───────────── 西元年份
版本號範例
| 版本 | 意義 |
|---|---|
2026.1.0 | 2026 年第 1 次功能發布 |
2026.1.1 | 2026 年第 1 次功能發布的第 1 次修訂 |
2026.1.2 | 2026 年第 1 次功能發布的第 2 次修訂 |
2026.2.0 | 2026 年第 2 次功能發布 |
2027.1.0 | 2027 年第 1 次功能發布 |
為什麼選擇 CalVer?
- 不被依賴:應用程式是最終產物,沒有其他專案會依賴它
- 用戶導向:最終用戶關心的是「這是什麼時候的版本」
- 相容性無意義:應用程式升級是整體替換,不存在 API 相容性問題
- 直觀易懂:看到
2026.2.0就知道是 2026 年第 2 次更新
托管層版本同步
托管層與被托管的應用保持版本同步:
app-office (2026.1.0) ──build──→ app-office-host (2026.1.0-SNAPSHOT)
app-docs (2026.1.0) ──build──→ app-docs-host (2026.1.0-SNAPSHOT)
理由:當部署 app-office-host:2026.1.0 時,立即知道它包含的是 app-office:2026.1.0。
知名專案的版本策略參考
SemVer 範例
- React: 18.2.0, 19.0.0
- Spring Boot: 3.2.0, 4.0.0
- Node.js: 20.10.0, 22.0.0
CalVer 範例
- Ubuntu: 24.04, 24.10(YYYY.MM)
- JetBrains IDEs: 2024.1, 2024.2(YYYY.N)
- pip: 24.0, 24.1(YY.N)
版本變更流程
框架層版本變更
- PATCH(如 4.0.0 → 4.0.1):Bug 修復
- MINOR(如 4.0.0 → 4.1.0):新增 API、deprecate API、移除已 deprecated 至少兩個 minor 的 API
- MAJOR(如 4.x → 5.x):跨底層框架主版本,或框架自身的重大架構變更
詳細的 Deprecation Cycle 與標準寫法見上方「API 演進政策」一節。
參考實作層版本變更
- 功能發布(如 2026.1.0 → 2026.2.0):新增功能或重大更新
- 問題修訂(如 2026.1.0 → 2026.1.1):Bug 修復或小幅調整
- 年度更新(如 2026.x.x → 2027.1.0):跨年度的第一次發布
版本號位置
Gradle 專案(Java/Kotlin)
版本定義在 gradle.properties:
# appfuse-server/gradle.properties
version=4.0.0-SNAPSHOT
# app-server/gradle.properties
version=2026.1.0-SNAPSHOT
npm 專案(JavaScript/TypeScript)
版本定義在 package.json:
{
"name": "app-office",
"version": "2026.1.0"
}
常見問題
Q: 為什麼 appfuse-server 用 4.0.0 而不是 0.1.0?
因為 appfuse-server 跟隨 Spring Boot 主版本。當使用 Spring Boot 4.x 時,版本號為 4.x.x,讓消費端立即知道需要 Spring Boot 4.x 環境和 JDK 25+。
Q: CalVer 的年份跨年時怎麼處理?
跨年後的第一次發布使用新年份,例如:
- 2026 年最後一次:
2026.12.0 - 2027 年第一次:
2027.1.0
Q: SNAPSHOT 是什麼意思?
-SNAPSHOT 表示這是開發中的版本,尚未正式發布。Gradle 會從 snapshot repository 下載,每次建構可能獲得不同的內容。正式發布時移除此後綴。
Q: npm 專案為什麼沒有 SNAPSHOT?
npm 生態系統使用不同的預發布標記方式(如 -alpha, -beta, -rc)。對於私有的應用程式專案,直接使用正式版本號即可。
Q: 為什麼 appfuse-web 用 19.0.0 而不是 0.1.x?
因為 appfuse-web 跟隨 React 主版本。當使用 React 19.x 時,版本號為 19.x.x,讓消費端立即知道需要 React 19.x。這與 appfuse-server 跟隨 Spring Boot 的策略一致。
Q: 同一個 React / Spring Boot 主版本內可以有 breaking change 嗎?
可以,但必須走 deprecation cycle:先在 minor 版本標記 @Deprecated / @deprecated 並註明預計移除版本,至少跨兩個 minor 後才能在另一個 minor 移除。MAJOR 保留給「跨底層框架主版本」與「框架自身的重大架構變更」。詳見「API 演進政策」一節。
Q: alpha / beta 版本適用 deprecation cycle 嗎?
不適用。pre-release 版本(-alpha、-beta、-rc、-SNAPSHOT)允許自由 breaking,這是 SemVer 規格明文許可的;deprecation cycle 僅約束正式版之間的演進。
總結
框架層 (Library) → SemVer(跟隨底層框架主版本)
├── appfuse-server: 4.0.0-SNAPSHOT (跟隨 Spring Boot 4.x)
├── appfuse-web: 19.0.0 (跟隨 React 19.x)
├── appfuse-docs: 4.0.0
└── appfuse-docs-host: 4.0.0-SNAPSHOT
參考實作層 (Application) → CalVer(YYYY.N.P)
├── app-server: 2026.1.0-SNAPSHOT
├── app-office: 2026.1.0
├── app-office-mockup: 2026.1.0
├── app-docs: 2026.1.0
├── app-office-host: 2026.1.0-SNAPSHOT
└── app-docs-host: 2026.1.0-SNAPSHOT